(2018.7.30. 公開) (2018.8.26. 対象をバージョン 11 系列に変更)
シューティングゲームにしても、RPG タイプのゲームにしても、スクロールする背景の上でキャラクタが動く、という設定はよくある。これをやってみましょう。
まず、背景の画像素材を用意する。ここがけっこうハードルが高かったりします。今回は、ぴぽや さんが「ぴぽや倉庫」(http://pipoya.net/sozai/
) で無料公開されているマップ用素材を使いました。
画像素材を並べて背景を作る時、画面サイズが特定されていないとデザインしにくい。そこで、ゲーム画面を固定サイズ(ここでは640x480)に設定する。
PC や Mac の場合は love.window.setMode(640, 480)
でウィンドウの大きさそのものを変更できるのだが、ラズパイで X window なしで動かした場合は、この命令が効かない。そこで、「画面サイズの設定」で紹介したテクニックを使って、ゲーム画面を 640x480 に限定する。ゲーム画面の外は、灰色の枠で埋めておく。
-- サンプルプログラム 11-01 main.lua
function set_screen_size()
width = 640 -- ゲーム画面の横幅
height = 480 -- ゲーム画面の高さ
love.window.setMode(width, height) -- これが効くなら問題なし
swidth = love.graphics.getWidth() -- 実画面の横幅
sheight = love.graphics.getHeight() -- 実画面の高さ
transx, transy = 0, 0
scale = 1
if swidth ~= width or sheight ~= height then
-- setMode が効いてない場合
if swidth / sheight > width / height then -- 実画面の方が横長
scale = sheight / height -- 縦方向で倍率を決める
transx = math.floor((swidth - width * scale) / 2) -- 空白の幅
transy = 0
else
scale = swidth / width -- 横方向で倍率を決める
transx = 0
transy = math.floor((sheight - height * scale) / 2) -- 空白の高さ
end
end
love.graphics.setBackgroundColor(0.82, 0.82, 0.82) -- ゲーム画面の外は灰色の枠
end
function love.load()
set_screen_size()
end
function love.draw()
love.graphics.translate(transx, transy) -- 原点移動
love.graphics.scale(scale, scale) -- 拡大率を設定
-- 画面サイズを (width,height) と思って描画する
love.graphics.setColor(1, 1, 1)
love.graphics.rectangle("fill", 0, 0, width, height) -- ゲーム画面を白く塗る
end
今回使う背景画像は、32x32 のタイルを並べて背景を作るようにデザインされている。これを画面に表示するには、「画像の一部を使う」で紹介した Quad
を使うのがよい。まずはシンプルに、草原のバックグラウンドに木が並んでいる背景を作ってみた。背景画像の中で、「草原」は (0, 0) の位置に、「木」は (64, 32) の位置にあるので、そこから Quad
を作っておき、love.draw()
の中で必要な位置に描画する。
-- サンプルプログラム 11-02 main.lua
-- pipo-map001.png が必要
-- function set_screen_size() 〜 end までは 11-01 と同じ
function love.load()
set_screen_size()
-- マップ(0: 草原、1: 木)
map = {
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,
0,0,0,0,1,1,1,1,1,1,1,0,0,0,1,0,0,0,1,0,
0,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,
0,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,1,
0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,
0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,
0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,
0,0,0,1,0,0,0,1,0,0,0,1,1,1,1,1,0,0,0,0,
0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,
0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,
0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }
atlas = love.graphics.newImage("pipo-map001.png") -- テクスチャアトラス
wid0, high0 = atlas:getDimensions()
quad1 = love.graphics.newQuad(0, 0, 32, 32, wid0, high0) -- 草原
quad2 = love.graphics.newQuad(64, 32, 32, 32, wid0, high0) -- 木
end
function love.draw()
love.graphics.translate(transx, transy) -- 原点移動
love.graphics.scale(scale, scale) -- 拡大率を設定
-- 画面サイズを (width,height) と思って描画する
love.graphics.setColor(1, 1, 1)
love.graphics.rectangle("fill", 0, 0, width, height) -- ゲーム画面を白く塗る
for i = 1, 20 do
for j = 1, 15 do
local x, y = (i - 1) * 32, (j - 1) * 32
love.graphics.draw(atlas, quad1, x, y) -- 草原のタイルを描く
if map[(j - 1) * 20 + i] == 1 then
love.graphics.draw(atlas, quad2, x, y) -- 木のタイルを描く
end
end
end
end
上のプログラムでは、背景を描くために、love.draw()
の中でループを回して画像を1枚ずつ描いている。これらの画像は、すべて1つのテクスチャアトラスから取られている。LÖVE には、このように「1つの画像の一部を何度も繰り返して描く」ことに特化した仕組みがある。これを SpriteBatch
(スプライトバッチ)と呼ぶ。
1つのスプライトバッチには、画像1つと、その画像の「どの部分」を「どこに描くか」という情報が含まれている。
上のプログラムで描いた「背景」をスプライトバッチで描くには、love.load()
の中で、次のようにすればよい。
atlas = love.graphics.newImage("pipo-map001.png") -- テクスチャアトラス
batch = love.graphics.newSpriteBatch(atlas) -- スプライトバッチ
wid0, high0 = atlas:getDimensions()
quad1 = love.graphics.newQuad(0, 0, 32, 32, wid0, high0) -- 草原
quad2 = love.graphics.newQuad(64, 32, 32, 32, wid0, high0) -- 木
for i = 1, 20 do
for j = 1, 15 do
local x, y = (i - 1) * 32, (j - 1) * 32
batch:add(quad1, x, y) -- 草原のタイル
if map[(j - 1) * 20 + i] == 1 then
batch:add(quad2, x, y) -- 木のタイル
end
end
end
love.graphics.draw()
でスプライトバッチを一斉に描画できる。
love.graphics.draw(batch)
プログラム例は次の通り。
-- サンプルプログラム 11-03 main.lua
-- pipo-map001.png が必要
-- function set_screen_size() 〜 end までは 11-01 と同じ
function love.load()
set_screen_size()
-- マップ(0: 草原、1: 木)
map = {
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,
0,0,0,0,1,1,1,1,1,1,1,0,0,0,1,0,0,0,1,0,
0,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,
0,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,1,
0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,
0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,
0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,
0,0,0,1,0,0,0,1,0,0,0,1,1,1,1,1,0,0,0,0,
0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,
0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,
0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }
atlas = love.graphics.newImage("pipo-map001.png") -- テクスチャアトラス
batch = love.graphics.newSpriteBatch(atlas) -- スプライトバッチ
wid0, high0 = atlas:getDimensions()
quad1 = love.graphics.newQuad(0, 0, 32, 32, wid0, high0) -- 草原
quad2 = love.graphics.newQuad(64, 32, 32, 32, wid0, high0) -- 木
for i = 1, 20 do
for j = 1, 15 do
local x, y = (i - 1) * 32, (j - 1) * 32
batch:add(quad1, x, y) -- 草原のタイル
if map[(j - 1) * 20 + i] == 1 then
batch:add(quad2, x, y) -- 木のタイル
end
end
end
end
function love.draw()
love.graphics.translate(transx, transy) -- 原点移動
love.graphics.scale(scale, scale) -- 拡大率を設定
-- 画面サイズを (width,height) と思って描画する
love.graphics.setColor(1, 1, 1)
love.graphics.rectangle("fill", 0, 0, width, height) -- ゲーム画面を白く塗る
love.graphics.draw(batch) -- 一気に描画
end
プログラム例 11-02
(画像を1枚ずつ描画)と 11-03
(スプライトバッチ使用)で、love.draw()
1回の処理にかかる時間を比較してみた。これは大差がつきました。スプライトバッチを活用すべきですね。
機種 (CPU) | 1枚ずつ | スプライトバッチ |
MacBook (2.4 GHz Core 2 Duo) | 1.9 ミリ秒 | 0.077 ミリ秒 |
Raspberry Pi Model A+ (700 MHz ARMv6) | 23 ミリ秒 | 0.26 ミリ秒 |
Raspberry Pi 3 (1.2GHz ARMv7) | 16 ミリ秒 | 0.036 ミリ秒 |
背景をスクロール表示するにはどうすればいいだろうか。スプライトバッチは、描画する時に位置を指定することができる。下のように、2つのスプライトバッチを位置をずらして表示すれば、スクロールを実現することができる。
ただ、ゲーム画面が実画面よりも小さい時は、スプライトバッチの表示がゲーム画面からはみ出さないようにする必要がある。これは、love.graphics.setScissor()
という関数で実現できる。具体的には、love.draw()
の最初に、描画範囲をゲーム画面の中だけに設定し、love.draw()
から抜ける直前に、元に戻す。
function love.draw()
love.graphics.setScissor(transx, transy, width * scale, height * scale) -- 描画範囲を設定
... -- 描画処理
love.graphics.setScissor() -- 描画範囲をリセット
end
これを使ったプログラム例を下に示す。
-- サンプルプログラム 11-04 main.lua
-- pipo-map001.png が必要
-- function set_screen_size() 〜 end までは 11-01 と同じ
function love.load()
set_screen_size()
-- マップ(0: 草原、1: 木)
map = {
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,
0,0,0,0,1,1,1,1,1,1,1,0,0,0,1,0,0,0,1,0,
0,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,
0,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,1,
0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,
0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,
0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,
0,0,0,1,0,0,0,1,0,0,0,1,1,1,1,1,0,0,0,0,
0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,
0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,
0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }
atlas = love.graphics.newImage("pipo-map001.png") -- テクスチャアトラス
batch = love.graphics.newSpriteBatch(atlas) -- スプライトバッチ
wid0, high0 = atlas:getDimensions()
quad1 = love.graphics.newQuad(0, 0, 32, 32, wid0, high0) -- 草原
quad2 = love.graphics.newQuad(64, 32, 32, 32, wid0, high0) -- 木
for i = 1, 20 do
for j = 1, 15 do
local x, y = (i - 1) * 32, (j - 1) * 32
batch:add(quad1, x, y) -- 草原のタイル
if map[(j - 1) * 20 + i] == 1 then
batch:add(quad2, x, y) -- 木のタイル
end
end
end
basex = 0 -- スクロール位置
end
function love.update(dt)
basex = basex + 100 * dt
if basex >= width then basex = basex - width end
end
function love.draw()
love.graphics.setScissor(transx, transy, width * scale, height * scale) -- 描画範囲を設定
love.graphics.translate(transx, transy) -- 原点移動
love.graphics.scale(scale, scale) -- 拡大率を設定
-- 画面サイズを (width,height) と思って描画する
love.graphics.setColor(1, 1, 1)
love.graphics.rectangle("fill", 0, 0, width, height) -- ゲーム画面を白く塗る
love.graphics.draw(batch, -basex, 0) -- 一気に描画
love.graphics.draw(batch, -basex + width, 0) -- 一気に描画
love.graphics.setScissor() -- 描画範囲をリセット
end
キャラクタを重ね書きして、動かしてみましょう。さすがに、草原+木の背景にさかなのキャラクタはおかしいので、「ぴぽや倉庫」さんから飛行艇のキャラクタをいただいてきました。
-- サンプルプログラム 11-05 main.lua
-- pipo-map001.png, pipo-airship02.png が必要
-- function set_screen_size() 〜 end までは 11-01 と同じ
function love.load()
set_screen_size()
-- マップ(0: 草原、1: 木)
map = {
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,
0,0,0,0,1,1,1,1,1,1,1,0,0,0,1,0,0,0,1,0,
0,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,
0,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,1,
0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,
0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,
0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,
0,0,0,1,0,0,0,1,0,0,0,1,1,1,1,1,0,0,0,0,
0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,
0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,
0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }
atlas = love.graphics.newImage("pipo-map001.png") -- テクスチャアトラス
batch = love.graphics.newSpriteBatch(atlas) -- スプライトバッチ
wid0, high0 = atlas:getDimensions()
quad1 = love.graphics.newQuad(0, 0, 32, 32, wid0, high0) -- 草原
quad2 = love.graphics.newQuad(64, 32, 32, 32, wid0, high0) -- 木
for i = 1, 20 do
for j = 1, 15 do
local x, y = (i - 1) * 32, (j - 1) * 32
batch:add(quad1, x, y) -- 草原のタイル
if map[(j - 1) * 20 + i] == 1 then
batch:add(quad2, x, y) -- 木のタイル
end
end
end
basex = 0 -- スクロール位置
ship = love.graphics.newImage("pipo-airship02.png") -- 飛行艇
quad3 = love.graphics.newQuad(0, 64, 32, 32, ship:getWidth(), ship:getHeight())
sx, sy = 0, math.floor(height / 2) - 32 -- 飛行艇の位置
speed = 200
end
function love.update(dt)
basex = basex + 100 * dt
if basex >= width then basex = basex - width end
local dx, dy = 0, 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
sx = sx + dx
sy = sy + dy
if sx < 0 or sx > width - 32 then sx = sx - dx end
if sy < 0 or sy > height - 32 then sy = sy - dy end
end
function love.draw()
love.graphics.setScissor(transx, transy, width * scale, height * scale) -- 描画範囲を設定
love.graphics.translate(transx, transy) -- 原点移動
love.graphics.scale(scale, scale) -- 拡大率を設定
-- 画面サイズを (width,height) と思って描画する
love.graphics.setColor(1, 1, 1)
love.graphics.rectangle("fill", 0, 0, width, height) -- ゲーム画面を白く塗る-
love.graphics.draw(batch, -basex, 0) -- 一気に描画
love.graphics.draw(batch, -basex + width, 0) -- 一気に描画
love.graphics.draw(ship, quad3, sx, sy, 0, 2, 2) -- 飛行艇(2倍拡大)
love.graphics.setScissor() -- 描画範囲をリセット
end
目次