Published on: 2021-06-10
Updated on: 2021-06-19
ほとんどの敵のAIはいわゆるバイトコードで実装されている。
つまり、直接 6502 で書くのではなく、「ある方向へ移動する」「弾を撃つ」などの命令を備えたバイトコードで記述し、それを 6502 で書かれた仮想マシン上で動かしている。
この方式の最大の利点は「処理の中断」が可能なことだろう。例えば「30フレーム横へ動き、60フレーム下へ動き、...」などといった処理を 6502 で直接書くと、多くの場合複雑なステートマシンを実装することになり面倒である。
バイトコードでなく専用ルーチンで動いている敵はルイド(ID: 0x11), リューク(ID: 0x14), ソープラー(ID: 0x21), スターブレイン(ID: 0x22, 0x23)のみ。
ここではバイトコードに関する解説のみ行う。
以下、バイトコードで実装された敵のうちジェリコ、ラザロを「ボス」、それ以外を「ザコ」と総称する。
バイトコードのアセンブラ/逆アセンブラを作成した。以下、命令のニーモニックはこのアセンブラを基準とする。
playground で実際のバイトコードの動作が見られる。
敵1体ごと(ボスの場合、1パーツごと)に仮想マシンが割り当てられる。
なお、1つの敵編隊内ではバイトコードが共有されるが、エントリポイントは敵1体ごとに異なる。
ここでは仮想マシンの実装をやや抽象化して記述する。また、レジスタ名などは筆者が独自に命名したものである。
仮想マシンのアドレス空間は 8bit で、ROM 上のバイトコードがマップされる。このアドレス空間は読み取り専用である。
仮想マシンは以下のレジスタを持つ:
名前 | 型 | 用途 |
---|---|---|
pc | u8 | プログラムカウンタ |
loop_counter | u8 | ループカウンタ |
loop_start_addr | u8 | ループ開始アドレス |
jump_on_damage | u8 | ザコの場合:被弾時のジャンプ先 ボスの場合:HP |
sleep_timer | u8 | 仮想マシンのスリープタイマー |
homing_timer | u8 | 自機追尾タイマー |
inv_x | bool | x 方向の移動を反転 |
inv_y | bool | y 方向の移動を反転 |
ループ関連レジスタが1組しかないことからわかるように、1重ループのみサポートしている。
毎フレーム以下の動作を行う:
sleep_timer
レジスタが非0ならばデクリメントして戻る。homing_timer
レジスタが非0ならば自機を追尾する方向へ移動して戻る。ただし敵が再行動フラグを持つとき、特定条件下でさらにバイトコードを実行する。pc
の指すアドレスから 1 ステップ(実行を中断する命令まで)実行する。jump_on_damage
レジスタが非0ならば pc
をそのアドレスに変更し、さもなくば破壊される。jump_on_damage
レジスタをHPとして扱い、この値が非0ならばデクリメントし、さもなくば破壊される。(HP)+1
発の撃ち込みが必要であることに注意。仮想マシンは以下の命令を持つ:
オペコード | ニーモニック | 機能 | 実行の中断 |
---|---|---|---|
0x00..=0x3F | move | 敵を指定方向へ移動 | Yes |
0x40 | jump | ジャンプ | No |
0x41..=0x4F | set_sleep_timer | sleep_timer を設定 | Yes |
0x50 , 0x52..=0x5F | loop_begin | 指定回数のループを開始 | No |
0x51 | loop_end | ループ末尾 | No |
0x60..=0x6F | shoot_direction | 方向指定弾を撃つ (未使用) | No |
0x70..=0x7F | set_sprite | 敵メタスプライトIDを設定 | No |
0x80..=0x8F | set_homing_timer | homing_timer を設定 | No |
0x90..=0x93 | set_inversion | inv_x , inv_y を設定 | No |
0xA0 | set_position | 敵座標を設定 | No |
0xA1 | set_jump_on_damage | jump_on_damage を設定 | Yes |
0xA2 | increment_sprite | 敵メタスプライトIDをインクリメント | No |
0xA3 | decrement_sprite | 敵メタスプライトIDをデクリメント | No |
0xA4 | set_part | 敵パーツ番号を設定 | No |
0xA5 | randomize_x | 敵座標x をランダムに変更 | No |
0xA6 | randomize_y | 敵座標y をランダムに変更 | No |
0xB0 | bcc_x | (敵座標x) < (自機座標x) ならば分岐 | No |
0xB1 | bcs_x | (敵座標x) >= (自機座標x) ならば分岐 | No |
0xB2 | bcc_y | (敵座標y) < (自機座標y) ならば分岐 | No |
0xB3 | bcs_y | (敵座標y) >= (自機座標y) ならば分岐 | No |
0xC0..=0xCF | shoot_aim | 自機狙い弾を撃つ | No |
0xF0 | restore_music | BGMをデフォルトに戻す | No |
0xF1..=0xFF | play_sound | 指定した効果音を鳴らす | No |
0x00..=0x3F
(move
)1Byte 命令。
敵をオペコードの表す方向へ移動させる。
inv_x
, inv_y
の設定に従って方向が反転する。
敵が高速化フラグを持つとき、特定条件下でスピードが増す。
実行を中断する。
ただし、敵が再行動フラグを持つとき、特定条件下でさらに 1 ステップ実行する。
0x40
(jump
)2Byte 命令。0x40 <addr>
の形式。
プログラムカウンタを <addr>
に設定する。
実行を継続する。
0x41..=0x4F
(set_sleep_timer
)1Byte 命令。
sleep_timer
の値を 4*(オペコード下位4bit)
にする。
実行を中断する。
0x50
, 0x52..=0x5F
(loop_begin
)1Byte 命令。
loop_counter
の値をオペコード下位4bitに、loop_start_addr
の値を次命令のアドレスにする。
オペコード 0x50
は 256 回のループとなることに注意。
実行を継続する。
0x51
(loop_end
)1Byte 命令。
loop_counter
をデクリメントし、その結果が 0 なら次命令のアドレスへ、さもなくば loop_start_addr
へジャンプする。
実行を継続する。
0x60..=0x6F
(shoot_direction
)1Byte 命令。ゲーム内では未使用。
オペコード下位4bitの方向に方向指定弾を撃つことを試みる。
敵編隊パラメータの影響を受けない。
敵弾ランクなどの影響により、発射がキャンセルされることがある。
また、特定条件で誘導弾になることがある。
実行を継続する。
0x70..=0x7F
(set_sprite
)1Byte 命令。
敵メタスプライトIDを (基本値)+(オペコード下位4bit)
にする。
実行を継続する。
0x80..=0x8F
(set_homing_timer
)1Byte 命令。
オペコード 0x80 なら homing_timer
の値を 252
にする。
それ以外の場合、homing_timer
の値を 4*(オペコード下位4bit)
にする。
自機追尾処理に戻って実行を継続する。
0x90..=0x93
(set_inversion
)1Byte 命令。
inv_x
をオペコードの bit0 に、inv_y
をオペコードの bit1 にする。
実行を継続する。
0xA0
(set_position
)3Byte 命令。0xA0 <x> <y>
の形式。
敵座標x を <x>
に、敵座標y を <y>
にする。
実行を継続する。
0xA1
(set_jump_on_damage
)2Byte 命令。0xA1 <addr>
の形式。
jump_on_damage
を <addr>
にする。
実行を中断する。
0xA2
(increment_sprite
)1Byte 命令。
敵メタスプライトIDをインクリメントする。
実行を継続する。
0xA3
(decrement_sprite
)1Byte 命令。
敵メタスプライトIDをデクリメントする。
実行を継続する。
0xA4
(set_part
)2Byte 命令。0xA4 <part>
の形式。
敵パーツ番号を <part>
にする。
実行を継続する。
0xA5
(randomize_x
)2Byte 命令。0xA5 <mask>
の形式。
敵座標x の <mask>
で指定されたbitたちをランダムに変更する。
実行を継続する。
0xA6
(randomize_y
)2Byte 命令。0xA6 <mask>
の形式。
敵座標y の <mask>
で指定されたbitたちをランダムに変更する。
実行を継続する。
0xB0..=0xB3
(bcc_x
, bcs_x
, bcc_y
, bcs_y
)2Byte 命令。0xB0..=0xB3 <addr>
の形式。
敵座標と自機座標を比較し、条件を満たしていれば <addr>
へ、さもなくば次命令のアドレスへジャンプする。
実行を継続する。
0xC0..=0xCF
(shoot_aim
)1Byte 命令。
オペコード下位4bitは意味を持たない(何らかの拡張予定があったのかも)。
自機狙い弾を撃つことを試みる。
弾抑制フラグ、弾高速化フラグ、誘導弾化フラグの影響を受ける。
敵弾ランクなどの影響により、発射がキャンセルされることがある。
また、誘導弾化フラグを含む各種条件により誘導弾になることがある。
0xF0
(restore_music
)1Byte 命令。
BGMをデフォルトに戻す。ラザロのBGMを元に戻すのに使われる。
ただし、現在ゲームオーバーBGMが流れている場合は何もしない。
実行を継続する。
0xF1..=0xFF
(play_sound
)1Byte 命令。
オペコード下位4bitの効果音を鳴らす。
実行を継続する。