ボススプライトを書くような高度なASMになると詰まっても助けてくれる人がいない…orz
そこで、みんなで知識を持ち寄ってより高度でよりスタイリッシュなASMを作くったり、高度なASMの解析をしましょう!
というページです。
↓みたくJSRやJMPを複数のBEQなどで#??の値を1づつ増やしたりして分岐している方のための解説です。
;code1 LDA MOTION,x BNE NOT_MOTION_0 JMP MOTION_0 NOT_MOTION_0 CMP #$01 BNE NOT_MOTION_1 JMP MOTION_1 NOT_MOTION_1 CMP #$02 BNE NOT_MOTION_2 JMP MOTION_2 : : : MOTION_0 ---code--- MOTION_1 ---code--- MOTION_2 ---code--- : : :
↑はこの項目を書いた私が昔に書いたコードです。汚いです、はぃ…
↑のコードは忘れて↓のサンプルコードを覚えてもらいたいです。
*下記のコードの成立条件:DBがPBと同一されてること
;code2 MOTION_POS_TXT dcw MOTION_0&$FFFF,MOTION_1&$FFFF,MOTION_2&$FFFF,... ;↑dcwより値は16bit、MOTION_?のアドレスの下位16bitが直書きされる MOTION_ROTINE LDA MOTION,x ;A=0の時MOTION_0にジャンプ、A=1の時MOTION_1... ASL ;ここから TAY REP #$20 LDA MOTION_POS_TXT,y STA $00 SEP #$20 JMP ($0000) ;ここまでで14bytes分 MOTION_0 ---code--- MOTION_1 ---code--- MOTION_2 ---code---
MOTION_?の?を0以外の任意の数とすると分岐コードだけの総byte数は、~ code1では分岐コード分だけで?×7+1、code2は?×2+16
結論:分岐が2以下だとcode2方がcode1より容量を食います。(´;ω;`)ウッ…
なので使い分けてください。
GET_DRAW_INFOを逆汗中に詰まってたコードがやっと理解できたので載せておきます。
例えば
org $012345 ;loromとかは省略しています。
JSR ROTINE
org $013456 ROTINE
というコードを挿入したとします。
この場合はJSRは$20,ROTINEの下位2bytesの3bytesで構成されます
つまりこの場合JSR ROTINEは$20,$56,$34にアセンブルされます。
そして、JSRはPH命令を使う命令です。なので例えば
org $012345
JSR ROTINE PLA RTS
ROTINE PHA
RTS
こんなコードを入れるとフリーズします。
そのPHされる数はJSRの命令の最後尾のアドレス(例では$012347)の下位2bytes(例では$2347)がPHされます。そしてそれをRTSの命令によって2bytes分PLされてアドレス$012348目に戻ってくるわけです。(JSL、RTLの場合は3bytes分のPH,PLが実行されるだけ)
〇の数字順通りに命令が実行されます。
;この時スタックには何も入っていないとする ;ここをアドレス$012345と置く JSR CODE ;?JSRによって$2347がPHされる(stack=$47,$23) RTS ;?ここに戻ってくる ;ここをアドレス$013456と置く CODE JSR INVALID ;?JSRによって$3458がPHされる(stack=$47,$23,$58,$34) RTS ;実質ここには来ません INVALID PLA ;?1byteがPLされる(stack=$47,$23,$58)
PLA ;?1byteがPLされる(stack=$47,$23) RTS ;?stackの中にある2byteを読み込んで飛ぶ こんな感じです。なのでxkasなどでGET_DRAW_INFOをRTS→RTLだけして入れてJSLで読み込もうとすると画面外に出た瞬間にフリーズします。注意してください。
どうしてもJSLで飛ばしたいお、と言う人のために一応載っけておきます。
「TABLE1」〜「BNE INVALID」は変わらず
PHB PHK PLB
「LDY #$00」〜「BPL ON_SCREEN_LOOP」は変わ(ry
PLB
「LDY $15EA,x」〜「STA $01」は(ry
RTL
INVALID REP #$20 PLA
PLY
PLA
PHY
PHA
SEP #$20
RTL
ちなみに、JSLでGET_DRAW_INFOに飛ばす前にJSRでGFX_ROTINEに飛ばす的なコードなのが前提なコードです。
カスタムスプライト(コマンド除く)での共通事項、それはグラフィックです。
カスタムスプライトを作る上でかなりの人が躓くであろうグラフィックの描写を攻略していきます。
では早速解説からいきましょう!
グラフィックの描写 = スプライトの絵が描かれたタイルの設置です。
つまりグラフィックを描写するには、
タイルの位置、タイルの番号、タイルの情報(パレットなど)、タイルのサイズを決めてあげるのです。
スプライトにはそれぞれインデクス番号という番号が割り振られています。
65c816資料を見ると分かるようにスプライト関連のアドレスは他のRAMアドレスと違い長さ(12bytes)があります。
スプライトの向きや座標、速度のアドレスが被ると誤動作を起こすので被らないようにしてあるのです。
インデックス番号はXレジスタにスプライトルーチン開始時に代入されます。
つまり実際のスプライト用アドレスにインデクス番号を足したアドレスがそのスプライトのアドレスとなるのです。
ややこしや
カスタムスプライトのグラフィックは$300~ 1つのタイルにつき4つ分のRAMを使います。
| RAMアドレス | 説明 |
| $0300,y | タイルの画面上でのX座標 |
| $0301,y | タイルの画面上でのY座標 |
| $0302,y | タイルのタイル番号下位バイト |
| $0303,y | タイルの属性 |
GET_DRAW_INFOによりOAMインデックスがYレジスタに格納されるので、$300,yのようにそれぞれのアドレスの後ろに,yをつけてあげます。
また、2タイル目以降も同様に
| $0304 | タイル2の画面上でのX座標 |
| $0305 | タイル2の画面上でのY座標 |
| $0306 | タイル2のタイル番号下位バイト |
| $0307 | タイル2の属性 |
となっています。
GET_DRAW_INFOについては詳しく触れません、がGET_DRAW_INFOを呼び出すと、
$00に画面枠と比較したスプライトのX位置 つまりスプライトの画面上のX座標
$01に画面枠と比較したスプライトのY位置 つまりスプライトの画面上のY座標
がストアされます。この程度の認識でいいでしょう。
それではasmを編集していきましょう。
まず、適当なところでグラフィックルーチンを呼び出します。
普通はメインスプライトルーチンの最初に記述します。名前はなんでもいいですが、ここではSUB_GFXとします。
JSR SUB_GFX ;---------------------------------- ; GET_DRAW_INFOを呼び出します。当然同asm内にGET_DRAW_INFOの内容を記述してください。 ; GET_DRAW_INFOの内容はsprite tool(v135)のtutorialsフォルダ内のlibrary.asmにあるので ; そこからそっくりそのままコピペしましょう。そしてJSRで呼び出します。 SUB_GFX: JSR GET_DRAW_INFO ;----------------------------------- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; GET_DRAW_INFO ; This is a helper for the graphics routine. It sets off screen flags, and sets up ; variables. It will return with the following: ; ; Y = index to sprite OAM ($300) ; $00 = sprite x position relative to screen boarder ; $01 = sprite y position relative to screen boarder ; ; It is adapted from the subroutine at $03B760 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; TABLE1 dcb $0C,$1C TABLE2 dcb $01,$02 GET_DRAW_INFO: STZ $186C,x ; reset sprite offscreen flag, vertical STZ $15A0,x ; reset sprite offscreen flag, horizontal LDA $E4,x ; \ CMP $1A ; | set horizontal offscr een if necessary LDA $14E0,x ; | SBC $1B ; | BEQ ON_SCREEN_X ; | INC $15A0,x ; / ON_SCREEN_X: LDA $14E0,x ; \ XBA ; | LDA $E4,x ; | REP #$20 ; | SEC ; | SBC $1A ; | mark sprite invalid if far enough off screen CLC ; | ADC.W #$0040 ; | CMP.W #$0180 ; | SEP #$20 ; | ROL A ; | AND #$01 ; | STA $15C4,x ; / BNE INVALID ; LDY #$00 ; \ set up loop: LDA $1662,x ; | AND #$20 ; | if not smushed (1662 & 0x20), go through loop twice BEQ ON_SCREEN_LOOP ; | else, go through loop once INY ; / ON_SCREEN_LOOP: LDA $D8,x ; \ CLC ; | set vertical offscreen if necessary ADC TABLE1,y ; | PHP ; | CMP $1C ; | (vert screen boundry) ROL $00 ; | PLP ; | LDA $14D4,x ; | ADC #$00 ; | LSR $00 ; | SBC $1D ; | BEQ ON_SCREEN_Y ; | LDA $186C,x ; | (vert offscreen) ORA TABLE2,y ; | STA $186C,x ; | ON_SCREEN_Y: DEY ; | BPL ON_SCREEN_LOOP ; / LDY $15EA,x ; get offset to sprite OAM LDA $E4,x ; \ SEC ; | SBC $1A ; | $00 = sprite x position relative to screen boarder STA $00 ; / LDA $D8,x ; \ SEC ; | SBC $1C ; | $01 = sprite y position relative to screen boarder STA $01 ; / RTS ; return INVALID: PLA ; \ return from *main gfx routine* subroutine... PLA ; | ...(not just this subroutine) RTS ; /
今後GET_DRAW_INFOの記述はソースが冗長になるので省略させていただきますが、記述があると仮定して話を進めていきます。
ここからグラフィックの大きさや向きなどによって内容が分岐していきます。よって話の導入はここまで。
余談ですがlibrary.asmはGET_DRAW_INFOの他にも役に立つ処理やアドレスが載っているので目を通しておくといいかもしれません。
まずはX,Y座標の指定です。スプライトの画面上の座標 = タイルの画面上での座標とすればいいですね。
GET_DRAW_INFOを呼び出しその結果を用います。
$00がスプライトの画面上のX座標で$01がスプライトの画面上のY座標でしたね。
SUB_GFX: JSR GET_DRAW_INFO LDA $00 ; \ 画面上のX座標 STA $0300,y ; / LDA $01 ; \ 画面上のY座標 STA $0301,y ; /
こうなりますね。次にタイル番号の前に属性を指定します。
スプライトにはYXPPCCCTフォーマットという独自の形式があります。
これらの英文字の位置は2進数のbit位置と同じです。bit0がTでbit7がYになりますね。
| Y | 1で上下反転 |
| X | 1で左右反転 |
| PP | タイルの優先度...10で手前に表示され、11で透過します |
| CCC | パレット...SMWにおいて000がPalette 8…111がPalette Fです |
| T | 1でSP3,4のタイルを使います。0だとSP1,2になります |
これと$303,yに入るフォーマットとは一致しています。
これをそのままストア、つまり
LDA #%???????? STA $0303,y
とすればよいのですが、タイルが多くなると大変面倒になります。
タイル属性を設定するにあたって以下のRAMも覚えましょう。
・$15F6,x
スプライトのパレットです。Palette 8を使う場合$00が,9の場合$02が…Fの場合$0Eです。
またタイル番号上位バイト(つまりSP1,2/SP3,4のどちらか)でもあります。
SP3,4のページのタイル番号を使う場合、上の値に+$01しましょう。その数値が$15F6,xに入ります。
スプライトに関するRAMなので,xをつけてあげましょう。
・$64
そのLevelのLevel ModeのSprite Layerの値が入ります。
入る値はLunar Magicで
を押すと出る、マップのモードのSprite Layerの値です。
[パレットA,Sprite Layer:20,SP3,4のページを使用する]とした時の例です。
; まずパレットの値を読み込みからです。 LDA $15F6,x ; ↑パレットA...$04 / SP3,4を使うので+$01 = $05 よって2進数で #%00000101 ORA $64 ; ↑Sprite Layer:20...$20なので2進数で #%00100000 ; ORAは論理和...2進数で足し算をすればいいです。 ; つまり#%00000101 + #%00100000 = #%00100101となりますね。 ; そしてこれを代入します。 STA $0303,y
パレットやらSP3,4ページを使うといった設定はどこでするのでしょうか?
簡単な方法だとcfg_editorで設定します。どこかに日本語化を置いてあるので分かりやすいかと。
もちろんYXPPCCCTフォーマットを詳しく指定することでも設定できますが、
$15F6,xと$64を上のように使えば勝手にやってくれるので面倒な計算がいりません。
次にタイルのタイル番号の指定です。とにかく先に番号を決めてしまいましょう。~
LDA #$80 STA $0302,y
Lunar Magicの
(8x8エディタ)Tile 0x200からがスプライトのタイルです。
SP3,4のページを使う場合 Tile 0x300~のページを参照し、使わない場合は0x200~を参照します。
したがってSP3,4のページを使わずLDA #$70の場合Tile 0x270となり、SP3,4のページを使いLDA #$80の場合Tile 0x380となりますね。
それからそのタイルが8x8か16x16であるかを決めます。
$460,xが第1タイル、$461,xが第2タイル…と大きさを決めれます。値は$00が8x8で$02が16x16となります。
が、これはタイル毎に大きさを決める場合に使えばいいので今回はスルーします。
これを設定しない場合 Yレジスタに値を入れることですべての大きさが決まります。~
LDY #$00で8x8,LDY #$02で16x16と統一されます。指定した場合は$FFを入れましょう。
続いてA(アキュームレータ)に[設置するタイル数 - 1]の値を入れます。ここでの解説は1タイルのグラフィックなので[1 - 1 = 0]よりLDA #$00とします。
その後、画面外で描写しないルーチン(SNES $01:B7B3)にJSLで飛ばして終了です。
LDY #$02 ; タイルサイズを16x16で統一 LDA #$00 ; タイル数(1) - 1 = 0 JSL $01B7B3 ; 画面外で描写しない RTS ; 終了
↓ 最後に、今回できたグラフィックルーチンです。
SUB_GFX: JSR GET_DRAW_INFO LDA $00 ; \ 画面上X座標 STA $0300,y ; / LDA $01 ; \ 画面上Y座標 STA $0301,y ; / LDA $15F6,x ; \ ORA $64 ; | タイル属性 STA $0303,y ; / LDA #$80 ; \ タイル番号 0x?80 STA $0302,y ; / LDY #$02 ; タイルサイズを16x16で統一 LDA #$00 ; タイル数(1) - 1 = 0 JSL $01B7B3 ; 画面外で描写しない RTS ; 終了
ここまでで、1タイルのグラフィックの基本は学びました。知識を発展させましょう。
スプライトの向きを指定する
上で学んだグラフィックはあくまで必要な情報だけを取り入れたものなので、常に同じ方向しか向いていません。
一定方向に進むスプライトにしか使えませんね(´・ω・`)そこで$0303,yのタイル属性を弄ります。
$157C,x
これはスプライトの向きを表すRAMです。($00-右向き,$01-左向き) スプライトに関するRAMなので,xがついてます。
$00〜$0F
あらゆる場面において計算に使われているRAMです。だからと言って空きRAMというわけではないのでご注意を
ここからはグラフィックの記述において必要以外の部分は省略します。;----省略内容---という風に省略されます。
(ほぼ文字説明と言えど量が多いとページ表示が遅くなりますし)
SUB_GFX: JSR GET_DRAW_INFO LDA $157C,x ; まずこのスプライトの向きを STA $02 ; $02というRAMに代入します。 ; これで$02は、スプライトが右向きなら$00,左なら$01が入ることになります。 ;--------座標指定略--------- LDA $15F6,x PHX ; まずこのカスタムスプライトのインデクス番号を保管しておきます。 LDX $02 ; 続いてこのスプライトの向きを呼び出します。 BNE NO_FLIP ; $00(右向き)でないなら[NO_FLIP]に移動させます。 ORA #$40 ; 左向きの場合 タイルを左右反転させます。 NO_FLIP: ORA $64 ; 右向きなら左右反転をせずにそのまま$0303,yに代入します。 STA $0303,y PLX ; スプライトのインデクス番号を戻してあげます。 ;--------以下略---------
なぜ#$40(= #%01000000)を加えるのか...
それはYXPPCCCTフォーマットに当てはめるとXが1になり、左右反転するからですね。
左向き時はORA #$40によりタイルは左右反転、右向き時はこれを飛ばすので反転無しとなりますね。
こんな感じです。もちろん他の場所でマリオの方を向かせるように設定しないと効果がありません。(墓場のカスタムスプライトのうんちく参照)
LDXなどとXレジスタを使う命令を用いる場合、このようにPHXとPLXを使ってインデクス番号を保管します。
でないとインデックス番号が変わってしまい誤動作を起こすからです。
アニメーションさせる
$14
フレームカウンタです。常に1フレームに$01増加していますがマリオのダメージ中などは停止します。
ちなみに止まらないのは$13です。RAMアドレスについてはCにあるRAM Mapを見ましょう。
$14は常に$00~$FFまで増加し、また$00に戻ります。$FFの時に1/2(LSR)にするとどうなるでしょうか?
$FF * 1/2 = $7Fですね。さらに1/2倍で$3F、3回目{1/8(LSR *3)}で$1Fになりますね。
LDA $14 ; フレームカウンタを LSR A ; 1/2倍 LSR A ; 計1/4倍 LSR A ; 計1/8倍して STA $03 ; $03に代入
これで$03は$00~$1Fに増加し、$1Fになったあと$00に戻ることになります。
この$03をAND #$03してみましょう。ANDは論理積です。
$03の中身が$1Fだとすると $1F -> #%00011111 AND #$03 -> * #%00000011 ---------------------------- = #%00000011
となり答えは#$03になります。同様に計算していくと、中身が$1Eなら答えは#$02,$1Dなら#$01,$1Cなら#$00,$1Bなら#$03...
もうお分かりのはずです、この場合$03は$00~$03まで増加し$00へ戻るものとなります。
LDA $14 ; フレームカウンタを LSR A ; 1/2倍 LSR A ; 計1/4倍 LSR A ; 計1/8倍して AND #$03 ; さらに#%00000011で論理積を出し STA $03 ; $03に代入
こうすればOKです。これで$00~$03まで増加し、また$00へ戻るという4種類のタイル番号専用カウンタができました。
2種類にしたい場合はどうでしょうか?AND #$01とすれば$00~$01まで増加し$00へ戻るので、
2種類のタイル番号専用カウンタが出来上がりです。
また、アニメーションの速度を変えるにはLSRを増やすことでさらに遅くなり、ASL(*2倍)を加えると速くなります。
次にタイルの指定方法です。
TILEMAP dcb $80,$82,$84,$86 ; まずタイルのテーブルを組みます。SUB_GFXの上に置けばいいでしょう。 ; この場合$80→$82→$84→$86→$80→$82...と変わっていきます。 ; 2種類の場合は dcb $80,$82とすれば$80→$82→$80→$82...となります。 ; その場合は上のAND #$03を#$01に、TILEMAPを dcb $80,$82としましょう。 ; --------------------- LDX $03 ; 上で設定したようにXは$00→$01→$02→$03→$00...と変わっていきます。 LDA TILEMAP,x ; したがってXが$00のときTILEMAPは$80,$01のとき$82...となります。 STA $0302,y ; それを代入すればOKです。
これまでの物を組み合わせましょう。
TILEMAP dcb $80,$82,$84,$86 SUB_GFX: JSR GET_DRAW_INFO LDA $14 ; フレームカウンタを LSR A ; 1/2倍 LSR A ; 計1/4倍 LSR A ; 計1/8倍して AND #$03 ; さらに#%00000011で論理積を出し STA $03 ; $03に代入 ;--------座標,属性指定略--------- PHX ; LDXを使うのでスプライトインデックス番号を格納しておく LDX $03 ; 上で設定したようにXは$00→$01→$02→$03→$00...と変わっていきます LDA TILEMAP,x ; したがってXが$00のときTILEMAPは$80,$01のとき$82...となります STA $0302,y ; それを代入すればOKです PLX ; Xを使い終わったのでインデックス番号を戻してあげます ;--------以下略---------
こうなります。これが1タイルで4種類にアニメーションするタイルのグラフィックとなります。
条件は何でも好きに決めましょう。
たとえばON/OFF or Pスイッチ、座標を調べたりあるいは空きRAMだったり…今回はON/OFFスイッチにしましょう。
TILEMAP dcb $80,$82 ; ↑ ON時,OFF時 グラフィック ;----GET_DRAW_INFOの記述--- LDA $14AF ; \ ON/OFFスイッチの現在の値を STA $03 ; / $03に代入 ;----座標指定---- LDX $03 LDA TILEMAP,x STA $0302,y ;--------以下略---------
$14AFはご存じ、ON/OFFスイッチのRAMです。ON: $00 / OFF: $01がAに入ります。
したがってONの時にはTILEMAPを$00右にずらす(=ずれない)ので$80、OFF時はTILEMAPを$01右にずらすので$82となるのです。
次にフレームと組み合わせます。これが一番厄介です。
1タイル分のグラフィックは簡単ですが、タイル数が大きいとTILEMAPも多くなり指定がかなり面倒になります。
「ON時 $80,$82のアニメーション有り、OFF時 $A0のアニメ無し」というグラフィックの記述です。
TILEMAP dcb $80,$82,$A0 ; ↑ ON /OFF ;----GET_DRAW_INFOの記述--- LDA $14 ; \ LSR A ; | 1フレーム毎に$00,$01,$00…となり LSR A ; | $03に代入されます。 LSR A ; | 詳しくは前章『アニメーションさせる』を参照してください。 AND #$01 ; | STA $03 ; / LDA $14AF ; \ ON(A=0)の場合は BEQ NEXT ; | ラベル:NEXTに飛び LDA #$02 ; | OFF(A=1)の場合は、#$02を STA $03 ; / $03に代入 NEXT: ;----座標指定---- LDX $03 LDA TILEMAP,x STA $0302,y ;--------以下略---------
まずLDA $14~STA $03でアニメーションをさせているのがわかります。
$03に$00,$01.$00…と入るので、TILEMAPは右に[$00ずれる,$01ずれる,$00ずれる…]
即ち、TILEMAPは[$80,$82,$80…]となりますね。
次にOFFの場合は$03を$02で固定させます。これにより右に[$02ずれるで固定]される…
つまりTILEMAPは [$A0で固定]されるのです。ONの場合はそれを飛ばしています。
この場合LDA $14AF〜の記述は必ず後に持ってきます。でないとアニメーションが変になります。
これでアニメーション/固定はできました。 つぎにアニメーション/アニメーションをやってみましょう。
TILEMAP dcb $80,$82,$A0,$A2 ; ↑ ON / OFF ;----GET_DRAW_INFOの記述--- LDA $14AF ; \ OFF(≠$00)なら BNE NEXT ; / ラベル:NEXTへ LDA $14 ; \ LSR A ; | 1フレームごとのアニメーション LSR A ; | $03は[$00,$01,$00…]と毎フレーム変動する LSR A ; | AND #$01 ; | STA $03 ; / BRA NEXT2 ; 次のNEXTの部分は必要ないので飛ばす NEXT: LDA $14 ; \
LSR A ; | 1フレームごとのアニメーション LSR A ; | LSR A ; |
AND #$01 ; | CLC ; | ADC #$02 ; | に +02をする。 STA $03 ; / これで$03は[$02,$03,$02…]と毎フレーム変動する NEXT2: ;----座標指定---- LDX $03 LDA TILEMAP,x STA $0302,y ;--------以下略---------
これはTILEMAPの指定位置をずらす方法です。
ON時はTILEMAPが右に[$00,$01,$00…]分ずれるので、TILEMAPは[$80,$82,$80…]となり、
OFF時は右に[$02,$03,$02…]分ずれるので、TILEMAPは[$A0,$A2,$A0…]となります。
今回は1フレーム毎のアニメーションだってので+02をしましたが、フレーム数によって変わるので注意してください。
別解があります。状況に応じてどちらを使うか決めましょう。
TILEMAP dcb $80,$82 ; ON時のアニメーション TILEMAP2 dcb $A0,$A2 ; OFF時のアニメーション ;----GET_DRAW_INFOの記述--- LDA $14 ; \ LSR A ; | 1フレームごとのアニメーション LSR A ; | $03は[$00,$01,$00…]と毎フレーム変動する LSR A ; | AND #$01 ; | STA $03 ; / ;----座標指定---- LDX $03 LDA $14AF ; \ OFF(≠$00)なら BNE NEXT ; / ラベル:NEXTへ LDA TILEMAP,x ; ONだったのでTILEMAPを読み込む BRA NEXT2 NEXT: LDA TILEMAP2,x ; ONだったのでTILEMAP2を読み込む NEXT2: STA $0302,y ;--------以下略---------
こちらは二つのTILEMAPを用いる方法です。
ON/OFFの状況に応じてどちらのTILEMAPを読み込むか決めているのが分かりますね。こっちの方が分かりやすいと思いますが、タイル数が多いと変わってくるでしょう。
最初の方で説明しましたが$460,xが第1タイルの大きさ、$461,xが第2タイル…と個別に大きさを決めることが可能です。値は$00が8x8で$02が16x16となります。
4フレームのアニメーションをするタイル描写をしてみましょう。
TILEMAP: dcb $2C,$2D,$2C,$2D PROPERTIES: dcb %00000100,%00000100,%11000100,%11000100 ; XYPPCCCTフォーマット ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; SUB_GFX: JSR GET_DRAW_INFO ; Yレジスタ = OAM offset LDA $14 ; フレームカウンタを LSR A ; 1/2倍 LSR A ; 計1/4倍 LSR A ; 計1/8倍して AND #$03 ; さらに#%00000011で論理積を出し STA $03 ; $03に代入 LDA $00 ; \ 画面上X座標 STA $0300,y ; / LDA $01 ; \ 画面上Y座標 STA $0301,y ; / PHX ; Xを使うのでスプライトインデックス番号を格納しておく LDX $03 ; \ LDA PROPERTIES,x ; | タイル属性 ORA $64 ; | STA $0303,y ; / PLX ; Xを使い終わったのでインデックス番号を戻してあげます PHX ; Xを使うのでスプライトインデックス番号を格納しておく LDX $03 ; \ LDA TILEMAP,x ; | タイル番号を代入 STA $0302,y ; / PLX ; Xを使い終わったのでインデックス番号を戻してあげます LDA #$00 ; \ タイルの大きさを8x8に STA $0460,x ; / LDY #$FF ; タイルサイズを個別に指定 LDA #$00 ; タイル数(1) - 1 = 0 JSL $01B7B3 ; 画面外で描写しない RTS ; 終了
始めに気付くのはPROPERTIESです。$15F6だと一括指定しかできません。そこでこのようにタイル毎に決めて上げるのです。
PROPERTIESはXYPPCCCTフォーマットの書式で決めます。分かり易いように2進数表記なので%aaaaaaaaとなっていますが、2進数や16進数がバッチリな方は16進数表記($aa)に直すと見やすくなるでしょう。
またこれで指定するのはフォーマットのX,Y,CCC,Tの部分のみで大丈夫です。ORA $64がPPの判断をしてくれるからです。
一通りタイルの指定が終わった後、$0460,xに00を入れ、『このタイルは8x8サイズだ』という宣言をします。
タイルの大きさを個別に宣言した場合は最後にYレジスタに$FFを入れればいいんでしたよね。
これで8x8サイズのタイル描写の完成です。
縦に2タイル並べたり、横に並べたりいろいろありますがとりあえず縦の描写をやってみましょう。
まず知っておきたいことはループです。ループについてはこちらを見ることで説明の代わりとさせていただきます。
次に概念的なお話です。
2タイルの描写をするので描かれるタイルは2枚、当然タイル毎に位置を決めてあげないと描写する位置が被ってしまいます。
そこでタイルの描写位置をずらします。
何も指定しない(位置をずらさない)場合の位置を(つまり基準)$00とすると、
X座標において10加えると1タイル分右にずれ、F0加えると1タイル分左にずれます。
Y座標において10加えると1タイル分下にずれ、F0加えると1タイル分上にずれます。
16進数では、1~7Fが正、80~FFが負となります。これは2進数に直した場合bit7が0か1かによるものです。
また正の場合横(X)移動では右に、縦(Y)移動には下に移動します。負の場合は横移動では左に、縦(Y)移動には上に移動します。
このことから上2つの説明が成り立ちますね。また1タイルの半分ずらすには正方向には08、負方向にはF8ずらせばいいです。
10は10進数で16なので半分の08となるからですね。
詳しくはこれを見るといいでしょう。(鬼畜王氏の講座にほぼ同じものがあるケド)
これを見る限り、縦に2タイル分を作る場合は
(X:00/Y:F0)+(X:00/Y:00)
もしくは
(X:00/Y:00)+(X:00/Y:10)
とずらして描けばいいみたいですね。
しかし後者(Y:00-Y:10)の場合を考えてみましょう。(X:00/Y:00)が基準と説明しました。
なので地面と接するスプライト(例えばノコノコ)は基準の位置が地面に接します。
したがって、Y:10だと下に1タイル分ずれるので下半分のタイルが地面に埋まってしまうのです。
なので (X:00/Y:F0)+(X:00/Y:00) の方を用いましょう。まずは、必要なものを決めちゃいます。
TILEMAP: dcb $82,$A2 ; 上のタイル番号,下のタイル番号 Y_OFFSET: dcb $F0,$00 ; 同上
続いてループ処理の設定です。
PHX ; ループにXレジスタを使用するので、インデクス番号を保護する LDX #$01 ; ループ回数 = タイル数(2) - 1 = 1 LOOP_START: ; ここからループさせたい処理 DEX ; \ ループの処理でこの記述は必須ですよね BPL LOOP_START ; / PLX ; ループが終了したのでインデクス番号を戻してあげる
65C816プログラミングのループについてををみるとこのように指定していますね。この中にタイル描写をする処理を入れて繰り返させるのです。
ではソースを見てみましょう。
TILEMAP: dcb $82,$A2 Y_OFFSET: dcb $F0,$00 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; SUB_GFX: JSR GET_DRAW_INFO ; Yレジスタ = OAM offset PHX ; ループにXレジスタを使用するので、保護する LDX #$01 ; ループ回数 = タイル数(2) - 1 = 1 LOOP_START: LDA $00 ; \ X座標はこれまで通り STA $0300,y ; / LDA Y_OFFSET,x ; \ CLC ; | タイル位置(基準=00)にF0と00を足すと ADC $01 ; | タイル位置はF0と00になりますね STA $0301,y ; / LDA TILEMAP,x ; \ タイルマップを代入 STA $0302,y ; / PHX ; Xレジスタを使用するのでインデクス番号を保管 LDX $15E9 ; スプライトのインデクス番号に関するRAM これがないとパレットなどがおかしくなります LDA $15F6,x ; \
ORA $64 ; | タイル属性
STA $0303,y ; / PLX ; Xレジスタを使い終わったのでインデクス番号をもどす INY ; \ INY ; | INY ; | INY ; / DEX ; \ ループの処理でこの記述は必須ですよね BPL LOOP_START ; / PLX ; ループが終了したのでインデクス番号を戻してあげる LDY #$02 ; タイルサイズを16x16で統一 LDA #$01 ; タイル数(2) - 1 = 1 JSL $01B7B3 ; 画面外で描写しない RTS ; 終了
これまでの説明が分かる人なら、座標とタイル番号、タイル属性をどう指定しているか大体分かるハズです。
しかし1つ気にかかる点があることでしょう。なぜINYを4つも入れているのでしょうか?
講座の最初の方にタイルに関するRAMの説明をしましたね。$0300,$0301,$0302,$0303のことです。例えば1タイル目のX座標は$0300で2タイル目のX座標は$0304です。察しのいい人は気付いたかも知れません。
$0300に4を足すと$0304になります。もともと$0300,yとなっているのでYに4を足せば$0304になるのが分かるでしょう。
つまり最初のループで1タイル目に関して一通りの設定(座標、属性etc...)が終わった後にYを増加させます。
すると$030Xは+4されて『2タイル目の設定ができますよー』という状態になります。そこでループ処理(BPL LOOP_START)でタイル指定の最初に戻すのです。
そうすれば2タイル目の設定をやってくれるのです。ちょっと日本語がおかしいですが大体の流れはつかめたはずです。
横へ2タイルは座標指定の部分をこうすればいいですね。
LDA Y_OFFSET,x CLC ADC $00 STA $0300,y LDA $01 STA $0301,y
この説明では必要な情報のみでアニメーションもなく向きも変わらないものなので、それらの機能をつけて上げましょう。
やりたいことの説明は既に済んでます。これを読み直すといいでしょう。2タイルになってもほとんど変わりません。
ということで早速ソースを見てみましょう。
TILEMAP: dcb $82,$A2 ; 1フレーム目のタイル dcb $82,$A0 ; 2フレーム目のタイル Y_OFFSET: dcb $F0,$00 ; 上のタイル.下のタイル ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; SUB_GFX: JSR GET_DRAW_INFO ; Yレジスタ = OAM offset LDA $157C,x ; \ スプライトの向きを$02に代入 STA $02 ; / LDA $14 ; \ フレームカウンタを LSR A ; | (1/2) LSR A ; | (1/4) LSR A ; | 計1/8倍 AND #$01 ; | これで $00 → $01 → $00 → $01 となるカウンタになる ASL A ; | 2倍すると $00 → $02 → $00 → $02 となるカウンタになる STA $03 ; / これを$03に代入 PHX ; ループにXレジスタを使用するので、保護する LDX #$01 ; ループ回数 = タイル数(2) - 1 = 1 LOOP_START: LDA $00 ; \ X座標はこれまで通り STA $0300,y ; / LDA Y_OFFSET,x ; \ CLC ; | タイル位置(基準=00)にF0と00を足すと ADC $01 ; | タイル位置はF0と00になりますね STA $0301,y ; / PHX TXA ; X → A(つまりインデクス番号をAに持ってくる) CLC ; \ Aにカウンタの分を足してあげます ADC $03 ; / TAX ; A → X(足した値を戻す) LDA TILEMAP,x ; \ タイルマップを代入 STA $0302,y ; / PLX PHX LDX $15E9 ; スプライトのインデクス番号に関するRAM これがないとパレットなどがおかしくなります LDA $15F6,x ; パレット LDX $02 ; \ このスプライトの向きを呼び出します BNE NO_FLIP ; | = 0(右向き)でないなら[NO_FLIP]に移動させます ORA #$40 ; | 左向きの場合、タイルを左右反転させます NO_FLIP: ORA $64 ; | 右向きなら左右反転をせずにそのまま$0303,yに代入します STA $0303,y ; / タイル属性を代入 PLX INY ; \ INY ; | ループ毎にタイルのRAMのアドレスを増やす INY ; | INY ; / DEX ; \ ループの処理でこの記述は必須ですよね BPL LOOP_START ; / PLX ; ループが終了したのでインデクス番号を戻してあげる LDY #$02 ; タイルサイズを16x16で統一 LDA #$01 ; タイル数(2) - 1 = 1 JSL $01B7B3 ; 画面外で描写しない RTS ; 終了
スプライトをどうアニメーションさせるかは、どのようにカウンタ(今回は$03)をつくるかと同じです。
ソース内の記述にあるように$03は$00 → $02 → $00 → $02…と変化します。
つまりTILEMAPは$00分右にずれる → $02分右にずれる → $00分右にずれる → $02分右にずれる…
よって$82,$A2 → $82,$A0 → $82,$A2 → $82,$A0…となるのです。
あとはほとんど変わりないはずです。これで2タイル分のグラフィックは終了です。
書いた人は疲れてしまいました。続きを見たい方はワッフルワッフル
スプライトの座標 = マリオの座標として、マリオを隠せばいいだけです。
したがって、
LDA #$7F ; \ マリオを透明化 STA $78 ; / ;------ LDA $94 ; \ スプライトX座標 low = マリオX座標 low STA $E4,x ; / LDA $96 ; \ スプライトY座標 low = マリオY座標 low STA $D8,x ; / LDA $95 ; \ スプライトX座標 high = マリオX座標 high STA $14E0,x ; / LDA $97 ; \ スプライトY座標 high = マリオY座標 high STA $14D4,x ; /
これをメインルーチンに記述すればいいのです。
しかしこの場合、マリオがチビの時はマリオの頭上(スーパーマリオの顔の位置)にスプライトが来てしまいます。
なのでマリオのY座標(low)を修正しましょう。LDA $96/STA $D8,xを以下に変えてください。
LDA $96 ; \ スプライトY座標 low = マリオY座標 low + $10 CLC ; | $10で1タイル分です ADC #$10 ; | STA $D8,x ; /
これまでの講座で学んできたグラフィックルーチンの参考例です。
スプライト製作に少しでも役立てられたら幸いです。
ASL/LSRのご指摘ありがとうございました。
間違っているところがあったら勝手に直すか、コメントお願いします。