JRPGの作り方:ゲーム開発者向けの手引き
Japanese (日本語) translation by Sambashi (you can also view the original English article)
この記事は、JRPG(Japanese Role-Playing Game)を作るための概要です。JRPGには、初期の頃のファイナルファンタジー(以下FF)などがあります。 本記事では、JRPGの骨格を作るためのアーキテクチャやシステムについて触れます。内容は、ゲームモデルの管理の仕方、タイルマップの使い方と世界の表示方法、そしてRPGの戦闘システムのコーディング法などです。
ノート:この記事ではJavaに似た疑似言語を使っていますが、記事の核心はどの開発環境にも適用できるものです。
コンテンツ
JRPGの意外な発祥の地

1983年に、堀井雄二、中村光一、千田幸信の3人がアメリカに渡り、AppleFest '83に参加しました。開発者が集い、Apple II(※PCの名前)向けの最新の作品を発表する会です。 彼ら3人は、WizardryというRPGの最新版に衝撃を受けました。
日本に帰るとき、彼らはDungeon Warrior(※海外版のドラクエの名前)を作ることを決めました。Wizardryと同じくRPGですが、NES(海外版スーパーファミコン)向けに最適化されたゲームです。 猛烈に売れて、ドラクエはJRPGというジャンルを定義しました。 ドラクエはアメリカではそれほど売れませんでしたが、数年後、別のゲームが大ヒットしました。
1987年、元祖ファイナルファンタジーがリリースされ、地球上で最も売れたゲームの1つとなり、少なくとも西ではJRPGの象徴になりました。
RPGというジャンルについて
ゲームジャンルは決して正確には定義されません。ジャンルとはあいまいなものです。 RPGにはレベル制があったり、武器・防具・スキル・ステータスを持つ複数のプレイヤキャラがいたり、戦闘と探索モードがあり、強力な物語がある傾向があります。ゲーム進行は、よくマップを進むことに依存します。
JRPGはドラクエを型にして作られました。直線的なゲーム進行で、戦闘はターン制で、たいていワールド・ローカルの二種類のマップがあります。 典型的なJRPGは、ドラクエ、FF、Wild Arms、ファンタシースター、クロノトリガーなどです。 この記事で扱うのは、初期のFFに似たものです。



RPGを作るべき5つの理由
1.JRPGは時の試練に耐え続けている
FF6やクロノトリガーといったゲームは、今遊んでもおもしろいです。 もしあなたがJRPGを作れば、モダンなプレイヤも受け入れる、時代に依存しづらいゲームの形式を知ることになります。 JRPGはあなたのひねりを加えたり実験するのに素晴らしい枠組みです。物語や、描画やメカニクスなどにおいて。 あなたが初めにリリースするゲームが数十年も楽しんで遊ばれるとしたら、素晴らしいことです!
2.RPGのゲームメカニクスは、広く応用できる
Call of Dutyは最も人気のFPSの1つですが、RPGの要素を使っています。FarmVilleでにぎわっているソーシャルゲームブームは、基本的に海外版ファミコンのRPG、Harvst Moonのクローンです。さらに、グランツーリスモのようなレーシングゲームにさえ、レベルや経験値があります。
3.制約があるため創造性が高まる
作家は白紙に脅かされ、ゲーム開発者は新しいゲームを設計する時に無限の可能性に麻痺するでしょう。 JRPGにおいてはいくつもの選択が決まっており、あなたは迷うことなく、あなたを妨げる問題のほとんどにおいて慣習に従うことができます。
4.一人で作れる
FFはほぼ一人のプログラマによってコーディングされました。Nasir Gebelliです。しかも彼は、アセンブラリでそれをやってのけました! この手のゲームを作るのは、現代の言語やツールをもってすれば遙かに簡単です。 ほとんどのRPG制作で最も大きな部分は、プログラミングではなくコンテンツですから。けれども、あなたのゲームにおいてもそうとは限りません。 もしあなたがコンテンツを絞り、量よりも質に注目すれば、RPG(プログラミング)は巨大なソロプロジェクトになります。
チームを作るのはどんなゲームを作るのにも役立ちます。絵や音楽を外注依頼したり、opengameart.orgなどにある素晴らしいクリエイティブコモンのアセットを利用するのもいいかもしれませんね。 (Editor's note: 私達の姉妹サイトのGraphicRiverはスプライトシートの販売もしています。
5.儲けるために!
JRPGには熱心なファンがいます。下の写真のようないくつものインディーズゲームがよく宣伝されており、Steamなどで購入できます。



アーキテクチャ
JRPGはとても多くの型とメカニックを共有しているため、典型的なRPGをいくつものシステムに分解することができます。

ソフトウェア開発において、あるパターンが何度も何度もみられます。階層化です。 階層化とは、プログラムのシステムが、どのようにお互いの上部を利用し合うかを指します。底のレイヤは広く適用され、上のレイヤは使いやすいです。 JRPG(のシステム)も全く同じで、いくつものレイヤとして捉えることができます。下層は画像系の関数などを扱い、上層はクエストやキャラのステータスを扱います。
図を見れば分かるように、JRPGを作るにはたくさんのシステムが必要です。ですが、ほとんどのシステムは、ゲームの独立した"モード"としてくくり出すことができます。 ワールドマップモード、ローカルマップモード、戦闘モード、メニューモードという風に。 これらのモードはほぼ完全に独立しているので、それぞれの開発は単純になります。
モードは重要ですが、ゲームの内容がなければ無意味です。 RPGには、いくつものマップファイル、モンスター定義、テキスト、カットシーンを動かすためのスクリプト、どのようにプレイヤが進んでいくかを管理するゲームプレイコードなどがあります。 JRPGの作り方を詳しくおさえると、本になってしまいます。そこでこの記事では、最も重要な部分に集中することにします。 ゲームのモードを扱うことは明らかにJRPGを作るのに必須ですので、まずはそこから見てみましょう。
ゲームステートを管理する
下の画像はゲームループを表します。毎フレームUpdate()を呼んでいます。 これがこの記事で扱うゲームのハートビートであり、ほとんどすべてのゲームはこの方法で構築されています。



プロジェクトを始めたけれど、新しい特徴を加えるために失速したり、不可解なバグに閉口したことはありますか? たぶんあなたは、Update()に全てのコードを詰め込もうとし、ほとんど構造化せず、コードは難解な迷宮になったことがあるでしょう。 そこで、コードをいくつかの状態に分割するのがとても有効です。何が起こっているのか、よりはっきりと分かるようになります。
ゲーム開発に一般的な、ある道具があります。ステートマシンです。アニメーション、メニュー、ゲームフロー、AI、あらゆる場所で使われます。あなたのキットに入れるべき重要な道具です。 JRPGでは、ステートマシンは複数のゲームのモードを扱うのに使えます。 普通のステートマシンを見てから、JRPGにあったものへと少しだけ改変しましょう。 でもまずは、ゲームの大きな流れを考えます。



典型的なJRPGはローカルマップ・ゲームモードから始まり、町を自由にあるき回って住人に話しかけることができます。 町から出ると別のゲームモードに移行し、あなたはワールドマップを見ることになります。
ワールドマップはローカルマップにとても似た風に機能しますが、大きなスケールのマップです。木やフェンスの代わりに山や町が見えます。 ワールドマップにいる間に町へ戻れば、ローカルマップモードに戻ります。
ローカルマップ、ワールドマップのいずれにいても、メニューを開きキャラを調べられます。ワールドマップではときどき戦闘が起こります。 上の図は、これらのゲームモードと遷移を表しています。これがJRPGのゲームプレイの基本的なフローであり、私たちがゲームの状態を作るときの元となるものです。
ステートマシンで複雑さに対処する
ステートマシンは、ここではゲームの多様な全ての状態を持ちます。ステートマシンを使って状態を変えることができ、ステートマシンは現在の状態を更新・描画します。
言語の実装によりますが、ステートマシンは、多くの場合StateMachine
クラスと、インタフェイスのIState
から構成されます。
ステートマシンを説明するには、疑似コードを見てもらうのが一番でしょう。
class StateMachine { Map<String, IState> mStates = new Map<String, IState>(); IState mCurrentState = EmptyState; public void Update(float elapsedTime) { mCurrentState.Update(elapsedTime); } public void Render() { mCurrentState.Render(); } public void Change(String stateName, optional var params) { mCurrentState.OnExit(); mCurrentState = mStates[stateName]; mCurrentState.OnEnter(params); } public void Add(String name, IState state) { mStates[name] = state; } }
上のコードは、エラーチェックをしない単純なステートマシンです。
このステートマシンが、ゲームにおいてどのように使われるか見てみましょう。 ゲームの始めにステートマシンが作られ、マシンに全てのゲーム状態が加えられ、初めの状態がセットされます。 それぞれの状態は、Change()の中で使用されるString型のnameによって判別されます。 現在の状態は1つだけであり(mCurrentState)、その状態が更新・描画されます。
例えば下のように使われるでしょう。
StateMachine gGameMode = new StateMachine(); // A state for each game mode gGameMode.Add("mainmenu", new MainMenuState(gGameMode)); gGameMode.Add("localmap", new LocalMapState(gGameMode)); gGameMode.Add("worldmap", new WorldMapState(gGameMode)); gGameMode.Add("battle", new BattleState(gGameMode)); gGameMode.Add("ingamemenu", new InGameMenuState(gGameMode)); gGameMode.Change("mainmenu"); // Main Game Update Loop public void Update() { float elapsedTime = GetElapsedFrameTime(); gGameMode.Update(elapsedTime); gGameMode.Render(); }
例では必要なゲーム状態を作り、StateMachine
に追加し、メインメニューへの開始状態をセットしました。 もしこのコードを走らせれば、MainMenuState
が最初に更新・描画されます。 この状態が、ほとんどのゲームで最初に見る、State GameやLoad Gameのような選択肢のメニューを描画します。
ユーザがStart Gameを選択したとき、MainMenuState
はgGameMode.Change("localmap", "map_001")
のようにし、LocalMapState
が新たな現在の状態になります。 このステートはマップを更新・描画し、プレイヤがゲームを探索できるようになります。
下の図は、WorldMapState
とBattleState
を行き来する様子を視覚化したものです。 ゲームにおいては、プレイヤがマップを歩き回り、魔物に攻撃され、戦闘状態に移行し、マップに戻ることに相当します。



ステートのインタフェイスに軽く目を通しましょう。EmptyState
クラスはインタフェイスを実装しています。
public interface IState { public virtual void Update(float elapsedTime); public virtual void Render(); public virtual void OnEnter(); public virtual void OnExit(); } public EmptyState : IState { public void Update(float elapsedTime) { // Nothing to update in the empty state. } public void Render() { // Nothing to render in the empty state } public void OnEnter() { // No action to take when the state is entered } public void OnExit() { // No action to take when the state is exited } }
インタフェイスIState
は、それぞれの状態に、Update()
, Render()
,OnEnter()
, OnExit()
の4つの関数を持つ事を要求します。
Update()
とRender()
は、現在の状態である場合は毎フレーム呼ばれます。OnEnter()
とOnExit()
は、ステートを替える時に呼ばれます。 後は全て単純です。 これで、ゲームの全ての部品の、全ての状態を作ることができます。
以上が基本的なステートマシンです。 様々な状況下で便利ですが、ゲームモードを扱う場合は、さらに改良できます。 現在のシステムでは、高いオゥヴァヘッド("間接費")がかかりえます。例えば、WorldStateからBattleStateに移り、戦闘し、WorldStateに戻るとき、WorldStateは戦闘前と全く同じ状況でなければなりません。 この手の操作は、説明したような標準的なステートマシンでは扱いづらいです。 より良い解決法は、ステートのスタックを使うことです。
ステートスタックでより簡単にゲームロジックを作る
標準的なステートマシンをスタック式のステートマシンに変えることができます。スタック式ステートマシンは、下図のように動作します。 例えば、ゲームの開始にMainMenuState
が、まずスタックに積まれされます。 Start Gameを選ぶと、LocalMapState
がMainMenuStateの上に積まれます。 このとき、MainMenuState
はもう更新・描画されませんが、後から元のMainMenuStateに戻ることができます。
次です。もし戦闘を始めれば、BattleStateが一番上に置かれます。戦闘が終われば、スタックをポップ※し、元と全く同じマップから再開できます。 もしも死亡すれば、LocalMapStateもポップされ、MainMenuStateに戻ります。 (※ポップ…pop pushの対義語。ここではトースタで焼いたパンが上に飛んでいくイメージ)
下の図はステートスタックを可視化しており、InGameMenuStateがスタックに積まれてから、再び取り出されるまでの様子を表しています。



これでスタックがどのように動くか分かりました。その実装を見てみましょう。
public class StateStack { Map<String, IState> mStates = new Map<String, IState>(); List<IState> mStack = List<IState>(); public void Update(float elapsedTime) { IState top = mStack.Top() top.Update(elapsedTime) } public void Render() { IState top = mStack.Top() top.Render() } public void Push(String name) { IState state = mStates[name]; mStack.Push(state); } public IState Pop() { return mStack.Pop(); } }
上のステートスタックはエラーチェックをしておらず、とても単純です。 ステートはPush()
で積まれ、Pop()
で取り除かれます。スタックの一番上のステートが更新・描画されます。
スタックベースのやり方はメニューにも有効で、多少修正すれば、会話文や通知にも使えます。 もし興味をそそられたら、そのスタックとステートマシンを統合し、スタックも扱うステートマシンを作りましょう。
StateMachine
とStateStack
か、それらを組み合わせたものが、あなたのRPG築くのに素晴らしい土台を作ります。
次の行動:
- ステートマシンをあなたの好きな言語で実装する
-
MainMenuState
とGameState
をIState
を継承して作る - MainMenuStateを初めのステートとしてセットする
- 両方の状態に別々の画像を描画させる
- ボタンを押したとき、MainMenuStateからGameStateに移行するようにする
マップ
マップは世界です。砂漠や宇宙、ジャングルもタイルマップで表現できます。 タイルマップは、大きな画像を作るために小さな画像を組み合わせる1つの方法です。 下の図は、タイルマップがどのように機能するかを表しています。



上の図は3つの部分に分かれています。タイルパレットと、タイルマップの組み立ての表現と、最終的にスクリーン描画されたマップです。
タイルパレットとは、マップを作るのに使われる全てのタイルの集合です。 それぞれのタイルは、整数値で判別されます。 たとえば、図では1番のタイルは草です。
タイルマップはただの数値の配列で、それぞれの数値はパレットのタイルと結びついています。 全て草で覆われたマップを作る場合は、1で埋められた大きな配列を作ればいいです。描画すれば、草タイルで作られたマップになります。 タイルパレットは、たいてい大きなテクスチャとして読み込まれますが、それぞれのタイルは別々のファイルとして保存されていても構いません(読み込むときに1つのテクスチャにできます)。
配列の配列を使わないのは、単純に簡潔さと効率のためです。 もし整数値の単体の配列を作れば、配列は連続したメモリ領域に置かれます。 配列の配列を使えば、横列をへのポインタを持つ配列が作られます。 この間接参照は、キャッシュミスを起こして動作を*かなり*遅くする可能性があります。そしてマップは毎フレームを描画されるので、動作は速いほどいいのです!
タイルマップについて知るため、少々コードを見てみましょう。
// // Takes a texture map of multiple tiles and breaks it up into // individual images of 32 x 32. // The final array will look like: // gTilePalette[1] = Image // Our first grass tile // gTilePalette[2] = Image // Second grass tile variant // .. // gTilePalette[15] = Image // Rock and grass tile // Array gTilePalette = SliceTexture("grass_tiles.png", 32, 32) gMap1Width = 10 gMap1Height = 10 Array gMap1Layer1 = new Array() [ 2, 2, 7, 3, 11, 11, 11, 12, 2, 2, 1, 1, 10, 11, 11, 4, 11, 12, 2, 2, 2, 1, 13, 5, 11, 11, 11, 4, 8, 2, 1, 2, 1, 10, 11, 11, 11, 11, 11, 9, 10, 11, 12, 13, 5, 11, 11, 11, 11, 4, 13, 14, 15, 1, 10, 11, 11, 11, 11, 6, 2, 2, 2, 2, 13, 14, 11, 11, 11, 11, 2, 2, 2, 2, 2, 2, 11, 11, 11, 11, 2, 2, 2, 2, 2, 2, 5, 11, 11, 11, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, ];
上のコードを図と比較すれば、どのようにタイルマップができあがるか明らかでしょう。 一度このようにタイルマップを説明されれば、単純なRender()関数を書いてマップを描画できます。 Render()の詳細は、表示領域(viewport)の設定などで変化します。 私たちのRender()は次のようなものです。
static int TilePixelSize = 32; // Draws a tilemap from the top left, at pixel position x, y // x, y - the pixel position the map will be rendered from // map - the map to render // width - the width of the map in tiles public void RenderMap(int x, int y, Array map, int mapWidth) { // Start by indexing the top left most tile int tileColumn = 1; int tileRow = 1; for(int i = 1; map.Count(); i++) { // Minus 1 so that the first tile draws at 0, 0 int pixelPosX = x + (tileColumn - 1) * TilePixelSize; int pixelPosY = y + (tileRow - 1) * TilePixelSize; RenderImage(x, y, gTilePalette[gMap1Layer1[i]]); // Advance to the next tile tileColumn += 1; if(tileColumn > mapWidth) { tileColumn = 1; tileRow += 1; } } } -- How it's used in the main update loop public void Update() { // Actually draw a map on screen RenderMap(0, 0, gMap1Layer1, gMap1Width) }
先ほどのマップはとても基本的なものです。ですが、ほとんどのJRPGは、タイルマップの複数のレイヤを使ってより面白い風景を作り出しています。 下の画像は私たちの最初のマップです。3つのレイヤを加えて、より喜ばしいマップになりました。



先ほど見たように、タイルマップはただの整数値の配列であり、したがってレイヤ式のマップは配列の配列で表現できます。 もちろん、あなたのゲーム内で探索をできるようにするためには、タイルマップは本当に最初の一歩です。マップには衝突に関する情報が必要ですし、動く物を扱い、インタラクションを"トリガ"を使って可能にすることも必要でしょう。
トリガとは、プレイヤがある行動を取ることで引かれる引き金です(※イベントがあり、それを呼ぶのがトリガで、トリガが条件を持つ という関係)。 トリガが認識する行動(条件)はたくさんあります。 たとえば、キャラがタイルの上を歩くことが、あるアクションを起動します。これは、玄関へ歩くとき、転送装置へ歩くとき、マップの端へ歩くときなどに起こるでしょう。 トリガはこれらのタイルにおかれ、屋内やワールドマップ、関係したローカルマップへキャラを転送します。
他には、"使用(決定)"ボタンが押されることで発動するトリガが考えられます。 たとえば、プレイヤが標識(看板)の前で"使用"ボタンを押すと、標識に書かれた文字がメッセージボックスと共に表示されるでしょう。 このように、トリガは、マップの接続や(ゲームの)双方向性(interactivity)の実現などのため、あらゆるところで使用されています。 (※双方向性…例えば、ドアに向かってインタラクションすると、ドアが開くという結果が返ってくる、などと捉える)
JRPGは多くの場合、とても複雑で丁寧に作られたマップを持ちます。したがって、マップを手打ちするのは勧められません。タイルマップのエディタを使うのがいいです。 すばらしい無料の解決法の1つを利用するか、自分のエディタを作るという手があります。 既存のツールを使う場合、私は強くTiledを試すことをお勧めします。
次の行動
- Tiledを手に入れる
- opengameart.org. からタイルを手に入れる
- マップを作りあなたのゲームで読み込む
- プレイヤキャラを加える
- キャラをタイルからタイルへ動かす
- キャラの動きを滑らかにする
- 衝突判定(collision detection)を加える(衝突情報を持つレイヤを加えるという手がある)
- マップ移動のための単純なトリガを加える
- 標識(看板)を読むためのトリガを加える。スタック式を検討してみてください。メッセージボックスを表示するのを簡単になります。
- "Start Game"という選択肢を含むMainMenuStateを作る。LocalMapStateを作って、MainMenuStateとつなぐ
- マップを作り、NPCs(non-player characters)を作り、単純なお使いクエストを作ってみましょう。あなたの妄想を解放するのです!
戦闘
ついに戦闘です! 戦闘あってこそのRPGです。 戦闘は多くのゲームが刷新することを選んだ部分であり、新しいスキルシステムや、新しい戦闘構造、異なった呪文システムが導入されてきました。非常に豊富な種類の戦闘システムがあります。
ほとんどの戦闘システムはターン制の構造で、キャラは1ターンに1回だけ行動できます。 初期のターン制のゲームはとても単純で、全てのキャラ(entity)が順に行動します。プレイヤのターン、敵のターン、プレイヤのターン、敵のターン…というように。 この単純な仕組みから始まり、すぐに複雑なシステムが考え出され、戦略性と戦術性が追求されてきました。
ここでは、アクティブタイム制の戦闘システムを詳しく見てみましょう。この場合、各キャラの行動回数は必ずしも一定ではありません。 速いキャラはより多く行動し、取った行動の種類によって次の行動までの時間が変わるはずです。 たとえば、戦士はダガーで切り裂くのに20秒を要しますが、魔法使いがモンスターを召還するには2分かかるでしょう。



上のスクリーンショットは典型的なJRPGの戦闘画面です。 プレイヤに操作されるキャラは右側、敵は左側にいます。下のテキストボックスは戦闘に関する情報を表示しています。
戦闘の始めに、モンスタとプレイヤのスプライトをシーンに加え、どの順番でキャラが行動するか決定します。 この順番は、どのように戦闘が始まったかにある程度依存するでしょう。つまり、もしプレイヤが襲撃されたならモンスタが先に行動し、そうでなければ、行動順は速さなどのキャラの能力に依存するでしょう。
プレイヤまたはモンスタが行うのは、全て1つのactionです(※commandパターン)。攻撃はactionで、魔法の使用もactionで、次にどの行動をとるかでさえもがactionです! actionの順序は、キューに入れて実現するのが一番です。 一番上にあるactionが、より速いactionに割り込まれない限り、次に発動します。 それぞれのactionはフレームが経過するのに応じてカウントダウンします。
戦闘の流れは、2つの状態を持ったステートマシンによって管理されます。カウントダウンする状態と、時が来た時に遷移して、一番上のアクションを実行する状態です。 いつもの通り、何かを理解するにはコードを見るのが一番ですね。 次の例では、アクションキューを使って基本的な戦闘を実装しています。
class BattleState : IState { List<IAction> mActions = List<IAction>(); List<Entity> mEntities = List<Entity>(); StateMachine mBattleStates = new StateMachine(); public static bool SortByTime(Action a, Action b) { return a.TimeRemaining() > b.TimeRemaining() } public BattleState() { mBattleStates.Add("tick", new BattleTick(mBattleStates, mActions)); mBattleStates.Add("execute", new BattleExecute(mBattleStates, mActions)); } public void OnEnter(var params) { mBattleStates.Change("tick"); // // Get a decision action for every entity in the action queue // The sort it so the quickest actions are the top // mEntities = params.entities; foreach(Entity e in mEntities) { if(e.playerControlled) { PlayerDecide action = new PlayerDecide(e, e.Speed()); mActions.Add(action); } else { AIDecide action = new AIDecide(e, e.Speed()); mActions.Add(action); } } Sort(mActions, BattleState::SortByTime); } public void Update(float elapsedTime) { mBattleStates.Update(elapsedTime); } public void Render() { // Draw the scene, gui, characters, animations etc mBattleState.Render(); } public void OnExit() { } }
上のコードは、戦闘モードのフローの管理を実演しています。ステートマシンとactionのキューを使っています。 最初に、戦闘に関係するキャラのdecide-action(行動を決めるというコマンド)がキューに入れられます。
プレイヤのdecide-actionは、RPGらしい選択肢のメニューを持ってくるでしょう。攻撃、魔法、アイテムといった項目のメニューです。一度プレイヤが行動を決定すると、decide-actionがキューから消去され、新たに選ばれたactionが追加されます。
AIのdecide-actionは、状況を見て次に何をするか決めるはずです。(behavior treeやdecision treeや似たテクニックを使って)。決めてから、やはり同様にdecide-acitonを消去し、キューに選んだ行動を加えます。
下のように、BattleTick
クラスはactionの更新を管理します。
class BattleTick : IState { StateMachine mStateMachine; List<IAction> mActions; public BattleTick(StateMachine stateMachine, List<IAction> actions) : mStateMachine(stateMachine), mActions(action) { } // Things may happen in these functions but nothing we're interested in. public void OnEnter() {} public void OnExit() {} public void Render() {} public void Update(float elapsedTime) { foreach(Action a in mActions) { a.Update(elapsedTime); } if(mActions.Top().IsReady()) { Action top = mActions.Pop(); mStateMachine:Change("execute", top); } } }
BattleStateはBattleModeのサブステート(内部状態)で、キューの一番上のactionのカウントダウンが0になるまで時間を進めます。 それから、キューから一番上のactionを取り出して、actionの実行状態に移ります。



左の画像は戦闘開始時のactionキューを示しています。 誰もまだ行動しておらず、全員が行動を決める順番に並べなれています。
Giant Plantのカウントダウンは0なので、次に時間が進むときにAIDecie
を実行します。 この場合、AIDecide
によって、モンスタは攻撃することを決めます。 攻撃コマンドはだいたい速く、ここではキューに2番目の行動として加えられます。
次にBattleTickが時間を進めるとき、プレイヤはドワーフの"Mark"の行動を決めます。これもキューを変更するでしょう。 BattleTick
がその次のactionのAttackを更新すると、Plantはドワーフの一人を攻撃します。 Attackはキューから除かれてBattleExecute状態に実行されます。BatttleExecute状態は戦闘に必要な計算をし、Plantの攻撃のアニメーションを実行します。
モンスタの攻撃が終わると、そのモンスタのAIDecide
コマンドがキューに加えられます。 このようにして、BattleState
は戦闘が終わるまで稼働します。
もしキャラが戦闘中に死亡した場合、キューからその全ての行動を取り消す必要があります。死亡したモンスタに突然動き出して攻撃してほしくありませんから。(私たちがゾンビやその手の不死を意図的に作っていない限りはね!)
actionキューと簡素なステートマシンは、この戦闘システムの心臓です。これらがどれだけ噛み合うか、その良さを味わってください。 これは独立した解決法としては不十分ですが、より機能的で複雑なシステムを築くための型として使えます。 action(commandパターン)と状態は良い抽象化で、これらが戦闘の複雑さへの対処を助け、拡張や開発を容易にします。
次の行動
-
BattleExecute
状態を書く -
BattleMenuState
やAnimationState
のような状態を加える - 背景と、体力情報付きの敵を描画する
- 簡単な攻撃コマンドを書き、パンチの応酬を繰り広げる
- キャラに特別なスキルや魔法を与える
- 体力が25%を下回ると自己回復する敵を作る
- 戦闘状態に移行するワールドマップを作る
- 戦利品と獲得経験値を表示する
BattleOver
状態を作る
まとめ
私たちはJRPGを作るための広い見通しを立て、詳細にも少々踏み込みました。 ステートマシンやスタック式ステートマシンでコードを構築する方法を知り、タイルマップの使い方と表示方法をおさえ、actionキューとステートマシンで戦闘のフローを管理する方法をおさえました。 私たちが学んだことは、ゲームを作っていくための基盤を作るはずです。
けれども、他にも全く触れていないことがたくさんあります。 JRPG全体を作るには、経験値とレベルシステム、セーヴとロード、メニューのためのGUIの大量のコード、基本的なアニメーションと特別なエフェクト、カットシーンを扱う状態、戦闘メカニック(睡眠、ポーション、属性ボーナスと抵抗値など)、名付けることなど!(※to name just a few things!)
これら全てが必要ではありません。例えば、To The Moonは、基本的に1つのマップの探索と会話だけでできています。 それに、ゲームを作るにつれて、だんだんと要素を足していくこともできます。
ここからどこへ行くか
あらゆるゲーム制作において最も難しい部分が終わりました。これからは、小さなことから始めて、ミニサイズのRPGを考えてみてください。ダンジョンを脱出する、1つのおつかいクエスト、そういったことから発展させてください。 もしつまづいたらゲームを小さくし、単純にして、完成させてください。 開発していれば、新しくて面白い多くのアイデアが思い浮かぶかもしれません。それは良いことです。それらを書き留めながらもゲームの範囲を広げたいという衝動に耐えるかもしれませんし、あろうことか新しいゲームを作り始めるのかもしれませんね。
JRPGを作るのはハードです。 たくさんのシステムがありますし、良いマップが無ければ、はじめにどこから手を着けるべきか分からず大変です。 徹底的なステップバイステップのJRPG制作ガイドは、本になるほど大量の情報を含むでしょう! ちょうどいいことに、私は本を書いているので、もしJRPGを作るためのより詳しい手引きがほしいなら、見てみてください。
使用した素材
いくつもの素材とクリエイティブコモンのアセットがこの記事をまとめるのに使用されました。
- Town Map Art by Zabin, Daneeklu, Jetrel, Hyptosis, Redshrike, Bertram.
- World Map Art by MrBeast.
- Background Art by Delfos.
- Potrait Art by Justin Nichol.
- Monster Art by Blarumyrran.
- Gift of Knowledge icon by Lorc and Dark Wood pattern by Omar Alvardo.
- Tiled for map creation.
- Concepts from my How to Make an RPG book. もしあなたがより詳しいJRPGの制作法を知りたければ、見てみてください。
Subscribe below and we’ll send you a weekly email summary of all new Game Development tutorials. Never miss out on learning about the next big thing.
Update me weekly