PICO-8でプログラミングを1から学ぶ連載、第8回です。前回はこちら。プログラミング経験者の方は、日本語マニュアルを読んで、すぐに創作にとりかかりましょう。作った作品は、ぜひ掲示板へ。
アイテムをたくさん置く
前回は、アイテムを一個取るたびに、別の場所にまたアイテムが出るというという簡単なゲームを作りました。ゲームとしては、これは退屈ですよね。もっとアイテムを一度にいっぱい出したいところです。スーパーマリオのように、画面上にいっぱい出ているコインをどんどん取っていったりする方が気持ちいいですよね?
今回は、こんなゲームを作ってみます:
たくさんのトマトがあらかじめ画面に敷き詰められています。これをオートマ坊やを動かしてどんどん取っていき、全部取るのにかかったタイムを競うゲームです。
前回のゲームでは、プレイヤーの位置を(x, y)アイテムの位置を(ix,iy)で表しました。アイテムをたくさん出すためには、アイテムの位置情報のための変数がもっと必要です。今回のゲームでは、横に16列、縦に16行で、全部で256個のトマトを画面に表示します。256個のトマトそれぞれに、変数を用意していったら大変です。
こんな感じでしょうか……?
i1x=0 i1y=0 i2x=8 i2y=0 i3x=16 i3y=0 … i255x=112 i255y=120 i256x=120 i256y=120
省略しましたが、こんなふうに書き連ねていくなんて、やってられません。そこで「配列」を使って、同じような役目をする変数をまとめて扱います。しかし、Luaには他のプログラミング言語のような「配列」がありません。代わりに「テーブル」を使います。
テーブルとは
テーブルは、複数の値をまとめて入れられる変数です。それぞれの値には「キー」を割り当てて、キーで呼び出せます。ほかのプログラミング言語の経験者の方は、配列と辞書が一緒になったものと考えればわかりやすいと思います。
a={} -- テーブルを初期化 a[1] = 4 -- テーブルの1番に値4を入れる a[2] = 3 -- テーブルの2番に値3を入れる a[3] = "aiueo" -- テーブルの3番に値"aiueo"を入れる a["fruit"] = "pineapple" -- テーブルのfruit要素に値"pineapple"を入れる a.animal = "panda" -- テーブルのanimal要素に値"panda"を入れる print(a[1]) -- テーブルaの1番の要素を表示 print(a[2]) -- テーブルaの2番の要素を表示 print(a[3]) -- テーブルaの3番の要素を表示 print(a["fruit"]) -- テーブルaのfruit要素のを表示 print(a.animal) -- テーブルaのanimal要素を表示
これを実行すると、
4 3 aiueo pineapple panda
a[“fruit”]とa.animalは書き方が違うだけで、同様にテーブルのキーに対応した値を指しています。ただし、数値のキーを使ってa.1というような書き方はできません。また、Luaのテーブルを配列のように使う場合の添字(数値のキー)は、ほかの多くの言語とは異なり、1から始まります。
テーブルの初期化と同時に、キー・値を複数設定しておく書き方もあります:
a={x=3, y=4, fruit=“banana”}
ところで、テーブルにはテーブルを入れることもできます。これを利用すると、アイテムの座標の管理がとてもしやすくなります。座標が(0,10)、(10, 20)、(20, 30)の位置にあるアイテムがあるとすると、下記のようにテーブルで座標データを表せます。
items={} items[1] = {x=0, y=10} items[2] = {x=10, y=20} items[3] = {x=20, y=30}
これらのアイテムを画面に描くとしたら、こんな感じでしょうか:
spr(0, item[1].x, item[1].y) spr(0, item[2].x, item[2].y) spr(0, item[3].x, item[3].y)
テーブルのテーブルを使えば、たくさんあるアイテムの座標情報をきれいにまとめられそうです。次に紹介するループと組み合わせると、もっとコードを短くできます。
ループとは
ループとは繰り返しの処理です。以下のコードを見てください。
for i=1,5 do print(i) end
これは、forから始まる行とendの行の間を、5回繰り返します。iはfor〜endの間でのみ有効なローカル変数で、その値は1から始まって、5になるまで繰り返しのたびに1ずつ増えます。これを実行すると:
1 2 3 4 5
繰り返しのたびに増える数値を、1以外にすることもできます。8ずつ増やすとしたら:
for i=0,120,8 do print(i) end
iは最初は0で、毎回8ずつ増え、120以上になるまで繰り返されます。これを実行すると:
0 8 16 ... 120
テーブル+ループ
さきほど、アイテムの座標のデータをテーブルのテーブルで表現しました。
items={} items[1] = {x=0, y=10} items[2] = {x=10, y=20} items[3] = {x=20, y=30}
このアイテムの座標データ作りを、ループを使ってやってみましょう。PICO-8の画面いっぱいに、8×8のスプライトで描かれたアイテムを敷き詰めたいと思います。こんな感じ:
これらの座標データを作るコードは:
items={} local i=0 for y in 0,120,8 do for x in 0,120,8 do items[i]={x=x, y=y} i += 1 end end
ここでは2重のループを使っています。16行×16列を1行ずつなめていく動きです。このループの中の流れは、以下のGIFアニメで理解できると思います。
これで16×16=256個のアイテム座標データを作る処理を、8行で書けました。次に、この256個のアイテムを画面に描画します。この処理はたった3行で済みます。
for i=1,#items do spr(0, items[i].x, items[i].y) end
テーブルの変数名の頭に#を付けると、テーブルの中身の要素の個数を得られます。ここでは64です。さて、ここで、テーブルの中身の要素について繰り返す、forループの便利な書き方も紹介しておきます。
for item in all(items) do spr(0, item.x, item.y) end
この書き方では、テーブル変数itemsの要素の個数分繰り返し処理が行われ、毎回1個ずつitemsの要素がローカル変数itemに入ります。
トマト収穫ゲーム
以上を踏まえ、前回のゲームに手を加えて、画面いっぱいのトマトを取りまくるゲームにしてみます。コードは以下のとおりです。スプライトは、前回のものと同じです。
x=64 y=64 s=1 -- sprite d=1 -- direction ipf=8 -- interval per frame nf=2 -- number of frames spd=2 items={} time=0 t=0 -- timer function update_timer() if #items>0 then time+=1 end end function draw_timer() local centi=flr(time*10/3)%100 if centi<10 then centi="0"..centi end local sec=flr(time/30) local min=flr(sec/60) sec=sec%60 if sec<10 then sec="0"..sec end local col=7 if #items==0 then col=rnd(16) end print(min..":"..sec.."."..centi, 2, 120, col) end -- collision function distance(x1,y1,x2,y2) return sqrt((x1-x2)^2+(y1-y2)^2) end function collision(x1,y1,x2,y2) if distance(x1,y1,x2,y2) < 6 then return true else return false end end -- items function place_items() local i=1 for y=0,120,8 do for x=0,120,8 do items[i]={x=x+4,y=y+4} i+=1 end end end function draw_items() for item in all(items) do spr(5, item.x-4, item.y-4) end end -- input function input() local pressed=false if btn(0) then x -= spd d=1 pressed=true end if btn(1) then x += spd d=2 pressed=true end if btn(2) then y -= spd d=3 pressed=true end if btn(3) then y += spd d=4 pressed=true end if pressed then s=d+flr((t%(nf*ipf))/ipf+1)*16 else s=d end end -- function _init() place_items() end function _update() t+=1 input() for item in all(items) do if collision(x, y, item.x, item.y) then del(items, item) sfx(0) end end update_timer() end function _draw() rectfill(0,0,127,127,13) draw_items() spr(s,x-4,y-4) draw_timer() end
_update()の中で、del()という関数を使用しています。これはPICO-8独自の組み込み関数で、テーブルの要素を一つ削除します。
del(t, v)
・t: テーブル
・v: 削除するテーブルの要素
ほかのプログラミング言語の経験者の方は、心配に思うかもしれませんが、del()はテーブルの要素についてのループの中においても、安全に使用することができます。
トマトを全部取ったタイムを競うゲームにするため、タイマーの表示も組み込まれています。このコードはちょっと難しいかもしれません。1/30秒おきに1ずつ増えるカウンターを「分:秒.1/100秒」に分解する処理が行われています。また、前回同様、アイテムを取ったときに飛び散る粒子のコードはここには含まれていません。
テーブルとループを使って、たくさんのアイテムを短いコードで扱うことができました。次回は、敵キャラクターを登場させてみましょう。
- 第1回: PICO-8って何?
- 第2回: プログラムで絵を描こう
- 第3回: アニメーションを作ろう
- 第4回: コントローラーを使おう
- 第5回: 3Dグラフィックスで遊ぼう
- 第6回: 効果音を鳴らそう
- 第7回: 人と物のふれあい……衝突判定
- 第8回: 1、2、3…無限大……繰り返しとテーブル
- 第9回: ビーム、撃っちゃうね。……繰り返しとテーブルその2
- 第10回: シンギュラリティは近い……ゲームAIの初歩の初歩
- 第11回: 撃たれると痛い……衝突判定その2
- 第12回: 画面効果その1・パーティクル