Top page  1/4
--
--

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
21
2018

GamepadAPIを使ってゲームを市販のコントローラーで操作できるようにしてみる:応用編

Category技術
こんにちは。なるとりっくのシステム担当です。

今回は応用編ということで、画面遷移を考慮したGamepadAPIの使い方を書いていきます。


ゲームを作るとなるとトップ画面やコンフィグ画面やセーブ画面などいろんな画面が必要になってきます。そしてそれら全てにそれぞれの画面に応じたボタン押下時のイベントをGamepadAPIで付与しなければなりません。それとイベントを付与するだけでなく、削除の事も考える必要があります。


例えば画面Aから画面Bに遷移するときのイベントのライフサイクルは以下のようになります。

1. 画面Aを表示する
  →この時に画面AのGamepadイベント付与
2. 画面Bに遷移する
  →この時に画面AのGamepadイベント削除、画面BのGamepadイベント付与

画面Bに遷移するときは、不要になった画面AのGamepadイベントをちゃんと削除しておかなければいけません。
余計なイベントが残っていると不具合の原因になります。


次にこんな画面遷移の場合はどうなるでしょうか?

1.画面Aを表示する
2.画面Bに遷移する
3.前の画面(この場合は画面A)に戻る

例えば画面Bがコンフィグのような画面の場合、コンフィグをいじり終わった後に前の画面に戻らないといけません。
単純に考えると、
1で画面Aのイベント付与
2で画面Aのイベント削除と画面Bのイベント付与
3で画面Bのイベント削除と画面Aのイベント付与
となります。
これをコードで書くと以下のような感じになります。
Gamepadイベント付与は前回の基礎編でやった通りカスタムイベントを使います。

// 画面Aを表示する
show_a_view();

// 画面Aのイベント付与
var press_a1 = function() {
    console.log("画面AでAボタンを押しました!")
}
addEventListener("gamepad_a_press", press_a1);


// ---画面Aでいろいろやる---


// 画面Bに遷移
show_b_view();

// 画面Aのイベント削除
removeEventListener("gamepad_a_press", press_a1);

// 画面Bのイベント付与
var press_a2 = function() {
    console.log("画面BでAボタンを押しました!")
}
addEventListener("gamepad_a_press", press_a2);


// ---画面Bでいろいろやる---


// 前の画面に戻る
return_previous_view();

// 画面Bのイベント削除
removeEventListener("gamepad_a_press", press_a2);

// 前の画面のイベント付与
var press_a3 = function() {
    console.log("画面Aボタンを押しました!")
}
addEventListener("gamepad_a_press", press_a3);

こんな感じで前の画面に戻る時に、一旦削除したイベントを再度付け直さないといけません。
画面Bには画面Aからしか遷移しない場合は、このように画面Aのイベントを付け直すと決めてしまえばいいのですが、画面Cからも画面Bに遷移する可能性が出てきた場合に対応できなくなります。

なので元の画面に戻ってくることが前提の画面遷移をするときは一旦イベントをどこか別の場所に退避させておいて、元の画面に戻ったときに復活させるようにします。

そのために以下のような関数を用意しました。

var gamepad_events = {};
var stash_gamepad_events = {};

// Gamepadのイベントを付与する処理
var add_gamepad_event_listener = function (event, listener) {
    if (gamepad_events[event] == undefined) {
        gamepad_events[event] = [];
    }
    gamepad_events[event].push(listener);
    addEventListener(event, listener)
}

// Gamepadのイベントを退避しておく処理
var stash_gamepad_event_listener = function (key) {
    stash_gamepad_events[key] = gamepad_events;
    Object.keys(gamepad_events).forEach(function (key_) {
        for (var i = 0; i < gamepad_events[key_].length; i++) {
            var listener = gamepad_events[key_][i];
            removeEventListener(key_, listener);
        }
    });
    gamepad_events = {};
}

// 退避していたGamepadのイベントを復活させる処理
var restore_gamepad_event_listener = function (key) {
    Object.keys(gamepad_events).forEach(function (key_) {
        for (var i = 0; i < gamepad_events[key_].length; i++) {
            var listener = gamepad_events[key_][i];
            removeEventListener(key_, listener);
        }
    });
    gamepad_events = stash_gamepad_events[key];
    delete stash_gamepad_events[key];
    if(gamepad_events) {
        Object.keys(gamepad_events).forEach(function (key_) {
            for (var i = 0; i < gamepad_events[key_].length; i++) {
                var listener = gamepad_events[key_][i];
                addEventListener(key_, listener);
            }
        });
    }
}

// Gamepadのイベントをクリアする処理
var clear_gamepad_event_listener = function () {
    Object.keys(gamepad_events).forEach(function (key) {
        for (var i = 0; i < gamepad_events[key].length; i++) {
            var listener = gamepad_events[key][i];
            removeEventListener(key, listener);
        }
    });
    gamepad_events = {};
}

add_gamepad_event_listener(event, listener)はGamepadのイベントを付与すると同時に、付与したイベントを保持しておいてくれます。引数はaddEventListenerと同じなのでそのまま置き換えることができます。 
stash_gamepad_event_listener(key)は、add_gamepad_event_listener(event, listener)を使って付与したイベントを退避させてくれます。その際、現在付与されているイベントが一旦外れます。引数のkeyは必須です。
restore_gamepad_event_listener(key)は、stash_gamepad_event_listener(key)で退避したイベントを復活させてくれます。stashの際に指定したkeyと対応したイベントを復活させるので、引数のkeyは必須です。

clear_gamepad_event_listener()は、add_gamepad_event_listener(event, listener)を使って付与したイベントを退避無しで削除してくれます。


これらの関数を使って画面遷移の際のGamepadイベントを制御します。

すると以下のような感じになります。
 
// 画面Aを表示する
show_a_view();

// 画面Aのイベント付与
var press_a1 = function() {
    console.log("画面AでAボタンを押しました!")
}
add_gamepad_event_listener("gamepad_a_press", press_a1);


// ---画面Aでいろいろやる---


// 画面Bに遷移
show_b_view();

// 前の画面のイベントを退避
stash_gamepad_event_listener("b_view");

// 画面Bのイベント付与
var press_a2 = function() {
    console.log("画面BでAボタンを押しました!")
}
add_gamepad_event_listener("gamepad_a_press", press_a2);


// ---画面Bでいろいろやる---


// 前の画面に戻る
return_previous_view();

// 退避していた前の画面のイベントを復活
restore_gamepad_event_listener("b_view");

どこの画面から遷移してきたかというのは意識せずにとにかく今付与されているイベントを退避し、また必要になったら復活させるというやり方です。
これでどこから画面Bに遷移したとしても、前の画面に戻ったときにイベントを復活させることができるようになりました。
keyを指定して複数退避させることができるようにしているので、画面遷移がA→B→C→B→Aの様に深くなっても対応できます。

今回はここまでで、次回は具体例を交えた実践編をやりたいと思います。
04
2018

GamepadAPIを使ってゲームを市販のコントローラーで操作できるようにしてみる:基礎編

Category技術

お久しぶりです! なるとりっくのシステム担当です!


今回はタイトル通りゲームをコントローラーで操作できるようにしてみたいと思います。
GamepadAPIを使うということで、Webベースのノベルゲームが対象になっています。

今回は基礎編という事で、基本的なGamepadAPIの使い方の紹介と導入をしていきます。
環境としてはOS:MacOS、ブラウザ:Chrome、同時に接続するコントローラーは一台まで、という前提でやっていきますので、環境によっては微妙に違うところも出てくるかもしれません。
Gamepadはちょうど手元にあったこちらの
「Nintendo Switch Proコントローラー スプラトゥーン2エディション」を使って行きます。

Gamepad APIの使い方

こちらのリファレンスによると、Gamepad APIには大まかに分けて三つの機能があります。
  • Gamepad接続イベントの取得
  • Gamepad切断イベントの取得
  • Gamepadの状態の取得
の三つです。
一つずつ使って行ってみましょう。


Gamepad接続イベントの取得

 
    window.addEventListener("gamepadconnected", function(e) {
        console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.",
        e.gamepad.index, e.gamepad.id,
        e.gamepad.buttons.length, e.gamepad.axes.length);
    });
サンプルの通り↑こちらのjsを記述し、MacとコントローラーをBluetoothでつなぐと・・・

 
    Gamepad connected at index 0: Pro Controller (Vendor: 057e Product: 2009). 16 buttons, 10 axes.
コンソールにこのような↑ログが出力され、無事に接続イベントが取得できました。簡単ですね。

Gamepad切断イベントの取得

    window.addEventListener("gamepaddisconnected", function(e) {
        console.log("Gamepad disconnected from index %d: %s",
        e.gamepad.index, e.gamepad.id);
    });
切断もサンプル通りのjs↑を記述してMacとコントローラーのBluetooth接続を解除すると・・・

    Gamepad disconnected from index 0: Pro Controller (Vendor: 057e Product: 2009)
コンソールにこのような↑ログが出力され、無事に切断されたことがわかります。


Gamepadの状態の取得

続いてGamepadの状態の取得をしていきます。これもリファレンスのサンプルを参考にやっていきます。
例えばAボタン、Bボタン、上下左右キーの状態を取得するなら、

function buttonPressed(b) {
  if (typeof(b) == "object") {
    return b.pressed;
  }
  return b == 1.0;
}
function stickFloor (s) {
  return Math.floor(s * 10) / 10;
}

(function gameLoop() {
    var gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads : []);
    if (gamepads && gamepads.length > 0 && gamepads[0]) {
        var gamepad = gamepads[0];

        if (buttonPressed(gamepad.buttons[0])) {
            console.log("Bボタン押下中")
        }

        if (buttonPressed(gamepad.buttons[1])) {
            console.log("Aボタン押下中")
        }

        if (stickFloor(gamepad.axes[9]) == -1) {
            console.log("上キー押下中");
        }

        if (stickFloor(gamepad.axes[9]) == -0.5) {
            console.log("右キー押下中");
        }

        if (stickFloor(gamepad.axes[9]) == 0.1) {
            console.log("下キー押下中");
        }

        if (stickFloor(gamepad.axes[9]) == 0.7) {
            console.log("左キー押下中");
        }
    }

    requestAnimationFrame(gameLoop);
})();

こんな感じになります。
Aボタンの状態はgamepad.buttons[1]で、
Bボタンの状態はgamepad.buttons[0]で、
十字キーの状態はgamepad.axes[9]で取得できます。
十字キーの状態は「1.2857143878936768」のような数値で取得出来るので、小数点第一位以下を斬り捨ててシンプルな数値にした後にそれぞれ対応する値と比較し判定します。
例えば十字キーの左を押下すると「0.7142857313156128」という数値が取得出来るので、小数点第一位以下を斬り捨てると0.7になります。
これで各ボタンを押している最中は「**ボタン押下中」というログが表示され続けます。

gamepadAPIではコントローラーのボタンを押下したらイベントが発生するのではなく、requestAnimationFrameを使用しフレーム毎にコントローラーの状態を取得しに行ってボタンが押されているかどうか確認する方式になっています。
なのでこのままではボタンが連打されっぱなしのような状態になってしまうので、一回の押下で一回のみ反応するように制御を加えます。

function buttonPressed(b) {
  if (typeof(b) == "object") {
    return b.pressed;
  }
  return b == 1.0;
}
function stickFloor (s) {
  return Math.floor(s * 10) / 10;
}

var is_pressed_b = false;
var is_pressed_a = false;
var is_pressed_cross_key_top = false;
var is_pressed_cross_key_right = false;
var is_pressed_cross_key_bottom = false;
var is_pressed_cross_key_left = false;
(function gameLoop() {
    var gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads : []);
    if (gamepads && gamepads.length > 0 && gamepads[0]) {
        var gamepad = gamepads[0];

        if (buttonPressed(gamepad.button[0])) {
            if (is_pressed_b != true) {
                is_pressed_b = true;
                console.log("Bボタンを押下しました!")
            }
        }
        else {
            is_pressed_b = false;
        }

        if (buttonPressed(gamepad.buttons[1])) {
            if (is_pressed_a != true) {
                is_pressed_a = true;
                console.log("Aボタンを押下しました!")
            }
        }
        else {
            is_pressed_a = false;
        }

        if (stickFloor(gamepad.axes[9]) == -1) {
            if (is_pressed_cross_key_top != true) {
                is_pressed_cross_key_top = true;
                console.log("上キーを押下しました!");
            }
        }
        else {
            is_pressed_cross_key_top = false;
        }

        if (stickFloor(gamepad.axes[9]) == -0.5) {
            if (is_pressed_cross_key_right != true) {
                is_pressed_cross_key_right = true;
                console.log("右キーを押下しました!");
            }
        }
        else {
            is_pressed_cross_key_right = false;
        }

        if (stickFloor(gamepad.axes[9]) == 0.1) {
            if (is_pressed_cross_key_bottom != true) {
                is_pressed_cross_key_bottom = true;
                console.log("下キーを押下しました!");
            }
        }
        else {
            is_pressed_cross_key_bottom = false;
        }

        if (stickFloor(gamepad.axes[9]) == 0.7) {
            if (is_pressed_cross_key_left != true) {
                is_pressed_cross_key_left = true;
                console.log("左キーを押下しました!");
            }
        }
        else {
            is_pressed_cross_key_left = false;
        }
    }

    requestAnimationFrame(gameLoop);
})();

これで一回の押下につき一回だけ処理をさせることができました。
ただこのままですとボタン押下時の処理を一箇所に記述していくことになってしまい使いづらいので分離させます。
どうやるのかと言いますと、カスタムイベントを作成・発火し、それを受信する事でクリック時のイベントと同じようなコードで処理を記述できるようになります。
ついでにgamepadのボタンの番号の定数化もしてしまいしょう。
そうすると以下の様になります。

function buttonPressed(b) {
  if (typeof(b) == "object") {
    return b.pressed;
  }
  return b == 1.0;
}
function stickFloor (s) {
  return Math.floor(s * 10) / 10;
}

const BUTTON_INDEX_B = 0;
const BUTTON_INDEX_A = 1;
const AXES_CROSS_KEY = 9;
var is_pressed_b = false;
var is_pressed_a = false;
var is_pressed_cross_key_top = false;
var is_pressed_cross_key_right = false;
var is_pressed_cross_key_bottom = false;
var is_pressed_cross_key_left = false;
(function gameLoop() {
    var gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads : []);
    if (gamepads && gamepads.length > 0 && gamepads[0]) {
        var gamepad = gamepads[0];

        if (buttonPressed(gamepad.button[BUTTON_INDEX_B])) {
            if (is_pressed_b != true) {
                is_pressed_b = true;
                dispatchEvent(new Event('gamepad_b_press'));
            }
        }
        else {
            is_pressed_b = false;
        }

        if (buttonPressed(gamepad.buttons[BUTTON_INDEX_A])) {
            if (is_pressed_a != true) {
                is_pressed_a = true;
                dispatchEvent(new Event('gamepad_a_press'));
            }
        }
        else {
            is_pressed_a = false;
        }

        if (stickFloor(gamepad.axes[AXES_CROSS_KEY]) == -1) {
            if (is_pressed_cross_key_top != true) {
                is_pressed_cross_key_top = true;
                dispatchEvent(new Event('gamepad_cross_key_t_press'));
            }
        }
        else {
            is_pressed_cross_key_top = false;
        }

        if (stickFloor(gamepad.axes[AXES_CROSS_KEY]) == -0.5) {
            if (is_pressed_cross_key_right != true) {
                is_pressed_cross_key_right = true;
                dispatchEvent(new Event('gamepad_cross_key_r_press'));
            }
        }
        else {
            is_pressed_cross_key_right = false;
        }

        if (stickFloor(gamepad.axes[AXES_CROSS_KEY]) == 0.1) {
            if (is_pressed_cross_key_bottom != true) {
                is_pressed_cross_key_bottom = true;
                dispatchEvent(new Event('gamepad_cross_key_b_press'));
            }
        }
        else {
            is_pressed_cross_key_bottom = false;
        }

        if (stickFloor(gamepad.axes[AXES_CROSS_KEY]) == 0.7) {
            if (is_pressed_cross_key_left != true) {
                is_pressed_cross_key_left = true;
                dispatchEvent(new Event('gamepad_cross_key_l_press'));
            }
        }
        else {
            is_pressed_cross_key_left = false;
        }
    }

    requestAnimationFrame(gameLoop);
})();
addEventListener("gamepad_b_press", function() {
    console.log("Bボタンを押下しました!")
})
addEventListener("gamepad_a_press", function() {
    console.log("Aボタンを押下しました!")
})
addEventListener("gamepad_cross_key_t_press", function() {
    console.log("上キーを押下しました!");
})
addEventListener("gamepad_cross_key_r_press", function() {
    console.log("右ボタンを押下しました!")
})
addEventListener("gamepad_cross_key_b_press", function() {
    console.log("下ボタンを押下しました!")
})
addEventListener("gamepad_cross_key_l_press", function() {
    console.log("左ボタンを押下しました!")
})

ボタンを押下したときに直接行うのは「ボタンを押下した」というカスタムイベントを作成し発火するだけで、そのカスタムイベントを受信するかどうかは受け手の自由です。
これでGamepadに関する処理とボタン押下時の処理を分離することができました。
不要になったイベントはremoveEventListenerしておくのを忘れずに。

こんな感じにしたら使いやすいのではないかと思います。
今回はここまで!

15
2017

画像ファイルの配列を自動で作成する

Category技術
 tf.images = ["aaa.png", "bbb.png", "ccc.png"];
画像のプリロードをするときに上記のような対象ファイルの配列を作成すると思いますが、ファイルの数が多くなってくると一つ一つ書いていくのは非常に手間になると思います。
そこで自動で画像ファイルの配列を作成してくれるスクリプトを作成してみました。
以下使い方です。

新規ファイルを作成し、以下の記述をコピー&ペーストしmakePreloadImages.shというファイル名で保存してください。


---ここから---
cd `dirname $0`
array=`find . -type f \( -name \*.png -or -name \*.jpg \)`
for file in $array; do
file=${file#*/}
echo $file
preloadImages=${preloadImages}`
echo $file | \
sed -e  's/^/"/g' | \
sed -e  's/$/", /g'`
done
preloadImages=${preloadImages%, }
preloadImages=[${preloadImages}]
echo $preloadImages>preloadImages.txt
---ここまで---


次にmakePreloadImages.shを、プリロードしたい画像が格納されているフォルダに配置してください。
例えばdataというフォルダ内に入っている画像ファイルの配列を作りたいときはこんな感じになります。
setti.png
dataフォルダの中はフォルダが入れ子になっていても大丈夫です。

ここからはMacの人とWindowsの人で手順が変わります。

【Macの人】
「ターミナル」というアプリケーションがインストールされていると思うので、それを起動してください。
terminal.png上から二番目右から二番目の黒いアプリケーションです。

起動したら「sh 」と入力し、先ほど保存した「makePreloadImages.sh」をドラッグ&ドロップしてEnterキーを押してください。
しばらく待つと、同じフォルダにpreloadImages.txtというファイルが生成されるので開いてみると画像ファイルの配列が生成されています。


【Windowsの人】
「Windows shell script」等のキーワードで検索し、上記スクリプトを実行してください。
手抜きかと思うかもしれませんが、ここで書くよりわかりやすい説明がたくさん出てきますw


以上です。
大幅な時間短縮になると思うので手動でプリロードのファイルを記述していた方はやってみてください。
不明点があればメールかtwitterでお気軽にどうぞ!
19
2017

【EP2ネタバレ】2017/6/19にいただいたweb拍手への回答

Category未分類
こんばんは!シナリオ担当の中三です。
web拍手の非公開コメントにてご質問いただいたので、この場を借りて回答させていただければと思います。

◆舞花が亡くなった際に七見が挙動不審な件
 例の写真のいたずらの件で、彼なりに舞花に対して罪悪感を感じていたからです。

◆なぜ19歳限定の求人だったのか
 美愛(と犯人)が、奈緒を呼んだのはメイドとしての役割はもちろんですが、何かあって人数が足りなくなった場合の予備枠としての意味もありました。
その際、玲愛や他の子と同じ年齢に合わせたい…という理由から19歳に限定した求人にした。
…という理由だったのですが、すみません。どこにもその記載がありませんでした。
申し訳ございません、シナリオミスです…!!お恥ずかしい限りです。
次回アップデートにて、追記させていただければと思います。(6月末~7月頭)になるかと思います。ご指摘いただき、ありがとうございました!

◆颯馬の件
 ごめんなさい。これは、この先のエピソードで明かされるので今は秘密でお願いいたします><

ご質問への回答は以上となります。
また、ご感想の方もありがとうございました!
各キャラに対して触れていただいて、とても嬉しかったです。
今後とも、よろしくお願いいたします。



05
2017

【ネタバレあり】EP2を遊んでくれた会社の先輩の感想とダメだし

Category未分類
こんばんは、シナリオ担当の中三です。
備忘も兼ねて、EP2を遊んでくれた会社の先輩(上司?)の感想をまとめました。ネタバレです。


先輩「全部やったよ!」
私「ど、どうでした……?」
先輩「全体的には良かったよ。特に最後の楓だっけ?相棒がメイドだったのは良かった」
私「楓じゃなくて颯馬ですね」
 私心の声(よかったーーーーー)
先輩「そう、颯馬。でも髪型がタイプじゃなかった」
私「何それウケる」
 私心の声(髪型はスルーだな……)
先輩「いや、真面目に言ってるから。重要なキャラだから、怪盗キッドみたいにクールな髪型のほうが良かった」
私「そうですか……」
 私心の声(クール!?キッドの髪型が!?クール!?クールなのかあれ!?)
先輩「あとはあれだな。犯人に意外性がなかった」
私「えっ。先輩思いっきりはずしてましたよね?この前奈緒(メイド)が犯人って言ってましたよね?」
先輩「そうなんだけど、普通に考えたらメイドが眼鏡が犯人じゃん。で、推理モードの選択肢もメイドか眼鏡でしょ?これで、特Aの生徒とか、依頼人(茅野)が犯人だったら意外性があるじゃん」
私「確かに……」
先輩「あとあの勘の子(信吾)に、眼鏡が怪しいって言わせちゃ駄目じゃね?」
私「あれは篤也のことじゃなくて奈緒のことを言ってたつもりだったんですよ。あの二人、コンビニ強盗でかちあってるんで」
先輩「ああ!コンビニ強盗ね!あれは意外性があって良かった。でももっと意外性だすためにも、言わない方が良かったんじゃないか?」
私「そうですかーーー」
先輩「推理の話に戻るけど。やっぱりさ、もっと生き残らせた方が良かったんじゃね?怪しいヤツがどんどん死んでいって、最後二人じゃん?もっと容疑者がいた方が良かったよ」
私「今回事件が複雑だったんで、簡単にしたかったんですよ。それで二択にしたんですけど……」
先輩「簡単がどうかで言うと推理は難しかったw」
私「まじすか」
先輩「そもそもコナンとかも解こうと思って見てないからかな。あんまりそういう思考も鍛えられてないし、答え聞いてなるほどーとは思ったけどあれは解けないよw」
私「ちなみにEP1はどうでした?あっちは解きやすかったっす?」
先輩「どうだったかなー。あれも解こうと思ってやってなかったしな」
私「そうですか……」
先輩「そんなに深く聞いてくれなくて良いよ。さっきも言ったけど、俺はコナンとか解こうと思って見てないから」
私「いや、そういう人結構多いと思うんすよ。私も、コナンとか金田一見る時推理なんかしないですもん。語弊があるかもしれないですけど、人がたくさん死んで意外な動機があればそれでおっけー」
先輩「なるほどw」
私「そういう意味でも、推理はなるべく簡単にしたいんですよね」
先輩「なるほどね」
私「実は今回、推理モードとばせる機能もつけようかと思ってたんですよ」
先輩「え?」
私「なんかこう……推理モードが始まる直前に、今回なら悠が、『どうする?俺から話す?それともお前から話す?』的なことを聞いてきて、『先輩よろ!』って言うと選択肢を選ばなくても悠が勝手に真相を話してくれる……的な」
先輩「それはダメだろ!」
私「え」
先輩「それはダメだよ!!」
私「だって先輩推理しないんですよね?」
先輩「そうだけど、それがやりたくてやってる人もいるだろうし、その形式を変えるのは絶対駄目だよ!」
私「でも選べるんですよ」
先輩「現状、ゲームオーバーになってもやり直せるじゃん?なら基本おっけーだし、とばせるようにする前にもっとやれることあるでしょ。簡単にするなら、推理モード直前にあの人の動きがどうだったとかヒントだすとか……。あとは選択肢ももっと工夫できるでしょ。そうそう、俺みたいに勘で解く場合は、逆に犯人の選択肢は多い方が良いと思った」
私「確かに選択肢は微妙だったかも……」
先輩「そうだよ。とばすのはダメだよ」
私「ちなみにキャラはどうでした?」
先輩「キャラはねー。紫髪の妹(美愛)が超かわいかった。新キャラはなんとも思わなかったな。あとは、みんな名前が難しくて覚えられなかった。9(悠)が相棒で赤髪(七見)が7ってことしか覚えてない」
 私心の声(ま、まじかよ……!名前そんなに難しかったか……!?)
私「ど、どっちかっていうと推理よりもキャラのがメインなのでそれだと困るんですよね……!!」
先輩「まあこれは俺の好みかな。7は、見た目がチャラくて欠点がないから親近感が持てないんだよ。9の方が暗い過去があって親近感がわく」
私「そっすかーーーーーまあ9がメインなんで良いんですけどそうですかーーーー」


……といった感じでした。
推理をつくるのは難しいですね、やっぱり……。
矛盾がなくて納得感があるのはもちろんですし、
要素をなるべくシンプルにして分かりやすくするのも必要で。
一応乙女ゲームなので推理はメインではないのですが、
とはいえ意外性の部分はかなり納得でした。
ここでいただいた指摘は、EP3の方で活かせればと思います。










上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。