PICO-8でゲーム制作を学ぶ(2)

[話者] 今回は

を学ぼう。

[合いの手] ふむふむ、

  • _INIT() 関数で初期化。
  • _UPDATE() 関数は1/30秒おきに呼ばれる。
  • _DRAW() 関数は、_UPDATE()が1/30秒以内に完了した場合、その後に呼ばれる。1/30秒以内に完了しなかった場合、呼ばれない。

Sprite Editorでキャラクターも描いたぞ。

図: キャラクターを描く

ソースコードも入力した。

図: ソースコードを入力

カーソルキーでキャラを動かせる。

図: キャラを動かせる

このソースコード、入力したけど、意味がいまいち分かってない。

x=64
y=64
sprite=1
dir=1
itvl_frame=8 -- interval =1/30sec
anim_frame=2
t=0
function input()
 local pressed=false
 if btn(0) then x-=1 dir=4 pressed=true end
 if btn(1) then x+=1 dir=3 pressed=true end
 if btn(2) then y-=1 dir=2 pressed=true end
 if btn(3) then y+=1 dir=1 pressed=true end
 if pressed then 
  sprite=dir+flr((t%(anim_frame*itvl_frame))/itvl_frame+1)*16
 else
  sprite=dir
 end
end
function _update()
 t+=1
 input()
end
function _draw()
 rectfill(0,0,127,127,13)
 spr(sprite,x-4,y-4)
end

[話者] このソースコードで大事なのはスプライト番号だね。

スプライト(sprite)とは、小さな絵の部品です。
PICO-8では、8×8ピクセルの正方形がスプライトの最小単位です。

スプライト番号は以下のようになっている。

図: スプライト番号

画像一枚にすれば、こうなる。

図: スプライト番号一覧

[合いの手] ふんふん。

[話者] ソースコードの変数の
sprite これが「スプライト番号」。
dir というのはdirection、つまり「体がどちらを向いているか」のこと。

dir 体の向き
1
2
3
4

という対応になっているのが画像と見比べると分かる。

 if btn(0) then x-=1 dir=4 pressed=true end
 if btn(1) then x+=1 dir=3 pressed=true end
 if btn(2) then y-=1 dir=2 pressed=true end
 if btn(3) then y+=1 dir=1 pressed=true end

というソースコードで、btn(0) から btn(3) までは、カーソルキー ←, →, ↑, ↓ に対応する

btn(キー) dir(体の向き)
0(←) 4(←)
1(→) 3(→)
2(↑) 2(↑)
3(↓) 1(↓)

というように、体の向きとカーソルキーを一致させるソースコードになっている。

このinput関数、1/30秒ごとに呼ばれて実行されるんだけど、

function input()
 local pressed=false

毎回 pressed 変数が false になる。
その後、キー入力を確認して、カーソルキーがどれか押されてれば pressed 変数は true になる。 local と付与すると、ブロック(endやelseで閉じられる領域)内だけで有効なローカル変数になる。localがないとグローバル変数になる。

  sprite=dir+flr((t%(anim_frame*itvl_frame))/itvl_frame+1)*16

この行はややこしい。カッコの奥のほうから先に解釈されるから、奥のほうから見ていこう。

anim_frame*itvl_frame この変数はソースコードの上のほうで、こう定義されている。

itvl_frame=8 -- interval =1/30sec
anim_frame=2

-- の後は、人に読ませるためのコメントで、機械は読まない部分。 フレームというのは、4コマ漫画でいうコマのこと。 PICO-8は1秒に30フレーム(30コマ)表示する。

anim_frame*itvl_frame の変数はソースコード中で変化しないので、要するに 16 と書いてあるのと同じ。

t%(anim_frame*itvl_frame)
t はtimer変数で、1/30秒ごとに1ずつ増加する。
t%16 は、tを16で割った余り(剰余)を計算する。要するに1/30秒ごとに 0, 1, 2,.., 14, 15, 0, 1, 2,.. という数字のどれかになる。

(t%(anim_frame*itvl_frame))/itvl_frame は、0/8, 1/8, 2/8,.., 14/8, 15/8, 0/8, 1/8,.. という数字のどれかになる。小数で表すと、0, 0.125, 0.25, 0.375, 0.5,.., 1.75, 1.875, 0, 0.125,.. になる。

(t%(anim_frame*itvl_frame))/itvl_frame+1 は、上記の数字に +1、つまり小数だと 1, 1.125, 1.25,.., 2.75, 2.875, 1, 1.125,.. になる。

flr((t%(anim_frame*itvl_frame))/itvl_frame+1)flrは floor つまり小数点以下を削除するので、つまり 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, .. というふうに 8回ごとに 1 か 2 が出てくる。

flr((t%(anim_frame*itvl_frame))/itvl_frame+1)*16 は上記の16倍、つまり 16, 16, 16, 16, 16, 16, 16, 16, 32, 32, 32, 32, 32, 32, 32, 32, 16, 16, .. というふうに8回ごとに 16 か 32 が出てくる。

dir+flr((t%(anim_frame*itvl_frame))/itvl_frame+1)*16 は、上記の数字に、体の向きが ↓だったときは +1 、↑だったときは +2、→のときは +3、←のときは +4

これで、分かったかな?

[合いの手] よく分からないぞ。

[話者] つまりだな、

 if pressed then 
  sprite=dir+flr((t%(anim_frame*itvl_frame))/itvl_frame+1)*16
 else
  sprite=dir
 end

ソースコードは、
カーソルキー押してるときは、
8/30秒ごとに、

図: 手を振るA=移動中

図: 手を振るB=移動中
を交互に表示する。体の向きにしたがって、横4つ並びのどれか1つのスプライトを表示するんだ。

カーソルキー押してないときは

図: 静止中
のうち、体の向きにしたがって、横4つ並びのどれか1つを表示する。

ということだ。

[合いの手] はー。そういうことか。

でも、それって歩くとき2パターンしか使ってないってことだよね。
普通は 3パターンあるなら、3パターンを順繰りに表示しない?

[話者] それ、してみると、なんか歩いてる感じにならないんだよなぁ。

anim_frame=3

 if pressed then 
  --sprite=dir+flr((t%(anim_frame*itvl_frame))/itvl_frame+1)*16
  sprite=dir+flr((t%(anim_frame*itvl_frame))/itvl_frame)*16
 else
  sprite=dir
 end

図: 3パターンを順繰りに表示

[合いの手] うーん、確かにそんな感じも。キャラデザインがあまり良くないのかもなー。

[話者]

ソースコードの残りは簡単だな。

function _update()
 t+=1
 input()
end

このコードは、
1/30秒ごとに、
t を1増加させて、
input関数を呼び出している。

function _draw()
 rectfill(0,0,127,127,13)
 spr(sprite,x-4,y-4)
end

このコードは、
rectfill で画面を全部塗りつぶして、前回描いたものを消している。
spr でスプライトを表示する。そのとき、スプライト番号と位置を指定してる。

[合いの手] うん、分かった。

[話者] 今回のソースコードはこちら。

図: 20240326-1.p8.png