LÖVE (Love2D) 入門編:3.キー入力と衝突判定

(2018.1.28. 公開) (2018.8.26. 対象をバージョン 11 系列に変更)

1. キー入力

 画面に絵を出して動かせるようになったので、次はそれをキー入力でコントロールしてみる。前ページで書いたプログラムを改造して、四角が自動的に動くのではなく、矢印キーを押している間その方向に動かすようにする。

 「現在あるキーが押されているかどうか」は、love.keyboard.isDown(キーの名前) で判定できる。

  if love.keyboard.isDown("right") then
    -- 右矢印キーが押されている
  elseif love.keyboard.isDown("left") then
    -- 左矢印キーが押されている
  elseif love.keyboard.isDown("up") then
    -- 上矢印キーが押されている
  elseif love.keyboard.isDown("down") then
    -- 下矢印キーが押されている
  end

 この判定を love.update() の中に入れればよい。移動速度を speed という変数に入れておき、左右の矢印キーが押されたら x 座標を更新し、上下の矢印キーが押されたら y 座標を更新する。なお、LÖVE の座標系は、数学で一般的な xy 座標系とは異なり、「y 軸は下方向が正」であることに注意。

 コーディング例。

-- サンプルプログラム 3-01 main.lua
-- 最初に1回だけ呼び出されるコールバック関数
function love.load()
  width = love.graphics.getWidth()    -- 現在の画面の横幅
  height = love.graphics.getHeight()  -- 現在の画面の高さ
  love.graphics.setBackgroundColor(0, 0.24, 0)  -- 背景を濃い緑色に
  rsize = width / 25                  -- 四角形のサイズ
  x = 0                               -- x 座標を0にする
  y = 0                               -- y 座標を0にする
  speed = (width - rsize) / 3         -- 移動速度:1秒間に横幅の1/3
end

-- 定期的に呼び出されるコールバック関数
function love.update(dt)
  local dx, dy, xx, yy  -- これらの変数はこの関数の中でしか使わない
  dx = 0
  dy = 0
  if love.keyboard.isDown("right") then    -- 右矢印キーが押されている
    dx = speed * dt
  elseif love.keyboard.isDown("left") then -- 左矢印キーが押されている
    dx = -speed * dt
  elseif love.keyboard.isDown("up") then   -- 上矢印キーが押されている
    dy = -speed * dt
  elseif love.keyboard.isDown("down") then -- 下矢印キーが押されている
    dy = speed * dt
  end
  -- 新しい位置を計算する
  xx = x + dx
  yy = y + dy
  -- 画面の中に収まっていれば x, y を新しい位置で置き換える
  if xx >= 0 and xx < width - rsize then x = xx end
  if yy >= 0 and yy < height - rsize then y = yy end
end

-- 画面を書き換えるコールバック関数
function love.draw()
  love.graphics.setColor(1, 0.5, 0.5)  -- 淡い赤色
  love.graphics.rectangle("fill", x, y, rsize, rsize)
    -- 塗りつぶした四角形を描く
end

2. 衝突判定

 2つの物体が衝突するのを判定するには、いろいろな方法が考えられる。一番単純なのは、x 座標の差・y 座標の差がともにある限界より小さくなった時に「衝突」と判定する方法(図1)。また、中心間の距離がある値より小さくなった時に「衝突」と判定する方法もある(図2)。物体が四角い場合は図1、丸い場合は図2の方が自然な判定になる。

 「相手」キャラを円で表して、図1の方法で判定してみたのが、以下のコード。相手キャラは一定速度で動き、壁に当たったら跳ね返ることにした。

-- サンプルプログラム 3-02 main.lua
-- 最初に1回だけ呼び出されるコールバック関数
function love.load()
  width = love.graphics.getWidth()    -- 現在の画面の横幅
  height = love.graphics.getHeight()  -- 現在の画面の高さ
  love.graphics.setBackgroundColor(0, 0.24, 0)  -- 背景を濃い緑色に
  rsize = width / 25                  -- 四角形のサイズ
  csize = width / 30                  -- 相手キャラのサイズ
  x = 0                               -- x 座標を0にする
  y = 0                               -- y 座標を0にする
  cx = width / 2                      -- 相手キャラの x 座標を画面幅の1/2にする
  cy = height / 2                     -- 相手キャラの y 座標を画面高さの1/2にする
  collide = 0                         -- 衝突しているか
  speed = (width - rsize) / 3         -- 移動速度:1秒間に横幅の1/3
  cdx = speed * 0.5                   -- 相手キャラの x 方向の移動速度
  cdy = speed * 0.5                   -- 相手キャラの y 方向の移動速度
end
  
-- 定期的に呼び出されるコールバック関数
function love.update(dt)
  local dx, dy, xx, yy, w, dw  -- これらの変数はこの関数の中でしか使わない
  dx = 0
  dy = 0
  if love.keyboard.isDown("right") then    -- 右矢印キーが押されている
    dx = speed * dt
  elseif love.keyboard.isDown("left") then -- 左矢印キーが押されている
    dx = -speed * dt
  elseif love.keyboard.isDown("up") then   -- 上矢印キーが押されている
    dy = -speed * dt
  elseif love.keyboard.isDown("down") then -- 下矢印キーが押されている
    dy = speed * dt
  end
  -- 新しい位置を計算する
  xx = x + dx
  yy = y + dy
  -- 画面の中に収まっていれば x, y を新しい位置で置き換える
  if xx >= 0 and xx < width - rsize then x = xx end
  if yy >= 0 and yy < height - rsize then y = yy end
  -- 相手キャラの位置を計算する
  cx = cx + cdx * dt
  cy = cy + cdy * dt
  if cx < 0 or cx >= width - csize then
    cdx = -cdx
    cx = cx + 2 * cdx * dt
  end
  if cy < 0 or cy >= height - csize then
    cdy = -cdy
    cy = cy + 2 * cdy * dt
  end
  -- 衝突したか?
  w = (rsize + csize) / 2
  dw = (rsize - csize) / 2
  collide = 0
  if math.abs(x - cx + dw) < w and math.abs(y - cy + dw) < w then
    collide = 1
  end
end
  
-- 画面を書き換えるコールバック関数
function love.draw()
  love.graphics.setColor(1, 0.5, 0.5)  -- 淡い赤色
  love.graphics.rectangle("fill", x, y, rsize, rsize)
    -- 塗りつぶした四角形を描く
  if collide == 0 then
    love.graphics.setColor(0, 1, 1)  -- 衝突したら濃い黄色
  else
    love.graphics.setColor(1, 1, 0)  -- 衝突しなければ水色
  end
  love.graphics.circle("fill", cx + csize / 2, cy + csize / 2, csize / 2)
end

 衝突判定のコードで、「x 座標の差が限界よりも小さくなったか」というのを math.abs(x - cx + dw) < w で判定していることに注意。図1の「x 座標」は「物体の中心の x 座標」を意味している。「四角形の中心」の x 座標は x + rsize / 2, 「円の中心」の x 座標は cx + csize / 2 なので、これらの差は (x + rsize / 2) - (cx + csize / 2) となる。これを x - cx + (rsize - csize) / 2 と変形し、(rsize - csize) / 2dw と置き換えて、さらに負の値である可能性を考慮して絶対値 math.abs() をとると、上の式が得られる。

 こんな風になりました。的が小さくてなかなか当たらない(苦笑)

目次