Tutorial:Efficient Tile-based Scrolling (日本語)
この教本では、より効率的なタイル・ベースのスクローリングに対する SpriteBatch クラスの手ほどきです。基本的なタイル・ベースのスクローリングの教本については、タイル・ベースのスクロールを参照してください。
Contents
Quads および SpriteBatch
love.graphics.draw メソッドは Quad により指定された画像を部分的に描画することができます。 Quad が単一画像の異なる部分から描画される場合は、SpriteBatch の使用およびフレームごとに Quad を変更しないことでシステムをより効率的にすることができます。
SpriteBatch の作成時に、 Quad ("タイル") が扱う画像および Quad へ追加する最大数を指定する必要があります。この場合は、画面上で可視化されるタイルの最大数です。
tilesetBatch = love.graphics.newSpriteBatch(tilesetImage, tilesDisplayWidth * tilesDisplayHeight)
マップの初期化
下記により地図を初期化します。説明に関してはタイル・ベースのスクロールを参照してください。
function love.load()
mapWidth = 60
mapHeight = 40
map = {}
for x=1,mapWidth do
map[x] = {}
for y=1,mapHeight do
map[x][y] = love.math.random(0,3)
end
end
mapX = 1
mapY = 1
tilesDisplayWidth = 26
tilesDisplayHeight = 20
zoomX = 1
zoomY = 1
end
SpriteBatch へのタイルマップ追加
次に、タイルセットを読み込み、 Quad に対して使用したいタイルを作成して、タイルを SpriteBatch オブジェクトを作成します。用例には、 http://silveiraneto.net/... にて配布されているフリーのタイルセットを使用します。
function love.load()
... -- マップの初期化
tilesetImage = love.graphics.newImage( "tileset.png" )
tilesetImage:setFilter("nearest", "linear") -- この "線形フィルタ" はタイルの大きさを変更した場合に発生する一部の人工生成物を削除します。
tileSize = 32
-- 芝生
tileQuads[0] = love.graphics.newQuad(0 * tileSize, 20 * tileSize, tileSize, tileSize,
tilesetImage:getWidth(), tilesetImage:getHeight())
-- 台所の床タイル
tileQuads[1] = love.graphics.newQuad(2 * tileSize, 0 * tileSize, tileSize, tileSize,
tilesetImage:getWidth(), tilesetImage:getHeight())
-- 床張り
tileQuads[2] = love.graphics.newQuad(4 * tileSize, 0 * tileSize, tileSize, tileSize,
tilesetImage:getWidth(), tilesetImage:getHeight())
-- 赤い絨毯の中間
tileQuads[3] = love.graphics.newQuad(3 * tileSize, 9 * tileSize, tileSize, tileSize,
tilesetImage:getWidth(), tilesetImage:getHeight())
tilesetBatch = love.graphics.newSpriteBatch(tilesetImage, tilesDisplayWidth * tilesDisplayHeight)
end
SpriteBatch へ現在可視化されるタイルのみを追加したいとします。これを行うには、タイルセットを更新する関数を作成して、マップのフォーカスを変更するたびに呼び出します。
function updateTilesetBatch()
tilesetBatch:clear()
for x=0, tilesDisplayWidth-1 do
for y=0, tilesDisplayHeight-1 do
tilesetBatch:add(tileQuads[map[x+mapX][y+mapY]], x*tileSize, y*tileSize)
end
end
tilesetBatch:flush()
end
最後に、 SpriteBatch を描画するためにそれを love.graphics.draw
へ送信します。
function love.draw()
love.graphics.draw(tilesetBatch)
end
個別の移動
マップの中を移動することは タイル・ベースのスクロールと同様の方法で実現します。キーが押されたり、それに従ってマップを更新するかどうかを確認します。必ず SpriteBatch の更新に関しても忘れず行う必要があります。
-- マップの移動において重要な関数
function moveMap(dx, dy)
oldMapX = mapX
oldMapY = mapY
mapX = math.max(math.min(mapX + dx, mapWidth - tilesDisplayWidth), 1)
mapY = math.max(math.min(mapY + dy, mapHeight - tilesDisplayHeight), 1)
-- 実際に移動した場合のみ更新します。
if math.floor(mapX) ~= math.floor(oldMapX) or math.floor(mapY) ~= math.floor(oldMapY) then
updateTilesetBatch()
end
end
function love.keypressed(key)
if key == "up" then
moveMap(0, -1)
end
if key == "down" then
moveMap(0, 1)
end
if key == "left" then
moveMap(-1, 0)
end
if key == "right" then
moveMap(1, 0)
end
end
連続的な移動
mapX および mapY に非整数値を利用することで移動を少し改善します。 SpriteBatch への Quad 追加時に、整数部分のみ考慮を行う一方で描画では端数部分を扱うために SpriteBatch のシフトを行うことがあります。 love.keypressed コールバックを love.update へ置き換えることで少ない手順にてマップを移動します。
function love.update(dt)
if love.keyboard.isDown("up") then
moveMap(0, -0.2 * tileSize * dt)
end
if love.keyboard.isDown("down") then
moveMap(0, 0.2 * tileSize * dt)
end
if love.keyboard.isDown("left") then
moveMap(-0.2 * tileSize * dt, 0)
end
if love.keyboard.isDown("right") then
moveMap(0.2 * tileSize * dt, 0)
end
end
SpriteBatch の更新へ floor を追加します。
function updateTilesetBatch()
tilesetBatch:clear()
for x=0, tilesDisplayWidth-1 do
for y=0, tilesDisplayHeight-1 do
tilesetBatch:add(tileQuads[map[x+math.floor(mapX)][y+math.floor(mapY)]],
x*tileSize/2, y*tileSize/2)
end
end
tilesetBatch:flush()
end
最後に、端数部分により SpriteBatch をシフトします。
function love.draw()
love.graphics.draw(tilesetBatch,
math.floor(-(mapX%1)*tileSize), math.floor(-(mapY%1)*tileSize))
love.graphics.print("FPS: "..love.timer.getFPS(), 10, 20)
end
まとめ
このコードにzoomX および zoomY 変数を挿入します。例えば 16x16 のタイルは 32x32 のタイルとして描画されます。
local map -- タイルデータの格納
local mapWidth, mapHeight -- タイルの幅と高さ
local mapX, mapY -- タイルにおける視点 x,y です。3.25 といった断片的な値です。
local tilesDisplayWidth, tilesDisplayHeight -- 表示するタイルの本数
local zoomX, zoomY
local tilesetImage
local tileSize -- ピクセル単位でのタイルの大きさ
local tileQuads = {} -- 異なるタイルに対して使用されたタイルの部品
local tilesetSprite
function love.load()
setupMap()
setupMapView()
setupTileset()
love.graphics.setFont(12)
end
function setupMap()
mapWidth = 60
mapHeight = 40
map = {}
for x=1,mapWidth do
map[x] = {}
for y=1,mapHeight do
map[x][y] = love.math.random(0,3)
end
end
end
function setupMapView()
mapX = 1
mapY = 1
tilesDisplayWidth = 26
tilesDisplayHeight = 20
zoomX = 1
zoomY = 1
end
function setupTileset()
tilesetImage = love.graphics.newImage( "tileset.png" )
tilesetImage:setFilter("nearest", "linear") -- この "線形フィルタ" はタイルの大きさを変更した場合に発生する一部の人工生成物を削除します。
tileSize = 32
tileSize = 32
-- 芝生
tileQuads[0] = love.graphics.newQuad(0 * tileSize, 20 * tileSize, tileSize, tileSize,
tilesetImage:getWidth(), tilesetImage:getHeight())
-- 台所の床タイル
tileQuads[1] = love.graphics.newQuad(2 * tileSize, 0 * tileSize, tileSize, tileSize,
tilesetImage:getWidth(), tilesetImage:getHeight())
-- 床張り
tileQuads[2] = love.graphics.newQuad(4 * tileSize, 0 * tileSize, tileSize, tileSize,
tilesetImage:getWidth(), tilesetImage:getHeight())
-- 赤い絨毯の中間
tileQuads[3] = love.graphics.newQuad(3 * tileSize, 9 * tileSize, tileSize, tileSize,
tilesetImage:getWidth(), tilesetImage:getHeight())
tilesetBatch = love.graphics.newSpriteBatch(tilesetImage, tilesDisplayWidth * tilesDisplayHeight)
updateTilesetBatch()
end
function updateTilesetBatch()
tilesetBatch:clear()
for x=0, tilesDisplayWidth-1 do
for y=0, tilesDisplayHeight-1 do
tilesetBatch:add(tileQuads[map[x+math.floor(mapX)][y+math.floor(mapY)]],
x*tileSize, y*tileSize)
end
end
tilesetBatch:flush()
end
-- マップの移動において重要な関数
function moveMap(dx, dy)
oldMapX = mapX
oldMapY = mapY
mapX = math.max(math.min(mapX + dx, mapWidth - tilesDisplayWidth), 1)
mapY = math.max(math.min(mapY + dy, mapHeight - tilesDisplayHeight), 1)
-- 実際に移動した場合のみ更新します。
if math.floor(mapX) ~= math.floor(oldMapX) or math.floor(mapY) ~= math.floor(oldMapY) then
updateTilesetBatch()
end
end
function love.update(dt)
if love.keyboard.isDown("up") then
moveMap(0, -0.2 * tileSize * dt)
end
if love.keyboard.isDown("down") then
moveMap(0, 0.2 * tileSize * dt)
end
if love.keyboard.isDown("left") then
moveMap(-0.2 * tileSize * dt, 0)
end
if love.keyboard.isDown("right") then
moveMap(0.2 * tileSize * dt, 0)
end
end
function love.draw()
love.graphics.draw(tilesetBatch,
math.floor(-zoomX*(mapX%1)*tileSize), math.floor(-zoomY*(mapY%1)*tileSize),
0, zoomX, zoomY)
love.graphics.print("FPS: "..love.timer.getFPS(), 10, 20)
end
そのほかの言語
Dansk –
Deutsch –
English –
Español –
Français –
Indonesia –
Italiano –
Lietuviškai –
Magyar –
Nederlands –
Polski –
Português –
Română –
Slovenský –
Suomi –
Svenska –
Türkçe –
Česky –
Ελληνικά –
Български –
Русский –
Српски –
Українська –
עברית –
ไทย –
日本語 –
正體中文 –
简体中文 –
Tiếng Việt –
한국어
More info