アットウィキロゴ
 

ここは?

ボススプライトを書くような高度な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より容量を食います。(´;ω;`)ウッ…
なので使い分けてください。

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に飛ばす的なコードなのが前提なコードです。

Graphics Routine

はじめに

カスタムスプライト(コマンド除く)での共通事項、それはグラフィックです。
カスタムスプライトを作る上でかなりの人が躓くであろうグラフィックの描写を攻略していきます。
では早速解説からいきましょう!
グラフィックの描写 = スプライトの絵が描かれたタイルの設置です。
つまりグラフィックを描写するには、
タイルの位置、タイルの番号、タイルの情報(パレットなど)、タイルのサイズを決めてあげるのです。

スプライトにはそれぞれインデクス番号という番号が割り振られています。
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の他にも役に立つ処理やアドレスが載っているので目を通しておくといいかもしれません。

1タイルのグラフィック

まずは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でhttp://vip.rgr.jp/sm4wiki/image/lm/map_base.gifを押すと出る、マップのモードの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のhttp://vip.rgr.jp/sm4wiki/image/lm/8graphic.gif (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タイルのグラフィック - 発展

ここまでで、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を読み込むか決めているのが分かりますね。こっちの方が分かりやすいと思いますが、タイル数が多いと変わってくるでしょう。

8x8サイズのタイルを描く

最初の方で説明しましたが$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タイルの描写をするので描かれるタイルは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となるからですね。
詳しくはこれを見るといいでしょう。(鬼畜王氏の講座にほぼ同じものがあるケド)

#ref error :ご指定のファイルが見つかりません。ファイル名を確認して、再度指定してください。 (pos.png)

これを見る限り、縦に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タイル分ずれるので下半分のタイルが地面に埋まってしまうのです。

#ref error :ご指定のファイルが見つかりません。ファイル名を確認して、再度指定してください。 (nokonoko.png)

なので (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タイル描写 -アニメーションetc

やりたいことの説明は既に済んでます。これを読み直すといいでしょう。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		; /

おまけ

#ref error :ご指定のファイルが見つかりません。ファイル名を確認して、再度指定してください。 (graphics.zip)

これまでの講座で学んできたグラフィックルーチンの参考例です。
スプライト製作に少しでも役立てられたら幸いです。


ASL/LSRのご指摘ありがとうございました。
間違っているところがあったら勝手に直すか、コメントお願いします。



おこめ

最終更新:2011年04月16日 17:43