LECTURE
Oeffentliches Kunst Nr.5
ビートバイスのシューティングアルゴリズム
 第五回目は当初の予定を変更してビートバイスのシューティング部分のアルゴリズムについて解説していくことにいたします。風雅システムはリボルティーIIというシューティンゲームでデビューしましたから、ある程度のノウハウは存在していましたが、横スクロールシューティングとしては初の作品でした。(実際にはOEMとして提供したゲーム中にもあり、ビートバイスで3作目になります。)
 非ビットマップと非スプライト
 今となっては「しなくてよい苦労」となっているのが、V-RAMの構造とスプライトに関する仕様による問題の回避です。第二回の高速横スクロールGRCGの項でも触れましたが、PC-9801という機種のV-RAMは1ドットが1バイトや1ワードに対応している「ビットマップタイプ」とは異なり、1ドットが1ビットに対応したモノクロ画面が重なっている「プレーンタイプ」でした。このためグラフィックパターンを1ドット単位で高速に移動して見せることが困難でした。(今にして思えば)非力なCPUであったため、ビートバイスでは1ドット単位での移動をあきらめ、横方向8ドット、縦方向4ドット(※1)を移動の最小単位としました。
 この点、当時のファミコンやMSX、SORD-M5、RX-78などのスプライトをハードウェア的に持つ機種では楽なものです。スプライトとは「妖精」という意味が一般的ですが、この場合は「移動画面」という意味です。特定のサイズの移動画面がいくつも用意されており、この画面に機体や弾丸、ビームなどのパターンをあらかじめ描画(登録)しておくのです。すると、この移動画面は背景となる画面の任意の場所にハードウェア的に合成表示可能なため、縦横それぞれ1バイト(0〜255)の座標を書き換えるだけで、グラフィックパターンを移動させることができるというわけです。
 余談になりますが、実際にはスプライトはいろいろな制約も受けます。サイズが8×8・16×16・32×32程度であり「デカキャラ」には使いにくいとか、色数が2色〜8色程度と少なく、2枚以上重ねて使わないと綺麗に見えないとか、同一捜査線上に一定の数を超えて表示されようとすると超えた分が消えてしまうとかです。しかし、これらの制約もDirectXのDirectDraw(3D)の一般化で無に等しくなっています。ただし、DirectXによるスプライトは「疑似スプライト」であり、完全にハードウェアで実現しているものではありません。でも、この方が理想的なスプライトではありますが。(^_^) ちなみに富士通のFM-TOWNSもいちはやくこの疑似スプライトを取り入れていました。
 また、VGAを擁するDOS/V機はスプライトこそ持っていませんでしたが、プレーンタイプと同時にビットマップタイプのV-RAMもサポートしていたため、PC-9801に比べてずっとシューティングゲームが作りやすくなっていました。また、画面の解像度も320×240ドット×256色モードを持っているなど、速度的にもかなり有利でした。逆に言うと、それだけPC-9801がシューティングゲームに不向きな機種だったのです。
二重スクロール例
 画面左下が自機。敵機が編隊を組んで弾丸を発射してくる。発射された弾丸は一定方向に直進する。
 結果として、ビートバイスでは完全にソフト的にスプライト(っぽいもの)を実現しています。スクロールする背景に、登録しておいたグラフィックパターン(自機・敵機・弾丸・ビーム・爆発パターンなど)を任意の位置に合成表示してくれるルーチンを作ったわけです。お察しの通り、内部では高速化とちらつき防止と省メモリのための涙ぐましいまでの工夫がなされています。もちろん、今(H17年8月)からゲームを作ろうと考えている方にはどうでもよい話です。Windows上で、ギガヘルツクロックの32/64bitCPUと数百メガバイトのメインメモリ、大量の超高速V-RAMと実質的に何の制約もない疑似スプライトを、便利なクラスライブラリを使って自由に利用し、鼻歌でも歌いながら好きなゲームを制作してください。(妬み?)
(※1) PC-9801が640×400ドットという高解像であることを考慮すると、これはそれほど“荒い”とは言えないでしょう。当時の一般的なコンシューマゲーム機「ファミコン」は256×240ドット、MSXのスプライト使用モードで256×212ドットですから、これらの解像度に換算すると、およそ横3ドット・縦2ドット単位になります。
 キャラクタの制御
 およそシューティングゲームには数多くのグラフィックパターン(キャラクタ)が刻々と表示されていきます。自機、敵機、弾丸、ビーム、爆発パターン、デカキャラ、ミサイルなど、多種多様です。そして、これらがそれぞれ個性的な動きを見せ、消滅、出現、変形、分裂というように生物を思わせる変化を見せたりします。これらをどのように制御するかが、シューティングゲームの最も重要で難しい部分でもあります。
 このころ、シューティングゲーム作成の参考書があるわけもなく、試行錯誤を交えながら辿り着いたのが、今で言うところの「クラス」を利用した手法です(正確にはちょっと違いますが)。各キャラクタごとにすべて共通のクラスオブジェクトを持たせます。そしてキャラクタの「動き」を定義したスクリプトを別途用意し、専用のコンバータ(アセンブラマクロを利用)で変換したオブジェクトへのポインタをクラスに持たせます。クラスの中にはそのキャラクタが現時点で出現しているか否かをはじめ、座標、当たり判定エリア、爆発カウント(被弾爆発フラグ含む)、硬さ、スコアなどの情報の他、動き制御スクリプト用の変数ワークエリア(数十バイト)などの領域が確保されています。メソッドに相当する関数も数多く用意してあり、動き制御スクリプトから呼び出されます。
 このような「キャラクタ制御クラスオブジェクト」のポインタテーブルを用意し、キャラクタの「出現」と「消滅」に合わせて逐次更新していきます。さらにこのテーブルはキャラクタのプライオリティ(表示優先順位)も決定します。触手や蛇キャラには必須の機能ともいえます。
 もちろんこれらのプログラムはアセンブラで記述してあるため実行は高速です。制御スクリプトの実行も最適化されたジャンプテーブルにより、あたかもアセンブラプログラムの一部のように実行するようになっています。
 下はごく簡単な実際の動き制御スクリプトソースの一例です。アセンブラのマクロ機能を利用した独自の専用言語形式になっています。コメントを見ていただければおおよその仕組みは理解してもらえると思います。
 このような専用言語スクリプトを用いることによって、速度的には多少のペナルティーを受けますが、毎回実行ファイルをアセンブル(コンパイル)することなく、複数人が手軽にデバッグをすることが可能になります。ターンアラウンドタイムを短くできるということも大きなメリットです。ビートバイスの敵機行動プログラムを書いた人は5人以上にもなります。
; 直進型ザコ敵7 制御スクリプト
Z71_S LB ; スクリプト開始
SIZESET 4, 8 ; キャラクタサイズ設定
PTNSET 31 ; キャラクタパターン設定
I6J1 Z71_B ; 情報6(爆発フラグ)が1ならば爆発中処理へ
I2J1 Z71_RE ; 情報2が1(弾を発射済み)ならば減速移動処理へ
I1J1 Z71_IN : 情報1が1(初期化済み)ならば初期化処理をスキップ
INFSET1 1 ; 情報1に1を設定
CNT1INI ; カウンタ1初期化
Z71_IN LB
COUNT1 15 ; カウンタ1をインクリメントし、値が15になったら
JLB Z71_ST ; 弾発射処理へ
XDEC2 ; X座標を-2する
JUMP Z71_L ; 当たり判定処理へ
Z71_ST LB
INFSET2 1 ; 情報2に1を設定
SHOT 1,1,0 ; 威力1の弾を方向0に発射
SHOT 1,1,1 ; 威力1の弾を方向1に発射
SHOT 1,1,2 ; 威力1の弾を方向2に発射
JUMP Z71_L ; 当たり判定処理へ
Z71_RE LB
XINC ; X座標を+1する
JUMP Z71_L ; 当たり判定処理へ
Z71_B LB
INFSET6 1 ; 情報6(爆発フラグ)に1を設定
BOMB 11,6 ; 爆発パターン設定(6パターンアニメーション)
JUMP Z71_E ; キャラクタ表示処理へ
Z71_L LB
HIT 20 ; 自機との当たり判定
JLB Z71_B ; 当たりならダメージ20を与えて自分が爆発
Z71_E LB
CPUT ; キャラクタを表示
CEND ; スクリプト終了
 ちなみに自機も敵機などと全く同様の仕組みで動かしています。異なるのは後述する「当たり判定」の処理と動き制御スクリプト内で「キー(ジョイスティック)入力をチェック」していることくらいです。このように専用ルーチンで処理せずに、できるだけ汎用ルーチンとして処理することでプログラム構造があまり複雑にならないように工夫しています。
 ただ、ビートバイスでは自機や敵機、デカキャラなどの「キャラクタ」と自機や敵機の発射する「弾丸」とは区別しています。敵機のビームや特殊兵器のようなものはキャラクタとして処理していますが、弾丸は大きさが3種類のみで直進しかしないため、高速化のために別扱いとしています。同時に画面に表示される数もかなり多いですから・・・・(いちおうビートバイスでは最大32個、ビートバイス+では40個に制限していますが)。
 当たり判定
 シューティングゲームにつきもの(?)なのがこの「当たり判定」です。これがないとゲームにならないということは容易にご理解いただけると思います(^_^o)。ハードウェアスプライトを持つ機種では、スプライト同士が重なると割り込みが発生する機能がある場合が多いので、判定は無駄なく簡潔に行うことができます。もちろんPC-9801にはこのような機能はありません。
 当たり判定は敵機同士で行う必要はありません。また、敵機と敵機の発射した弾丸やビーム同士でも不要です。自機と判定が必要なのは、敵機、および敵機の発射した弾丸やビーム、フィールドの障害物の3種類になります。また、敵機側からすると、自機、および自機の発射した弾丸やビーム類、フィールドの障害物の3種類になります。ただ、敵機が自らフィールド障害物には決して当たらないという仕様であれば、最後の敵機とフィールド障害物との当たり判定は省くことができます。しかし、敵機を誘導して障害物にぶつけて破壊する戦術も考えられますから、最初から考えないのではなく、「考慮しておく」というのがよいでしょう。
 当たり判定を考えるときに戸惑いがちなのが、弾が機体に当たるのか、機体に弾が当たるのかという部分です。つまり、弾が移動したときにその弾が機体の当たり判定エリアに入っているか否かを調べるのか、機体の移動処理時に弾が自分の当たり判定エリアに入っているか否かを調べるのかということです。どちらでも構わないようにも思えるのですが、これは後者の方が処理が簡潔でわかりやすくなります。基本的に弾は発射された後は直進し、機体や障害物に衝突するか、画面の外に出てしまうまで生存するという性質があります。弾の移動制御はこれだけでよいわけです。ここに複雑な当たり判定処理を入れるよりも、もともと弾よりも複雑な動きをする機体の動き制御処理に組み込んだ方がすっきりします。
 具体的に言うと、機体の動き制御の流れの中で、新しい位置に移動したときに現在生存している弾の座標をサーチし、自分の当たり判定エリアに入っていないかをすべて調べます。入っていれば、その弾を消滅させ、機体を爆発させるか、ダメージを加算するかするわけです。
 また、当たり判定は弾と機体だけではなく、機体と障害物、自機体と敵機体の間でも必要です。このとき、障害物側を主体とした当たり判定は現実的ではありません。機体側で障害物に接触したか否かをチェックすることになるはずです。このように考えても、機体の動き制御の中で当たり判定を行う方が都合がよいと感じてもらえると思います。
 また、当たり判定の難しさのひとつに、「プレイヤーを納得させられるかどうか」という問題があります。特に自機の当たり判定、障害物の当たり判定には気を配る必要があります。「なんで今のが当たってないんだ?!」とか「ちょっと自機が擦った程度でこのダメージかよ!」とか、プレイヤーの立場で当たり判定を設定しないと、プレイヤーにストレスか溜まってしまい、シューティングゲームの命ともいえる「爽快感」が台無しになってしまうからです。
 ビートバイスでは自機の当たり判定エリアは、敵弾に対してと敵機および障害物に対してとは異なっています。敵弾に対しては自機パターンに完全に隠れる矩形範囲になっていますが、敵機や障害物に対しては「自機の重心」になっています(重心といっても厳密に計算されているわけではありません)。つまり「点」ですね。これだと擦ったくらいではダメージを受けませんし、精神的に余裕を持ってプレイすることができます。自機の当たり判定はぬるめに、敵機はきつめにするのが、気持ちよくプレイしてもらえるコツともいえるでしょう。何しろ多勢に無勢なのがシューティングゲームなのですから、この程度のハンディキャップは当然といえば当然かもしれません。
 爆発
 これはシューティングゲームには必須ともいえる効果です。デザイナ陣も爆発アニメーションのパターン作りには特に力を入れます。敵を破壊した達成感と爽快感を演出する最も大きなファクターだからです。
キャラクタ+背景合成アニメーション例
 画面右上が爆発中の敵機。爆発中は機体が爆発パターンに変化した無敵状態の機体として生存していることになっています。爆発アニメーション終了と同時に本当に消滅します。
 ビートバイスではこの爆発アニメーションをより簡単に実現するために、自分自身の機体パターンを爆発パターンに変化させることで実現しています。そして、爆発アニメーションが完了したのちに、自分自身を消滅させるのです。この手法には、爆発後に新たな機体を発生させたり、その場では爆発せずに下に落ちてから爆発させたりと、柔軟な「やられパターン」に対応しやすいという利点もあります。実際にビートバイスではこのようなやられパターンの敵が存在します。
 また、中ボスのような大型の敵の爆発は、爆発が始まってから順次、「出現してすぐに爆発するキャラクタ」を適当な位置に重ねて発生させることで表現しています。
 ただし、ステージ最後の大ボスの爆発は例外で、専用のプログラムで派手な爆発効果を演出しています(ビートバイス+)
 特殊兵器
 シューティングをより戦術的に、より派手に演出するのが特殊兵器群です。自機の場合は単純に前方に弾丸を発射し、敵機の場合は自機に狙いを定めて、あるいは固定方向に弾丸を発射する・・・・・これだけではごく単調なシューティングになってしまいがちです。
 そこで登場するのが特殊兵器です。ビームをはじめ、誘導弾やエナジー砲など、プレイヤーも敵側(コンピュータ)も多種多様な攻撃パターンを構築することができるようになります。
サンダーテンペスト
サンダーテンペスト
 先に敵側、つまりプログラム側の特殊兵器の実現方法について解説します。ビートバイスでは敵の特殊兵器はすべて通常のキャラクタで実現されています。つまり、「特殊兵器の形をした敵キャラクタ」を発生させて、それを自機めがけて体当たり攻撃させているのです。例えば中ボスから青いレーザービームが発射されたとすると、それは中ボスの制御スクリプトの中で青いレーザーの形をした機体のキャラクタを指定位置に発生させているわけです。このキャラクタの制御スクリプトはビームらしく振る舞うために、一定方向に障害物に当たるまでひたすら直進するようにプログラムされています。ビームが命中するということは、これが自機に衝突することになるわけです。
 制御スクリプトの中では自機との相対座標や相対距離などが取得できるので、誘導弾やパトリオット式炸裂弾(目的物に近づいて炸裂)なども容易に実現できます。また、任意の数値テーブルの登録と参照も可能になっているので、サインテーブルや放物線テーブルを使用して自然な動きを演出することもできます。
パワーボール
パワーボール
 そして自機の特殊兵器です。こちらはすべて専用のプログラムで実現しています。ビートバイスの特殊兵器はすべてが粒子砲なので、その威力をプレイヤーが任意に決定できます(より強力にすればそれだけエナジーを消費するので使用可能時間が短くなります)。また、広範囲攻撃型であったり、近距離専用であったり、防御型であったりします。このため、12種類もある特殊兵器すべてを作り込んであります。手間暇はかかりますが、これは仕方のないところです。
 ちなみにビートバイスでは通常兵器のバルカン砲も含め、自機側の兵器はすべてテキスト画面で表現しています。これは広い面積の画面合成の負荷を減らし、迫力を演出するためです。実際に、パワーボールサンダーテンペストといった派手な特殊兵器を発射していてもスクロール速度が低下することはほとんどありません。
パイオンシャワー
パイオンシャワー
 特殊兵器の当たり判定ですが、これも専用のプログラムで行っています。敵機側の制御スクリプトの当たり判定内で、そのとき発射されている特殊兵器専用の当たり判定ルーチンを呼び出しています。ちなみに、何十個もの粒子が飛び出して見えるパイオンシャワーの当たり判定は、真面目にすべての粒子ごとに行われています。面倒くさい作業ですが、これもプレイヤーに不条理感を持たせないためには重要なことだと判断した結果です。
 柔軟性と生産性
 シューティングの面白さは「変化」と「攻略」にあると考えます。もし、敵機の攻撃パターンが数種類しかなかったとしたら、もし背景が単調な宇宙空間だけだったとしたら、もし自機の武器が一門の連射砲だけだったとしたら・・・・いかに工夫しても単調さは拭えないでしょう。わくわく感、どきどき感、達成感、これらを演出するには変化に富んだステージとプレイヤーに頭を使ってもらう仕掛けが必要です。
 ステージにバライエティーを持たせるためには、敵機やスクロールを制御する仕組みに工夫が必要になります。これが結構悩ましいところなのです。比較的簡単に制御できるように仕様を作ると、できることの制約が大きくなります。ツクール系ソフトなどもかなり工夫をして制約が小さくなるように努力されているようですが、やはり限界があります。かといって制約がないようにしようとするとスキルの高いプログラマしかできなくなってしまいます。このトレードオフをどうするかが、シューティングを作るときのコンセプトとも絡んできます。
 前出の制御スクリプトを見てもらえればわかると思いますが、ビートバイスでは生産性よりも柔軟性を重視しています。原則、制御スクリプトでできないことはありません。これは当然のことで、「こういうことをさせたい!」という希望が上がってきたら、制御スクリプトを拡張してしまうからです。例えば、「ビームを跳ね返す敵機を作りたい」という要望があれば、当たり判定命令"HIT"でビームか否かをチェックする機能を追加するといった具合です。もちろん、ビーム処理ルーチンにも手を入れてビームが一定の条件で反射するようにする必要もあります。また、これによってプレイヤーは敵機を見て判断し、ビームではない武器に切り替える必要が生まれます。直前までビームを使うと効率良く落とせる敵を出現させておけば、これは顕著になります。面倒ですが、ツクール系ソフトではまず実現できないことでもあります。生産性よりも柔軟性を重視してこそ、プレイヤーに伝えられるものがあると信じます。

特別講義メニューページへ
ディン:「頭は柔らかくないとダメよ!」