Ubuntuでファミコンプログラム その4(スプライト)

Ubuntuでファミコンエミュレーターで動くプログラムを作ってみてます。
Ubuntuでファミコンプログラム その3(背景描画とパレット)


こちらのサンプルソースを元に試行錯誤してます。
http://hp.vector.co.jp/authors/VA042397/nes/sample.html

前回までのソースは、
https://bitbucket.org/symfo/nes_sample/src
こちらのsample3においてます。




プログラムレビュー



前回までは背景を描画していましたが、今回はキャラクターを描画するための
「スプライト」を使ってみたいと思います。

これまた全然わからないので、例によって

ギコ猫でもわかるファミコンプログラミング - 第5章 スプライト

こちらを穴が開くほど眺めます。

すると、
「PPUの初期化で、$2000で背景パターンは$0000から、スプライトパターンは$1000からと指定しました。」

覚えが無いので、第3章を見なおしてみます。

ギコ猫でもわかるファミコンプログラミング - 第3章 PPU


抜粋です。


$2000 PPUコントロールレジスタ1

bit7: VBlank時にNMIを実行(0:実行しない,1:実行する)
    bit6: PPUマスター/スレーブセレクト(0にしてマスターモードを選択する)
    bit5: スプライトサイズ(0:8x8,1:8x16)
    bit4: BGパターンテーブルアドレス(0:$0000,1:$1000)
    bit3: スプライトパターンテーブルアドレス(0:$0000,1:$1000)
    bit2: PPUアドレスインクリメント(0:+1,1:+32)
    bit1-0: 表示するネームテーブルアドレス番号

    00 = $2000 (VRAM)
    01 = $2400 (VRAM)
    10 = $2800 (VRAM)
    11 = $2C00 (VRAM)



もうひとつ


$2001 PPUコントロールレジスタ2

    bit7-5: bit0=1のとき背景色/bit0=1のとき色強調

    000:なし
    001:緑
    010:青
    100:赤
    (それ以外の数字は不可)

    bit4: スプライト表示(0:非表示,1:表示)
    bit3: BG表示(0:非表示,1:表示)
    bit2: スプライトクリップ(0:画面の左8ドットを表示しない,1:クリップなし)
    bit1: BGクリップ(0:画面の左8ドットを表示しない,1:クリップなし)
    bit0: ディスプレイタイプ(0:カラー,1:モノクロ)




ソースの該当箇所はここになります。


  1. ; スクリーンオン
  2.     lda    #$08
  3.     sta    $2000
  4.     lda    #$1e
  5.     sta    $2001



$2000に16進数で08、2進数で00001000を指定しているので。


0:VBlank時にNMIを0:実行しない
0:PPUマスター/スレーブセレクト(0にしてマスターモードを選択する)
0:スプライトサイズ(0:8x8)
0:BGパターンテーブルアドレス(0:$0000)
1:スプライトパターンテーブルアドレス(1:$1000)
0:PPUアドレスインクリメント(0:+1)
00:表示するネームテーブルアドレス番号(00:$2000 (VRAM))



なるほど、たしかに背景が$0000、スプライトが$1000と指定してます。



$2001の方も見てみます。

$1eと指定してますので、2進数だと00011110になります。


000:bit0=1のとき背景色/bit0=1のとき色強調(000:なし)
1: スプライト表示(1:表示)
1:BG表示(1:表示)
1:スプライトクリップ(1:クリップなし)
1:BGクリップ(1:クリップなし)
0:ディスプレイタイプ(0:カラー)



こういう指定を知らないうちにやってたわけか。




でも、画像データの読み込みは

  1. ; パターンテーブル
  2. .segment "CHARS"
  3.     .incbin    "character.chr"



の一箇所だけなのになんで上手く行くんだろ?と思ったのですが、
YY-CHRでファイルを開いてよく見てみると、

267_01.png

意識してなかっただけで、1つのファイルの前半が背景、後半がスプライトの領域となってました。
なるほど。



スプライト領域の最初にかっこいいキャラを書いて保存しておきました。

267_02.png





パレットとスプライトの読み込み



改めて、cpu:6502のメモリマップを見てみます。

NES研究室 - メモリマップ

「0x3F00-0x3F0F」が「BGパレットテーブル」
「0x3F10-0x3F1F」が「スプライトパレットテーブル」

となっています。

ソースの該当箇所を見てみると、


  1. ; パレットテーブルへ転送(BG用のみ転送)
  2.     lda    #$3f
  3.     sta    $2006
  4.     lda    #$00
  5.     sta    $2006
  6.     ldx    #$00
  7.     ldy    #$10
  8.     
  9. copypal:
  10.     lda    palettes, x
  11.     sta    $2007
  12.     inx
  13.     dey
  14.     bne    copypal



確かに、0x3f00から16バイト分(0x3f0fまで)データを書きこんでいます。
それで「パレットテーブルへ転送(BG用のみ転送)」っていうコメントだったのか。
ようやく理解しました。

今回、背景は使用せずスプライトのみ表示してみようと思います。
この箇所を修正して、0x3f10-0x3f1fまでの領域にデータを書き込み用に修正してみます。
逆に背景用のパレットは書き込みません。


  1. ; パレットテーブルへ転送(パレット用のみ転送)
  2.     lda    #$3f
  3.     sta    $2006
  4.     lda    #$10 ;ここの開始アドレスを変更
  5.     sta    $2006
  6.     ldx    #$00
  7.     ldy    #$10
  8.     
  9. copypal:
  10.     lda    palettes, x
  11.     sta    $2007
  12.     inx
  13.     dey
  14.     bne    copypal




背景描画用のソースを削除し、とりあえず実行してみます。

267_03.png

座標を指定していない場合でも、左上に表示されるみたいです。



改めて、こちらを参考に座標を指定する処理を追加。
ギコ猫でもわかるファミコンプログラミング - 第5章 スプライト


見様見真似で、こんな感じの処理を追加しました。


  1.     ; スプライト描画
  2.     ;多分、スプライトの番号
  3.     lda #$00 ; $00(スプライトRAMのアドレスは8ビット長)をAにロード
  4.     sta $2003 ; AのスプライトRAMのアドレスをストア
  5.     
  6.     ;1バイト目 Y座標
  7.     lda #50     ; 50(10進数)をAにロード
  8.     sta $2004 ; Y座標をレジスタにストアする
  9.     
  10.     ;2バイト目 タイルインデクス番号 sprファイルの何番目のスプライトを表示するか)
  11.     lda #00     ; 0(10進数)をAにロード
  12.     sta $2004 ; 0をストアして0番のスプライトを指定する
  13.     
  14.     ;3バイト目 8ビットのビットフラグです。スプライトの属性を指定します。
  15.     sta $2004 ; 反転や優先順位は操作しないので、再度$00をストアする
  16.     
  17.     ;4バイト目 X座標
  18.     lda #20        ;    20(10進数)をAにロード
  19.     sta $2004 ; X座標をレジスタにストアする




実行し見ると、それっぽく表示されます。

267_04.png






複数のスプライトを表示



なんとなく理屈はわかったので、スプライトを4つ表示してみます。

まず、失敗したパターンから。
使用するスプライトの指定箇所です。


  1.     lda #$00 ; $00(スプライトRAMのアドレスは8ビット長)をAにロード
  2.     sta $2003 ; AのスプライトRAMのアドレスをストア




これ、0,1,2,3,...って指定すればいいんでしょ?と思い、こんなスプライトを2つ表示するプログラムを書きました。


  1.     ; スプライト描画
  2.     ;多分、スプライトの番号
  3.     lda #$00 ; $00(スプライトRAMのアドレスは8ビット長)をAにロード
  4.     sta $2003 ; AのスプライトRAMのアドレスをストア
  5.     
  6.     ;1バイト目 Y座標
  7.     lda #50     ; 50(10進数)をAにロード
  8.     sta $2004 ; Y座標をレジスタにストアする
  9.     
  10.     ;2バイト目 タイルインデクス番号 sprファイルの何番目のスプライトを表示するか)
  11.     lda #00     ; 0(10進数)をAにロード
  12.     sta $2004 ; 0をストアして0番のスプライトを指定する
  13.     
  14.     ;3バイト目 8ビットのビットフラグです。スプライトの属性を指定します。
  15.     sta $2004 ; 反転や優先順位は操作しないので、再度$00をストアする
  16.     
  17.     ;4バイト目 X座標
  18.     lda #20        ;    20(10進数)をAにロード
  19.     sta $2004 ; X座標をレジスタにストアする
  20.     
  21.     
  22.     ;スプライト2個目
  23.     lda #$01 ; $01のスプライトを使うよ
  24.     sta $2003 ; AのスプライトRAMのアドレスをストア
  25.     ;1バイト目 Y座標
  26.     lda #100
  27.     sta $2004
  28.     ;2バイト目 タイルインデクス番号 sprファイルの何番目のスプライトを表示するか)
  29.     lda #00
  30.     sta $2004
  31.     ;3バイト目 8ビットのビットフラグ
  32.     sta $2004
  33.     ;4バイト目 X座標
  34.     lda #100
  35.     sta $2004




事項してみると、スプライトは1つしか表示されません。
表示位置も狙った箇所になってないし。

267_05.png


冷静に考えてみると、スプライト1つ表示するために4バイトの情報を消費しています。
ということは、2つめのスプライトの指定は0x04から書き始めないといけないのでは?

267_06.png

ソースを修正して実行してみます。


  1.     ; スプライト描画
  2.     ;多分、スプライトの番号
  3.     lda #$00 ; $00(スプライトRAMのアドレスは8ビット長)をAにロード
  4.     sta $2003 ; AのスプライトRAMのアドレスをストア
  5.     
  6.     ;1バイト目 Y座標
  7.     lda #50     ; 50(10進数)をAにロード
  8.     sta $2004 ; Y座標をレジスタにストアする
  9.     
  10.     ;2バイト目 タイルインデクス番号 sprファイルの何番目のスプライトを表示するか)
  11.     lda #00     ; 0(10進数)をAにロード
  12.     sta $2004 ; 0をストアして0番のスプライトを指定する
  13.     
  14.     ;3バイト目 8ビットのビットフラグです。スプライトの属性を指定します。
  15.     sta $2004 ; 反転や優先順位は操作しないので、再度$00をストアする
  16.     
  17.     ;4バイト目 X座標
  18.     lda #20        ;    20(10進数)をAにロード
  19.     sta $2004 ; X座標をレジスタにストアする
  20.     
  21.     
  22.     ;スプライト2個目
  23.     lda #$04 ; $2つめのスプライトを使うよ※4を指定
  24.     sta $2003 ; AのスプライトRAMのアドレスをストア
  25.     ;1バイト目 Y座標
  26.     lda #100
  27.     sta $2004
  28.     ;2バイト目 タイルインデクス番号 sprファイルの何番目のスプライトを表示するか)
  29.     lda #00
  30.     sta $2004
  31.     ;3バイト目 8ビットのビットフラグ
  32.     sta $2004
  33.     ;4バイト目 X座標
  34.     lda #100
  35.     sta $2004



狙い通り、スプライトが2つ表示出来ました。

267_07.png

なるほど、そういうことか。

ならば、こんなソースで4つのスプライトを表示してみます。


  1.     ; スプライト描画
  2.     ;多分、スプライトの番号
  3.     lda #$00 ; $00(スプライトRAMのアドレスは8ビット長)をAにロード
  4.     sta $2003 ; AのスプライトRAMのアドレスをストア
  5.     
  6.     ;1バイト目 Y座標
  7.     lda #50     ; 50(10進数)をAにロード
  8.     sta $2004 ; Y座標をレジスタにストアする
  9.     
  10.     ;2バイト目 タイルインデクス番号 sprファイルの何番目のスプライトを表示するか)
  11.     lda #00     ; 0(10進数)をAにロード
  12.     sta $2004 ; 0をストアして0番のスプライトを指定する
  13.     
  14.     ;3バイト目 8ビットのビットフラグです。スプライトの属性を指定します。
  15.     sta $2004 ; 反転や優先順位は操作しないので、再度$00をストアする
  16.     
  17.     ;4バイト目 X座標
  18.     lda #20        ;    20(10進数)をAにロード
  19.     sta $2004 ; X座標をレジスタにストアする
  20.     
  21.     ;スプライト2個目
  22.     lda #$04 ; 2つめのスプライトを使うよ※4を指定
  23.     sta $2003 ; AのスプライトRAMのアドレスをストア
  24.     ;1バイト目 Y座標
  25.     lda #100
  26.     sta $2004
  27.     ;2バイト目 タイルインデクス番号 sprファイルの何番目のスプライトを表示するか)
  28.     lda #00
  29.     sta $2004
  30.     ;3バイト目 8ビットのビットフラグ
  31.     sta $2004
  32.     ;4バイト目 X座標
  33.     lda #40
  34.     sta $2004
  35.     
  36.     ;スプライト3個目
  37.     lda #$08 ; 3つめのスプライトを使うよ
  38.     sta $2003 ; AのスプライトRAMのアドレスをストア
  39.     ;1バイト目 Y座標
  40.     lda #150
  41.     sta $2004
  42.     ;2バイト目 タイルインデクス番号 sprファイルの何番目のスプライトを表示するか)
  43.     lda #00
  44.     sta $2004
  45.     ;3バイト目 8ビットのビットフラグ
  46.     sta $2004
  47.     ;4バイト目 X座標
  48.     lda #60
  49.     sta $2004
  50.     
  51.     
  52.     ;スプライト4個目
  53.     lda #$0c ; 4つめのスプライトを使うよ
  54.     sta $2003 ; AのスプライトRAMのアドレスをストア
  55.     ;1バイト目 Y座標
  56.     lda #200
  57.     sta $2004
  58.     ;2バイト目 タイルインデクス番号 sprファイルの何番目のスプライトを表示するか)
  59.     lda #00
  60.     sta $2004
  61.     ;3バイト目 8ビットのビットフラグ
  62.     sta $2004
  63.     ;4バイト目 X座標
  64.     lda #80
  65.     sta $2004




結果は上手く行きませんでした。

267_08.png


ただ、エミュレーターによっては狙い通りの表示になるものもあります。

267_09.png



原因はよくわかっていないのですが、「sta $2003」っていう命令を
2回以上実行するとなんかおかしくなります。

よく考えたら、一回アドレスを指定したらそこから連続してデータを書きこんでくれるんだから
改めて指定せず、すぐに次のスプライト情報を書きこんでみれば。


  1.     ; スプライト描画
  2.     ;多分、スプライトの番号
  3.     lda #$00 ; $00(スプライトRAMのアドレスは8ビット長)をAにロード
  4.     sta $2003 ; AのスプライトRAMのアドレスをストア
  5.     
  6.     ;1バイト目 Y座標
  7.     lda #50     ; 50(10進数)をAにロード
  8.     sta $2004 ; Y座標をレジスタにストアする
  9.     
  10.     ;2バイト目 タイルインデクス番号 sprファイルの何番目のスプライトを表示するか)
  11.     lda #00     ; 0(10進数)をAにロード
  12.     sta $2004 ; 0をストアして0番のスプライトを指定する
  13.     
  14.     ;3バイト目 8ビットのビットフラグです。スプライトの属性を指定します。
  15.     sta $2004 ; 反転や優先順位は操作しないので、再度$00をストアする
  16.     
  17.     ;4バイト目 X座標
  18.     lda #20        ;    20(10進数)をAにロード
  19.     sta $2004 ; X座標をレジスタにストアする
  20.     
  21.     ;スプライト2個目
  22.     ;1バイト目 Y座標
  23.     lda #100
  24.     sta $2004
  25.     ;2バイト目 タイルインデクス番号 sprファイルの何番目のスプライトを表示するか)
  26.     lda #00
  27.     sta $2004
  28.     ;3バイト目 8ビットのビットフラグ
  29.     sta $2004
  30.     ;4バイト目 X座標
  31.     lda #40
  32.     sta $2004
  33.     
  34.     ;スプライト3個目
  35.     ;1バイト目 Y座標
  36.     lda #150
  37.     sta $2004
  38.     ;2バイト目 タイルインデクス番号 sprファイルの何番目のスプライトを表示するか)
  39.     lda #00
  40.     sta $2004
  41.     ;3バイト目 8ビットのビットフラグ
  42.     sta $2004
  43.     ;4バイト目 X座標
  44.     lda #60
  45.     sta $2004
  46.     
  47.     
  48.     ;スプライト4個目
  49.     ;1バイト目 Y座標
  50.     lda #200
  51.     sta $2004
  52.     ;2バイト目 タイルインデクス番号 sprファイルの何番目のスプライトを表示するか)
  53.     lda #00
  54.     sta $2004
  55.     ;3バイト目 8ビットのビットフラグ
  56.     sta $2004
  57.     ;4バイト目 X座標
  58.     lda #80
  59.     sta $2004




これで上手く行きました。

267_10.png


別の命令を間に挟むとか、何か回避策があるのかもしれませんが、
それは今後の研究課題にしておきます。



ちなみにスプライトはsta $2003で指定できる0x00から0xffの256バイト。
これを1つのスプライトで消費する情報4バイトで割った値の64個使えることになるようです。








スプライト毎に色を変更する



スプライトの色を変更するには、3バイト目に渡しているデータに細工すれば良さそうです。


bit7:垂直反転(1で反転)
bit6:水平反転(1で反転)
bit5:BGとの優先順位(0:手前、1:奥)
bit4:0固定
bit3:0固定
bit2:0固定
bit0-1:パレットの上位2bit




プログラムはこのようになりました。


  1.     ; スプライト描画
  2.     ;多分、スプライトの番号
  3.     lda #$00 ; $00(スプライトRAMのアドレスは8ビット長)をAにロード
  4.     sta $2003 ; AのスプライトRAMのアドレスをストア
  5.     
  6.     ;1バイト目 Y座標
  7.     lda #50     ; 50(10進数)をAにロード
  8.     sta $2004 ; Y座標をレジスタにストアする
  9.     
  10.     ;2バイト目 タイルインデクス番号 sprファイルの何番目のスプライトを表示するか)
  11.     lda #00     ; 0(10進数)をAにロード
  12.     sta $2004 ; 0をストアして0番のスプライトを指定する
  13.     
  14.     ;3バイト目 8ビットのビットフラグです。スプライトの属性を指定します。
  15.     lda #%00000000
  16.     sta $2004 ; 反転や優先順位は操作しないので、再度$00をストアする
  17.     
  18.     ;4バイト目 X座標
  19.     lda #20        ;    20(10進数)をAにロード
  20.     sta $2004 ; X座標をレジスタにストアする
  21.     
  22.     ;スプライト2個目
  23.     ;1バイト目 Y座標
  24.     lda #100
  25.     sta $2004
  26.     ;2バイト目 タイルインデクス番号 sprファイルの何番目のスプライトを表示するか)
  27.     lda #00
  28.     sta $2004
  29.     ;3バイト目 8ビットのビットフラグ
  30.     lda #%00000001
  31.     sta $2004
  32.     ;4バイト目 X座標
  33.     lda #40
  34.     sta $2004
  35.     
  36.     ;スプライト3個目
  37.     ;1バイト目 Y座標
  38.     lda #150
  39.     sta $2004
  40.     ;2バイト目 タイルインデクス番号 sprファイルの何番目のスプライトを表示するか)
  41.     lda #00
  42.     sta $2004
  43.     ;3バイト目 8ビットのビットフラグ
  44.     lda #%00000010
  45.     sta $2004
  46.     ;4バイト目 X座標
  47.     lda #60
  48.     sta $2004
  49.     
  50.     
  51.     ;スプライト4個目
  52.     ;1バイト目 Y座標
  53.     lda #200
  54.     sta $2004
  55.     ;2バイト目 タイルインデクス番号 sprファイルの何番目のスプライトを表示するか)
  56.     lda #00
  57.     sta $2004
  58.     ;3バイト目 8ビットのビットフラグ
  59.     lda #%00000011
  60.     sta $2004
  61.     ;4バイト目 X座標
  62.     lda #80
  63.     sta $2004




狙い通りです。

267_11.png





反転



ここに来て、サンプルの画像がまずいことに気が付きました。
水平反転しても効果がない・・・

垂直反転だけ試してみます。


  1.     ; スプライト描画
  2.     ;多分、スプライトの番号
  3.     lda #$00 ; $00(スプライトRAMのアドレスは8ビット長)をAにロード
  4.     sta $2003 ; AのスプライトRAMのアドレスをストア
  5.     
  6.     ;1バイト目 Y座標
  7.     lda #50     ; 50(10進数)をAにロード
  8.     sta $2004 ; Y座標をレジスタにストアする
  9.     
  10.     ;2バイト目 タイルインデクス番号 sprファイルの何番目のスプライトを表示するか)
  11.     lda #00     ; 0(10進数)をAにロード
  12.     sta $2004 ; 0をストアして0番のスプライトを指定する
  13.     
  14.     ;3バイト目 8ビットのビットフラグです。スプライトの属性を指定します。
  15.     lda #%00000000
  16.     sta $2004 ; 反転や優先順位は操作しないので、再度$00をストアする
  17.     
  18.     ;4バイト目 X座標
  19.     lda #20        ;    20(10進数)をAにロード
  20.     sta $2004 ; X座標をレジスタにストアする
  21.     
  22.     ;スプライト2個目
  23.     ;1バイト目 Y座標
  24.     lda #100
  25.     sta $2004
  26.     ;2バイト目 タイルインデクス番号 sprファイルの何番目のスプライトを表示するか)
  27.     lda #00
  28.     sta $2004
  29.     ;3バイト目 8ビットのビットフラグ
  30.     lda #%10000001
  31.     sta $2004
  32.     ;4バイト目 X座標
  33.     lda #40
  34.     sta $2004
  35.     
  36.     ;スプライト3個目
  37.     ;1バイト目 Y座標
  38.     lda #150
  39.     sta $2004
  40.     ;2バイト目 タイルインデクス番号 sprファイルの何番目のスプライトを表示するか)
  41.     lda #00
  42.     sta $2004
  43.     ;3バイト目 8ビットのビットフラグ
  44.     lda #%00000010
  45.     sta $2004
  46.     ;4バイト目 X座標
  47.     lda #60
  48.     sta $2004
  49.     
  50.     
  51.     ;スプライト4個目
  52.     ;1バイト目 Y座標
  53.     lda #200
  54.     sta $2004
  55.     ;2バイト目 タイルインデクス番号 sprファイルの何番目のスプライトを表示するか)
  56.     lda #00
  57.     sta $2004
  58.     ;3バイト目 8ビットのビットフラグ
  59.     lda #%10000011
  60.     sta $2004
  61.     ;4バイト目 X座標
  62.     lda #80
  63.     sta $2004




ちょっとわかりにくいですが、2番目と4番目のスプライトの上下が反転してます。

267_12.png




今回のソースは、
https://bitbucket.org/symfo/nes_sample/src
こちらのsample4においてます。



続きはこちら。
Ubuntuでファミコンプログラム その5(スプライトを動かす)




【参考URL】

ギコ猫でもわかるファミコンプログラミング - 第3章 PPU
ギコ猫でもわかるファミコンプログラミング - 第5章 スプライト
NES研究室 - メモリマップ
関連記事

コメント

非公開コメント

プロフィール

Author:symfo
blog形式だと探しにくいので、まとめサイト作成中です。
Symfoware まとめ

PR




検索フォーム

月別アーカイブ