LÖVE (Love2D) 入門編:5.キャラクタを表示する

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

1. PNG ファイルでキャラクタを用意する

 丸とか四角だけでもゲームは作れるけど、やはりキャラクタが表示できた方が面白い。キャラクタを PNG 形式で用意して、使ってみよう。サイズは何でもよい。下の画像は 32x32(2倍拡大で表示しているけど)。

 PNG 画像を読み込むには、love.graphics.newImage() を使う。

fish0 = love.graphics.newImage('fish0.png')

 newImage() の引数 'fish0.png' は、画像のファイル名。この場合、main.lua と同じディレクトリに画像ファイルが置いてあることになる。

 画像ファイルがたくさんある時は、専用のディレクトリを作る方がいいかもしれない。

progname --- main.lua
          |- images --- fish0.png
                     |- fish1.png
                     |- ...

 この時は、もちろんコードは下のようになる。

fish0 = love.graphics.newImage('images/fish0.png')

 注意すべき点は、love.graphics.newImage() は1つの画像について一度だけ呼び出すようにすること。呼び出すたびに画像データ分のメモリを消費するため、何度も呼び出すとメモリ不足を引き起こす。このため、love.graphics.newImage() の呼び出しを love.update()love.draw() の中に書いてはいけない。これらのコールバックは、ゲーム中に何度も繰り返し実行されるからである。

 シンプルなゲームの場合は、love.load() の中で、すべてのキャラクタ画像を読み込んでおくのがよい。

function love.load()
  fish0 = love.graphics.newImage('fish0.png')
  man0 = love.graphics.newImage('man.png')
  ghost0 = love.graphics.newImage('ghost.png')
  ...
end

 なお、キャラクタの「周囲」は透明色にしておくこと。うっかり「白」のままにしておくと、下のように四角い枠がついてしまう。

 キャラクタのデザインって結構難しいんだよね。探せばフリー素材がいろいろ見つかるので、うまく活用しましょう。絵のうまい友達と仲良くなっておくのも一案です。

2. キャラクタを表示する

 上で読み込んだデータは、love.graphics.draw() で表示することができる。コールバックの love.draw() と混同しないように。

love.graphics.draw(fish0, x, y)

 x, y は画面上の位置。この場合、キャラクタの左上の位置がちょうど (x, y) に来るように表示される。

3. キャラクタを回転させる

 love.graphics.draw() には面白い機能がある。オプションの引数をつけることで、画像を回転させたり反転させたりできる。

 まず、回転させてみよう。画像, x座標, y座標 の次に回転角 を指定することで、画像を回転させて表示できる。回転角はラジアンで指定する(180度をπ=3.14159... として角度を測る単位)。45度は 0.785、90度は 1.571 となる。図からわかるように、角度が大きくなると時計回りに回転する。

 残念なのは、左上を中心に回転させるため、回転させるとキャラクタが左にずれてしまうこと。表示位置を合わせようとすると、かなり面倒な計算をしないといけなくなる。

 幸い、love.graphics.draw() には「画像の中心位置」を指定する機能がある。これを使って、32x32 の画像の真ん中を「中心位置」に設定してみる。

love.graphics.draw(fish0, x, y, 0, 1, 1, 16, 16)

 回転角 0 のあとの1, 1 は拡大率(後で説明する)で、そのあとの 16, 16 が「画像の中心位置」。このように指定すると、下のように「画像の中心」の回りに回転させてくれる。ただし、表示位置も「画像の中心」で指定することに注意する。

4. キャラクタを反転させる

 love.graphics.draw() で、画像を反転させることもできる。

 回転角 0 のあとの-1, 1 が x, y 方向の「拡大率」。これを -1 にすると、その方向が反転される。上の例では、x 方向の拡大率を -1 にして、左右を反転している。

 回転の時と同じく、左上を基準にして左右を反転させると、画像が左に寄ってしまう。これを防ぐには、「画像の中心位置」を指定して、下のようにすればよい。

 回転と反転を同時に指定したらどうなるのでしょうか。例えば、先ほどのこの状態で、x 方向の拡大率を -1 にしたらどうなるか。「回転してから反転」するなら、右上を向くことになるし、「反転してから回転」するなら、右下を向くことになる。

 正解はこちら。つまり、「反転してから回転」が正しい。

 拡大縮小・反転・回転が組み合わさっている時は、どういう姿になるのか、考えてもなかなかわかりにくい。こういう時は、実験して確かめる方が早道かもしれない。

5. 拡大・縮小・斜め変形

 拡大率は、もちろん±1である必要はないし、x, y 方向で異なる値でも構わない。

 また、「画像の中心位置」の後ろにさらに数値を置くと、「斜め変形」を指定することができる。

 斜め変形は少しわかりにくい。下のような変換である。x, y 方向を同時に指定すると混乱するので、どちらか一方だけ指定して、あとは拡大・縮小と回転の組み合わせで欲しい形に変形するとよい。

 デモを作ってみました。

-- サンプルプログラム 5-01 main.lua
-- fish0.png が必要
-- 最初に1回だけ呼び出されるコールバック関数
function love.load()
  width = love.graphics.getWidth()            -- 画面の横幅
  height = love.graphics.getHeight()          -- 画面の高さ
  love.graphics.setBackgroundColor(0, 0.24, 0)  -- 背景を濃い緑色に
  fish0 = love.graphics.newImage('fish0.png') -- 画像を読み込む
  stime = love.timer.getTime()                -- スタート時刻を記録
  sx = 5      -- x方向の倍率
  sy = 5      -- y方向の倍率
  rot = 0     -- 回転角
  kx = 0      -- x方向の斜め変形
  ky = 0      -- y方向の斜め変形
end

--  [0, 1] をシグモイド型に変形する関数
function sigmoid(x, p)
  local t = x
  if x > 0.5 then t = 1 - t end
  t = math.pow(t * 2, p) / 2
  if x > 0.5 then t = 1 - t end
  return t
end

  -- 定期的に呼び出されるコールバック関数
function love.update(dt)
  local t = love.timer.getTime() - stime    -- スタート時刻からの経過時間
  if t < 1 then
    rot = 0
  elseif t < 13 then
    -- 時計回り、速くなって遅くなって 51回転半(51.5*2π) で止まる
    rot = sigmoid((t - 1) / 12, 4) * 103 * 3.1416
  elseif t < 16 then
    -- 反時計回り、ゆっくり1回転半(1.5*2π)
    rot = (1 - sigmoid((t - 13) / 3, 2)) * 3 * 3.1416
  elseif t < 18 then
    -- 小さくなる
    sx = 5 * math.cos((t - 16) / 4 * 3.1415927)
    sy = math.abs(sx)
  elseif t < 21 then
    -- 左右を反転して大きくなって画面からはみ出す
    sx = -((t - 18) * 3 + 80 * math.pow((t - 18) / 3, 4))
    sy = math.abs(sx)
  elseif t < 24 then
    -- 左右を元に戻して元の大きさに戻る
    sx = (5 + (24 - t) * 4 / 3 + 80 * math.pow((24 - t) / 3, 4))
    sy = sx
  elseif t < 25 then
    sx = 5
    sy = 5
  elseif t < 30 then
    -- 横方向拡大・縮小
    sx = 5 * math.pow(4, math.sin((t - 25) * 3.1415927 * 2 / 5))
    sy = 5
  elseif t < 31 then
    sx = 5
    sy = 5
  elseif t < 36 then
    -- 縦方向拡大・縮小
    sx = 5
    sy = 5 * math.pow(4, math.sin((t - 31) * 3.1415927 * 2 / 5))
  elseif t < 37 then
    sx = 5
    sy = 5
  elseif t < 42 then
    -- 横方向斜め変形
    kx = 5 * math.sin((t - 37) * 3.1415927 * 2 / 5)
  elseif t < 43 then
    kx = 0
  elseif t < 48 then
    -- 縦方向斜め変形
    ky = 5 * math.sin((t - 43) * 3.1415927 * 2 / 5)
  else
    ky = 0
  end
  love.timer.sleep(0.01)
end
  
  -- 画面を書き換えるコールバック関数
function love.draw()
  local x, y
  x = width / 2
  y = height / 2
  love.graphics.draw(fish0, x, y, rot, sx, sy, 16, 16, kx, ky)
end

目次