PICO-8でプログラミングを1から学ぶ連載の第3回です。前回はこちら。プログラミング経験者の方は、日本語マニュアルを読んで、すぐに創作にとりかかってください。作った作品は、ぜひ掲示板へ。
スプライト
前回、直線や四角形などの基本的な図形の描画方法を学びましたが、今回は自分でPICO-8内のツールを使って絵を描き、それをプログラムによって表示します。
スプライト・エディターを開きましょう。スプライト(sprite)とは、小さな絵の部品です。PICO-8では、8×8ピクセルの正方形がスプライトの最小単位です。
描いてみる
スプライトを描いてみましょう。スプライト・エディターの画面下のスプライトシートにはスプライトごとに番号が振られています。最初に開いた状態では、1番が選ばれた状態です。
1番に何か描いてみましょう。8×8で絵を描くのって難しいですが、試しにひとつ。
私は、青いシャツに赤いサロペットを着た男性を描いてみました。サロペットの色に合わせた帽子もかぶっています。
表示してみる
絵が描けたら、プログラムで画面に表示してみましょう。スプライトを表示する関数は SPR() です。
SPR(N, X, Y)
- N: スプライトの番号
- X: スプライトを描画するX座標
- Y: スプライトを描画するY座標
番号は、スプライトシート中で振られている番号です。今回は1番ですので最初の引数は1です。第2、第3引数で指定する座標に、スプライトの左上の点が来るように描画されることに注意してください。たとえば、画面の真ん中に表示させようとすると、こんなコードです:
RECTFILL(0,0,127,127,12) SPR(1, 64-4, 64-4)
実行結果は:
画面の真ん中の点は、(64, 64)あたりです。8×8のスプライトを真ん中に表示させるために、x方向に-4、y方向に-4ずらしたのがこのコードです。
大きな絵
SPR()にはオプションの第4、第5の引数があります。これを使うと、8×8よりもっと大きい絵を描画できます。
SPR(N, X, Y, W, H)
- N: スプライトの番号
- X: スプライトを描画するX座標
- Y: スプライトを描画するY座標
- W: スプライトを描画する幅(横方向に何個分か)
- H: スプライトを描画する高さ(縦方向に何個分か)
たとえば、このようなスプライトを用意した場合、
以下のコードで描画できます。
RECTFILL(0,0,127,127,12) SPR(1, 64-12, 64-8, 3, 2)
もっと大きな絵、または小さな絵
SPR()とは別のスプライトを描画する関数SSPR()を使えば、スプライトの拡大縮小も可能です。これについては、また次回以降に。
アニメーション
いままで作ってきたプログラムはすべて、描画の処理を1回実行して終了、というものでした。アニメーションやゲームなど、動き続けるプログラムを作る方法を、これからご説明します。
特別な関数
PICO-8には3つの特別な関数、_INIT()、_UPDATE()、_DRAW()があります。これらの関数をプログラム中に用意しておくと、決められたタイミングでその関数が呼ばれるようになります。
_INIT(): プログラム実行開始直後に呼ばれる。
_UPDATE(): 1/30秒おきに呼ばれる。
_DRAW(): _UPDATE()が呼ばれた後に呼ばれる。
前回、プログラムは上の行から順番に一行ずつ実行されていくものだ、と説明しました。これらの関数は、一度すべてのプログラムが実行されたあとに実行されます。たとえば、
A=10 FUNCTION _INIT() A+=10 END FUNCTION _UPDATE() END FUNCTION _DRAW() PRINT(A) END
このプログラムは、Aに10を代入する処理の実行後、各関数の読み込みが行われ、最後の行で一旦終了します。いままでのプログラムは、ここで終わりです。しかしその後、_INIT()が呼ばれ、Aの値が+10されて20になります。その後は延々と_UPDATE()、_DRAW()の順番で呼ばれ続けます。結果は以下のとおり。
動き続けるプログラムは、ESCキーを押すと停止します。
_UPDATE()がプログラム中にあると、プログラムはいままでのように1回実行されただけでは終了せず、_UPDATE()が定期的に呼ばれます。このことで、ずっと動き続けるようになります(_UPDATE()抜きで_DRAW()だけを用意した場合は、プログラムは停止します)。
なお、_DRAW()は、_UPDATE()の中の処理を実行するのに1/30秒以上かかった場合は呼ばれません。いわゆる「処理落ち」です。そのため、プログラム中の状態に関する更新は_UPDATE()の中で行い、_DRAW()の中では描画の処理のみに徹するべきです。
アニメーションを作る
映画のフィルムは1秒に24コマあります。1/24秒おきに少しずつ違う絵を差し替えることで、映像が動いて見えます。それと同様に、PICO-8では1/30秒おきに画面を更新することで、動く映像を作れます。
さてここで、さっき描いたサロペットの男性が歩くアニメーションのコマを描いてみます。
スプライトシートの2~4番に、歩いているさまを描いてみました。2、3、4のスプライトを、2、3、4、2、3、4……と順番に表示させ続ければ、歩いているように見えるはずです。そのためのコードは、こんな感じ?:
S=2 FUNCTION _UPDATE() S+=1 END FUNCTION _DRAW() RECTFILL(0,0,127,127,12) SPR(S,60,60) END
Sはスプライトの番号を示す変数です。おわかりだと思いますが、このプログラムではダメです。何がダメかというと、2、3、4番のスプライトを表示するまではうまくいきますが、そのあとSの番号がどんどん増大していき、対応した番号のスプライトが存在しないので、男性の絵が消えてしまいます。ほんとうは4番を表示した後は2番に戻り、2、3、4、2、3、4、2…と繰り返し続けたいです。それを行うために、新たなLuaの文法、IF文を使います。
IF文
プログラムは上から下へ順番に命令を実行するものですが、IF文は、その直線的な処理の流れを変えるものです。処理の流れを変える文のことを、制御文といいます。IF文は、処理の流れに分岐点をもうける制御文です。以下のような書き方をします。
IF (条件文) THEN (条件文が真ならここを実行する) END
(条件文)が正しければ、IF~ENDの中身を実行します。たとえば、こんな感じ。
IF HOUR<12 THEN PRINT(“GOOD MORNING!”) END
12時より前だったら朝の挨拶をする、というプログラムです。HOURが12より大きい場合は、何もしないでIF~ENDを素通りします。
今回のアニメーションのプログラムでは、「Sの値が4より大きくなったら、2に戻す」ということをしたいので、_UPDATE()の中を以下のように書き換えます。
S=2 FUNCTION _UPDATE() S+=1 IF S>4 THEN S=2 END END FUNCTION _DRAW() RECTFILL(0,0,127,127,12) SPR(S,60,60) END
これを実行してみると……
コマが繰り返されて、歩いているように……見えますが、ちょっと動きが速すぎますね。
1コマの表示時間を長くする
歩く動きが3コマだけで、1コマにつき1/30秒ですから速すぎて当然です。これを、ましに見えるようにする方法は2つあります。
1.なめらかな動きになるように、各コマの間に中間の絵をもっと多く描く
2.1コマについての表示時間を長くする
ここでは、2を選びたいと思います。たくさんの絵を描く労力を厭わず1の方法をとれば、素晴らしい結果が期待できますが、PICO-8では使用できるスプライト枚数にも上限があるため、少ないコマ数でアニメーションする方法を知っておいた方が良いでしょう。
1コマの表示時間を長くするには、_UPDATE()の中でSの値を更新を、毎回ではなく、何回かおきに減らします。たとえば、2回に1回更新するのであれば、1コマの表示時間は2/30秒になります。3回に1回なら3/30秒、4回に1回なら4/30秒、という具合です。これを行う方法をご紹介します。以下のようなコードです:
T=0 S=2 FUNCTION _UPDATE() T+=1 IF T%4==0 THEN S+=1 IF S>4 THEN S=2 END END END FUNCTION _DRAW() RECTFILL(0,0,127,127,12) SPR(S,60,60) END
Tという新しい変数を導入しました。Tはプログラムが開始されてからずっと、1ずつカウントアップされていきます。いわば、プログラム開始からの経過時間をカウントする時計です。_UPDATE()の中の以下の箇所に注目してください。
IF T%4==0 THEN
%は、割り算の余りを求める演算子です。たとえば、”5%2”の答えは、5割る2の余りなので、1です。このIF文は、Tを4で割った余りが0のとき、Sの更新処理を行います。これはどういうことか、割り算の余りの値を並べてみるとわかります。
整数を2で割った余り
1%2==1
2%2==0
3%2==1
4%2==0
5%2==1
6%2==0
…
整数を3で割った余り
1%3==1
2%3==2
3%3==0
4%3==1
5%3==2
6%3==0
…
整数を4で割った余り
1%4==1
2%4==2
3%4==3
4%4==0
5%4==1
6%4==2
…
おわかりでしょうか。整数を割った余りの値には、周期性があります。周期的な繰り返しの中で、<割る数>回に1回、“0”が現れます。この法則を使って、上記のプログラムでは4回に1回だけ、Sの値を更新するようにしています。これを実行すると、
てけてけと歩いている感じが出ました。
Pro Tips: Tをインクリメントし続けると、いずれ最大値(32767)を超えてオーバーフローし、負数(-32768)になります。それでも、T%4==0の判別に使うかぎりは、問題は生じません。
次回
今回は、スプライトの表示の仕方とアニメーションの方法を学びました。次回は、プレイヤーのコントローラーの入力を受け取って、画面の中のキャラクターを動かせるようにします。いよいよゲームらしくなりますね。
こんなところにPICO-8
onomさんという方が、クラブイベントの宣伝のために、PICO-8でゲームを作られています。
今週金曜日はelemogだよ!
告知のために、イエガーを投げてビーバーを退治するゲームを作りました。PC専用です!! #mogra https://t.co/Wns0fOs0k7— onom (@onom) September 28, 2016
明日のPURE IBIZA 100回目をお祝いして、PURE IBIZAゲーム化しました!!目指せ最速寿司100貫!!!!(PC専用) #mogra https://t.co/3pxo3fSYzq pic.twitter.com/5W9ZNSd2y5
— onom (@onom) October 1, 2016
- 第1回: PICO-8って何?
- 第2回: プログラムで絵を描こう
- 第3回: アニメーションを作ろう
- 第4回: コントローラーを使おう
- 第5回: 3Dグラフィックスで遊ぼう
- 第6回: 効果音を鳴らそう
- 第7回: 人と物のふれあい……衝突判定
- 第8回: 1、2、3…無限大……繰り返しとテーブル
- 第9回: ビーム、撃っちゃうね。……繰り返しとテーブルその2
- 第10回: シンギュラリティは近い……ゲームAIの初歩の初歩
- 第11回: 撃たれると痛い……衝突判定その2
- 第12回: 画面効果その1・パーティクル