見出し画像

テキキャラを動かす その1

前回記事からだいぶ時間があいてしまいました。
前回まででスクロールを実現させましたが、今回はそのマップ上にテキキャラを配置してみました。

今回記事から、テキキャラについての解説になります。
解説したい箇所がいくつかあるので複数回の記事にわけて説明します。

テキをどう作るか?スプライト or PCG?

テキキャラをどう作るか?という点についてはいろんな意見がありますが、私は「自分のキャラ以外はすべてPCG」という宗派(笑)に属していますので、当記事ではテキキャラはPCGで動かします。
ということで、まずはサンプルコード(sample011)をゲットだぜ!!
https://github.com/sailorman-msx/games/tree/main/src/sample011

テキの移動処理のキホン

テキキャラについては、MAP上の論理座標で動かすようにして、今回次のような情報テーブルをテキキャラ1体ごと作成しています。

; テキキャラ管理用1体分(11byte x 100体ぶん)
;
; + 0  : テキキャラの種類(0:なし 1:ENEMY-TYPE1, 2:ENEMY-TYPE2, 3:GHOST)
;        ENEMY-TYPE1とENEMY-TYPE2はテキキャラ
;        GHOSTは味方キャラ
; + 1  : MAP論理X座標
; + 2  : MAP論理Y座標
; + 3  : 進行方向(1:3:5:7:左)
; + 4  : 進行距離(タイル数)移動ごとに1減らす(1-16)
; + 5  : 進行カウンタ
; + 6  : 当たり判定フラグ(0:1:壁ブロック 2:3:緑ブロック)
; + 7  : 初期スポーン位置MAP論理X座標
; + 8  : 初期スポーン位置MAP論理Y座標
; + 9  : 上書き前のタイル番号
; + A  : 倒した時のスコア
;

今回のサンプルではテキキャラは2種類。TYPE-1とTYPE-2だけ作成してます。青鬼と赤鬼を作ってみました。コメントにGHOSTっていう種類も書いていますがこれはまた後続記事で追加するキャラの予定です。

うーん・・・。
ドット絵師さんのスキルの1%でもいいから欲しい今日この頃です。。。

さて、テキキャラの仕様は次のようになっていて75体ぶん動かします。

・テキの移動はMAP上の論理座標だけで移動させる。
・テキを表示させるために最大12x12タイルのビューポートをベースにする。
・テキの論理座標がそのビューポート範囲内に存在していれば、移動処理の対象となる。
・テキの論理座標とプレイヤーの論理座標が12以上の場合、テキの移動は行わない。(ムダな処理の排除)
・テキの移動方向先の1タイルが床ではない場合は移動せずに、移動情報を初期化する。
・テキの進行カウンタがMAXになった場合はMAP座標上で進行方向に1タイル分移動させて進行カウンタを0にする。
・12x12タイルぶんの表示キャラクターのメモリ展開が完了したら、そのうちの10×10タイルぶんだけのキャラクター情報をVRAM(画面)に転送する。

1タイルぶん広いエリアで動かしてみる

テキキャラはMAP上の論理座標上で動かして、最大12x12タイルのビューポート内で移動処理を行います。(enemy.asmを参照してください)
画面に表示(VRAMに転送)するときだけそのビューポート内のさらに10x10タイルぶんだけを切り取って表示します。このことによって「見えないところからテキキャラが動いてきた!」というような視覚効果が表現できます。
12x12という範囲を広げればよりたくさんのテキの座標をワラワラと動かすことが可能ですが処理する対象のテキの数に比例して、処理速度が遅くなってしまう原因となるため調整が必要です。また、この「画面上で見える範囲外のテキを動かす処理」は、後続記事で説明する「テキの半タイル移動」の重要な要素となります。

テキの移動ロジック詳細

テキキャラは生成時にランダムに移動方向と移動距離を決めていて、1タイル動くたびに移動距離をデクリメントしています。移動距離が0になると、移動距離を再構築しています。再構築後の進行方向が再構築前の方向と同じであれば異なる方向になるまで進行方向決めをやり直しています。

テキの表示

テキキャラはMAP上にテキキャラのタイル番号を埋めることで表示を表現しています。enemy.asm、map.asm、data_map.asmを参照してください。

 TYPE1ならタイル番号は#11-#14
 TYPE2ならタイル番号は#15-#18

1タイル移動はハードモード。だ。

動かしてみるとわかるのですが、今回のサンプルでは移動単位は1タイル(16ドット)になっていますので「めちゃくちゃ速い」です。昔、スペランカーの3周目?とかはこんな感じだったような・・。

こんなゲームバランス無理やん。
クソゲーやん。テキキャラの動き半端ないやん。。。

ということで、半タイル(8ドット)で動かすことを考えてみましょう。

コード詳細は次回

どうやって実現しているの?どんなコードになっているの?
という点については次回記事から説明しますね。

では、また!!

マシン語講座:ジャンプ先をテーブル化する

ひさしぶりのマシン語講座です。
マシン語でよく書いてしまうのが次のようなコードです。

; CPの連続で分岐を繰り返す

; 分岐処理
; Aレジスタの値によって処理を分岐する
;

; A = 0 ?
ProcA:

cp 1
jr c, ProcB

ld b, 0
jr ProcEnd

; A = 1 ? 
ProcB:

cp 1
jr nz, ProcC

ld b, 1
jr ProcEnd

; A = 2 ?
ProcC:
 
cp 2
jr nz, ProcD
 
ld b, 2
jr ProcEnd
 
....たくさん続く
 
ProcEnd:
 ret

こういう分岐は、ムダコードの典型です。
上記のようなコードを書くと、Aレジスタの値が200だったら200回の「ムダな比較命令(CP)とジャンプ」が発生します。Z80は貧弱なCPUなのでムダなことで働かせてはいけません。Z80くんがかわいそうです。
最近のコンピュータであれば処理速度がかなり速いので、こういうコードを書いてしまいがち。プログラマーの新人くん「あるある」。
では、どうやって分岐させるのか?っていうと「処理ラベル(アドレス)をテーブルにしてしまう」という方法があります。以下の図のようにラベルのテーブルを定義して、値によってそのラベルにジャンプさせる。という形です。アセンブラによってはたぶんこういう書き方ができないやつもあるかもですが、z88dkでは書けます。

これだと分岐処理は次のような形になります。

; 修正後(直接アドレッシング指定で分岐をなくす)

; テーブルの作成
ld hl, WK_BUNKI_PROC
ld de, ProcA
ld (hl), e
inc hl
ld (hl), d
inc hl
ld de, ProcB
ld (hl), e
inc hl
ld (hl), d
inc hl
ld de, ProcC
ld (hl), e
inc hl
ld (hl), d
inc hl
ld de, ProcD
ld (hl), e
inc hl
ld (hl), d
inc hl


; ここから先が分岐処理

; HLレジスタに処理のラベル(アドレス)テーブルの先頭アドレスをセット
ld hl, WK_BUNKI_PROC

ld b, 0
ld c, a        ; Aレジスタの値をHLレジスタに加算すると処理すべきコードのアドレスになる
add hl, bc

jp (hl) ; HLレジスタのアドレスに強制ジャンプ

; A = 0
ProcA:

ld b, 0
jr ProcEnd

; A = 1 
ProcB:

ld b, 1
jr ProcEnd

; A = 2
ProcC:
 
ld b, 2
jr ProcEnd
 
....たくさん続く
 
ProcEnd:
 ret

CPの連続のほうが読みやすいコードなのはたしかですが、CPUにムダな負荷をかけてはいけません。なので値によって直接アドレッシングで強制ジャンプさせるやりかたです。JPは1回こっきり。CPの結果でJP。。になっていないことがわかりますか?今回のサンプルでも多用しています。探してみてください。
ちなみにこのやりかたは、ちょっとでも間違うと暴走します。(汗)






セーラー服が似合うおじさんです。猫好き、酒好き、ガジェット好き、楽しいことならなんでも好き。そんな「好き」をつらつらと書き留めていきます。