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

5. 壁でとめる

いよいよ、壁に当たると止まるようにしていきます。ここまでできれば、簡単なゲームならもう作れますね。

4. 背景をしらべる」の続きです。
前回、キャラクターの移動先の背景に何があるかを調べられるようになりました。
次は、その背景に対しての動作を設定します。

今回の完成コード

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

function collition(x,y)
  return get_map_flag(x+1,y+2) or get_map_flag(x+6,y+2) or get_map_flag(x+1,y+7) or get_map_flag(x+6,y+7)
end

function _init()
  x=8
  y=8
end

function _update()
  local prex = x
  local prey = y
  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
  if collition(x,y) then
    x=prex
    y=prey
  end
end

function _draw()
  cls(0)
  map(0,0,0,0,8,8)
  spr(1,x,y,1,1,false,false)
end

スプライト番号それぞれに対して「進めないようにする」という処理を書くと、一つ二つならともかく、何十種類もパーツが増えた場合に、無駄な記述であふれかえります。
そこで、「この種類のスプライトの時にはこうする」と、ひとまとめにして管理する方法があります。

フラグを立てろ!

スプライトには、それぞれ0〜7、合計8つの「フラグ」のオンオフを設定できます。
スプライトエディターを開いてスプライトを選択すると、それぞれカラーパレットの下に8つの丸いインジケーターがあります。これがフラグです。
左から0〜7、赤く点灯させると、その番号のフラグが立った状態になります。

今回は、壁になるスプライト2と3について、0番のフラグをオンにしました。

「背景にあるのが0番のフラグが立っているスプライトの時には進ませない」
という風にコードを書けば、先に何番のスプライトがこようが、フラグの有無を見ればいいだけなので、管理がとても楽になります。

ちなみに、「3. 背景をつくる」で省略した、map()の「レイヤー」には、このフラグを指定します。指定したフラグのマップのみ描画する…ようです。
まだ使ったことないですが、キャラクターが背景の後ろに隠れたりする場面を作るときなんかに使えそうな気がします。

フラグをしらべる

フラグをしらべる際に使うのが、fget()です。

fget( スプライト番号, フラグ番号 )

指定したフラグが立っていた場合にtrue、そうではない場合にfalseを返します。
4. 背景をしらべる」で作ったコードに組み込んでみます。

function get_map_flag(sprx,spry)
  local celx=flr(sprx/8)
  local cely=flr(spry/8)
  local celc=mget(celx,cely)
  return fget(celc,0)
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("lt:"..(lefttop and "true" or "false"),2,68,7)
  print("rt:"..(righttop and "true" or "false"),40,68,7)
  print("rb:"..(leftbottom and "true" or "false"),2,76,7)
  print("rb:"..(rightbottom and "true" or "false"),40,76,7)
end

5行目、スプライト番号を返していたところを、fget()を使って、そのスプライトのフラグ0の状態を返すように変更しました。
それに合わせて、28〜31行目の状態を表示する箇所も書き換えています。

PICO-8は、真偽値(ブーリアン/trueかfalseかの状態)をそのままprintで表示できません。
printで出すためには、

(変数名 and "true" or "false")

…と、書く必要があります。意外に落とし穴です。

フラグの状態確認

フラグの状態が確認できました!
falseの場所は、スプライト番号0の場所ですね。

移動をとめる

フラグの状態が確認できたので、trueの状態の背景に触った場合、進むのをやめるという処理を追加します。
流れとしては、

  1. 今の位置の座標を覚えておく
  2. 入力された方向とおりに座標を動かす(まだ絵は動かさない)
  3. 動かした座標の位置の背景のフラグを確認する
  4. フラグがtrueなら、変更した座標を元の数値に戻す
  5. 実際に絵を動かす

これであれば、上下左右斜め8方向についての処理を書かなくても、移動した先を見る、という一つだけでことが済んで管理がラクです。

4までは描画前の処理なので、これもfunction _update()に入れます。

function _update()
  local prex = x
  local prey = y
  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
  if collition(x,y) then
    x=prex
    y=prey
  end
end

localで、_update()の中だけで使うローカル変数として、いまのX座標とY座標を記録しておくprex、preyを作成しました。
collision(x,y)というのがありますが、これは、上記のステップで言うところの3にあたる関数です。まだ作っていないので、これも一緒に入力します。

function collition(x,y)
  return get_map_flag(x,y) or
  get_map_flag(x+7,y) or
  get_map_flag(x,y+7) or
  get_map_flag(x+7,y+7)
end

collision()にスプライトのX座標とY座標を渡すと、1セル(8×8px)の大きさのスプライトとして、get_map_flag()関数を使って、その四隅にあたる背景のフラグをしらべます。

スプライトの四隅の座標
get_map_flag(x,y)左上
get_map_flag(x+7,y)右上
get_map_flag(x,y+7)左下
get_map_flag(x+7,y+7)右下

これらそれぞれを「 or 」でつないでいます。
orでつなぐと、いずれかの条件がtrueになった場合は、結果としてtrueが返ります。
全部の条件がtrueのときだけ結果もtrueにした場合は、orの代わりに「 and 」を使います。
ここまでのコードをまとめるとこうなります。

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

function collition(x,y)
  return get_map_flag(x,y) or
  get_map_flag(x+7,y) or
  get_map_flag(x,y+7) or
  get_map_flag(x+7,y+7)
end

function _init()
  x=8
  y=8
end

function _update()
  local prex = x
  local prey = y
  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
  if collition(x,y) then
    x=prex
    y=prey
  end
end

function _draw()
  cls(0)
  map(0,0,0,0,8,8)
  spr(1,x,y,1,1,false,false)
  print("lt:"..(lefttop and "true" or "false"),2,68,7)
  print("rt:"..(righttop and "true" or "false"),40,68,7)
  print("rb:"..(leftbottom and "true" or "false"),2,76,7)
  print("rb:"..(rightbottom and "true" or "false"),40,76,7)
end
壁衝突確認

壁に当たると進めなくなりました!
フラグの状態が常にfalseですが、trueの場合には、描画前に座標をfalseの状態になるよう戻しているためです。

さて、実際に触ってみるとわかるんですが、壁の当たり判定ががっちりキャラクターの四隅なので、隙間を通るのがシビアすぎて非常にストレスが溜まります。
そこで、少し判定に「遊び」を設けてみます。

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

function collition(x,y)
  return get_map_flag(x+1,y+2) or
  get_map_flag(x+6,y+2) or
  get_map_flag(x+1,y+7) or
  get_map_flag(x+6,y+7)
end

function _init()
  x=8
  y=8
end

function _update()
  local prex = x
  local prey = y
  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
  if collition(x,y) then
    x=prex
    y=prey
  end
end

function _draw()
  cls(0)
  map(0,0,0,0,8,8)
  spr(1,x,y,1,1,false,false)
  print("lt:"..(lefttop and "true" or "false"),2,68,7)
  print("rt:"..(righttop and "true" or "false"),40,68,7)
  print("rb:"..(leftbottom and "true" or "false"),2,76,7)
  print("rb:"..(rightbottom and "true" or "false"),40,76,7)
end
判定に遊びを追加
遊びの判定位置

判定位置を、少し内側に変更しました。
これで、狭い場所にも入りやすくなりました。

マップを中央に持ってくる

わかりやすくするために、いったん背景を画面の左上に持ってきてしまいましたが、最後にこれをまた真ん中に移動させたいと思います。

今回の背景は、8×8セル(64×64ピクセル)の大きさです。
PICO-8の画面サイズは128×128ピクセルなので、表示場所をX,Yそれぞれ32にすれば、真ん中に来ました。
どこにそれを追加すればいいでしょうか?

function _draw()
  cls(0)
  map(0,0,32,32,8,8)
  spr(1,x,y,1,1,false,false)
  print("lt:"..(lefttop and "true" or "false"),2,2,7)
  print("rt:"..(righttop and "true" or "false"),40,2,7)
  print("rb:"..(leftbottom and "true" or "false"),2,8,7)
  print("rb:"..(rightbottom and "true" or "false"),40,8,7)
end

とりあえず、マップの表示位置をそれぞれ+32pxしてみました。キャラはそのままです。
絵にかぶってしまうので、フラグの状態の表示位置は左上に移動させました。

見えない壁に当たる

動かしてみるとおかしい…キャラクターが、見えない壁に阻まれて動けません。

うっかりすると忘れがちなのですが、スプライトが移動先の判定にみているのは、あくまでも、マップエディター上の座標です。見えているゲーム画面に出ている背景を見ているわけではありません
自分は、これに気がつくのにずいぶん時間がかかりました(むしろこれを書きたいがために、この記事を作りました!)。

マップエディターと表示画面の関係

ではどうすればいいのか。
キャラクターの表示位置も、背景に合わせてずらしてやればオッケーです。

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

function collition(x,y)
  return get_map_flag(x+1,y+2) or
  get_map_flag(x+6,y+2) or
  get_map_flag(x+1,y+7) or
  get_map_flag(x+6,y+7)
end

function _init()
  x=8
  y=8
end

function _update()
  local prex = x
  local prey = y
  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
  if collition(x,y) then
    x=prex
    y=prey
  end
end

function _draw()
  cls(0)
  map(0,0,32,32,8,8)
  spr(1,x+32,y+32,1,1,false,false)
  print("lt:"..(lefttop and "true" or "false"),2,2,7)
  print("rt:"..(righttop and "true" or "false"),40,2,7)
  print("rb:"..(leftbottom and "true" or "false"),2,8,7)
  print("rb:"..(rightbottom and "true" or "false"),40,8,7)
end

それっぽくなってきました!
次はいよいよ、キャラクターのアニメーションと、スクロールするステージを作ってみたいと思います。
ここらから、楽しさがさらに倍々になってきますよ。