娯ログ(β)

ゲームデベロッパー 娯匠 公式ブログ

プログラム

新人プログラマーが見た『剣闘士』の開発

 こんにちは!
 新人二年目のプログラマー 繁田です。

 今回初めてスタッフロールに載る事が出来たタイトル、『剣闘士 グラディエータービギンズ』の開発に参加した感想や、裏話を書きたいと思います。

 まずプログラムのお話です~。

 僕が主に担当したのはセーブロード周りです。まだまだ経験不足の中、SCEのライブラリを使うプログラミングに苦戦し、とても大変でした。特にセーブ・ロード周りは、メモリースティックへのアクセスやゲームデータの整合性(攻撃力やスタミナの数値が正しく保存・読み込みが出来ているか)など、ゲームに対して大きく影響を与える部分でしたので、とても細かく気を使いながら作業をしないといけませんでした。

 一番大変だったのが、予め決められている「これは守ってね」という作成上の約束事があるのですが、これが細かくて項目数も多かった事です。「書き込み中などにゲームを終了したら止まってはいけない」とか、「読み込み中にメモリースティックを抜いたらちゃんとメッセージが出る」などなど、どれも疎かに出来ないので、デバッグ報告やプレイしながらの対応、その都度の直し直しで長い時間をかけて出来上がりました。

 次にムービーの再生に関して。ムービーの再生にはマルチスレッドなプログラミングをしなくてはならず、今までちょっと触った程度だったので、中々思うように進まなかったです。少し期間が空いてしまったのでハッキリキッチリとは覚えてないのですが、動画再生と音楽再生には別々のスレッドを用いなくてはいけませんでした。しかし、ゲーム中ではこれ以外の別のスレッドも動いていて、そのスレッドの優先順位関係や、動画スレッド、音楽スレッド間のデータのやり取り、動画ファイルのデコードのしかた、時間のズレ、動画ファイルがPSPメモリの32MBに収まるように他のリソースの解放など、色々と問題が有ったように思います。

 最終的には、プログラマーでもある石川からアドバイスをもらいながら、どうにかこうにか出来上がりました。凄く「スッキリイイ感じ」に出来上がったので大満足で、作り終わった達成感がありました。ただ、ここの作業だけで一ヶ月近く進行が止まってしまいましたので、他スタッフの皆様にご迷惑をおかけしたと思います。



 今回の作成ではガッツリとSTLを使えたのも凄く勉強になりました。特にvectorクラスにはセーブ・ロードの際にお世話になりました。vectorは配列クラスなのでセーブデータを作成するときにとても役に立ちます。キャラクタ―などのデータをシリアライズする際には、とにかくバイナリデータ(vectorとか)として1byte情報にし、push()関数で入れまくりました。int型などの4byteデータも、シリアライズの際に1byte情報として分解してしまいます。

 これは確か石川のコードなのですが、とても参考になったので書いちゃうと…

template
void Serialize( const Type& _t, vector& _buf )
{
union {
Type data;
char byte[sizeof(Type)];
} temp;
temp.data = _t;

for( int i = 0; i < sizeof(Type); ++i )
{
_buf.push_back( temp.byte[i] );
}
}

 (※無断転載はご遠慮ください)

のようになります。

 ここでunionを使って複数バイトのデータ型を1byteのデータに分解出来るんです。この方法でセーブデータ用のバッファに色々なデータを格納しちゃいます。応用して文字列なども格納出来ますが、この関数ではダメですね。それにしてもやはり、templateとSTLは使いこなせるととても便利ですね。変な話ですが、これだけスッキリしてて上手く動くこの関数は少し気に入ってます(笑)。

 見ても動かしてもスッキリしてるようなスマートな関数っていいですよね!もっと頑張って勉強して、こんなプログラムを当たり前に書けるようにならないとなぁと思う次第です。



 プログラムは余り行っていませんが、ワールドマップ画面にも調整程度に関わりました。キャラクターのアイコンや、アリーナをぐるぐる回る駒画像の表示や動かし方などです。ここで印象的なのはサウンドの再生プログラムです。基本的な事なのですが、SEを再生する際に再生するタイミングを少し考えました。決定音とキャンセル音が同フレーム内で鳴ってしまうと問題がありますので、再生したい音を一通り判定してみて、そのフレームの最後に一つのSEだけ再生を行うようにしました。

enum {
SE_NONE = 0,
SE_OK = 1,
SE_CANCEL= 2
};
void update()
{
int play_sound = SE_NONE;
if ( push_ok() ) play_sound = SE_OK;
if ( push_cancel ) play_sound = SE_CANCEL;
if ( play_sound == SE_NON ) return;

char* se_name = "";
swtich ( play_sound )
{
case SE_OK: se_name = "se_ok"; break;
case SE_CANCEL: se_name = "se_cancel"; break;
default: return;
}
sound.play( se_name );
}

 (※無断転載はご遠慮ください)

といった感じで一か所だけ再生する場所を設けてそれ以外ではどのSEを流すかの判定をしています。再生するSEを“ふるいにかける”と言った所です。もちろん参考になるほど高度なプログラムではないのですが、プログラムって「こんな感じですよ~」って雰囲気だけでも伝われば嬉しいです。



 ROM焼きや製品版のリリースコンパイルも僕が担当することになりました。オーサリングというPSP上で動かせるようにする作業もありますし、デバッグ作業を行っていただいたポールトゥウィンさんへ渡す為のFTPへのアップロードなどなど、とにかく時間がかかる作業でした。酷い時にはリリース版のビルドに2時間かかったりしました(笑)。

 ぶっちゃけると、リソースデータのパッケージングを娯匠側のプログラムで行っていたのですが、それが長いんです。凄く長くて、一日一本ぐらいしか提出出来ない時もありました。たしか15時か17時ぐらいにデバッグ用に作成したROM(DVDじゃなくてデータです)をポールトゥウィンさんへFTPサーバ経由で提出していたのですが、それに間に合わないことすらありました。と言うのも、前回のデバッグ報告を受けて、それを確認し修正してからROMを作成、といった流れになるので、この修正に時間を取られてしまうと、その分だけROMの作成が遅れることになり、当然提出も遅れるんです!

 「これでは大変!」という状況でしたが、ロード時間短縮の目的もあってCRIウェアを導入することになり、その副次効果でフルコンパイル+パッケージングにも30分掛からなくなったのには驚きました。

 他にもプログラムにミスがあり、ビルドをはじめて数十分経ってからやり直し~なんて事もあったりなかったり。そんな作業ですから確認用に作ったROMも大量でした。このタイトルで焼いたROMは大体DVD50枚入りのケース4,5本分はあったと思います(汗)。DVDドライブさん、おつかれさまでした。

 さて、長い様な、読んでみると短い様な内容ですが、僕から見た開発のお話でした。出そうと思えばもっと話が出てきそうですが、今回はここまでにさせていただきます。

 ここまで駄文を読んでくださってありがとうございました!
 それではまたの機会に~

*記載しておりますソースコードは実際にゲーム内で使用しているものとは異なります。

チョーナゲーけど?

■はじめに

 娯匠のゲームに関わるとロクなことにならないし、ロクなこともしないプログラマー西森こと nistake の部屋へようこそ。あなたはこの糞長い文章についてこれるでしょうか?

 で、前回(?)と同じく、ゲーム内容の話を私がしたところで全く面白くありませんから、思い切ってビギンズで使用したデザインパターンの話でもします。

 いまさらパターンの話なんてツマランとか、いまどきパターンと提唱されているものなんて何兆個あるのかわからん、みたいなご時勢ですが、そんな文句は聞く耳も無く、古典 GoF(※) の 23(+1) パターンからテキトーに抜粋します。


■Template Method と Strategy

 手順やフレームはしっかり決めておいて、細かいところは適材適所で部下に責任を持たせてやらせる、という理想の上司と部下の関係的なパターンです。

 最近 iPad 持って、「僕ちゃんって有能~」みたいに勘違いしているクズが増えましたが、そういったゴミ未満の人間共は Template Method や Strategy を見習って欲しいものです。だからその iPad はオレにくれ。

 ビギンズでも、個人的には Template Method や Strategy パターンは大活躍でした。二つは細かくは違うんですが、結構似たような、両方のいいとこ取りみたいな実装をしてる人もいっぱいいるように思います(身勝手予想)。

 面白いかなーと思うものとして、シーングラフ中のキャラクタ(モデル)インスタンスの実装あたりを挙げます。

 キャラクタインスタンスは要するにポリゴンメッシュでできたオブジェクトのことなんですが、そのクラスのフレーム毎の更新と描画の関数は次のようになっています。

update() {
  インスタンスの全マニピュレーターの pre_update() を呼ぶ。
  インスタンス自身のコアな update() 処理。
  インスタンスの全マニピュレーターの post_update() を呼ぶ。
}

draw() {
  インスタンスの全マニピュレーターの pre_draw() を呼ぶ。
  インスタンス自身のコアな draw() 処理。
  インスタンスの全マニピュレーターの post_draw() を呼ぶ。
}


 マニピュレーターは、各キャラクタインスタンスがリストにして管理しています。で、マニピュレーターとは何かというと、

  ●二度描きが必要だったり、描画に少し小細工をかける処理
  ●直前のモーションと今のモーションをスムーズにつなぐ補間のような処理
  ●キャラの姿勢計算後に必要なデータ更新

のようなことをやっています。こういう処理って、

  ●毎フレームの姿勢の計算直前、直後に何かしたい
  ●モデルの描画直前、直後に何かしたい

といった要求なわけで、こういうのをヅラヅラひとつの関数やクラスに、インスタンス本体の処理と一緒に実装すると、全然違う処理なのにマゼコゼになるし、常に必要なものでなければ、フラグも増えて大変面倒です。

 そこで、処理タイミングだけちゃんと実装しておいてやって、あとは各マニピュレーターで勝手にやってもらえば、キャラクタインスタンス本体はずっとすっきりしたままですし、それぞれの処理もちゃんとクラスにわかれて気分も良いです。

 マニピュレーターは単純な配列やリストで管理できますから、動的にこれらの機能を追加したり取り外したりもできます。enable() とか disable() とかいうスイッチング関数やフラグも基本的には必要ないです(生成や廃棄のコストはあるけど)。

 と、ここまで書くといいことばっかりですが、


■Command

 エディタみたいなツールを作るときは、ベガ戦の波動拳や、天津飯戦のカメハメ波よりはるかに役に立つ、超必殺技としておなじみのパターンです。必殺度が高すぎるため、エディタを書くと思った瞬間にメクラ滅法に書き出したくなる麻薬的なところもありますが…。

 ビギンズのキャラクタはスクリプト(正しくはコンパイル後バイトコード)によって挙動処理されています。キャラクタのスクリプト(バイトコード)は、

 「ガード(条件式)」「処理本体」

の二つの組の羅列です。

 ガードはゲーム中の入力のチェックやモーションのフレーム検査、ゲーム中のシチュエーションチェックなど色々です(ガードはスクリプトの論理式なので、実際にどんな検査をしているかは、前野のヒゲに聞くほうが良いでしょう)。

 あるフレームのキャラクタの処理は次のようになっています。

 1:全キャラが持つ全ガードの評価をする。
   真なら対応する処理本体を実行するコマンド発行。
 2:評価後、全キャラについて、発行された「本体実行コマンド」を実行。

となっています。このようになっている理由は、プログラムやスクリプトの処理順によって結果が変化してしまうことを防ぐため -- 条件評価中のキャラクタは不変(invariant)にするためなんですね。

 一見、これは細かいんですが、キャラクタの挙動が複雑で、キャラクタ相互の関係が強いゲームの場合、この不変性をある程度担保できないと、修正が大変やっかいな不具合やバグに見舞われるケースがままあります。例えば、記述順を変えたら挙動が極端に変わったり、同時ヒットがきちんと処理できなかったり等…。

 ただ、でもここまで気にする必要があるのは、やっぱりビギンズがあのようにビギンズなゲームだからです。よくあるジャンプアクションぐらいならどうでもいいことだと思います(でもビギンズはジャンプしないけどね)。

 「今回のゲームは以前(road to freedom)に比べて簡単なゲームですよ」と最初に言われてましたが、結局こうしているところ等からするに、私ははじめから娯匠の人間たちを、

「大して信用もしていなかった」

ということになります。えぇ、信用するわけ無いでしょ。奴らが単純簡単なゲーム作るわけ無いでしょ。そんなゲームは他の会社に作らせとけ。



■State

 状態遷移図で書くとわかりやすくなりそうなブツを実装するときにすぐ頼ってしまう、かっこいいアニキのようなパターンです。

 外面は変わらなくても中身がポンポン変わる、という点では最近の日本みたいですが、日本の場合は中身が変っても挙動まで変わることは少ないですし、大体遷移自体が一方通行で図にする必要もありません。

 ビギンズでは先の「ガードと処理本体」の集合となっています。
 たとえば、攻撃をするモーションであれば、その State (ビギンズ中はとおりが良いのでアクションと呼んでました)は、乱暴には、

 「攻撃フレームになったかどうかのガード」「攻撃設定処理」
 「攻撃フレームまでかどうかのガード」「他のシフト入力処理」

といった感じの「ガード+処理本体」スクリプト(バイトコード)の集合として定義されていると思います(詳しくは前野のヒゲに聞くのが良い)。

 ビギンズは複雑なゲームなので、この State が大量に定義されています。処理本体には他の State への遷移、というのもありますから、大量の State に大量の遷移となり、もう

「図にするのが馬鹿らしい」

ぐらいなはずです。それでも、アクションの遷移だけに限れば、他の細かい対戦格闘ゲームほどではないとは思いますけど。

 この State とスクリプトのあたりについては、実はコンパイラやスクリプト言語(と呼ぶか?)の仕様含めて結構

「チョーヘンテコ」

な面白いもので、専門の言語屋さんからみても「なんだそれー」的なところがいっぱいだと思うのですが、紙面の都合上、割愛させていただきます。



■Abstract Factory

 「僕ちゃん、こんなスゴいメカ欲しいんだけど。これでトッキョ取るョ」とクーピーで描いたテキトーな設計図を持っていっても、がんばってスゴそうなモノを作ってしまう工場です。そんな奴はアメリカに行って、ビショビショのネコを入れてもちゃんと乾燥できる電子レンジでも発明してればいいと思います。

 ビギンズの Factory といえば、

「超ウルトラスーパー大好評」(つまり逆)

だった、BattleBuilder クラスでしょう。

 Builder という名前なのに Factory なのは、最初は Factory という名前だったのが重ね重ねの書き換えにより、名前も変えざる得なかった事情によります。

 この Builder という名の Factory も、キャラクタと同じくスクリプト駆動です。

 スクリプト処理によりバトル中のあらゆるオブジェクトを「生成する」、というところでは、Factory と呼べますが、作られたインスタンス(群)のクラスは大抵、フツーの具象クラスか具象クラスと同等のものなので、Abstract とは呼べないかもしれません。まぁ気にするな!ボーイ!

 この BattleBuilder、実は開発中の歴史的(?)な経緯やその扱われ方などお話すると、

「目が回るような面白さ?(他人事)」

と個人的には思うのですが、紙面の都合上、割愛させていただきます。



■Visitor

 全くあたらしい工場ラインです。恐ろしいことにベルトコンベアの上に乗っているのが作業する人で、ベルトコンベアの脇に車や機械や材料が並んでいます。「シートをつける人」はカウンタックの前にきたらそこにあるシートをカウンタックのボディに取り付け作業をします。ミニクーパーの前にきたらミニクーパーのシートを取り付けます。

 シートをつける人はシート付けのスペシャリストです。逆に言えばシート以外については全くしらなくても大丈夫。どこにどの車があるなんてことも知らなくて平気ですから、工場長は好きなときに好きな車種を好きなように配置できます。新しい「カイゼン」なのです。

 で、この visitor、ビギンズでは意外にいろんなところで使っています。わかりやすいのは木構造になっているシーングラフ中のノードのトラバースとかでしょうか。

 シーングラフ中からキャラのモデルノードだけにこんな処理したい、背景だけにこんなことやりたい、といったことはアリガチなんですが、そういうときに、シーン中のインスタンスをトラバースする InstanceVisitor クラスを派生実装して、シーンにぽいっと渡せばもうOK、というのは個人的には重宝しました。

 ただ、色々めんどくさがって、なんでもかんでも visitor 使ってトラバースしていたので、真にオブジェクトが大量なゲーム向きの実装ではなかったかもしれないなーとも思ったりします。visitor ってダブルディスパッチ(関数ポインタ二回参照)なんで、命令キャッシュにも優しくなくて、高速とは言いがたいかもしれないです。

 と、↑のように思う私は多分富豪になりきれていないのです。イカンイカン。

「Viva! 富豪プログラミング!」



■Chain of Responsibility

 できないことはどんどん他人に流して自分は知らん顔。役に立たないバブル時代の就職組がどんどん偉くなった結果、こんな風潮の社会ができあがってしまいました。全員シねば日本はバラ色です。

 ただ、ホンモノの Chain of Responsibility は責任範囲であれば確実に対処する、責任感の強いモノたちの連なりです。脳細胞が実は豆腐のバブルバカ共とはまったく違います。

 ビギンズでは Chain of Responsibility を何箇所かで利用しています。一番いい採用だったな、と思うのは、コリジョン発生時の処理かと思います。

 ビギンズに限らず、衝突(剣と体、とか、剣と盾等)発生時の処理は、いくつかの種類に分かれます。例えば、

  ●ダメージの計算
  ●リアクションの決定、設定
  ●エフェクトを出す
  ●経験値などの計算

等です。

 これを下手にコードにするととりあえずはこんな感じでしょう。

ヒット処理( キャラA , キャラB ) {
  if ( キャラA の武器がキャラB の体に当たった ) {
   ●ダメージの計算
   ●リアクションの決定、設定
   ●エフェクトを出す
   ●経験値などの計算
  } else if ( キャラA の体がキャラB の防具に当たった ) {
   ●ダメージの計算
   ●リアクションの決定、設定
   ●エフェクトを出す
   ●経験値などの計算
    :
    :
}


 ビギンズはヒットしたブツの組み合わせにより色々処理が変わるのでベタで書くとこうなりますが、下手すぎです。もう少しなんとかします。クラスにして、上記の if - else if ... (相当の処理)はどっかにすっ飛ばすことにします。

class ヒット処理クラス {
 void 武器→体 ( 攻撃キャラ , やられキャラ ) {
   ●ダメージの計算
   ●リアクションの決定、設定
   ●エフェクトを出す
   ●経験値などの計算
 }

 void 体→防具 ( 攻撃キャラ , やられキャラ ) {
   ●ダメージの計算
   ●リアクションの決定、設定
   ●エフェクトを出す
   ●経験値などの計算
 }
  :
  :
}


 少しマシになりました。ただ、このクラス、きっとメンバ変数や #include は、

   ●ダメージの計算
   ●リアクションの決定、設定
   ●エフェクトを出す
   ●経験値などの計算

すべてについて包含していることになります。そして、特にこのあたりが細かいタイプのアクションゲームは、この辺のコードが得てして

「いろんなものがあってぱっと見、なんだかぐっちゃぐちゃ」

になりがちです。だって「呼び出しタイミング」「参照する衝突データ」という点以外に、上記の●の処理間には大きな意味での共通点がないのですから…。

 こういう場合、多少関数に分けたぐらいではグチャグチャ感は解消できません。ひとりで書いてればマシですが、二人以上でやるとスゴイことになることがよくあります。そしてビギンズでも石川ヒゲが言い出したのです。

「観客の評価処理入れたり、AI の処理やりたいんですがー」

 ということで、上記の「ヒット処理クラス」には空の仮想関数だけ定義して、上記の●や石川ヒゲの評価処理は全部別のクラスにすることにしました。

 で、それらのクラスのインスタンスは、単方向リストでつなげておいて、衝突時は、リストをたどって全部の対応関数を呼び出してやれば、シヤワセ~なわけです。

 実は Chain of Responsibility は Chain 上の個々の責任者は、ある処理を次へ委譲するかどうかを決定できるのですが、この実装はそうでないので、正しくこのパターンとは言い切れないです。でも「衝突処理という要求の結合度を低くする」「それぞれの責任の割り振りを柔軟にする」という点が上手く利用できていると思うので、いいかな~とか思ったり。

 開発後半は、前野ヒゲも実装に加わって(プランナーなのに!)、三つ巴でヒット処理をグチャグチャいじってましたが、なんとか、

「あー、その行うっかり消しやがったなー、バカー!」
「先にそれやられると困るのよ」

みたいなことは無かったかなーと思います。あくまで、この衝突確定後の処理についてだけですが…。

 この点について本人達がどう思ってたかはわかりません。コーディングがうまくいったとか、上手に書けた、なんてのは結局、

「プログラマーのオナニーの話」

ですし。



■デザインパターン総括

 大体、こういう文章というのは、いいとこどりで、悪いことをちっとも書かないような感じになるので、読むと、

「この方法でばら色の人生が~」

みたいに思えてしまうことがあります。そういえばデザインパターンの本を最初に読んだのはもう10年以上前ですが、そのときは、

「これでオレも天才プログラマーになれる」

のようなことを本気で思ったものでした。そして、あのころも今もずーっと同じように、

「たらたら汚いコードを書いて、バグを生産し続けている」

のですね。デザインパターンを勉強して利用して、いったい何が変わったのだろう。



■終わりに

 娯匠では、中の人々が意識しなくてもいつのまにか、

「全然簡単じゃない、ゴリゴリのゲームゲームしたゲーム」

へ勝手に話や方向が流れていきます。何が今回は簡単なゲームだか知りませんが、「いや、このシステムは実は簡単なんです」とか言いながら、ホワイトボードまで使って説明してるのに

「ちっともわからない」

ようなものを提案するあたり、もうこういう方向でしかゲームが作れない集団なのです(これは言い杉)。要するに、個人的に思うのは、

「娯匠みたいな会社が本当にやっていけなくなったら、ゲーム屋廃業しようかな」

ということでしょうか。そんなゲーム業界ならいらないよね。勝手にヤッテロ、みたいな感じ。

 やー、でも本当に、今回のプロジェクトは何回「簡単」とか「シンプル」とか聞いたかなー。プレイヤーの感想も是非聞きたいですね。簡単でしたか?シンプルでしたか?


おしまい。



※GoF
Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides:
Design Patterns: Elements of Reusable Object-Oriented Software
Addison-Wesley

本位田真一、吉田和樹 監訳
オブジェクト指向における再利用のためのデザインパターン
SOFTBANK BOOKS

誰が言ったか知らないが‥

 株式会社トーユー・キネマテック 平田です。

 先日、空耳アワーを観ていたら、われらがチタシチーナこと佐藤かおりさん(既にネットで名前が出てるから問題ないよね?)が出演していらっしゃいました!空耳役者です。

 すごかった!巨乳レースでプルンプルンしてました!ぜひ、全力坂(深夜にやってる「この坂も、実に走りたくなる坂である。」ってナレーションの番組)にも出てほしい…

 以下、エロ自重。

 さて、僕はPS2の前作も参加しているのですが、今回担当した部分では、DLCシステムが印象に残っています。

 まず、ミッションとして、「湯水のようにDLCを使う」「プレイヤーにとって空気のような存在」というのを目指し、基本的に何でもDLCに出来ちゃうんじゃないかというような仕組み造りに取り組みました。

 ところが、それだけであればスムーズに済んだはずなのですが、現実には、DLCをダウンロードしていない人のPSPでの対策の都合などがあって、ゲームプログラムのほぼ全体を把握して、DLCが使われた場合に問題が出そうな部分を適宜修正しなければならないなど、DLCの仕組み以外にも手を付けなければならない部分が多々あり、思った以上に手間がかかりました。

 空気のような存在として、決して目立つわけではないけれど、プレイヤーの皆さんに楽しんでいただけたのであれば、ミッション達成です。

 DLC、思う存分楽しんでいただけましたでしょうか?
記事検索
プロフィール

有限会社 娯匠

最新コメント