mieki256's diary



2017/11/27(月) [n年前の日記]

#1 [love2d][lua] love2dでシューティングゲームの自機っぽいものを書いてたり

love2dを使って、縦スクロールシューティングゲームの自機っぽい処理を書いてたり。

こんな感じに。カーソルキーで移動。Zキーで弾を撃つ方向の変更/固定を切り替え。

stg_player_test01_ss.gif

動作確認環境は、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

ソース。 :

_conf.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.png

airplane_02_64x64_000.png
_airplane_02_64x64_000.png

airplane_02_64x64_001.png
_airplane_02_64x64_001.png

airplane_03_64x64.png
_airplane_03_64x64.png

airplane_03_64x64_000.png
_airplane_03_64x64_000.png

airplane_03_64x64_001.png
_airplane_03_64x64_001.png

airplane_04_64x64.png
_airplane_04_64x64.png

airplane_04_64x64_000.png
_airplane_04_64x64_000.png

airplane_04_64x64_001.png
_airplane_04_64x64_001.png

bullet_01_32x32.png
_bullet_01_32x32.png

#3 [cg_tools] 使い勝手の良いオープンな画像フォーマットは存在するのだろうか

ドット絵を作る際は、 _EDGE2_GrafX2 を使って作業しているのだけど。GrafX2 で描いてgifで保存してから EDGE2 で開こうとしたら「アニメーションのデータがおかしいから開けんわい」と怒られてしまったり。

どうも GrafX2 は、レイヤーも含んだ状態で画像を保存するために、アニメGIFとして保存してる気配が。アニメGIFのフレームが、レイヤー扱いになってるのではないかなあ…。そしておそらく、アニメGIFではあり得ない形で情報を格納してるのかも。

こういう状態になるのも、レイヤーを含めることができて、かつ、仕様が公開されてるオープンな画像フォーマットが当時は存在してなかったから、なのだろうなと。

いやまあ、GIMP の .xcf とか一応あったけど、当時はフォーマット仕様が文書の形で存在してなくて「ソースが仕様だ」とか言ってたらしいし。Photoshop の .psd は仕様が公開されてなくて各自が解析しながら独自実装してたし。そもそもその手のCGソフトは、機能追加するたびに作業用ファイルに保存する情報も増えていくから、事前にカッチリと決めておくのが難しいという事情も。

しかし…。今なら何か使えそうなフォーマットがありそうな気も。

あるいは、今から新規に策定するなら、どういう仕様にするのが妥当なんだろう。各レイヤーは png にして、xmlで管理情報を保持、とかそんな感じだろうか…。

以上、1 日分です。

過去ログ表示

Prev - 2017/11 - Next
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project