本当にゲームを完成させるためのイベントの作り方

VX以降のツクールシリーズではコモンイベントとスクリプトを活用して、直しに強いイベント処理を手早く作ることができます。ゲーム制作は細かな改善の繰り返しであり、よく使う処理をまとめて修正の手間を減らし、ファイルの差し替えを容易にしておくことは非常に重要です。

特にイベント処理の中で小さなスクリプトが役立つ場面は多く、使いどころがわかるとイベント作成が格段に楽になるはずです。この記事ではそういった自分使い用のちょっとしたスクリプトの作例と、イベントコマンド／コモンイベントだけでは対応できない問題を解決するための基本的な手法を紹介します。

# 下準備

ツクールMVのプロジェクトフォルダにある index.html を開いて、rpg_windows.js の次の行に以下のコードを挿入してください。

```html:index.html
<script type="text/javascript" src="js/myapp.js"></script>
```

jsフォルダの中に myapp.js を作成して編集していきます。MVからテストプレイ時にコンソールを開くオプションがなくなってしまったので、開いてくれるようにしておきましょう。

```js:myapp.js
if (Utils.isNwjs() && Utils.isOptionValid('test')) {
    require('nw.gui').Window.get().showDevTools().moveTo(0, 0);
    window.focus();
}
```

次にプロジェクト用の名前空間を確保しておきます。これで準備は整いました。

```js:myapp.js
var MYAPP = {
    se: {},
    json: {}
};
```

# イベントコマンドの作成

ゲーム中、最も頻出するであろう「アイテム／お金を入手する機会」を、スクリプトで効率よく処理できるようにしてみます。下記のコードでは「イベントの簡単作成」で生成される宝箱イベントを再現してあります。さすがにそのままだと味気ないので、ちょっとだけ気を利かせました。

- アイテム名の色を変えて目立たせる
- アイテム名の前にそのアイテムのアイコンを表示して、カテゴリをわかりやすくする
- 取得するアイテムの個数を設定できるようにする
- 空の宝箱を設定できるようにする
- 既に開けた宝箱を調べたとき、空の宝箱と同じメッセージを表示させる

```js:myapp.js
var MYAPP = {
    se: {
        openTreasure: {name: 'Chest1', pan: 0, pitch: 100, volume: 90},
    },
    json: {
        openTreasureRoute: '{"list":[{"code":36,"indent":null},{"code":17},{"code":15,"parameters":[3]},{"code":18},{"code":15,"parameters":[3]},{"code":0}],"repeat":false,"skippable":false,"wait":true}',
    }
};

Game_Interpreter.prototype.openTreasure = function(item, i) {
    var event = $gameMap.event(this._eventId);
    AudioManager.playSe(MYAPP.se.openTreasure);
    event.forceMoveRoute(JSON.parse(MYAPP.json.openTreasureRoute));
    var q = setInterval(function() {
        if (event._moveRouteIndex === 0) {
            clearInterval(q);
            $gameSelfSwitches.setValue([event._mapId, event._eventId, 'A'], true);
            switch (item) {
                case undefined:
                    this.emptyTreasure();
                    break;
                case 'money':
                    this.getMoney(i);
                    break;
                default:
                    this.getItem(item, i);
            }
        }
    }.bind(this), 0);
};

Game_Interpreter.prototype.emptyTreasure = function() {
    $gameMessage.add('からっぽ！');
};

Game_Interpreter.prototype.getItem = function(item, i) {
    var text = '\\I[' + item.iconIndex + ']\\c[6]' + item.name + '\\c[0]';
    text += i > 1 ? ' を ' + i + '個 手に入れた！' : ' を手に入れた！';
    $gameParty.gainItem(item, i);
    $gameMessage.add(text);
};

Game_Interpreter.prototype.getMoney = function(i) {
    $gameParty.gainGold(i);
    $gameMessage.add(i + '\\G 手に入れた！');
};
```

使い方はイベントコマンドのスクリプトで、下記のように入力するだけです。

```js
this.openTreasure($dataItems[1], 2); // 宝箱を開けてアイテム[1]を2個手に入れる
this.openTreasure('money', 500); // 宝箱を開けて500\Gを手に入れる
```

![](https://qiita-image-store.s3.amazonaws.com/0/100043/df37d340-c444-07a8-e775-7bc34ccc2bc1.png)

副産物として、ファイル名を直接指定せずにSEを鳴らしたり、アイテム／お金を手に入れる際に決まった書式の通知を出したりすることができるようになりました。こうしておくことで効果音ファイルの差し替えや演出の調整を、後からでも簡単に行えるようになります。

```js
AudioManager.playSe(MYAPP.se.openTreasure); // 宝箱を開けるSEを再生する
this.getItem($dataItems[1], 1); // アイテム[1]を1個手に入れる
this.getMoney(500); // 500\Gを手に入れる
```

# 条件分岐の判定

例えば主人公の強さによって対応が変わるNPCを作るとして、その条件はどうするのが妥当でしょうか。仮にレベルにするとして、何レベルから？　これは結局のところゲームバランス次第であり、ゲームが完成するまで常に調整される可能性があります。NPCが特定の1～2人程度ならまだしも、ゲーム中の全村人が対象だったりすると、手が付けられなくなってしまうでしょう。

こういったケースでは、条件分岐の判定をスクリプトで設定しておくことで問題を解消できます。式によって真偽値を返す関数を作り、イベントエディタ上でその関数を条件に設定してください。これなら後から条件を変えたくなっても関数を修正するだけで済みます。大量のイベントを個別に編集する必要はありません。

```js:myapp.js
// this.isStrongActor(1); // アクター[1]を強者と見なすか
Game_Interpreter.prototype.isStrongActor= function(i) {
    return $gameActors.actor(i)._level >= 40;
};
```

# スクリプト解説

宝箱コマンドのスクリプトではいくつか妙なことをやっているので、簡単に解説をしておきます。

## 移動ルートのJSON作成

移動ルートを自力で組むのは大変なので、イベントコマンドで作ったものをJSON文字列にして使っています。Game_Interpreter.prototype.command205 に以下のコードを挿入すると、移動ルート実行時にJSON文字列がコンソールに出力されます。

```js:rpg_objects.js
console.log(window.JSON.stringify(this._params[1])); // 移動ルートをJSON文字列で出力
```

## 特定の処理を待つ方法

MVでは yield が使えないため、代わりに setInterval で対応しています。宝箱コマンドではそのまま実行するとすぐにセルフスイッチがオンになってしまい、宝箱が開く演出が終わる前に完全に開いたグラフィックになってしまうため、移動が終わるまで次の処理が行われないようにしています。

setInterval を使った遅延処理は「次の処理に進む条件」を変えるだけで、大抵の処理を待つことができます。

```js
Game_Interpreter.prototype.hoge = function() {
    this.wait(60);
    var queue = setInterval(function() {
        if (this._waitCount === 0) {
            clearInterval(queue);
            // ウェイト終了後に実行
        }
    }.bind(this), 0);
};
```

## Rubyで書いた場合

見比べるとそれぞれの違いがわかりやすいかと思い、記事中のjavascriptと同じ動作をするコードをRuby（VX Ace）でも作ってみました。Rubyの場合はアイテムオブジェクトをそのまま引数で渡すことができないため、ひとまず文字列で渡して、アイテム取得時に eval を使ってオブジェクトに変換しています。

```rb
module Sound
  def self.play_open_treasure
    Audio.se_play("Audio/SE/Chest", 100, 100)
  end
end

class Game_CharacterBase
  attr_writer :direction_fix
end

class Game_Interpreter
  def open_treasure(item_object_s, i=1)
    event = $game_map.events[@event_id]
    event.direction_fix = false
    Sound.play_open_treasure
    event.set_direction(4)
    wait(3)
    event.set_direction(6)
    wait(3)
    $game_self_switches[[@map_id, @event_id, "A"]] = true
    case item_object_s
    when nil
      empty_treasure
    when "money"
      get_money(i)
    else
      get_item(item_object_s, i)
    end
  end

  def empty_treasure
    $game_message.add("からっぽ！")
  end

  def get_item(item_object_s, i)
    item = eval item_object_s
    text = "\\I[#{item.icon_index}]\\c[6]#{item.name}\\c[0]"
    text += i > 1 ? " を #{i}個 手に入れた！" : " を手に入れた！"
    $game_party.gain_item(item, i)
    $game_message.add(text)
  end

  def get_money(i)
    $game_party.gain_gold(i)
    $game_message.add("#{i}\\G 手に入れた！")
  end

  def strong_actor?(i)
    $game_actors[i].level >= 40
  end
end

# open_treasure("$data_items[1]", 2) # 宝箱を開けてアイテム[1]を2個手に入れる
# open_treasure("money", 500) # 宝箱を開けて500\Gを手に入れる

# Sound.play_open_treasure # 宝箱を開けるSEを再生する
# get_item("$data_items[1]", 1) # アイテムを1個手に入れる
# get_money(500) # 500\Gを手に入れる

# strong_actor?(1) # アクター[1]を強者と見なすか
```

![](https://qiita-image-store.s3.amazonaws.com/0/100043/1ae5b3a6-aa45-1b7f-b5e9-26c267ee5ff1.png)