2017/11/27(月) [n年前の日記]
#1 [love2d][lua] love2dでシューティングゲームの自機っぽいものを書いてたり
love2dを使って、縦スクロールシューティングゲームの自機っぽい処理を書いてたり。
こんな感じに。カーソルキーで移動。Zキーで弾を撃つ方向の変更/固定を切り替え。
動作確認環境は、Windows10 x64 + love2d 0.10.2。
弾を加算合成で描画してるので、love2d 0.10.0以降が必要。もっとも、加算合成の指定部分を修正すれば love2d 0.9.x でも動きそうだけど…。
こんな感じに。カーソルキーで移動。Zキーで弾を撃つ方向の変更/固定を切り替え。
動作確認環境は、Windows10 x64 + love2d 0.10.2。
弾を加算合成で描画してるので、love2d 0.10.0以降が必要。もっとも、加算合成の指定部分を修正すれば love2d 0.9.x でも動きそうだけど…。
◎ 使用画像。 :
以下が自作の使用画像。License : CC0 / Public Domain ってことで。
_airplane_03_64x64_000.png
_airplane_03_64x64_001.png
_bg_640x480_02.png
_bullet_01_32x32.png
_airplane_03_64x64_000.png
_airplane_03_64x64_001.png
_bg_640x480_02.png
_bullet_01_32x32.png
◎ ソース。 :
_conf.lua
_main.lua
function love.conf(t) t.window.title = "Stg Player Test01" t.window.width = 1280 t.window.height = 720 t.window.vsync = true -- t.window.fullscreen = true -- t.window.fullscreentype = "exclusive" end
_main.lua
-- Stg Player Test01
angle_change_key = false
function love.load()
-- init
-- set filter
love.graphics.setDefaultFilter("nearest", "nearest")
scr_w = 640
scr_h = 480
canvas = love.graphics.newCanvas(scr_w, scr_h)
-- get window width and height
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
-- load image
bgimg = love.graphics.newImage("bg_640x480_02.png")
player_img_a = love.graphics.newImage("airplane_03_64x64_000.png")
player_img_b = love.graphics.newImage("airplane_03_64x64_001.png")
bullet_img = love.graphics.newImage("bullet_01_32x32.png")
-- define player class
Player = {}
Player.new = function(x, y, img_a, img_b)
local obj = {}
obj.activate = true
obj.speed = 300
obj.x = x
obj.y = y
obj.gun_angle = -90
obj.gun_angle_change_mode = false
obj.img_a = img_a
obj.img_b = img_b
obj.img_w = img_a:getWidth()
obj.img_h = img_a:getHeight()
setmetatable(obj, {__index = Player})
return obj
end
Player.update = function(self, dt)
if not self.activate then return end
if angle_change_key then
self.gun_angle_change_mode = not(self.gun_angle_change_mode)
angle_change_key = false
end
local ang = -1
if love.keyboard.isDown("left") then ang = 180
elseif love.keyboard.isDown("right") then ang = 0
end
if love.keyboard.isDown("up") then
if ang < 0 then ang = 270
elseif ang == 0 then ang = 270 + 45
else ang = 180 + 45
end
elseif love.keyboard.isDown("down") then
if ang < 0 then ang = 90
elseif ang == 0 then ang = 45
else ang = 180 - 45
end
end
if ang >= 0 then
local spd = self.speed * dt
local ra = math.rad(ang)
self.x = self.x + (spd * math.cos(ra))
self.y = self.y + (spd * math.sin(ra))
if self.gun_angle_change_mode then
-- gun angle change
local tgt_ang = (ang + 180) % 360
if self.gun_angle ~= tgt_ang then
local a = (tgt_ang - self.gun_angle) % 360
if a > 180 then a = a - 360 end
local spd = 5
if a > 0 then
if a < spd then
self.gun_angle = tgt_ang
else
self.gun_angle = (self.gun_angle + spd) % 360
end
elseif a < 0 then
if a > -spd then
self.gun_angle = tgt_ang
else
self.gun_angle = (self.gun_angle - spd) % 360
end
end
end
end
end
-- move area check
local wh = self.img_w / 2
local hh = self.img_h / 2
local xmin, ymin = wh, hh
local xmax, ymax = scr_w - wh, scr_h - hh
self.x = math.min(math.max(self.x, xmin), xmax)
self.y = math.min(math.max(self.y, ymin), ymax)
end
Player.draw = function(self)
if not self.activate then return end
local ox, oy = self.img_w / 2, self.img_h / 2
local x, y = self.x, self.y
local ang = math.rad(self.gun_angle)
local scale = 1.0
love.graphics.draw(self.img_a, x, y, 0, scale, scale, ox, oy)
love.graphics.draw(self.img_b, x, y, ang, scale, scale, ox, oy)
end
-- define player bullet class
PlayerBullet = {}
PlayerBullet.new = function(x, y, ang, img)
local obj = {
speed = 600,
activate = true, x = x, y = y, ang = ang, img = img
}
obj.img_w, obj.img_h = img:getWidth(), img:getHeight()
obj.img_wh, obj.img_hh = obj.img_w / 2, obj.img_h / 2
setmetatable(obj, {__index = PlayerBullet})
return obj
end
PlayerBullet.update = function(self, dt)
if not self.activate then return end
local ra = math.rad(self.ang)
self.x = self.x + (math.cos(ra) * self.speed * dt)
self.y = self.y + (math.sin(ra) * self.speed * dt)
local xmin, ymin = -self.img_wh, -self.img_hh
local xmax, ymax = scr_w + self.img_wh, scr_h + self.img_hh
if self.x < xmin or self.x > xmax or self.y < ymin or self.y > ymax then
self.activate = false
end
end
PlayerBullet.draw = function(self)
if not self.activate then return end
love.graphics.draw(self.img, self.x, self.y,
math.rad(self.ang), 1.0, 1.0,
self.img_wh, self.img_hh)
end
-- work
math.randomseed(0)
player = Player.new(scr_w / 2, scr_h / 2, player_img_a, player_img_b)
player_bullets = {}
shot_timer = 0
-- framerate steady
min_dt = 1 / 60
next_time = love.timer.getTime()
end
function love.update(dt)
-- update
next_time = next_time + min_dt
if dt > 1.0 then return end
player:update(dt)
shot_timer = shot_timer + dt
if shot_timer >= 0.1 then
-- born player bullet
shot_timer = shot_timer - 0.1
local x = player.x
local y = player.y
local ang = player.gun_angle + math.random(-20, 20) / 10
local obj = PlayerBullet.new(x, y, ang, bullet_img)
table.insert(player_bullets, obj)
end
-- move player bullets
for i, spr in ipairs(player_bullets) do
spr:update(dt)
end
-- remove bullet
local elen = #player_bullets
for i=elen,1,-1 do
if not player_bullets[i].activate then
table.remove(player_bullets, i)
end
end
end
function love.draw()
-- set canvas
love.graphics.setCanvas(canvas)
-- draw BG
love.graphics.setColor(255, 255, 255)
love.graphics.draw(bgimg, 0, 0)
-- draw objects
love.graphics.setColor(255, 255, 255)
player:draw()
love.graphics.setBlendMode("add")
for i, spr in ipairs(player_bullets) do
spr:draw()
end
love.graphics.setBlendMode("alpha")
-- unset canvas
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("env: "..tostring(love.system.getOS()), 10, 40)
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)
-- ESC to exit
if key == "escape" then
love.event.quit()
elseif key == "z" then
angle_change_key = true
end
end
◎ 少し説明。 :
加算合成描画は、love.graphics.setBlendMode("add") を指定してから描画すればいいらしい。通常描画に戻すときは、love.graphics.setBlendMode("alpha") を呼ぶ。
love.graphics.setBlendMode("add")
for i, spr in ipairs(player_bullets) do
spr:draw()
end
love.graphics.setBlendMode("alpha")
[ ツッコむ ]
#2 [cg_tools] シューティングゲームの自機に使えそうな画像
ゲームっぽいサンプルソースを書く際に自機の画像があったほうが雰囲気が出てモチベーションが上がる気がするので、自作画像を置いときます。License : CC0 / Public Domain ってことで。好きに使ってください。砲台部分は重ねて使うことを前提にして描いてある…けど無くてもいいような。

_airplane_02_64x64.png

_airplane_02_64x64_000.png

_airplane_02_64x64_001.png

_airplane_03_64x64.png

_airplane_03_64x64_000.png

_airplane_03_64x64_001.png

_airplane_04_64x64.png

_airplane_04_64x64_000.png

_airplane_04_64x64_001.png

_bullet_01_32x32.png









[ ツッコむ ]
#3 [cg_tools] 使い勝手の良いオープンな画像フォーマットは存在するのだろうか
ドット絵を作る際は、
_EDGE2
や
_GrafX2
を使って作業しているのだけど。GrafX2 で描いてgifで保存してから EDGE2 で開こうとしたら「アニメーションのデータがおかしいから開けんわい」と怒られてしまったり。
どうも GrafX2 は、レイヤーも含んだ状態で画像を保存するために、アニメGIFとして保存してる気配が。アニメGIFのフレームが、レイヤー扱いになってるのではないかなあ…。そしておそらく、アニメGIFではあり得ない形で情報を格納してるのかも。
こういう状態になるのも、レイヤーを含めることができて、かつ、仕様が公開されてるオープンな画像フォーマットが当時は存在してなかったから、なのだろうなと。
いやまあ、GIMP の .xcf とか一応あったけど、当時はフォーマット仕様が文書の形で存在してなくて「ソースが仕様だ」とか言ってたらしいし。Photoshop の .psd は仕様が公開されてなくて各自が解析しながら独自実装してたし。そもそもその手のCGソフトは、機能追加するたびに作業用ファイルに保存する情報も増えていくから、事前にカッチリと決めておくのが難しいという事情も。
しかし…。今なら何か使えそうなフォーマットがありそうな気も。
あるいは、今から新規に策定するなら、どういう仕様にするのが妥当なんだろう。各レイヤーは png にして、xmlで管理情報を保持、とかそんな感じだろうか…。
どうも GrafX2 は、レイヤーも含んだ状態で画像を保存するために、アニメGIFとして保存してる気配が。アニメGIFのフレームが、レイヤー扱いになってるのではないかなあ…。そしておそらく、アニメGIFではあり得ない形で情報を格納してるのかも。
こういう状態になるのも、レイヤーを含めることができて、かつ、仕様が公開されてるオープンな画像フォーマットが当時は存在してなかったから、なのだろうなと。
いやまあ、GIMP の .xcf とか一応あったけど、当時はフォーマット仕様が文書の形で存在してなくて「ソースが仕様だ」とか言ってたらしいし。Photoshop の .psd は仕様が公開されてなくて各自が解析しながら独自実装してたし。そもそもその手のCGソフトは、機能追加するたびに作業用ファイルに保存する情報も増えていくから、事前にカッチリと決めておくのが難しいという事情も。
しかし…。今なら何か使えそうなフォーマットがありそうな気も。
あるいは、今から新規に策定するなら、どういう仕様にするのが妥当なんだろう。各レイヤーは png にして、xmlで管理情報を保持、とかそんな感じだろうか…。
[ ツッコむ ]
以上、1 日分です。
