2022/10/31(月) [n年前の日記]
#1 [tic80] TIC-80 1.0.2164でラスタースクロール
TIC-80 1.0.2164 + Windows10 x64 21H2で、ラスタースクロールができるか試してみた。以前もやってみたことがあるのだけど、TIC-80の仕様が微妙に変わったので、ソースをちょっと修正して再度試してみた次第。
_TIC-80でラスタースクロール処理
_TIC-80でラスタースクロール処理その2
_TIC-80でラスタースクロール処理
_TIC-80でラスタースクロール処理その2
◎ BDR関数についての簡単な説明。 :
TIC-80 0.90 以降には、BDR() という関数が用意されてる。
_BDR - nesbox/TIC-80 Wiki
この関数は、ウインドウの横1ライン分を描画する直前に呼ばれる関数。描画しようとしているy座標 + 4 の値が入った状態で呼ばれる。
要するに、昔のゲーム機で言えば、水平帰線期間(HBlank)割込みに相当する。この BDR() の中で、Screen Offset値を書き換えてやれば、1ライン毎に表示位置を変えられるから、ラスタースクロールもできる。
ちなみに、TIC-80 0.90 より前の版(0.70.6 や 0.80.1344)では、BDR() ではなくて SCN() という関数名だった。
_SCN - nesbox/TIC-80 Wiki
また、SCN() に渡される値と、BDR() に渡される値も、若干違っている。
_BDR - nesbox/TIC-80 Wiki
この関数は、ウインドウの横1ライン分を描画する直前に呼ばれる関数。描画しようとしているy座標 + 4 の値が入った状態で呼ばれる。
要するに、昔のゲーム機で言えば、水平帰線期間(HBlank)割込みに相当する。この BDR() の中で、Screen Offset値を書き換えてやれば、1ライン毎に表示位置を変えられるから、ラスタースクロールもできる。
ちなみに、TIC-80 0.90 より前の版(0.70.6 や 0.80.1344)では、BDR() ではなくて SCN() という関数名だった。
_SCN - nesbox/TIC-80 Wiki
また、SCN() に渡される値と、BDR() に渡される値も、若干違っている。
- SCN(n) : n = 描画するラインのy座標。0 - 135 の値が入る。
- BDR(m) : m = 描画するラインのy座標 + 4。0 - 143 の値が入る。0 - 3 は TOP BORDER。140 - 143 は BOTTOM BORDER になっている。
◎ Screen Offsetを書き換えてラスタースクロール。 :
まずは、BDR() の中で、Screen Offset にsin値やcos値を書き込んでみる。
_raster1.zip (raster1.tic, tiles3.png, map3.map)
ローカル環境で動かしたい場合は、以下で実行できる。
画像(tiles3.png)やマップデータ(map3.map)を利用したい時は、TIC-80 のコンソール上からインポートできる。
ソースは以下。
_raster1.lua
_raster1.zip (raster1.tic, tiles3.png, map3.map)
ローカル環境で動かしたい場合は、以下で実行できる。
tic80.exe raster1.tic
画像(tiles3.png)やマップデータ(map3.map)を利用したい時は、TIC-80 のコンソール上からインポートできる。
tic80.exe --fs=.
import tiles tiles3.png import map map3.map
ソースは以下。
_raster1.lua
t=0 x=96 y=24 function TIC() cls(13) map(0,0) local col=0 local scale=3 spr(1+t%60//30*2,x,y,col,scale,0,0,2,2) col=12 print("HELLO WORLD!",84,84,col) t=t+1 end --function SCN(row) function BDR(row) local rx,ry rx=8*math.cos(math.rad(3*t+4*row)) ry=6*math.sin(math.rad(2*t+4*row)) poke(0x3FF9,rx) poke(0x3FF9+1,ry) end
- function BDR(row) - end の中が、ラスタースクロール処理。
- poke(0x3FF9,rx) で、Screen Offset の x値を書き換え。
- poke(0x3FF9+1,ry) で、Screen Offset の y値を書き換え。今気づいたけど、poke(0x3FFA,ry) と書いたほうが良かったかも…。
- Lua で sin,cos を使う時は、math.sin(), math.cos() を使う。
- sin, cos関数に渡される値はラジアン単位。math.rad() を使って、度からラジアンに変換できる。
◎ OVR()を使う。 :
前述のラスタースクロール処理を行うと、ウインドウ内の全体の見た目が変わってしまう。背景はラスタースクロールさせるけど、手前のスプライト相当はラスタースクロールをかけないようにしたい。
そんな時は OVR() 関数を使う。OVR()関数は、BDR() が描画処理を終えた後、一番最後に呼ばれるので、その中で描画処理をすれば、BDR() の処理が影響しない状態の描画ができる。
_OVR - nesbox/TIC-80 Wiki
_raster2.zip (raster2.tic)
_raster2.lua
OVR() の中で、スプライト(コンピュータみたいなキャラクタ)の描画と、文字描画("HELLO WORLD!")をしているので、背景はラスタースクロールしているけれど、スプライトと文字はラスタースクロールしていない状態になっている。
注意点。TIC-80 1.0以降では、OVR() の中で描画指定をすると、パレットの0番色が必ず透明色として扱われてしまう。
上記の例では、0番色を透明色、それ以外を不透明色扱いにしてドット絵を描いてあるので、問題無さそうに見えてるけれど。TIC-80 のデフォルトのパレットデータでは、0番色 = 黒色なので、黒色を使ったドット絵を描いて OVR() の中で描画してしまうと、黒い部分が透明になってしまう。
以下のやり取りによると、任意の色番号を透明色として扱うように指定することも可能らしいけど、やり方がよく分からなかった…。何にせよ、その場合もどこか1色は透明になってしまうので、OVR() の中で描画する何かしらは15色しか使えないと思っておいたほうが良さそう。
_everything drawn in OVR with color 0 is transparent - Issue #1897 - nesbox/TIC-80
そんな時は OVR() 関数を使う。OVR()関数は、BDR() が描画処理を終えた後、一番最後に呼ばれるので、その中で描画処理をすれば、BDR() の処理が影響しない状態の描画ができる。
_OVR - nesbox/TIC-80 Wiki
_raster2.zip (raster2.tic)
_raster2.lua
t=0 x=96 y=24 function TIC() local spd=2 if btn(0) then y=y-spd end if btn(1) then y=y+spd end if btn(2) then x=x-spd end if btn(3) then x=x+spd end cls(13) map(0,0) t=t+1 end --function SCN(row) function BDR(row) local rx,ry local sx,sy=x/10,y/5 rx=sx*math.cos(math.rad(3*t+4*row)) ry=sy*math.sin(math.rad(2*t+4*row)) poke(0x3FF9,rx) -- Screen Offset x poke(0x3FFA,ry) -- Screen Offset y end function OVR() local col=0 local scale=2 spr(257+t%60//30*2, x,y,col,scale,0,0,2,2) col=12 print("HELLO WORLD!",84,84,col) end
OVR() の中で、スプライト(コンピュータみたいなキャラクタ)の描画と、文字描画("HELLO WORLD!")をしているので、背景はラスタースクロールしているけれど、スプライトと文字はラスタースクロールしていない状態になっている。
注意点。TIC-80 1.0以降では、OVR() の中で描画指定をすると、パレットの0番色が必ず透明色として扱われてしまう。
上記の例では、0番色を透明色、それ以外を不透明色扱いにしてドット絵を描いてあるので、問題無さそうに見えてるけれど。TIC-80 のデフォルトのパレットデータでは、0番色 = 黒色なので、黒色を使ったドット絵を描いて OVR() の中で描画してしまうと、黒い部分が透明になってしまう。
以下のやり取りによると、任意の色番号を透明色として扱うように指定することも可能らしいけど、やり方がよく分からなかった…。何にせよ、その場合もどこか1色は透明になってしまうので、OVR() の中で描画する何かしらは15色しか使えないと思っておいたほうが良さそう。
_everything drawn in OVR with color 0 is transparent - Issue #1897 - nesbox/TIC-80
◎ 水面があるような処理。 :
そこに水面があるような見た目を、ラスタースクロールを使って実現してみる。
_raster3.zip (raster3.tic)
_raster3.lua
まず、BDR() に渡された値(y座標 + 4)と、値が上下に揺れるグローバル変数 sy を比較して、もし同じ値なら、そのタイミングでパレットデータを少し暗めな値に書き換えている。これで、水面相当がちょっと暗い感じの色になってくれる。
ただ、このままだと次のフレームを描画する時に、暗くなったパレットデータを使って描画してしまうので、TIC() の中の最後のあたりで sync(1<<5) を呼ぶことでパレットデータを初期化してる。
BDR() の中では、グローバル変数 sy より下を描画するときだけ、ラスタースクロールをかけている。 Screen Offset のy値を書き換えて、縦方向の表示位置を変化することで、上下反転した見た目にしている。
ただ…。TIC-80 0.70.6 / 0.80.1344 / 0.90.1723 と、TIC-80 1.0.2164 では、Screen Offset値の正負が逆なようで…。以前の版で書き込んでいた値の正負を反転して利用しないと、期待していた見た目にはならなかった。このあたりは別記事でまとめておく。
_raster3.zip (raster3.tic)
_raster3.lua
t=0 x=64 y=32 sy=0 function TIC() local spd=2 if btn(0) then y=y-spd end if btn(1) then y=y+spd end if btn(2) then x=x-spd end if btn(3) then x=x+spd end cls(0) map(0,0) local col=0 local scale=2 spr(1+t%60//30*2,x,y,col,scale,0,0,2,2) col=12 print("HELLO WORLD!",84,28,col) t=t+1 sy=math.floor((136*0.7)+16*math.sin(math.rad(t))) -- reset palette sync(1<<5) end --function SCN(row) function BDR(row) local rx,ry=0,0 if row==sy then -- palette change local PAL_ADDR=0x3FC0 for i=PAL_ADDR,PAL_ADDR+16*3-1 do poke(i,peek(i)*0.5) end end if row<sy then rx=0 ry=0 else local dy=row-sy rx=1.5*math.cos(math.rad(4*t+55*dy)) -- ry=-dy*2 -- TIC-80 0.70.6 ry=dy*2 -- TIC-80 1.0.2164 end local SCROFSX=0x3FF9 local SCROFSY=0x3FFA poke(SCROFSX,rx) poke(SCROFSY,ry) end
まず、BDR() に渡された値(y座標 + 4)と、値が上下に揺れるグローバル変数 sy を比較して、もし同じ値なら、そのタイミングでパレットデータを少し暗めな値に書き換えている。これで、水面相当がちょっと暗い感じの色になってくれる。
ただ、このままだと次のフレームを描画する時に、暗くなったパレットデータを使って描画してしまうので、TIC() の中の最後のあたりで sync(1<<5) を呼ぶことでパレットデータを初期化してる。
BDR() の中では、グローバル変数 sy より下を描画するときだけ、ラスタースクロールをかけている。 Screen Offset のy値を書き換えて、縦方向の表示位置を変化することで、上下反転した見た目にしている。
ただ…。TIC-80 0.70.6 / 0.80.1344 / 0.90.1723 と、TIC-80 1.0.2164 では、Screen Offset値の正負が逆なようで…。以前の版で書き込んでいた値の正負を反転して利用しないと、期待していた見た目にはならなかった。このあたりは別記事でまとめておく。
[ ツッコむ ]
以上です。