2017/12/23(土) [n年前の日記]
#1 [love2d] love2d + stiライブラリでTiledで作ったタイルマップのオブジェクトレイヤー情報を取得
レトロ風の2Dゲームを作る際、えてして背景(BG、Background)は、タイル状の画像を並べたマップ(タイルマップ)で表現したりするわけだけど。
そういったタイルマップの作成ツールとして、 _Tiledマップエディタ があり。例えば love2d も、 _sti (Simple Tiled Implementation) というライブラリを使わせてもらうことで、Tiled で作ったタイルマップを簡単に表示できたりするわけで。
で。単に表示するだけなら話はこれで終わりなのだけど。せっかくだから、Tiled で作ったタイルマップに敵発生用の情報も含めておいて、それを読み取って使いたいものだよなと。でも、どうすればそんなことができるのだろう。
Tiled は、色々な種類のレイヤーを作ることができるけど…。「この位置に、この種類の敵を置きたい」なんて時は、オブジェクトレイヤー(Object Layer)を使えば目的を果たすことができるかもしれないなと。love2d + sti で、オブジェクトレイヤーの情報を読み取ることができれば…。
てなわけで、そのあたりを調べて実験したり。
こんな感じになった。「a」「b」と表示されてる白い四角があるけれど、これがオブジェクトレイヤーの情報を読み取って、その位置に表示してみたソレ。
そういったタイルマップの作成ツールとして、 _Tiledマップエディタ があり。例えば love2d も、 _sti (Simple Tiled Implementation) というライブラリを使わせてもらうことで、Tiled で作ったタイルマップを簡単に表示できたりするわけで。
で。単に表示するだけなら話はこれで終わりなのだけど。せっかくだから、Tiled で作ったタイルマップに敵発生用の情報も含めておいて、それを読み取って使いたいものだよなと。でも、どうすればそんなことができるのだろう。
Tiled は、色々な種類のレイヤーを作ることができるけど…。「この位置に、この種類の敵を置きたい」なんて時は、オブジェクトレイヤー(Object Layer)を使えば目的を果たすことができるかもしれないなと。love2d + sti で、オブジェクトレイヤーの情報を読み取ることができれば…。
てなわけで、そのあたりを調べて実験したり。
こんな感じになった。「a」「b」と表示されてる白い四角があるけれど、これがオブジェクトレイヤーの情報を読み取って、その位置に表示してみたソレ。
◎ Tiledで作業する際の前提条件。 :
- 敵発生位置は、オブジェクトレイヤー(Object layer)を作って指定する。
- オブジェクトレイヤーには、複数のオブジェクトを置くことができる。
- 今回は、32x32ドットの四角形(rectangle)を置いて、位置を指定した。
- 敵種類の判別は、各オブジェクトの名前を利用する。「a」「b」等を指定した。
◎ 画像とソース。 :
使った画像・タイルマップデータは以下。
_enemy_mark.png
_tile01_b.png
_mecha_bg2_map_with_enemy.lua
_mecha_bg2_map_with_enemy.tmx
ソースは以下。 _conf.lua
_main.lua
動作には、 _stiライブラリ が必要。 _karai17/Simple-Tiled-Implementation: Tiled library for LOVE の右上の「Clone or download」から、zip をダウンロードして解凍してもいいし、git がインストールしてある環境なら、
画像、タイルマップデータ(.tmx)、main.lua は License: CC0 / Public Domain ってことで。
stiライブラリは MIT/X11 Open Source License なので注意。
_enemy_mark.png
_tile01_b.png
_mecha_bg2_map_with_enemy.lua
_mecha_bg2_map_with_enemy.tmx
ソースは以下。 _conf.lua
function love.conf(t) t.window.title = "Tilemap Test 04 get object" t.window.width = 1280 t.window.height = 720 t.window.vsync = true t.console = true -- t.window.fullscreen = true -- t.window.fullscreentype = "exclusive" end
_main.lua
-- tilemap test 04 get object -- -- use sti library -- https://github.com/karai17/Simple-Tiled-Implementation local sti = require "sti" function love.load() love.graphics.setDefaultFilter("nearest", "nearest") scr_w, scr_h = 640, 480 canvas = love.graphics.newCanvas(scr_w, scr_h) -- load image mark_img = love.graphics.newImage("enemy_mark.png") mark_quads = {} for i, k in ipairs({"a", "b", "c", "d"}) do local x, y = (i - 1) * 32, 0 local w, h = 32, 32 mark_quads[k] = love.graphics.newQuad(x, y, w, h, mark_img:getDimensions()) end -- load tilemap map = sti("mecha_bg2_map_with_enemy.lua") map.getGidByPixel = function(self, x, y, layer_name) local tilex, tiley = self:convertPixelToTile(math.floor(x), math.floor(y)) tilex = math.floor(tilex) tiley = math.floor(tiley) local layer = map.layers[layer_name] local tilew, tileh = layer.width, layer.height if tilex < 0 or tilex >= tilew or tiley < 0 or tiley >= tileh then return -2 end local tile = layer.data[tiley + 1][tilex + 1] if tile == nil then return -1 end return tile.gid end -- get map objects enemy_objs = {} for k, obj in pairs(map.objects) do local x, y, w, h = obj.x, obj.y, obj.width, obj.height x = x + w / 2 y = y + h / 2 table.insert(enemy_objs, {kind=obj.name, x=x, y=y}) end -- sort objects by y table.sort(enemy_objs, function(a, b) return (a.y > b.y) end) map.layers["enemy_tbl"].visible = false tx_start = 32 ty_start = 480 * 7 tx = tx_start ty = ty_start gid = 0 -- framerate steady min_dt = 1 / 60 next_time = love.timer.getTime() end function love.update(dt) next_time = next_time + min_dt wdw_w, wdw_h = love.graphics.getDimensions() scr_scale = math.min((wdw_w / scr_w), (wdw_h / scr_h)) scr_ofsx = (wdw_w - (scr_w * scr_scale)) / 2 scr_ofsy = (wdw_h - (scr_h * scr_scale)) / 2 map:update(dt) -- keyboard check local speed = 160 * dt local kd = love.keyboard.isDown if kd("left") or kd("a") then tx = tx - speed end if kd("right") or kd("d") then tx = tx + speed end if kd("up") or kd("w") then ty = ty - speed end if kd("down") or kd("s") then ty = ty + speed end map.layers["enemy_tbl"].x = -tx map.layers["enemy_tbl"].y = -ty map.layers["bg_a"].x = -tx map.layers["bg_a"].y = -ty map.layers["bg_b"].x = -tx / 2 map.layers["bg_b"].y = -ty / 2 map.layers["bg_c"].x = -tx / 4 map.layers["bg_c"].y = -ty / 4 -- get mouse position local mx, my = love.mouse.getPosition() mx = (mx - scr_ofsx) / scr_scale my = (my - scr_ofsy) / scr_scale -- get tile gid mx = mx + tx my = my + ty gid = map:getGidByPixel(mx, my, "bg_a") end function love.draw() love.graphics.setCanvas(canvas) love.graphics.clear(0, 0, 0, 255) -- draw tilemap BG love.graphics.setColor(255, 255, 255, 255) map:draw() -- draw objects love.graphics.setColor(255, 255, 255, 255) for i, obj in ipairs(enemy_objs) do local x = obj.x - tx local y = obj.y - ty love.graphics.draw(mark_img, mark_quads[obj.kind], x, y, 0, 1, 1, 16, 16) end love.graphics.setCanvas() -- draw canvas to window love.graphics.setColor(255, 255, 255) love.graphics.draw(canvas, scr_ofsx, scr_ofsy, 0, scr_scale, scr_scale) love.graphics.print("FPS: "..tostring(love.timer.getFPS()), 10, 10) love.graphics.print("tx = "..tostring(tx), 10, 40) love.graphics.print("ty = "..tostring(ty), 10, 60) love.graphics.print("R : reset", 10, 100) love.graphics.print("ESC : exit", 10, 120) love.graphics.print("objs = "..tostring(#enemy_objs), 10, 140) love.graphics.print("gid = "..tostring(gid), 10, 160) if love.system.getOS() == "Windows" then -- wait local cur_time = love.timer.getTime() if next_time <= cur_time then next_time = cur_time else love.timer.sleep(next_time - cur_time) end end end function love.keypressed(key, isrepeat) if key == "escape" then -- ESC to exit love.event.quit() elseif key == "r" then -- reset scroll position tx = tx_start ty = ty_start end end
動作には、 _stiライブラリ が必要。 _karai17/Simple-Tiled-Implementation: Tiled library for LOVE の右上の「Clone or download」から、zip をダウンロードして解凍してもいいし、git がインストールしてある環境なら、
git clone https://github.com/karai17/Simple-Tiled-Implementation.gitと打って入手してもいい。main.lua と同じ階層に、stiフォルダが置いてあるようなファイル・フォルダ配置にして使う。
画像、タイルマップデータ(.tmx)、main.lua は License: CC0 / Public Domain ってことで。
stiライブラリは MIT/X11 Open Source License なので注意。
◎ 少し解説。 :
stiライブラリは、最低限の使い方であれば、以下で済む。
オブジェクトレイヤーに乗ってるオブジェクトの情報を取得するなら、map.objects にアクセスすればいい。
local sti = require "sti" function love.load() map = sti("hoge_tilemap.lua") end function love.update(dt) map:update(dt) end function love.draw() love.graphics.clear(0, 0, 0, 255) love.graphics.setColor(255, 255, 255, 255) map:draw() end
- 最初に、local sti = require "sti" と書くことで「stiライブラリを使うよ」と指定して。
- 初期化処理を行う love.load() の中で、Tiled からエクスポートした .lua を読み込んで。
- 更新処理を行う love.update(dt) の中で、map:update(dt) を呼んで。
- 描画処理を行う love.draw() の中で、map:draw() を呼んで描画する。
オブジェクトレイヤーに乗ってるオブジェクトの情報を取得するなら、map.objects にアクセスすればいい。
enemy_objs = {} for k, obj in pairs(map.objects) do local x, y, w, h = obj.x, obj.y, obj.width, obj.height x = x + w / 2 y = y + h / 2 table.insert(enemy_objs, {kind=obj.name, x=x, y=y}) end各オブジェクトは、以下の情報を持ってる。
{ id = 16, -- ID name = "a", -- 名前 type = "", -- 種類 shape = "rectangle", -- 形状 x = 96, -- x y = 3360, -- y width = 32, -- 幅 height = 32, -- 高さ rotation = 0, -- 角度 visible = true, -- 表示/非表示 properties = {} -- カスタムプロパティ },
[ ツッコむ ]
#2 [love2d][cg_tools] Tiledマップエディタでオブジェクトレイヤーを使う際の操作手順をメモ
Tiledマップエディタでオブジェクトレイヤーを使う際の操作手順を一応メモ。
文章で説明すると分かりづらいだろうから、一応動画でキャプチャしてみたり。
キャプチャし忘れた操作についてもメモ。
文章で説明すると分かりづらいだろうから、一応動画でキャプチャしてみたり。
- レイヤーウインドウの左下のボタンを押すとレイヤーが追加できる。その中から「Object Layer」を選んで追加する。
- オブジェクトウインドウを表示すれば、オブジェクトレイヤーに登録されてるオブジェクトの一覧が確認できる。
- 上のツールバーから、「四角形を追加」を選ぶ。
- メインウインドウ内で四角を描くようにドラッグすると、四角形(rectangle)が追加できる。
- プロパティウインドウを表示して、幅、高さ、名前を変更。
- 上のツールバーから、「オブジェクトを選択」を選ぶ。
- オブジェクトをクリックして選択。ドラッグ操作でオブジェクトを移動できる。
- Ctrlキーを押しながらドラッグすると、タイル単位でオブジェクトを移動できる。
- Ctrl + C、Cirl + V で、オブジェクトのコピーと貼り付けができる。
キャプチャし忘れた操作についてもメモ。
- オブジェクトウインドウ内で、オブジェクト名をダブルクリック、もしくはF2キーを押せば、リネームができる。
- オブジェクトウインドウで、オブジェクトを選択した状態で「↑」「↓」をクリックすれば、オブジェクトの並びを変更できる。
- メインウインドウ内でオブジェクトを選択してある状態で、カーソルキーを使えば、上下左右に1ドット単位で移動ができる。
◎ タイルマップデータを love2d で使う方法。 :
タイルマップデータを love2d で使いたいなら、Lua形式でエクスポートすればいい。ファイル → 名前を付けてエクスポート → Lua形式を選んでエクスポート。中身は Lua のテーブルになってるので使い易いはず。
[ ツッコむ ]
以上、1 日分です。