どんばのプログラム
更新日付 1998-07-15
「どんば」のプログラムで特徴的な部分を取り上げ、
変てこりんな解説をする予定です。
もっといいアルゴリズムがあったらツッコミを入れてください。
この講座では、汎用的な技術についてのみ詳しく解説します。
SFC固有の技術についてはソースを解析してください。
・OBJ(スプライト)について
OBJ(スプライト)情報はOAMという領域に格納されます。
このOAMという領域は、メインメモリとは違う世界にあり、
垂直帰線期間しか読み書きできません。
しかし、アクセスの度に垂直帰線期間を待っていては面倒です。
そこでメモリ中に仮想OAMという領域を確保します。
仮想OAMの内容は、垂直帰線期間割り込みを使って、
本来のOAMにDMA転送するようにしました。
X68000では、垂直帰線期間でなくても、
スプライトを描き換えることができましたが、
ちらつきのない美しい表示を実現するため、
当たり前の技術となっていました。
この部分のプログラムについて、詳しい解説はしませんが、
前提として理解しておいてください。
・BGについて
BGはV-RAMに格納されます。
このV-RAMもメインメモリとは違う世界にあり、
やはり垂直帰線期間しか読み書きできません。
背景など、たくさんのBGを書き換えたい場合は、
一度だけ垂直帰線期間を待ち、DMA転送などで一気に転送します。
しかし、この方法はデータをリニアに転送するしかないので、
MAPE形式のデータには使えません。
もし画面が一度消えても構わないのであれば、
画面を表示禁止状態(強制Vブランク)にすることで、
思う存分好きなタイミングで書き換えることができます。
・マップデータの転送例
MAPE形式のマップデータを転送します。
縦に並んでいるので、DMAで転送しても劇的な高速化は望めません。
上で説明したお手軽な転送を紹介します。
------------------------------------------------------------
マップの転送
test_map:
off16a
sep #$20
lda #$81 ;address +32 at $2119
sta #2115 ;VMAINC.b
on16a
on16i
rep #$30
ldx #$0000 ;転送元アドレス
ldy #BG1SC0
_loop:
sty $2116 ;VMADD.w書き込みアドレス
_put:
lda map_data,x ;1キャラロード
and #$00ff ;上位バイトクリア
ora #$0200 ;たとえばパレット1を設定
sta $2118 ;VMDATA.w
inx
txa
and #32-1
bne _put
iny
cpy #BG1SC0+32
bne _loop
rts
------------------------------------------------------------
このサブルーチンはアドレスが固定なので、あまり使い道がありません。
どんばではもっと汎用性の高い転送ルーチンを使っています。
転送アドレスなどをインラインで記述しているので、
興味があればソースを解析してください。
転送ルーチンの名前はxfer_map:です。
・ポーズの表示
まず現在のアクション番号を調べます。
そして、それをテーブルを使って、
ポーズデータへのオフセットに変換します。
つまりアクションの先頭アドレスを求めたことになります。
次にポーズ番号を調べます。
このアクションにおける何番目のポーズを表示するのかという情報です。
一つのポーズデータは12キャラ×4バイト=48バイトですから、
ポーズ番号は48倍しなければなりません。
かけ算を真面目にするのは、ばかばかしいので、
これもテーブルを使って48倍しています。
上で求めた二つの値を加算すれば、
次のポーズデータのアドレスがわかります。
これにアクション開始時の座標を加算して表示位置を求めます。
こうやって計算した座標データや、OBJ番号などを、
仮想OAMに確保された自機ワーク(pl_xとかpl_No)に転送すれば、
自機キャラが画面に表示されます。
------------------------------------------------------------
; ==============================
; 自機の表示(ポーズデータの転送)
; ==============================
play_disp:
on16a
on16i
rep #$30
ldx p_action ;動作(アクション)
lda tableACT,x ;現在の動作(アクション)データのアドレス
ldx p_pose ;何番目のポーズか
clc
adc table48,x ;値を48倍するテーブル
tax ;x=現在のポーズデータ
lda base_px
sec
sbc map_x
sta temp_px
ldy #$0000 ;y=転送先アドレス
_loop:
off16a
sep #$20
lda base_py ;動作(アクション)開始時のx
xba
lda temp_px ;動作開始時のx、現在A=[py|px]
on16a
rep #$20
sta work2 ;動作開始時の座標
lda pose_data,x ;x,y座標を同時に取り出す
bne _skipB
jsr bug_trap ;$0000ならバグ
_skipB:
clc
adc work2 ;動作開始時の座標
sta pl_x+0,y ;OBJ座標x,y書き込み
lda pose_data+2,x ;OBJ番号|OBJ属性
ora #$3000 ;優先度%11、パレット%000、OBJ番号$0xx
sta pl_x+2,y ;OBJ属性書き込み
inx
inx
inx
inx
iny
iny
iny
iny
cpy #48 ;1ポーズ=48バイト
bne _loop
_exit:
rts
table48: ;値を48倍するためのテーブル
dw 00*48,01*48,02*48,03*48
dw 04*48,05*48,06*48,07*48
dw 08*48,09*48,10*48,11*48
dw 12*48,13*48,14*48,15*48
dw 16*48,17*48,18*48,19*48
dw 20*48,21*48,22*48,23*48
dw 24*48,25*48,26*48,27*48
dw 28*48,29*48,30*48,31*48
------------------------------------------------------------
・アクションの移行
一つのアクションが終わると、次のアクションに移行します。
たとえ何もすることがない場合でも、
それは「立つ」というアクションをしていることになります。
アルゴリズムを説明しましょう。
上と同じ方法で、ポーズデータのアドレスを計算し、
該当するポーズデータの座標を試しに取り出します。
その値が(x,y)=(0,0)、つまり$0000だったら、
次のアクションに移行するという仕掛けになっています。
次のアクション番号は、レバー入力などを考慮して、
やはりテーブルによって求めます。
------------------------------------------------------------
; ==============================
; 自機の移動
; p_action/p_poseの移行
; ==============================
play_move:
on16a
on16i
rep #$30
inc p_pose
inc p_pose
ldx p_action ;動作(アクション)
lda tableACT,x ;現在の動作(アクション)データのアドレス
ldx p_pose ;何番目のポーズか
clc
adc table48,x ;値を48倍
tax ;x=現在のポーズデータ
lda pose_data,x
bne _exit
; 動作が終了したので、パーツ01の位置から移動量を計算し、
; 現在の座標(base_px/py)を更新する。
lda pose_data+4,x ;パーツ01のx座標
and #$00ff
sec
sbc #$0040 ;PEDITの編集画面の中央値
clc
adc base_px
sta base_px
lda p_action ;動作(アクション)
sta work2 ;×2保存(p_actionは元々2倍なので)
asl a ;×4
asl a ;×8
clc
adc work2 ;8+2=10倍
sta work2
lda vector ;テンキー形式の入力
and #$00ff
clc
adc work2 ;×10加算
tax
lda act_table,x ;次の動作(アクション)をロード
and #$00ff
sta p_action
stz p_pose ;先頭のポーズへ
_exit:
rts
------------------------------------------------------------
・テーブルいろいろ
プログラム中で参照されるデータです。
こういったデータはすべてデザイナがdo_dat.sに記述します。
参考のためここにも掲載しました。
------------------------------------------------------------
; ------------------------------------
; 動作(アクション)移行表
; 動作(アクション)番号とキー入力から、
; 次の動作(アクション)番号を引き当てる
; ------------------------------------
DribL equ 0 ;ドリブルL
DribR equ 2 ;ドリブルR
GDriL equ 4 ;ガード付きドリブルL
GDriR equ 6 ;ガード付きドリブルR
ChgLR equ 8 ;チェンジLR
ChgRL equ 10 ;チェンジRL
LegLR equ 12 ;レッグスルーLR
LegRL equ 14 ;レッグスルーRL
Dash7 equ 16 ;ダッシュ7
Dash9 equ 18 ;ダッシュ9
BTnLR equ 20 ;バックターンLR
BTnRL equ 22 ;バックターンRL
Fumei equ 24 ;不明
act_table:
; -----+-----+-----+-----+-----+-+-----+-----+-----+-----
; 入力 0, 1, 2, 3, 4,0, 6, 7, 8, 9
; -----+-----+-----+-----+-----+-+-----+-----+-----+-----
db DribL,GDriL,DribL,ChgLR,GDriL,0,ChgLR,GDriL,DribL,ChgLR ; 0
db DribR,ChgRL,DribR,GDriR,ChgRL,0,GDriR,ChgRL,DribR,GDriR ; 2
db GDriL,GDriL,GDriL,LegLR,GDriL,0,LegLR,GDriL,GDriL,LegLR ; 4
db GDriR,LegRL,GDriR,GDriR,LegRL,0,GDriR,LegRL,GDriR,GDriR ; 6
db DribR,DribR,DribR,DribR,DribR,0,DribR,DribR,DribR,DribR ; 8
db DribL,DribL,DribL,DribL,DribL,0,DribL,DribL,DribL,DribL ;10
db DribR,DribR,DribR,DribR,DribR,0,DribR,DribR,DribR,DribR ;11
db DribL,DribL,DribL,DribL,DribL,0,DribL,DribL,DribL,DribL ;12
db DribL,DribL,DribL,DribL,DribL,0,BTnLR,DribL,DribL,BTnLR ;14
db DribR,DribR,DribR,DribR,BTnRL,0,DribR,BTnRL,DribR,DribR ;16
; -----+-----+-----+-----+-----+-+-----+-----+-----+-----
; --------------------------------------------
; 動作(アクション)番号→ポーズデータオフセット
; --------------------------------------------
ACT equ 48 ;1ポーズ=48バイト
tableACT:
dw ACT*000,ACT*010 ;ドリブルL/R
dw ACT*020,ACT*030 ;ガード付きドリブルL/R
dw ACT*100,ACT*120 ;チェンジLR/RL
dw ACT*140,ACT*160 ;レッグスルーLR/RL
dw ACT*180,ACT*200 ;バックターンLR/RL
dw ACT*100,ACT*120,ACT*140,ACT*160,ACT*180
dw ACT*200,ACT*220,ACT*240,ACT*260,ACT*280
dw ACT*300,ACT*320,ACT*340,ACT*360,ACT*380
dw ACT*400,ACT*420,ACT*440,ACT*460,ACT*480
------------------------------------------------------------
質問には答えます...
koh@inetmie.or.jp