開発日誌

開発進捗など

RPGのバトルパートについて考えてみる(1)

RPGのバトルシーン

8bit 風 RPG を開発しはじめました。
まずはバトルシーン(内部処理のみ)から着手することにしました。
考えたことをメモしていこうと思います。 考えながら書いているのではじめと終わりで内容が違う…(^^;

前提

ファミコンのターン制のコマンド式 RPG を思い浮かべてください。
あれです。とりあえず 1 ターン実行するという部分を考えてみます。

全体の流れ

1ターンの流れを確認してみます。

  1. 全体コマンド(たたかう、にげる)を選択する
  2. 各味方キャラクタの行動を選択する
  3. 行動順にメッセージと画面効果が表示され、パラメータやステータスが変動する

※敵味方のいずれかが全滅していた場合は戦闘終了の流れへ移動する
※全キャラクタの行動が終わり、敵味方いずれの全滅もない場合は(1)に戻る

こんな感じでしょうか。

ターン再生

まずは一番最後の「行動順にメッセージと画面効果が表示され、パラメータやステータスが変動する」を考えてみます。
イメージとしては↓のようなメッセージが出て、画面が揺れたりフラッシュしたりする感じです。

◯◯◯◯のこうげき!
◯◯◯◯◯◯に✕✕のダメージ!

名前はとりあえず「ターン再生(PlayTurn)」としておきます。

メッセージを次々と表示していくにはそのテキストを予め溜めておいて順番に表示していく
ような処理になると想像できます。

ただし、画面が揺れたりフラッシュしたり、ウェイトがあったりするので、
単なるテキストというよりは画面表示のための命令という感じでしょうか。

例えば

  • [命令]ウェイト
  • [テキスト]◯◯◯◯のこうげき!
  • [命令]ウェイト
  • [命令]画面揺らす
  • [テキスト]◯◯◯◯◯◯に✕✕のダメージ!

のような。

画面のプログラムはそれをひとつひとつ実行していくことになりますが、
ここではまだ考えないことにします。
とりあえず「ターン再生」の出力としては例で上げたような命令群を出力できれば良さそうです。

出力は決まりましたが入力はどうすれば良いでしょうか。
画面だけをイメージした場合、各キャラクタ毎に選んだ選択(コマンド)が入力となりそうですが
例えば攻撃しようとした相手が別のキャラクタの攻撃によって既に死んでいた時は別の相手を自動的に
選んで攻撃したいですし、他にも眠っているキャラクターがいた場合、コマンドは選んでいませんが
眠っているというメッセージを出したいですよね。

そう考えると、各キャラクタ毎に選んだコマンドを入力として先ほどのメッセージ出力を得ようとすると乖離が大きくなり過ぎている気がします。 間にワンクッション挟んだ方が良さそうなので、アクション(Action)という名前の中間物を作ることにします。

コマンド→アクション→メッセージ(命令)という形です。
アクションを実行することでパラメータの変動が起き、メッセージ(命令)が生成されるというイメージです。
入力としてはアクション群ということで良さそうです。

「ターン再生」という名前なのに具体的にアクションからパラメータの変動を起こすのは変なので
名前はアクション実行(ActionExecutor)に変更します。

とは言え、メッセージ(命令)を入力として、画面と連携(演出の完了を待つなど)をとりつつ
1ターン分の再生をする部分というのはそれはそれで必要なので、
(3)がアクション実行とターン再生の2つに分かれたという感じですね。

まとめ

(3)のターン再生部分は以下の2つから成る

◆アクション実行
入力:各キャラクタの選択したコマンドおよび敵キャラクタのコマンド
出力:画面への命令を含むメッセージ
役割:全キャラクタのコマンドをひとつずつ実行することで、パラメータの変動を行い、メッセージを生成する

◆ターン再生
入力:アクション実行が生成したメッセージ
出力:なし
役割:アクション実行が生成したメッセージを順に解釈し、外部への通知を行う

Observer パターンの実装

以前に作りかけだったRPGでは
cocos2d::Spriteを保持するインスタンス(View)とキャラクタのデータ(Model)を分けて、
キャラクタのデータの方はフィールド画面でも戦闘画面でも使うものとして作っていました。

ここで Observer パターンを使い Model を Observable、View を Observer として、
データに変更があれば見た目も変わるようにしていました。
例えばキャラクタが死んだら棺桶の表示になるとか。

ところが、View の方は、マップ全体での位置座標から画面上での座標に変換して Sprite へ値を設定する関係上、
カメラ(マップ上のどこを画面に表示しているかの情報を保持する)の Observer にもなる必要が出てきましたが Model の Observer と Camera の Observer とで継承メソッドのシグネチャがバッティングして 困りました(その時はそれぞれに独自のクラスを作り、それぞれのインスタンスを持つことで解決しました)。

今回は template を使って Observer クラスを定義することで、
複数の Observer を継承できるようにしてみました。 また、weak_ptr を使って参照を保持しているので、通知解除せず Observer が死んでも 問題なく動くようにできました。

以下がコードです。
(記事タイトルをクリックして詳細を見ないと見えないようです)

cocos2d-x ライブラリの警告を抑制する

cocos2d-x ライブラリの警告を抑制する

Xcode で開発している場合ですが、ライブラリ側のプロジェクトに 大量に警告が出て、自分のプロジェクトの方のエラーが埋もれてしまいます。

ライブラリ側のエラー表示を抑制するには、 Project Navigator (左側のペイン)で cocos2d_libs.xcodeproj を選択し、 BuildSettings > Apple LLVM 8.1 - Warning Policies > Inhibit All Warnings を Yes に設定します(Xcode 8.3.2 の場合)。

これで、次回ビルド時はライブラリ側の警告が表示されなくなります。

git のサブモジュールを使う

git のサブモジュールを使う

アプリ制作の過程で得た知見を、再利用可能なモジュールとは言わないまでも せめて小さなサンプル形式でいいから GitHub に残していこうと思いました。

そこで、複数のプロジェクトをアップロードするにあたり気になるのは 各プロジェクトに入っている cocos2d ディレクトリの重複。

ここの重複を、git のサブモジュールを使って共通の別リポジトリとして括り出す ことで無駄を省くことができます。

サブモジュール用リポジトリの作成

まず、サブモジュールとなるリポジトリを作成していきます。

GitHub でリポジトリを作成する

GitHub でリポジトリを作成します。

新規プロジェクトの作成

サブモジュールにする cocos2d ディレクトリを得るため、まずは新規のプロジェクトを作成します。

cocos new -l cpp [プロジェクト名]

cocos2d ディレクトリを git リポジトリにしてコミットする

cd [プロジェクトディレクトリ内の cocos2d ディレクトリ]

git init
git add .
git commit -m [コミットメッセージ]

GitHub へプッシュする

git remote add origin git [リポジトリのURL]
git push -u origin master

これでサブモジュールのリポジトリは設定完了です。

サブモジュールを使う

新規プロジェクトを作成し、 git リポジトリにするまでは同じです。

サブモジュールを設定する

まず元々入っている cocos2d ディレクトリを削除します。

cd [プロジェクトのディレクトリ]
rm -rf ./cocos2d
git submodule add [サブモジュールのリポジトリURL] cocos2d

これで cocos2d がサブモジュールのリポジトリから clone されます。

サブモジュールを使ったリポジトリをチェックアウトする場合

git clone [プロジェクトのリポジトリURL]

clone してくると、サブモジュールはディレクトリのみで中身が空になっていますので 初期化と更新を行います。

git submodule update -i

応用

サブモジュールを特定のタグに紐付ける

submodule の master 以外のブランチやタグを使う場合は、サブモジュールのディレクトリで git 操作(checkout等) を行い、外に出て(元の利用側プロジェクトで) git add すると、変更が記録されます。

cocos2d-x のバージョンアップをする

cocos2d-x の新しいバージョンがリリースされたら、新規プロジェクトを作成して中のcocos2dをサブモジュールディレクトリに上書きすれば利用側のプロジェクトでもサクっとバージョンアップ対応できます(cocos2d ディレクトリ以外も変更がある可能性があるのでそこは注意してください)。

変数名

ローカル変数:スネークケース

例:int snake_case

クラス定数:アッパースネークケース

例:static const int CONSTANT_VARIABLE

メンバ変数:スネークケース、接頭辞として m_ を付ける

例:int m_member_variable

メンバ関数:ローワーキャメルケース

例:void lowerCamelCase()

クラス名/構造体:アッパーキャメルケース

例:class CamelCase

列挙型:アッパーキャメルケース

例:enum class Enumeration

列挙定数:アッパースネークケース

例:Enumeration::SOME_ELEMENT

グローバル変数:スネークケース、接頭辞として g_ を付ける(基本的に使わない)

例:int g_global_variable

名前空間:小文字

例:namespace somenamespace

MSX 開発に入門

MSX 開発に入門

MSX2 を入手したのでどうせならMSXのプログラミングにもチャレンジしてみたいと思います。

MSX 開発環境構築

まずは開発環境の準備ですが、
実機は普通に使っているとキーボードが壊れてしまうのではないかと不安になるので
エミュレーター上で開発の環境を整えようと思います。

本体システムROMの吸い出し

Mac を使用しているので、エミュレーターにはCocoaMSXをダウンロードしました。
エミュレータには互換BIOSしか乗っておらず、ゲームをやる分には問題ないですが
MSX-BASICなどは動かないようです。

そこで、実機からROMを吸い出してエミュレータでMSX-BASICが実行できるようにします。

ディスクを用意

幸いにも2HDのフロッピーディスクが家にありましたので、
左の穴をテープで塞いで2DDとして使うことにします。

Macでもディスクユーティリティから2DDがフォーマット出来ます。
実機側でフォーマットする場合はMSX-BASIC上で

CALL FORMAT

とコマンドを打ちます。

ROM吸い出しプログラムを用意

パソコンでfMSX98をダウンロードし、添付されている「MAKEROM.BAS」を
ディスクにコピーして、実機から実行します。

プログラムの入ったディスクを実機に挿入して

RUN "MAKEROM.BAS"

とコマンドを打ち、出てきたメニューから1を選びます。
完了するとファイルがディスクにファイルが作成されています。

CocoaMSX に適用

ここから実機と同じ定義ファイルをダウンロードして

/Users/ユーザー名/Library/Application Suport/CocoaMSX

にディレクトリを作成して、吸い出したシステムファイルを配置します。
config.iniに記載されているファイル名に合わせて吸い出したファイルをリネームします。

MSX の選択

正しく設定ができていれば
CocoaMSX - Preference - System から実機と同じMSXが選べるようになっているはずです。 f:id:sacopon:20161209234539p:plain