ここは?
ボススプライトを書くような高度な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: dw MOTION_0,MOTION_1,MOTION_2,... ;↑dwより値は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より容量を食います。(´;ω;`)ウッ…
なので使い分けてください。
JSR命令の仕様
GET_DRAW_INFOを逆汗中に詰まってたコードがやっと理解できたので載せておきます。
JSRについて知っておくべきこと
例えば
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が実行されるだけ)
GET DRAW INFO内のINVALIDについて
〇の数字順通りに命令が実行されます。
;この時スタックには何も入っていないとする ;ここをアドレス$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でGET_DRAW_INFOに飛ばす
どうしても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に飛ばす的なコードなのが前提なコードです。
PIXIでは多くのカスタムスプライトで%GetDrawInfo()を使い、JSLで同じアドレスのルーチンを共有しています。
Graphics Routine
はじめに
カスタムスプライト(コマンド除く)での共通事項、それはグラフィックです。
カスタムスプライトを作る上でかなりの人が躓くであろうグラフィックの描写を攻略していきます。
では早速解説からいきましょう!
グラフィックの描写 = スプライトの絵が描かれたタイルの設置です。
つまりグラフィックを描写するには、
タイルの位置、タイルの番号、タイルの情報(パレットなど)、タイルのサイズを決めてあげるのです。
スプライトにはそれぞれインデクス番号という番号が割り振られています。
65c816資料を見ると分かるようにスプライト関連のアドレスは他のRAMアドレスと違い長さ(12bytes)があります。
スプライトの向きや座標、速度のアドレスが被ると誤動作を起こすので被らないようにしてあるのです。
インデックス番号はXレジスタにスプライトルーチン開始時に代入されます。
つまり実際のスプライト用アドレスにインデクス番号を足したアドレスがそのスプライトのアドレスとなるのです。
ややこしや
カスタムスプライトのグラフィックは$300~ 1つのタイルにつき4つ分のRAMを使います。
RAMアドレス | 説明 |
$0300,y | タイルの画面上でのX座標 |
$0301,y | タイルの画面上でのY座標 |
$0302,y | タイルのタイル番号下位バイト |
$0303,y | タイルの属性 |
GetDrawInfoによりOAMインデックスがYレジスタに格納されるので、$300,yのようにそれぞれのアドレスの後ろに,yをつけてあげます。
また、2タイル目以降も同様に
$0304 | タイル2の画面上でのX座標 |
$0305 | タイル2の画面上でのY座標 |
$0306 | タイル2のタイル番号下位バイト |
$0307 | タイル2の属性 |
となっています。
GetDrawInfoについては詳しく触れません、がGET_DRAW_INFOを呼び出すと、
$00に画面枠と比較したスプライトのX位置 つまりスプライトの画面上のX座標
$01に画面枠と比較したスプライトのY位置 つまりスプライトの画面上のY座標
がストアされます。この程度の認識でいいでしょう。
それではasmを編集していきましょう。
まず、適当なところでグラフィックルーチンを呼び出します。
普通はメインスプライトルーチンの最初に記述します。名前はなんでもいいですが、ここではSUB_GFXとします。
JSR SUB_GFX ;---------------------------------- ; 最初にGetDrawInfoを呼び出します。 ; 多くのカスタムスプライトで同じアドレスのルーチンを共有します。 ; 内容はRoutinesフォルダ内付属の同ルーチン名asmファイルを参照してください。 SUB_GFX: %GetDrawInfo() ...
ここからグラフィックの大きさや向きなどによって内容が分岐していきます。よって話の導入はここまで。
余談ですがRoutinesフォルダ内にはGetDrawInfoの他にも役に立つ処理が載っているので目を通しておくといいかもしれません。
1タイルのグラフィック
まずはX,Y座標の指定です。スプライトの画面上の座標 = タイルの画面上での座標とすればいいですね。
GetDrawInfoを呼び出しその結果を用います。
$00がスプライトの画面上のX座標で$01がスプライトの画面上のY座標でしたね。
SUB_GFX: %GetDrawInfo() LDA $00 : STA $0300|!Base2,y ; 画面上のX座標 LDA $01 : STA $0301|!Base2,y ; 画面上の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|!Base2,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|!Base2,y
パレットやらSP3,4ページを使うといった設定はどこでするのでしょうか?
簡単な方法だとcfg_editorで設定します。どこかに日本語化を置いてあるので分かりやすいかと。
もちろんYXPPCCCTフォーマットを詳しく指定することでも設定できますが、
!15F6,xと$64を上のように使えば勝手にやってくれるので面倒な計算がいりません。
次にタイルのタイル番号の指定です。とにかく先に番号を決めてしまいましょう。~
LDA #$80 STA $0302|!Base2,y
Lunar Magicの Tile 0x400からがスプライトのタイルです。
SP3,4のページを使う場合 Tile 0x500~のページを参照し、使わない場合は0x400~を参照します。
したがってSP3,4のページを使わずLDA #$70の場合Tile 0x470となり、SP3,4のページを使いLDA #$80の場合Tile 0x580となりますね。
それからそのタイルが8x8か16x16であるかを決めます。
$0460,xが第1タイル、$0461,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|!BankB ; 画面外で描写しない RTS ; 終了
↓ 最後に、今回できたグラフィックルーチンです。
SUB_GFX: %GetDrawInfo() LDA $00 : STA $0300|!Base2,y ; 画面上X座標 LDA $01 : STA $0301|!Base2,y ; 画面上Y座標 LDA !15F6,x ; \ ORA $64 ; | タイル属性 STA $0303|!Base2,y ; / LDA #$80 ; \ タイル番号 0x?80 STA $0302|!Base2,y ; / LDY #$02 ; タイルサイズを16x16で統一 LDA #$00 ; タイル数(1) - 1 = 0 JSL $01B7B3|!BankB ; 画面外で描写しない RTS ; 終了
1タイルのグラフィック - 発展
ここまでで、1タイルのグラフィックの基本は学びました。知識を発展させましょう。
スプライトの向きを指定する
上で学んだグラフィックはあくまで必要な情報だけを取り入れたものなので、常に同じ方向しか向いていません。
一定方向に進むスプライトにしか使えませんね(´・ω・`)そこで$0303,yのタイル属性を弄ります。
!157C,x
これはスプライトの向きを表すRAMです。($00-右向き,$01-左向き) スプライトに関するRAMなので,xがついてます。
$00〜$0F
あらゆる場面において計算に使われているRAMです。だからと言って空きRAMというわけではないのでご注意を
ここからはグラフィックの記述において必要以外の部分は省略します。;----省略内容---という風に省略されます。
(ほぼ文字説明と言えど量が多いとページ表示が遅くなりますし)
SUB_GFX: %GetDrawInfo() LDA !157C,x ; まずこのスプライトの向きを STA $02 ; $02というRAMに代入します。 ; これで$02は、スプライトが右向きなら$00,左なら$01が入ることになります。 ;--------座標指定略--------- LDA !15F6,x PHX ; まずこのカスタムスプライトのインデクス番号を保管しておきます。 LDX $02 ; 続いてこのスプライトの向きを呼び出します。 BNE NO_FLIP ; $00(右向き)でないなら[NO_FLIP]に移動させます。 EOR #$40 ; 左向きの場合 タイルを左右反転させます。 NO_FLIP: ORA $64 ; 右向きなら左右反転をせずにそのまま$0303,yに代入します。 STA $0303|!Base2,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 #3 ; 1/2倍を3回 計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 #3 ; 1/2倍を3回 計1/8倍して AND #$03 ; さらに#%00000011で論理積を出し STA $03 ; $03に代入
こうすればOKです。これで$00~$03まで増加し、また$00へ戻るという4種類のタイル番号専用カウンタができました。
2種類にしたい場合はどうでしょうか?AND #$01とすれば$00~$01まで増加し$00へ戻るので、
2種類のタイル番号専用カウンタが出来上がりです。
また、アニメーションの速度を変えるにはLSRを増やすことでさらに遅くなり、ASL(*2倍)を加えると速くなります。
次にタイルの指定方法です。
TILEMAP db $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 db $80,$82,$84,$86 SUB_GFX: %GetDrawInfo() LDA $14 ; フレームカウンタを LSR #3 ; 1/2倍を3回 計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|!Base2,y ; それを代入すればOKです PLX ; Xを使い終わったのでインデックス番号を戻してあげます ;--------以下略---------
こうなります。これが1タイルで4種類にアニメーションするタイルのグラフィックとなります。
条件によって異なるグラフィックを描く
条件は何でも好きに決めましょう。
たとえばON/OFF or Pスイッチ、座標を調べたりあるいは空きRAMだったり…今回はON/OFFスイッチにしましょう。
TILEMAP db $80,$82 ; ↑ ON時,OFF時 グラフィック SUB_GFX: %GetDrawInfo() LDA $14AF|!Base2 ; \ ON/OFFスイッチの現在の値を STA $03 ; / $03に代入 ;----座標指定---- LDX $03 LDA TILEMAP,x STA $0302|!Base2,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 db $80,$82,$A0 ; ↑ ON /OFF SUB_GFX: %GetDrawInfo() LDA $14 ; \ LSR #3 ; | 8フレーム毎に$00,$01,$00…となり、$03に代入されます。 AND #$01 ; |詳しくは前章『アニメーションさせる』を参照してください。 STA $03 ; / LDA $14AF|!Base2 ; \ ON(A=0)の場合は BEQ NEXT ; | ラベル:NEXTに飛び LDA #$02 ; | OFF(A=1)の場合は、#$02を STA $03 ; / $03に代入 NEXT: ;----座標指定---- LDX $03 LDA TILEMAP,x STA $0302|!Base2,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 db $80,$82,$A0,$A2 ; ↑ ON / OFF SUB_GFX: %GetDrawInfo() LDA $14AF|!Base2 ; \ OFF(≠$00)なら BNE NEXT ; / ラベル:NEXTへ LDA $14 ; \ LSR #3 ; | 8フレームごとのアニメーション AND #$01 ; | $03は[$00,$01,$00…]と毎フレーム変動する STA $03 ; / BRA NEXT2 ; 次のNEXTの部分は必要ないので飛ばす NEXT: LDA $14 ; \ AND #$08 ; | LSR #3 ; | 8フレームごとのアニメーション ADC #$02 ; | に +02をする。 STA $03 ; / これで$03は[$02,$03,$02…]と毎フレーム変動する NEXT2: ;----座標指定---- LDX $03 LDA TILEMAP,x STA $0302|!Base2,y ;--------以下略---------
これはTILEMAPの指定位置をずらす方法です。
ON時はTILEMAPが右に[$00,$01,$00…]分ずれるので、TILEMAPは[$80,$82,$80…]となり、
OFF時は右に[$02,$03,$02…]分ずれるので、TILEMAPは[$A0,$A2,$A0…]となります。
今回は1フレーム毎のアニメーションだってので+02をしましたが、フレーム数によって変わるので注意してください。
別解があります。状況に応じてどちらを使うか決めましょう。
TILEMAP db $80,$82 ; ON時のアニメーション TILEMAP2 db $A0,$A2 ; OFF時のアニメーション SUB_GFX: %GetDrawInfo() LDA $14 ; \ LSR #3 ; | 8フレームごとのアニメーション AND #$01 ; | $03は[$00,$01,$00…]と毎フレーム変動する STA $03 ; / ;----座標指定---- LDX $03 LDA $14AF|!Base2 ; \ OFF(≠$00)なら BNE NEXT ; / ラベル:NEXTへ LDA TILEMAP,x ; ONだったのでTILEMAPを読み込む BRA NEXT2 NEXT: LDA TILEMAP2,x ; ONだったのでTILEMAP2を読み込む NEXT2: STA $0302|!Base2,y ;--------以下略---------
こちらは二つのTILEMAPを用いる方法です。
ON/OFFの状況に応じてどちらのTILEMAPを読み込むか決めているのが分かりますね。こっちの方が分かりやすいと思いますが、タイル数が多いと変わってくるでしょう。
8x8サイズのタイルを描く
最初の方で説明しましたが$460,xが第1タイルの大きさ、$461,xが第2タイル…と個別に大きさを決めることが可能です。値は$00が8x8で$02が16x16となります。
4フレームのアニメーションをするタイル描写をしてみましょう。
TILEMAP: db $2C,$2D,$2C,$2D PROPERTIES: db %00000100,%00000100,%11000100,%11000100 ; XYPPCCCTフォーマット ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; SUB_GFX: %GetDrawInfo() ; Yレジスタ = OAM offset LDA $14 ; フレームカウンタを LSR #3 ; 1/2倍を3回 計1/8倍して AND #$03 ; さらに#%00000011で論理積を出し STA $03 ; $03に代入 LDA $00 : STA $0300|!Base2,y ; \ 画面上X座標 LDA $01 : STA $0301|!Base2,y ; \ 画面上Y座標 PHX ; Xを使うのでスプライトインデックス番号を格納しておく LDX $03 ; \ LDA PROPERTIES,x ; | タイル属性 ORA $64 ; | STA $0303|!Base2,y ; / PLX ; Xを使い終わったのでインデックス番号を戻してあげます PHX ; Xを使うのでスプライトインデックス番号を格納しておく LDX $03 ; \ LDA TILEMAP,x ; | タイル番号を代入 STA $0302|!Base2,y ; / PLX ; Xを使い終わったのでインデックス番号を戻してあげます LDA #$00 ; \ タイルの大きさを8x8に STA $0460|!Base2,x ; / LDY #$FF ; タイルサイズを個別に指定 LDA #$00 タイル数(1) - 1 = 0 JSL $01B7B3|!BankB ; 画面外で描写しない 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タイルの描写をするので描かれるタイルは2枚、当然タイル毎に位置を決めてあげないと描写する位置が被ってしまいます。
そこでタイルの描写位置をずらします。
何も指定しない(位置をずらさない)場合の位置を(つまり基準)$00とすると、
X座標において10加えると1タイル分右にずれ、F0加えると1タイル分左にずれます。
Y座標において10加えると1タイル分下にずれ、F0加えると1タイル分上にずれます。
複数のタイルを重ねた場合、$0200に近い方が手前になります。
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: db $82,$A2 ; 上のタイル番号,下のタイル番号 Y_OFFSET: db $F0,$00 ; 同上
続いてループ処理の設定です。
PHX ; ループにXレジスタを使用するので、インデクス番号を保護する LDX #$01 ; ループ回数 = タイル数(2) - 1 = 1 LOOP_START: ; ここからループさせたい処理 DEX ; \ ループの処理でこの記述は必須ですよね BPL LOOP_START ; / PLX ; ループが終了したのでインデクス番号を戻してあげる
65C816プログラミングのループについてををみるとこのように指定していますね。この中にタイル描写をする処理を入れて繰り返させるのです。
ではソースを見てみましょう。
TILEMAP: db $82,$A2 Y_OFFSET: db $F0,$00 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; SUB_GFX: %GetDrawInfo() ; Yレジスタ = OAM offset PHX ; ループにXレジスタを使用するので、保護する LDX #$01 ; ループ回数 = タイル数(2) - 1 = 1 LOOP_START: LDA $00 ; \ X座標はこれまで通り STA $0300|!Base2,y ; / LDA Y_OFFSET,x ; \ CLC ; | タイル位置(基準=00)にF0と00を足すと ADC $01 ; | タイル位置はF0と00になりますね STA $0301|!Base2,y ; / LDA TILEMAP,x ; \ タイルマップを代入 STA $0302|!Base2,y ; / PHX ; Xレジスタを使用するのでインデクス番号を保管 LDX $15E9|!Base2 ; スプライトのインデクス番号に関するRAM これがないとパレットなどがおかしくなります LDA !15F6,x ; \
ORA $64 ; | タイル属性
STA $0303|!Base2,y ; / PLX ; Xレジスタを使い終わったのでインデクス番号をもどす INY #4 DEX ; \ ループの処理でこの記述は必須ですよね BPL LOOP_START ; / PLX ; ループが終了したのでインデクス番号を戻してあげる LDY #$02 ; タイルサイズを16x16で統一 LDA #$01 ; タイル数(2) - 1 = 1 JSL $01B7B3|!BankB ; 画面外で描写しない 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|!Base2,y LDA $01 STA $0301|!Base2,y
この説明では必要な情報のみでアニメーションもなく向きも変わらないものなので、それらの機能をつけて上げましょう。
2タイル描写 -アニメーションetc
やりたいことの説明は既に済んでます。これを読み直すといいでしょう。2タイルになってもほとんど変わりません。
ということで早速ソースを見てみましょう。
TILEMAP: db $82,$A2 ; 1フレーム目のタイル db $82,$A0 ; 2フレーム目のタイル Y_OFFSET: db $F0,$00 ; 上のタイル.下のタイル ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; SUB_GFX: %GetDrawInfo() ; Yレジスタ = OAM offset LDA !157C,x ; \ スプライトの向きを$02に代入 STA $02 ; / LDA $14 ; \ フレームカウンタを LSR #3 ; | 計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|!Base2,y ; / LDA Y_OFFSET,x ; \ CLC : ADC $01 | タイル位置(基準=00)にF0と00を足すと STA $0301|!Base2,y ; / タイル位置はF0と00になりますね PHX TXA ; X → A(つまりインデクス番号をAに持ってくる) CLC : ADC $03 ; \ Aにカウンタの分を足してあげます TAX ; A → X(足した値を戻す) LDA TILEMAP,x ; \ タイルマップを代入 STA $0302|!Base2,y ; / PLX PHX LDX $15E9|!Base2 ; スプライトのインデクス番号に関するRAM これがないとパレットなどがおかしくなります LDA !15F6,x ; パレット LDX $02 ; \ このスプライトの向きを呼び出します BNE NO_FLIP ; | = 0(右向き)でないなら[NO_FLIP]に移動させます EOR #$40 ; | 左向きの場合、タイルを左右反転させます NO_FLIP: ORA $64 ; | 右向きなら左右反転をせずにそのまま$0303,yに代入します STA $0303|!Base2,y ; / タイル属性を代入 PLX INY #4 ; ループ毎にタイルのRAMのアドレスを増やす DEX ; \ ループの処理でこの記述は必須ですよね BPL LOOP_START ; / PLX ; ループが終了したのでインデクス番号を戻してあげる LDY #$02 ; タイルサイズを16x16で統一 LDA #$01 ; タイル数(2) - 1 = 1 JSL $01B7B3|!BankB ; 画面外で描写しない 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 : STA !E4,x ; スプライトX座標 low = マリオX座標 low LDA $96 : STA !D8,x ; スプライトY座標 low = マリオY座標 low LDA $95 : STA !14E0,x ; スプライトX座標 high = マリオX座標 high LDA $97 : STA !14D4,x ; スプライトY座標 high = マリオY座標 high
これをメインルーチンに記述すればいいのです。
しかしこの場合、マリオがチビの時はマリオの頭上(スーパーマリオの顔の位置)にスプライトが来てしまいます。
なのでマリオのY座標(low)を修正しましょう。LDA $96/STA $D8,xを以下に変えてください。
LDA $96 ; \ スプライトY座標 low = マリオY座標 low + $10 CLC : ADC #$10 ; | $10で1タイル分です STA $D8,x ; /
おまけ
graphics.zip
これまでの講座で学んできたグラフィックルーチンの参考例です。
スプライト製作に少しでも役立てられたら幸いです。
ASL/LSRのご指摘ありがとうございました。
間違っているところがあったら勝手に直すか、コメントお願いします。
サインコサインの使い方
ここでは、サインコサインの求め方とその応用を説明します
アルゴリズムで考える
・1値を代入する
・2角度テーブルから値を受けとる
・3掛け算をし、位置や速度を求める
・4終わる
ね?簡単でしょ?
簡単じゃないって?
まぁ使えれば良いんだよ...
値を代入する.asm1
早速コード書いていくよー
;A,X,Yを16bitモードにする ;要するに2桁しか書けないのを4桁まで書けるようにする REP #$30 ~ ;角度を代入する、範囲は $0000 - $01FFまで ; 0度 - $0000 ; 90度 - $0080 ;180度 - $0100 ;270度 - $0180 ;360度 - $0000 ($0200ではない) LDA #$00C0 ;135度 STA $00 ;・・・① ;90度分加算する ;サインとコサインは90度ずれるからね CLC ADC #$0080 ;もし90度加算しても360度を超えないようにする ;例 315度 + 90度 = 405度 -> 45度 ;"->"の部分が "AND #$01FF" と思えばいい AND #$01FF STA $02 ;・・・②
これで$00と$02にサインとコサインを求めるときの値(数学でいうところのシータ)を代入しました。
角度テーブルから値を受けとる.asm2
まあ、順番にね
LDA $00 ;$00(角度)を読み込む ASL ;2倍にする(理由はまた後に) AND #$01FF ;360度を越えないようにする TAX ;AからXに(値を受け取れるように) LDA $07F7DB,x ;角度テーブル STA $04 ;サイン($00)の値 ;コサインのほうも同じように ;説明は省略 LDA $02 ASL AND #$01FF TAX LDA $07F7DB,x STA $06
さて、なぜ2倍にするのか
とりあえず、こんなテーブルがあるとします
table: dw $0114,$0514,$0810,$1919
そしてxにインデックスを入れて
LDX #$00 LDA table,x ;$0114
LDX #$01 LDA table,x ;$1405
LDX #$02 LDA table,x ;$0514
LDX #$03 LDA table,x ;$1408
LDX #$04 LDA table,x ;$0810
このように2倍した値でないと正しく読み込めないから
左シフト(ASL)で2倍にしてテーブルを読み込んでいるわけです
掛け算して、位置や速度を求める.asm3
一応解説しておきます
半径r、角度θの位置x,yは
x = r × sinθ
y = r × cosθ
速度v、角度θの速度vx,vyは
vx = v × sinθ
vy = v × cosθ
以下コード
;A,X,Yを8bitモードに戻す SEP #$30 ;v sinθを計算する ;掛けられる数に角度を入れる LDA $04 STA $4202 ;掛ける数に半径(速度)を入れる LDA #$30 ;半径3タイル(速度30) LDY $05 BNE + STA $4203 ;半径(r)か速さ(v) ;処理に時間がかかるのでNOPで時間稼ぎ NOP #4 ;繰り上がり判定 ASL $4216 LDA $4217 ADC #$00 + ;角度が180度以上の時 LSR $01 BCC + ;値を反転させる EOR #$FF INC A + STA $04 ;v cosθを計算する LDA $06 STA $4202 LDA $08 LDY $07 BNE + STA $4203 NOP #4 ASL $4216 LDA $4217 ADC #$00 + LSR $03 BCC + EOR #$FF INC A + STA $06 PLX
これで$04と$06をスプライトの速度に代入するなりすればOKです
終わりです、ありがとうございました
これらをまとめた
asm
があるのでpixiのroutinsフォルダに入れて使ってみてください
おまけのサンプルプログラム
print "INIT ",pc RTL print "MAIN ",pc PHB PHK PLB JSR sprite PLB RTL return: RTS sprite: LDA $9D BNE return REP #$20 LDA $7FC070 CLC ADC #$0010 AND #$01FF STA $7FC070 SEP #$20 LDA !1540,x BNE return LDA #$20 STA !1540,x LDA $7FC070 STA $00 LDY #$30 ;速度 %GetAngle() STZ $00 ;スプライトの位置から STZ $01 ;スプライトの位置から LDA $04 ;スプライトの速度を設定 STA $02 LDA $06 ;スプライトの速度を設定 STA $03 LDA #$20 ;魔法弾 %SpawnSprite() RTS
ルーチン関数を使った時機狙い
Rutinesファイルの関数を使えば簡単にスプライトを置いたりブロックを置いたりできるぞ
今回紹介するのは%Aiming()だ
関数をやる前に
ルーチンには各々「引数」と「戻り値」がある詳しくはggr
例えば%Aimingの場合は
引数AはLDA等で指定したAレジスタの値
引数$00,$01にX座標の差
引数$02,$03にY座標の差
戻り値$00にX速度
戻り値$02にY速度
という風になる。意味が分からないって方は「引数」と「戻り値」で検索DA!
実際に書いてみる
後日公開(未定)