カテゴリー
やってみようPICO-8

4. 背景をしらべる

キャラクターの移動先には何が…?どんな絵が置かれているかを調べる方法です。

3. 背景を作る」の続きです。
前回は、背景は表示されても、通り抜けてしまうただの絵でした。
それではゲームにならないので、壁にぶつかって止まるようにしてみたいところです。

まず最初のステップとして、キャラクターから見て、移動先に壁があるかどうか調べられるようにしてみます。

今回の完成コード

function get_map_flag(sprx,spry)
  local celx=flr(sprx/8)
  local cely=flr(spry/8)
  local celc=mget(celx,cely)
  return celc
end

function _init()
  x=8
  y=8
end

function _update()
  if btn(0) then x-=1 end
  if btn(1) then x+=1 end
  if btn(2) then y-=1 end
  if btn(3) then y+=1 end
  lefttop=get_map_flag(x,y)
  righttop=get_map_flag(x+7,y)
  leftbottom=get_map_flag(x,y+7)
  rightbottom=get_map_flag(x+7,y+7)
end

function _draw()
  cls(0)
  map(0,0,0,0,8,8)
  spr(1,x,y,1,1,false,false)
  print(lefttop.."/"..righttop,68,2,7)
  print(leftbottom.."/"..rightbottom,68,8,7)
end

壁にぶつかって止まる方法を考える

壁にぶつかって止まる、という動作を、細かく分解して考えてみます。

  1. キャラクターを移動させようとする
  2. 移動先の背景の状態を調べる
  3. 移動先が壁だったらキャラクターは動かない
  4. 移動先に壁がなかったら、決められただけ移動する

これをコードで書けば、壁に当たったら止まる、が実現します。
1はもうできているので、2以降の実現方法を考えます。

前回、背景を画面の真ん中に表示したのですが、ちょっと話がややこしくなるので、左上(0,0)に位置を戻して進めます。
2、3、13行目を変更しています。

function _init()
  x=8
  y=8
end
function _update()
  if btn(0) then x-=1 end
  if btn(1) then x+=1 end
  if btn(2) then y-=1 end
  if btn(3) then y+=1 end
end
function _draw()
  cls(0)
  map(0,0,0,0,8,8)
  spr(1,x,y,1,1,false,false)
end

背景の状態を調べる

まずは、「背景の状態を調べる」を作ります。
今ゲーム画面にある背景は、マップエディターでセルにスプライトを一つ一つ置いて作ったものです。そこから、必要な部分を切り取って表示させています。

PICO-8には、マップエディター上のセルを指定して、そのセルに何番のスプライトが置いてあるか、調べる関数(命令)「mget()」があります。
これを使えば、キャラクターがいるのと同じ座標のセルにどんな絵が置いてあるかわかります。

mget(調べるセルのX座標, 調べるセルのY座標)

ここで一つ問題になるのが、マップとキャラクターの座標の単位の違いです。
キャラクターはピクセルで座標を指定していますが、マップは8px四方を一つにしたセルを座標にしています。そのため、キャラクターの座標をもとに、それがどこのセルに当たるのか、計算して変換する必要があります。

celx=flr(x/8)
cely=flr(y/8)

celx、celyという変数に、座標をセルに変換した値を入れる式です。
flr()は、()の中の数字に最も近い整数を返す…つまり、「切り捨て」を行う関数です。
8で割ることで、その座標がどのセルに属するのか、確認することができます。

スプライトのX座標/8セルのX座標(切り捨て)
0/8=0セル0
2/8=0.25セル0
15/8=1.875セル1
46/8=5.75セル5

そして、このセルの座標をもとに、そこに何が置いてあるかを確認します。

celc=mget(celx,cely)

これで、変数celcに、指定した座標にあるスプライトの番号が入ります。
もとのコードに組み込みます。
描画ではないので、これらはfunction _update()の中に入ります。

function _update()
  if btn(0) then x-=1 end
  if btn(1) then x+=1 end
  if btn(2) then y-=1 end
  if btn(3) then y+=1 end
  celx=flr(x/8)
  cely=flr(y/8)
  celc=mget(celx,cely)
end

キャラクターが動いたら、celcの中に背景の情報が入るはずですが、ゲーム画面に変数が表示されるわけではないので、このままでは中身を確認することができません。
そこで、変数の中身を、ゲームのスコアのように画面上に表示させてみます。

print(表示させる文字列,X座標,Y座標,文字色)

print()は、画面にテキストを表示させる関数です。

print(celc,68,2,7)

これは描画処理なので、今度はfunction _draw()に挿入します。
この時点で、コードはこうなります。

function _init()
  x=8
  y=8
end
function _update()
  if btn(0) then x-=1 end
  if btn(1) then x+=1 end
  if btn(2) then y-=1 end
  if btn(3) then y+=1 end
  celx=flr(x/8)
  cely=flr(y/8)
  celc=mget(celx,cely)
end
function _draw()
  cls(0)
  map(0,0,0,0,8,8)
  spr(1,x,y,1,1,false,false)
  print(celc,68,2,7)
end
4-2

マップの横に、キャラクターの移動に合わせて、0,2,3いずれかの番号が表示されました。
マップエディターを思い出すと、黒が0、灰色ブロックが2、オレンジブロックが3です。

が、キャラが背景に重なっても、数字が変わらない場合があります。
いまは、キャラクターの左上の座標のみを調べているだけで、キャラの見た目そのままに背景を調べているわけではないからです。少なくともあと、キャラの右上、左下、右下も判断しないと、背景にキャラが重なったかどうか判断できません。

機能をまとめる

あと3回、さらに変数を追加して四隅を調べるようにしてもいいのですが、そうすると、無駄に変数ばかり増えていってしまいます。

そこで、「指定した座標のマップセルの番号を返す」という独自の機能(function/関数)を新たに作ることにします。

function 関数名(引数,引数,...)
  色々な処理1
  色々な処理2
  色々な処理3
  ...
end

こうすることで、複数の命令を一つにまとめてしまうことができます。
必要な情報は、引数を使って関数に引き渡します。
今回の場合、言葉にすると、

function マップのセルのスプライト番号を知る(スプライトのX座標, スプライトのY座標)
  スプライトのX座標をセルに変換
  スプライトのY座標をセルに変換
  そのセルにあるスプライトの番号を取得
  答えを返す
end

コードにすると、

function get_map_flag(sprx,spry)
  local celx=flr(sprx/8)
  local cely=flr(spry/8)
  local celc=mget(celx,cely)
  return celc
end

「local」は、この関数の中だけで使う変数を用意します。
localをつけずに変数を使うと、プログラム全体から見える変数となり、全体として膨大な数の変数を覚えておかなければならなくなって、管理がややこしいことになってしまいます。
この場合、celx、cely、celcは、この関数を使っている部分だけに存在するため、プログラムの他の部分からは見ることができません。関数が呼び出されるたびに新たに作られるので、中身は常に残りません。

returnは、関数の呼び出し元に、値を返す命令です。この場合は、celcに入った数字を呼び出したところに返します。

この関数を使って、さっきのコードを修正します。

function get_map_flag(sprx,spry)
  local celx=flr(sprx/8)
  local cely=flr(spry/8)
  local celc=mget(celx,cely)
  return celc
end
function _init()
  x=8
  y=8
end
function _update()
  if btn(0) then x-=1 end
  if btn(1) then x+=1 end
  if btn(2) then y-=1 end
  if btn(3) then y+=1 end
  lefttop=get_map_flag(x,y)
end
function _draw()
  cls(0)
  map(0,0,0,0,8,8)
  spr(1,x,y,1,1,false,false)
  print(lefttop,68,2,7)
end

16行目、スプライトの左上のX,Y座標を関数に渡して、そこのマップセルのスプライト番号を「leftop」という変数に入れました。
22行目で、それを画面に表示させています。
これを拡張して、上下左右、それぞれ別の変数に値を取得してみます。
スプライトの四隅の座標は8px四方なので、(X,Y)とすると、次のような数字になります。

左上0,0、右上7,0、左下7,0、右下7,7
function get_map_flag(sprx,spry)
  local celx=flr(sprx/8)
  local cely=flr(spry/8)
  local celc=mget(celx,cely)
  return celc
end
function _init()
  x=8
  y=8
end
function _update()
  if btn(0) then x-=1 end
  if btn(1) then x+=1 end
  if btn(2) then y-=1 end
  if btn(3) then y+=1 end
  lefttop=get_map_flag(x,y)
  righttop=get_map_flag(x+7,y)
  leftbottom=get_map_flag(x,y+7)
  rightbottom=get_map_flag(x+7,y+7)
end
function _draw()
  cls(0)
  map(0,0,0,0,8,8)
  spr(1,x,y,1,1,false,false)
  print(lefttop.."/"..righttop,68,2,7)
  print(leftbottom.."/"..rightbottom,68,8,7)
end
4-f

それぞれ数字が見えるようになりました!

printの部分も少し直しています。
「..」をつかうと、文字列をつないで表示させることができます。固定の文字列を表示させたい場合は、クオート(””)で囲みます。
ここでは、変数名と、””で囲った/をつないで、「左上/右上」「左下/右下」と数字を表示させています。

これで、壁の状態を調べる準備ができました。
次はいよいよ、壁にぶつかって止まる処理をつくります。