RPGの作り方 (マップ・イベント編)

この記事ではRPGの作り方を解説します。

なおRPGすべてを網羅するのは大変なので、この記事ではRPGの核となる「マップとイベント」のシステムについて解説します。

「マップとイベント」のシステムを作る

「マップとイベント」のシステム構築に必要な要素は以下のとおりです。

  1. マップデータの作成、読み込みと配置
  2. マップをキャラクターが移動できるようにする(別のマップへの移動も含む)
  3. オブジェクトを調べられるようにする (またはイベントの発生)
  4. 調べると会話メッセージが表示される
  5. イベントフラグとイベント変数の管理
  6. アイテムの管理(インベントリ)

1. マップデータの作成、読み込みと配置

マップデータの作成は、ゲームエンジンがサポートしているエディタがあればそれを使うのが早いです。Unityの場合はアセットを使う方が効率化できるようです。

もし開発環境にマップエディタがない場合は「Tiled Map Editor」を使うのが良いと思います。

2Dゲームを作るのであれば、このエディタがあればできないことはないと思います。

Tiled Map Editorについては以下の記事で解説しています。

Tiled Map Editorの使い方

Tiled Map Editorのデータは XML形式で、たいていのプログラム言語のライブラリにはXMLのパーサーが用意されているので読み込みは難しくないです。もしXMLのパーサーが用意されていなくても、Tiled Map Editorには JSONやCSVなど様々なテキストフォーマットで出力する機能が用意されています。

マップのデータ構造

RPGに必要なマップのデータ構造は以下の3つです。

マップのデータ構造
  • 1. 地形情報
  • 2. 地形コリジョン
  • 3. イベント配置情報

まず地形の見た目となる情報を配置します。次にプレイヤーが移動できる場所「地形コリジョン」を定義します。そしてさらに、会話イベントや調べられるポイントとなる「イベント配置情報」を持ちます。

この3つのデータがRPGに最低限必要なマップ情報となります。

なお、Tiled Map Editorでこのデータ構造を定義するとこのようなレイヤー構造になると思います。

マップデータのレイヤー構造の例

2.マップをキャラクターが移動できるようにする (マップとの当たり判定)

アクションゲームが作れればこのステップは難しくないと思います。

マップとの当たり判定について通常はゲームエンジンがサポートしているのですが、もし自作しなければならない場合には以下のページが参考になると思います。

2D矩形マップとの当たり判定

2Dでの当たり判定は、通常XY軸を分離して衝突応答を行えば、正しく押し返しができるようになります。RPGツクールのようなマス目移動であれば、移動先に障害物があるかどうかを判定すれば実装できます。

3.イベント発生条件の指定(オブジェクトを調べられるようにするなど)

アクションイベントと自動イベント

ここで実装するのは、例えば落ちているアイテムを調べる処理です。

RPGツクールのように、マス目状にマップが区切られている場合は、4方向のどちらから調べられるのか、という情報が必要です。

またはキャラクターに話しかけるイベントです。

なお、この2つは「ユーザーがアクションを起こす (決定ボタンを押す)」にことにより発生するイベントです。

これに対して、プレイヤーが特定のポイントに移動したことで自動的に発生するイベントがあります。これを「自動イベント」と呼びます。

イベントの発生条件には大きく分けてこの2種類があることを知っておくと、イベントの仕組みが作りやすくなります。

  • アクションイベント: プレイヤーが行動を起こすことで発生するイベント (発生方向がある)
  • 自動イベント:プレイヤーが特定のポイントに移動したら自動で発生するイベント (位置で発生)

イベント発生条件

イベントについてもう一つ考える必要があるのが、発生条件です。例えば「棚からものが落ちてくる」というイベントは通常1回のみとなります。これを制御するのが「イベントフラグ」という考え方です。

イベントフラグは「ON」「OFF」の2つの値のみを持ちます。いわゆる「フラグが立つ」というのはイベントフラグが ON になった状態です。

イベントフラグは以下のルールで運用すると、バグの少ないRPGを作ることができます。

  • ゲーム開始時はすべて「OFF」にする
  • 一度「ON」にしたら、基本的には「OFF」にしない(※使いまわし禁止)
  • グローバルフラグとローカルフラグを用意する
  • イベント開始フラグとイベント終了フラグを用意する

まずゲーム開始時にはイベントフラグはすべて「OFF」の状態で開始します。そして、イベントフラグを「ON」にしたら基本的には「OFF」にしない方が良いです。

なぜかというと、いったん「ON」にしたイベントフラグを「OFF」に戻すということは、そのイベントフラグをまたどこかで「ON」にするからです。これはイベントフラグが複数の意味で使用されることとなり、イベントフラグの状態遷移が複雑になるためです。ですので、例えば「棚からものが落ちてきた」というフラグであれば、それ以外の用途としてフラグを使用しない方が良い、ということになります。(※ただし、棚から物が落ちたことで新しいアイテムが手に入るフラグとして使う……など直列的なイベントの発生条件として使用する場合は問題ありません)

次に、「グローバルフラグ」と「ローカルフラグ」を用意することです。

  • グローバルフラグ:ゲーム内のどこからでも参照できるフラグ
  • ローカルフラグ:そのイベント内からのみ参照できるフラグ

例えば、登場人物Aとの会話の進行(話しかけるごとにメッセージが変化するなど)をグローバルフラグで制御すると、そのキャラクターとの会話だけでイベントフラグを消費してしまいます。そのため、そのイベント(キャラクター)のみが参照できるローカルフラグを使えるようにすると、管理がシンプルになります。

またイベントフラグは「ON」「OFF」の2値ですが、イベント変数を用意しておくとさらに幅広い制御が可能となります

  • イベント変数:イベント制御に使える整数値。4byte整数値(-2147483648〜2147483647)が使えれば基本的に問題ない

例えばキャラクターとの会話の進行のようなものは、フラグよりも整数値で管理すれば、状態が把握しやすくなります。

  • 会話進行度0:話しかけても無言
  • 会話進行度1:少しだけ情報をくれる
  • 会話進行度2:相談ごとを持ちかけられる(サブクエストの発生など)
  • 会話進行度3:フレンドリーに会話してくれる

また数値を扱えるようにすることで、謎解きで入力されたダイヤル番号をイベント変数に保存して、別の場所のイベントで正解判定を行うといった応用もやりやすいです。

イベント変数も、イベントフラグと同様にそのイベントからのみ参照できる「ローカルイベント変数」、ゲーム全体で扱うことができる「グローバルイベント変数」を用意しておくと良いでしょう。

まとめ:イベントに必要な情報の定義

よって1つのイベントには以下の情報が必要となります。

  • イベントID:発生するイベントID、もしくはイベント名(スクリプトとの紐付けに使う)
  • イベントコリジョン:コリジョンの座標とサイズ。RPGツクールのようなマス目の場合は方向
  • 発生条件 > イベント開始フラグ:イベントが開始可能となるフラグ
  • 発生条件 > イベント終了フラグ:イベントが発生しなくなるフラグ
  • 発生種別:「アクションイベントまたは自動イベント」などのイベントが発生する種類

この情報を定義したテーブルの実装例を記載しておきます。

イベントID種別座標(X)座標(Y)方向開始フラグ終了フラグコメント
NPC_001会話105全てなしなしNPCの001に話しかけたときに発生するイベント
TRASURE_001調べる822全てなしLF_01宝箱イベント。開けると “LF_01” がONになる
AUTO_NPC_002自動57全てFLG_ITEM_001LF_01宝箱を開けて得られるITEM_001がONになると発生するイベント
NPC_003調べる2125なしLF_01木の後ろに隠れているNPCとのイベント
イベント発生条件の実装例

“NPC_001” は、開始フラグと終了フラグが指定されていませんが、このイベントはいつでも発生するイベントとなります。それに対して “TRASURE_001” には終了フラグが設定されており、宝箱を開けることでこのイベントは存在しなくなります。正確には宝箱を開けたという見た目の変化に対応する設定が必要となるかもしれませんが今回は省略しています。

“AUTO_NPC_002” は、宝箱を開けたことにより得られたアイテムのフラグが発生条件となります。

なお、Tiled Map Editor で「オブジェクトレイヤー」を使うと、”カスタムプロパティ” で細かくイベントの発生条件を指定できるので、Tiled Map Editorを使う場合はこの機能を使って良いかもしれません。

オブジェクトレイヤーのカスタムプロパティを使用する

4.イベントスクリプトの実装

会話メッセージを表示したり、キャラクター画像(立ち絵など)の表示、イベントフラグの制御などには、専用のスクリプトを用意した方が良いです。

とは言え、独自のスクリプト言語を実装するのは大変なので、既存のゲーム用のスクリプト言語を導入するのが良いと思います。

C# が使えれば、Miniscriptというのが評判が良いようです。

上記は英語ページですが、日本語で組み込み方法が紹介されているのでこちらを参考にするのが良いかもしれません。

もしくは「Lua」を組み込んでみても良いかもしれません。

補足として、イベントの呼び出しは「名前(関数名)」指定で呼び出せるようにしておくと、スクリプトの記述がやりやすいと思います。

スクリプト言語を自作する場合

もし、MiniscriptやLuaの組み込みができない場合は、スクリプト言語を自作する必要があります。

自作……となると「すごい大変そう」という印象を受けてしまうかもしれませんが、どこまで作り込むかで実装コストは変わります。

  • “演算子” や “条件分岐”、 “関数呼び出し” などの「制御構造」
  • 定数やイベントフラグ、イベント変数など、ゲームと連動するためのデータの扱い
  • ゲームから指定の関数を呼び出す機能
  • ローカル変数の定義
  • データ構造の利用(配列やリスト、ハッシュなど)
  • データ構造の宣言(クラス宣言など)

MiniscriptやLuaではこれらに相当する機能が実装されており、とても多機能となっています。ただこれをいきなり実装するのは大変です。

そこで自作する場合、まず以下のように仕様を限定すると難しくないと思います。

  • 上から下に実行するだけ(IF文や関数呼び出しなどの制御構造はなし)
  • プログラムに直接テーブルを記述する(外部テキストにすらしない)
  • 使用可能なイベントフラグ (変数) は 0番から100番 までの固定長にする

例えばC++による実装例です。

// コマンドオブジェクト.
struct COMMAND {
  int Code; // 命令コード.
  int Params[8]; // パラメータ。最大8個まで.
};
 
// 命令コード.
enum eCode {
  eCode_Message, // テキストメッセージ表示.
  eCode_Bg,      // 背景表示.
  eCode_BgOff,  // 背景非表示.
  eCode_Wait,    // 一時停止.
  eCode_End,     // 終了.
};
 
// 実行コードテーブル.
static COMMAND_OBJ s_CodeList[] = {
  {eCode_Bg,      {1}},  // 背景画像 "1" を表示する.
  {eCode_Message, {3}},  // テキストID "3" を表示する.
  {eCode_Wait,    {30}}, // 30フレーム待つ.
  {eCode_BgOff,   {}},   // 背景消去.
  {eCode_End,     {}},   // 終了.
};

もしデータを外部化したい場合は、以下のようなテキストに置き換えても良いかと思います。

BG,1     // 背景画像 "1" を表示する.
MSG,3    // テキストID "3" を表示する.
WAIT,30  // 30フレーム待つ.
BG_ERASE // 背景消去.
END      // 終了.

1行が1つの命令となり、カンマ区切りでパラメータを指定します。会話で分岐が発生しない場合はこれだけで実装可能です。

「こんな単純なスクリプト言語使い物にならないのでは……?」と疑問に思うかもしれませんが、イベント中に分岐が発生しない場合にはこれで問題ありません。RPGだとやはり会話で分岐を発生させたくなるかもしれませんが、それについては「イベントの発生条件」に細かく条件を指定することである程度の制御は可能です。

ですが「やはりちゃんとしたスクリプト言語が欲しい……」となった場合は、本格的なスクリプト言語を作る本が参考になるかと思います。

こういった本では、「字句解析」「構文解析」といったスクリプト言語を作る上での必要な知識が書かれています。字句解析と構文解析について簡単に説明します。

例えば以下の独自スクリプトがあるとします。

if(a == 0) {
  print("hello");
}

これを字句解析すると、以下のようにスクリプトが分解されます。

 キーワード説明
ififキーワード
(式のかたまり開始
a変数 “a”
==比較演算子
0数値 “0”
)式のかたまり終了
{ブロック開始
printprint関数キーワード
(関数パラメータ開始
“hello”文字列 “hello”
)関数パラメータ終了
}ブロック終了

プログラムにおける最低銀の意味を持った単語に分解すること「字句解析」と呼ばれます。そして次の構文解析でこの字句を意味のある階層構造にします。

構文解析とは、以下のような構文木を作ることです。

構文木

構文木とは、文字通りプログラム構文を木(ツリー構造)にデータ化したものとなります。

この構文木を上からたどり、下から評価してきます。

構文木の評価

そして評価結果が「真」であれば、IF文のブロックを処理していきます。

構文木の評価結果

以上、字句解析と構文解析の処理についてざっくり説明しましたが、考慮すべきことがたくさんあります。

  • 演算子の評価方法(左辺・右辺の考え方)
  • 変数 “a” の評価方法
  • 評価結果の保持方法
  • print命令の処理方法
  • IF文が「偽」だった場合の処理
  • そもそも字句解析をどうやってやるのか。構文木をどうやって作るのか……?

このあたりは大変ですが、本やネット上の資料を調べて少しずつ実装していくのが良いと思います。

まずは、四則演算などの式の評価や、変数の実装から始めてみても良いかもしれません。

5. イベントフラグとイベント変数の管理

これについてはすでに紹介したので、省略します。

6. アイテムの管理 (インベントリ)

RPGのアイテムには大きく分けて、以下の3種類あります。

  • 消耗品:使うとなくなるアイテム
  • 装備品:装備してキャラクターを強化するアイテム
  • 貴重品:イベント専用アイテム

これらを明確に分ける必要がある場合とそうでないときがありますが、通常のRPGは明確に分けます。理由としては、すべてを同じアイテムとして扱ってしまうと、ユーザーが使いたいアイテムが見つかりにくくなるためです。そのためインベントリのカテゴリをそれぞれで分けて管理できるようにしたほうが親切となります。

ただローグライクの場合はインベントリ上、消耗品と装備品を一緒に入れることもあります。

参考資料として、昔ローグライクを作成したときのアイテムテーブルの実装例です。

消耗品アイテムテーブル

  • id: アイテムID
  • type: アイテム種別 (Food:食べ物。Potion:薬。Scroll:巻物。Wand:杖。Orb:オーブ)
  • name: アイテム名
  • hp: HP回復量
  • food: 満腹度回復量
  • atk: 投げたときの攻撃力
  • range: 効果範囲 (all:全体攻撃)
  • extra: 状態異常
  • extval: 状態異常の効力
  • buy: 販売価格
  • sell: 売却価格
  • sort: ソートキー
  • detail: 説明文
  • log: ログに含めるかどうか
idtypenamehpfoodatkrangeextraextvalbuysellsortlog
intstrstrintintintstrstrintintintintint
FOOD1Foodリンゴ5021003511
FOOD2Food大きなリンゴ100430010521
FOOD3Food固いリンゴ258501831
FOOD4Food毒リンゴ252poison60021041
POTION1Potion回復薬1010102072001
POTION2Potion回復薬20201050182011
POTION3Potion回復薬303010100352021
POTION4Potion回復薬404010250882031
POTION5Potion回復薬5050105001752041
POTION6Potion回復薬6060107502632051
POTION7Potion回復薬70701010003502061
POTION8Potion回復薬80801012004202071
POTION9Potion回復薬90901014004902081
POTION10Potion回復薬1001001016005602091
POTION11Potion回復薬1101101018006302101
POTION12Potion回復薬1201201020007002111
POTION13Potion回復薬1301301022007702121
POTION14Potion回復薬1401401024008402131
POTION15Potion回復薬1501501026009102141
POTION16Potion回復薬1601601028009802151
POTION17Potion命の薬10hpmax35001752161
POTION18Potion胃拡張の薬10food203001052171
POTION19Potion力の薬10str15001752181
POTION20Potion毒薬10poison5001752191
POTION21Potion眠り薬10sleep3001052201
POTION22Potionしびれ薬10paralysis5001752211
POTION23Potion混乱の薬10confusion3001052221
POTION24Potion怒りの薬10anger10003502231
POTION25Potion元気になる薬10powerful5001752241
POTION26Potionいやしの薬10recover3001052251
POTION27Potion封印の薬10closed3001052261
POTION28Potionワープの薬10warp5001752271
SCROLL1Scroll魔弾の巻物Lv110all5001752321
SCROLL2Scroll魔弾の巻物Lv230all15005252331
SCROLL3Scroll魔弾の巻物Lv350all500017502341
SCROLL4Scroll袋拡大の巻物Lv1itemadd120007002351
SCROLL5Scroll袋拡大の巻物Lv2itemadd2300010502361
SCROLL6Scroll武器強化の巻物weapon110003502371
SCROLL7Scroll防具強化の巻物armor110003502381
SCROLL8Scroll麻痺の巻物1paralysis10003502391
SCROLL9Scroll眠りの巻物1sleep10003502401
SCROLL10Scroll混乱の巻物1confusion10003502411
SCROLL11Scroll武器合成の巻物weponadd1500525242
SCROLL12Scroll防具合成の巻物armoradd1500525243
SCROLL13Scroll魔除けの巻物nightmare100300010502441
WAND1Wand毒の杖poison300010502481
WAND2Wand眠りの杖sleep9003152491
WAND3Wand麻痺の杖paralysis20007002501
WAND4Wand混乱の杖confusion9003152511
WAND5Wand怒りの杖anger600210252
WAND6Wand火の杖0300105253
WAND7Wand封印の杖closed9003152541
WAND8Wandワープの杖warp15005252551
WAND9Wandチェンジの杖change9003152561
WAND10WandHP交換の杖hpswap15005252571
WAND11Wand炎の杖109003152581
ORB1Orb赤ネコのオーブ312641
ORB2Orb青ネコのオーブ312651
ORB3Orb白ネコのオーブ312661
ORB4Orb緑ネコのオーブ312671
消耗品テーブル

装備品テーブル

  • id: アイテムID
  • type: アイテム種別 (Weapon:武器。Armor:鎧。Ring:指輪
  • atk: 攻撃力
  • def: 防御力
  • attr: 属性
  • extra: 特殊効果 (drill:壁を壊す。counter:反撃可能)
  • ext_val: 特殊効果の威力
  • food: 満腹度の減少補正
  • buy: 購入価格
  • sell: 売却価格
  • sort: ソートキー
  • detail: 説明文
  • log: ログに含めるかどうか
idtypenameatkdefattrextraext_valfoodbuysellsortlog
intstrstrintintintstrintintintintintint
WEAPON1Weapon木の棒250181001
WEAPON2Weapon木刀4100351011
WEAPON3Weaponダガー6250881021
WEAPON4Weaponレイピア107002451031
WEAPON5Weapon三日月刀1410003501041
WEAPON6Weapon妖刀ムラマサ1615005251051
WEAPON7Weapon聖騎士の剣20300010501061
WEAPON8Weaponドリル5drill1005001751071
ARMOR1Armorローブ2150181201
ARMOR2Armor毛皮の鎧41100351211
ARMOR3Armor鎖かたびら61250881221
ARMOR4Armorエルフの鎧1017002451231
ARMOR5Armorメタルアーマー14110003501241
ARMOR6Armor銀のジャケット16115005251251
ARMOR7Armorプラチナメイル201300010501261
ARMOR8Armor反撃の鎧10counter120007001271
RING1Ring力の指輪13001051401
RING2Ring守りの指輪13001051411
RING3RingHP増加の指輪hpmax103001051421
RING4Ring眠りよけの指輪sleep20007001431
RING5Ring混乱よけの指輪confusion20007001441
RING6Ring通過の指輪passage25008751451
RING7Ring力の指輪+125001751461
RING8Ring力の指輪+2310003501471
RING9Ring力の指輪+3425008751481
RING10Ring力の指輪+45500017501491
RING11Ring守りの指輪+125001751501
RING12Ring守りの指輪+2310003501511
RING13Ring守りの指輪+3425008751521
RING14Ring守りの指輪+45500017501531
RING15RingHP増加の指輪+1hpmax205001751541
RING16RingHP増加の指輪+2hpmax3010003501551
RING17RingHP増加の指輪+3hpmax4025008751561
RING18RingHP増加の指輪+4hpmax50500017501571
RING19Ringプラチナリング11hpmax1015005251581
RING20Ringドラゴンの指輪3hpmax30500017501591
装備品テーブル

YouTube

今回の記事を動画にしました