見出し画像

テキキャラとの当たり判定とエフェクト

まず、はじめに。半タイル処理はしばらく実装しません(できません)
筆者自身の技術力に起因するものであり、もっと鍛錬が必要だと気づきました。はじめにお詫びいたします。

テキキャラとの当たり判定を実装する

今回のサンプルではテキキャラの当たり判定を実装しました。
・テキキャラと当たると効果音が鳴る
・テキキャラと当たると自分のキャラクターが一定期間点滅する
・点滅期間中は当たり判定は行わない
・テキキャラと当たるとライフゲージが減る
・ライフゲージが0になるとゲームオーバー
こんな感じの処理を付け足しています。

と、いうことでまずはサンプルコード(sample013)をゲットだぜ!
https://github.com/sailorman-msx/games/tree/main/src/sample013

動きを先に確認したい?のであれば、こちらからどうぞ!

最初にライフゲージの仕様を設定する

当たり判定時に減らしていくライフゲージの変数は以下のとおりです。

(initialize.asm)
 
; 8バイト分用意する
; 8バイト目から減算していきそのバイトが0になったら
; ライフゲージには空白を出力する
WK_PLAYERLIFEGAUGE:equ $C0B3  ; 8バイト
WK_PLAYERLIFEGAUGE_CHARS:equ $C0BB  ; 8バイト

WK_PLAYERLIFEGAUGE変数は8バイトで各バイトには数値が入っています。
数値は0-2の値となっており、各数値は以下のキャラデザに対応しています。
0: 空白
1: ゲージ半分
2: ゲージ満タン
WK_PLAYERLIFEGAUGEの値をキャラデザに変換して、WK_PLAYERLIFEGAUGE_CHARSにそれぞれ詰めていくと以下の図の赤枠で囲ってあるようなライフゲージを表現できるようになります。

最初はWK_PLAYERLIFEGAUGEの8バイトすべてには2が格納されていて、テキキャラと当たったらWK_PLAYERLIFEGAUGEの一番右端(+7バイト目)に該当する数値を1減算していき0になったらその左(+6バイト目)の値を減算していく。という形です。ライフが16あるみたいな感じですね。

この処理を行っているのは status.asm の DecLifeGaugeサブルーチンです。数値をキャラデザに変換して表示しているのが DisplayLifeGaugeサブルーチンになっています。

(status.asm)
 
;--------------------------------------------
; ライフゲージを減算する
;--------------------------------------------
DecLifeGauge:
 
(中略)
 
;--------------------------------------------
; ライフゲージを表示する
;--------------------------------------------
DisplayLifeGauge:

もうちょっと効率化させたいコードになっていますが、まあ、おいおいそれはやりましょう(汗)

テキキャラとの当たり判定

当たり判定の処理は単純です。sample013.asm を見てみましょう。

;--------------------------------------------
; 割り込み処理開始
;--------------------------------------------
GameProc:

    ; 当たり判定情報の変数に値が入っていなければ
    ; 当たり判定をチェックする
    ; 当たっている場合はAレジスタに1がセットされる

    ld a, (WK_PLAYERCOLLISION)
    cp 1
    jr nc, GameProcMoveEnemy

    call CheckEnemyCollision
    cp 1
    jr c, GameProcMoveEnemy

    ; 当たり判定情報に30をセットする
    ld a, 30
    ld (WK_PLAYERCOLLISION), a

GameProcMoveEnemy:
    
    call MoveEnemies
    
    call CreateViewPort
    call DisplayViewPort
    
    ; ライフゲージを表示する
    call DisplayLifeGauge
    
(中略)

GameProcEnd:

    ; ワーク用スプライトアトリビュートテーブルを
    ; 作成する
    ld hl, SPRDISTPTN_TBL
    ld b, 0
    ld a, (WK_PLAYERDIST)
    ld c, a

    add hl, bc
    ld ix, hl

    ld a, (WK_PLAYERCOLLISION)
    cp 1
    jr c, SpriteColorNormal  ; 当たり判定情報が0なら何もせず終了

    ; 衝突時のSFXを鳴らす
    ; (WK_PLAYERCOLLISIONの値が30の場合のみ)
    cp 30
    jr nz, SoundSFXEnd

    ld hl, SFX_00
    call SOUNDDRV_SFXPLAY

    ; ライフゲージをデクリメントする
    call DecLifeGauge

SoundSFXEnd:

    ; 当たり判定情報の第0ビットが1の場合は黄色にする(点滅させる)
    ; 当たり判定情報の第0ビットが0の場合は通常にする(点滅させる)
    and 00000001B
    call z, SpriteColorNormal

SpriteColorWarn:

    ld a, $0B
    ld (WK_PLAYERSPRCLR1), a ; スプライトの表示色

    jr SpriteDisplay

SpriteColorNormal:
    
    ld a, $0D
    ld (WK_PLAYERSPRCLR1), a ; スプライトの表示色

SpriteDisplay:

    call CreateWorkSpriteAttr

    ; スプライトを表示する
    ld de, $1B00
    ld bc, 8 ; スプライト2枚分を表示
    call PutSprite

    ld a, (WK_PLAYERCOLLISION)
    cp 1
    jr c, SpriteDisplayEnd
    
    dec a ; 当たり判定情報をデクリメントする
    ld (WK_PLAYERCOLLISION), a
    
SpriteDisplayEnd:

    ; ライフゲージがなくなったらGAME OVER画面を呼び出す
    ld a, (WK_PLAYERLIFEGAUGE+0)
    cp 1
    jp c, GameOverProc
    
    ret  

割込み処理時に呼ばれるGameProcサブルーチンの一番最初で、WK_PLAYERCOLLISION変数の値が0であれば、CheckEnemyCollisionサブルーチンを呼び出しています。CheckEnemyCollisionサブルーチンではプレイヤーのスプライトに重なっているキャラクタコードを調べてテキキャラを構成するキャラクタコードであれば「衝突した」と判定しています。

(sprite.asm)
 
;--------------------------------------------
; SUB-ROUTINE: CheckEnemyCollision
; 当たり判定処理を行う
; (返却値)
; Aレジスタ=0 : 当たっていない
; Aレジスタ=1 : 当たっている
;--------------------------------------------
CheckEnemyCollision:

    push bc
    push hl

    ;--------------------------------------------
    ; 移動先のVRAM情報判定
    ; 現在自分がいる場所の周囲44の情報を取得する
    ;--------------------------------------------
    ld a, (WK_PLAYERPOSX)
    ld (WK_CHECKPOSX), a

    ld a, (WK_PLAYERPOSY)
    ld (WK_CHECKPOSY), a

    call GetVRAM4x4

    ld a, (WK_VRAM4X4_TBL+$05) ; プレイヤーの左上の重なりをチェック
    cp $98
    jr nc, CheckEnemyCollisionAtari

    ld a, (WK_VRAM4X4_TBL+$06) ; プレイヤーの右上の重なりをチェック
    cp $98
    jr nc, CheckEnemyCollisionAtari

    ld a, (WK_VRAM4X4_TBL+$09) ; プレイヤーの左下の重なりをチェック
    cp $98
    jr nc, CheckEnemyCollisionAtari

    ld a, (WK_VRAM4X4_TBL+$0A) ; プレイヤーの右下の重なりをチェック
    cp $98
    jr nc, CheckEnemyCollisionAtari

    ld a, 0 ; 当たっていない

    jr CheckEnemyCollisionEnd

CheckEnemyCollisionAtari:

    ld a, 1 ; 当たっている

CheckEnemyCollisionEnd:

    pop hl
    pop bc

    ret

衝突したと判定された場合、WK_PLAYERCOLLISION変数に30をセットして、GameProc処理が行われるたびにデクリメントしていきます。
デクリメントしている最中は、WK_PLAYERCOLLISION変数の第0ビットの値によってスプライトの髪の毛の色を変化させて「点滅」しているような効果(エフェクト)を加えています。
この仕様によって、当たり判定で衝突したと判定されると1/60秒の間隔で30/60秒の期間、チカチカとスプライトの色が変化しながら表示されるようになります。
WK_PLAYERCOLLISION変数の値が0でなければ、「当たり判定」が行われないためその間は「無敵」状態になる。という仕組みです。

ライフゲージがなくなるとゲームオーバー画面になります。
いまは簡単に表示しているだけですが、ゲームオーバー時のエフェクトもプログラミングで実現させる必要があります。
うーん、道のりは遠いですね・・。

BGMと効果音の絶妙なテクニック

効果音はちょっとしたテクニックを使っています。BGMはトラック1とトラック2(チャンネル1とチャンネル2)、効果音はトラック2とトラック3(チャンネル2とチャンネル3)の和音で実現しています。
効果音もトラック1とトラック2で鳴らすと効果音が出てるときにBGMが止まってしまうことになります。
なので、トラック1でBGMの主旋律を流し、効果音はトラック1を使わないことでBGMが途切れていないようにみせかけています。
まあ、ほんとにちょっとしたテクニックです。

次回は攻撃機能を実装予定

今回はここまでです。
これでプレイヤーの寿命が実装されました。
でも、ただ逃げ回るだけではゲームになりません。
なので、プレイヤーによる攻撃機能の追加を検討しています。
そこまでいけば、ほぼゲームができたも同然です。

では、また!ノシ

マシン語講座:クロック数を意識しよう

今回のサンプルではDisplayLifeGaugeが1/60秒ごとに毎回呼び出されるのは改善点ですね・・。
DecLifeGaugeを呼び出した時だけ呼べばいい・・。
無駄コードの積み重ねは負荷をかけるだけです。
Z80くんがかわいそうです。
MSXでのZ80のマシン語プログラミングではCPUには負荷をとにかくかけない。という意識が強く必要になります。
現代においてクロック数を強く意識するプログラミングはほとんどみかけませんが、クロック数を意識することはハードウェアを理解することにもつながるのでとても意義のあることです。
さて、MSXのZ80は3.58MHzです。
1秒間に358万回、1クロックの命令を実行できます。
逆算すると、1クロックあたり1/3.58マイクロ秒(0.279マイクロ秒)かかるということになります。
ただし命令を実施するために必要なクロック数は命令ごとにそれぞれ決まっていて、それらの命令のトータルクロック数を1/60秒(0.016秒)内に収めなければいけません。以下のサイトにMSX1での各命令に必要なクロック数が記載されています。表中のZ80+M1という列がMSX1でのクロック数になっています。クロック数が多ければ多いほど遅い。という理解で良いです。
例えば
INC A は 5クロック(=0.279マイクロ秒 x 5=1.395マイクロ秒)
ですが
INC IX は 12クロック(=0.279マイクロ秒 x 12=3.348マイクロ秒)
です。同じINCでも3倍も速度が変わります。
全体的に、8ビット命令はクロック数が少なく済む。というかんじです。

私が作成しているコードではJRやIX、IYレジスタ操作をひんぱんにやってますが、これらはかなりクロック数が必要(=低速=高負荷)なようです・・。改善しなければ・・ならない・・。
Visual Studio Codeだとクロック数のトータルを見れるプラグインがあるとかないとか聞いたのでそっちに切り替えようかな・・。



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