プログラミング未経験だけどゲームつくる - 5 : 座標からのダメージ判定と無敵時間【LOVE2d】

セブンイレブンの冷凍食品は企業努力。
どうも、サトウダイスケです。

猫ちゃんのゲームですが、当たり判定となるx座標とy座標を設定することでダメージ判定ができるようになりました。座標が点だとあまりにもぬるいので、x軸は足から足くらい(大雑把なのでこれもどうにかしたい)の幅にし、対象の石ころと重なっているときにy座標が地とイコールか否かで判断することにしました。

取り敢えず長いコードを載せます。

function love.load()
	love.graphics.setBackgroundColor( 256, 256, 256 )
    love.graphics.setDefaultFilter('nearest', 'nearest')

    cat = love.graphics.newImage('images/neko_for_sprite.png')
    background = love.graphics.newImage('images/background.png')

    cat_walk = love.graphics.newQuad(0, 0, 16, 16, cat:getDimensions())
    cat_jump = love.graphics.newQuad(0, 0, 16, 16, cat:getDimensions())
    cat_idle = love.graphics.newQuad(0, 0, 16, 16, cat:getDimensions())

	cat_now = cat_idle

	x = 0
  neko_x = window_w-20
	
	speed = 300
	dir = 1
	right_to_left = 0
	scale = 8
	chara_w_h = 16
  neko_center_x = neko_x+chara_w_h*scale/2+x
  zimen_h = window_h/2.8+scale*chara_w_h
  neko_center_y = zimen_h
  damage = 0
  damage_muteki = 0
  damage_muteki_time = 2
  damage_muteki_time_remaing = damage_muteki_time

	walk = {}
	walk = {fps=4, num_frames=2, xoffset, timer=1/4 ,frame=1}

	jump = {}
	jump = {fps=8, num_frames=10, xoffset, timer=1/8 ,frame=1}

	local snd

	-- フォントや文字の設定
	utf8 = require("utf8")
  message_font = love.graphics.newFont("k8x12.ttf", 30)
  love.graphics.setFont(message_font)
  message01 = {"めがさめると、おいしそうなにおいがしたんだ。","なぜかうれしくなって、いっぱいはしってた。"}
  message02 = {"このままどこかにいきたい。","どこかとおくにいってしまいたい。"}
  message_h = 40 -- 1行の高さ
  st = love.timer.getTime() -- 開始時刻を記録

  -- 音楽と効果音の設定
  snd_jump = love.audio.newSource("sound/jump06.wav", "static")
  snd_message = love.audio.newSource("sound/select03.wav", "static")
  snd_walk = love.audio.newSource("sound/putting_a_jar.wav", "static")
  snd_damage = love.audio.newSource("sound/surprising_girl.wav", "static")
  bgm = love.audio.newSource("sound/yuuenchi.wav", "static")

  -- 音楽の再生
  bgm:play()
  bgm:setLooping(true)

end

function love.update(dt)

	if dt > 0.035 then return end	--処理落ち対策

	-- 猫が停止している表現
	if cat_now == cat_idle then
		cat_idle:setViewport(0, 0, 16, 16)
	end

	-- 猫が歩いているアニメーション表現
	if cat_now == cat_walk then
		walk.timer = walk.timer - dt
		if walk.timer <= 0 then
			walk.timer = 1 / walk.fps
			walk.frame = walk.frame + 1
			if walk.frame > walk.num_frames then walk.frame = 1 end
			walk.xoffset = 16 * walk.frame
			cat_walk:setViewport(walk.xoffset-16, 0, 16, 16)
		end
	end

	-- 猫がジャンプしているアニメーション表現
	if cat_now == cat_jump then
		jump.timer = jump.timer - dt
		if jump.timer <= 0 then
			jump.timer = 1 / jump.fps
			jump.frame = jump.frame + 1
      if jump.frame == jump.num_frames-8 then
          neko_center_y = neko_center_y - 10
      end
      if jump.frame == jump.num_frames-4 then
          neko_center_y = neko_center_y + 10
      end
			if jump.frame > jump.num_frames then
				jump.frame = 1
				cat_now = cat_idle
      end
			jump.xoffset = 16 * jump.frame
			cat_jump:setViewport(jump.xoffset-16, 0, 16, 16)
		end
	end

	-- キー操作による移動の計算
	local dx = 0
	if love.keyboard.isDown("right") then
		dx = speed * dt
		if dir == 1 then
			dir_switch = "left_to_right"
		end
		if cat_now ~= cat_jump then
			cat_now = cat_walk
      love.audio.play(snd_walk)     
		end
		dir = -1
	elseif love.keyboard.isDown("left") then
		dx = -speed * dt
		if dir == -1 then
			dir_switch = "right_to_left"
		end
		if cat_now ~= cat_jump then
			cat_now = cat_walk
      love.audio.play(snd_walk)
		end
		dir = 1	
	else
		if cat_now ~= cat_jump then
			cat_now = cat_idle	
		end
	end
  neko_center_x = neko_center_x + dx
	x = x + dx

	-- キー操作によるジャンプの計算
	if cat_now ~= cat_jump then	--ジャンプしてる最中ではないことの確認
		if love.keyboard.isDown("space") then
			love.audio.stop(snd_jump)
			love.audio.play(snd_jump)
			cat_now = cat_jump
		end
	end

  --  ダメージを受けた際の計算
  if damage_muteki == 0 then
    if neko_center_x-chara_w_h*(scale-5)/2 < 200
      and
      200 < neko_center_x-chara_w_h*(scale-5)/2+chara_w_h*(scale-5)
      or
      neko_center_x-chara_w_h*(scale-5)/2 < 210
      and
      210 < neko_center_x-chara_w_h*(scale-5)/2+chara_w_h*(scale-5)
      then
        if neko_center_y == zimen_h then
          damage = damage + 1
          damage_muteki = 1
          love.audio.play(snd_damage)
          damage_muteki_time_remaing = damage_muteki_time
        end
    end
  end

  --  ダメージを受けた際の無敵時間の計算
  if damage_muteki == 1 then
    damage_muteki_time_remaing = damage_muteki_time_remaing - dt
    if damage_muteki_time_remaing <= 0 then
      damage_muteki = 0
      damage_muteki_time_remaing = damage_muteki_time
    end
  end

end

function love.draw()

  -- love.graphics.setColor(255, 255, 255 , 0.4)
  -- love.graphics.draw(background,1,-220,0,1,1)

  -- 地面の描写
  love.graphics.setColor(0, 0, 0 , 255)
  love.graphics.rectangle("fill", 0, zimen_h, window_w,  window_h)

  -- 石ころの描写
  love.graphics.rectangle("fill", 200, zimen_h-10, 10,  10)  

	-- 猫の描写
	love.graphics.setColor(255, 255, 255)
	if dir_switch == "left_to_right" then
		x = x + chara_w_h*scale
	elseif dir_switch == "right_to_left" then
		x = x - chara_w_h*scale
	end
	
  if damage_muteki == 1 then
    love.graphics.setColor(255, 255, 255 , 0.5)
    love.graphics.draw(cat, cat_now, neko_x+x, window_h/2.8, 0, scale*dir, scale)
  else
    love.graphics.setColor(255, 255, 255 , 1)
    love.graphics.draw(cat, cat_now, neko_x+x, window_h/2.8, 0, scale*dir, scale)
  end

	dir_switch = 0

  -- 当たり判定の位置描写
  love.graphics.setColor(255, 0, 0,0.5)
  love.graphics.rectangle("fill", neko_center_x-chara_w_h*(scale-5)/2, neko_center_y - 10,chara_w_h*(scale-5) , 10)  

  -- 猫の座標の描写
  love.graphics.setColor(0, 0, 0)
  love.graphics.print(window_w-20+x, 10, 10)

  -- ダメージ数の描写
  love.graphics.setColor(0, 0, 0)
  love.graphics.print(damage, 10, 40)

  -- 無敵判定か否かの描写
  love.graphics.setColor(0, 0, 0)
  love.graphics.print(damage_muteki, 10, 70)

	-- 文字出しの描写
  love.graphics.setColor(255, 255, 255)
  message_quantity = (love.timer.getTime() - st) / 0.1  -- 表示する文字数(経過時間÷0.1秒)
  message_y = window_h-window_h/3.8 -- 1文字目を表示するy座標
  i = 1 -- 何文字目か
  for j, s in ipairs(message01) do -- message を順に処理
    message_x = math.floor(window_w/14)  -- 行の1文字目を表示するx座標(行変わりで初期化)
    for pos, code in utf8.codes(s) do
      if i > message_quantity then break end  -- 表示する文字数を超過していれば終了
      local ss = utf8.char(code) -- 文字を1つずつ取り出す
      love.graphics.print(ss, message_x, message_y) -- 表示
      message_x = message_x + message_font:getWidth(ss) -- 文字の幅だけ右にずらす
      i = i + 1 -- 忘れないように!
    end
    message_y = message_y + message_h -- 行の高さだけ下にずらす
    i = i + 5 -- 行ごとに間を置く(10文字ぶん)
  end

  
end

-- command+F5で更新する関数
love.keypressed = function(key, unicode)
  if 'up' == key then
    love.filesystem.load('main.lua')()
    love.load()
    love.audio.play(snd_message)
    love.audio.stop(bgm)
  end
end

相変わらず汚いコードですいません。
来週くらいには少しは綺麗になっている予定...

まず、猫ちゃんの当たり判定ですが、

  --  ダメージを受けた際の計算
  if damage_muteki == 0 then
    if neko_center_x-chara_w_h*(scale-5)/2 < 200
      and
      200 < neko_center_x-chara_w_h*(scale-5)/2+chara_w_h*(scale-5)
      or
      neko_center_x-chara_w_h*(scale-5)/2 < 210
      and
      210 < neko_center_x-chara_w_h*(scale-5)/2+chara_w_h*(scale-5)
      then
        if neko_center_y == zimen_h then
          damage = damage + 1
          damage_muteki = 1
          love.audio.play(snd_damage)
          damage_muteki_time_remaing = damage_muteki_time
        end
    end
  end

こんな感じで半ば強引に設定しています。

ドット絵の塗りに対して正確に当たり判定となると少し面倒臭そう。
見えないけど画像ファイルとしては透明な部分もあったりするから、画像全体を当たり判定にするわけにもいかないし。

	-- 猫がジャンプしているアニメーション表現
	if cat_now == cat_jump then
		jump.timer = jump.timer - dt
		if jump.timer <= 0 then
			jump.timer = 1 / jump.fps
			jump.frame = jump.frame + 1
      if jump.frame == jump.num_frames-8 then
          neko_center_y = neko_center_y - 10
      end
      if jump.frame == jump.num_frames-4 then
          neko_center_y = neko_center_y + 10
      end
			if jump.frame > jump.num_frames then
				jump.frame = 1
				cat_now = cat_idle
      end
			jump.xoffset = 16 * jump.frame
			cat_jump:setViewport(jump.xoffset-16, 0, 16, 16)
		end
	end

y座標の移動はここで設定。
jump.num_frames-8=2なので、2フレーム目で地面から浮いて、
jump.num_frames-4=6なので、6フレーム目で着地してます。

-- 当たり判定の位置描写
  love.graphics.setColor(255, 0, 0,0.5)
  love.graphics.rectangle("fill", neko_center_x-chara_w_h*(scale-5)/2, neko_center_y - 10,chara_w_h*(scale-5) , 10)  

開発段階では当たり判定位置がわかった方が良いのでうっすら赤く表示しています。

あと、

neko_center_x-chara_w_h*(scale-5)/2 < 200 < neko_center_x-chara_w_h*(scale-5)/2+chara_w_h*(scale-5)
or
neko_center_x-chara_w_h*(scale-5)/2 < 210 < neko_center_x-chara_w_h*(scale-5)/2+chara_w_h*(scale-5)

みたいな表現にしたかったけど、エラーが出たから取り敢えず上のような記述にしました。

石ころの位置もx軸の200~210に設定。
ゲームにするにはランダムで設定しなければいけないし、その場合どうすればいいんだろうかとか課題は山積みですね。

ダメージを受けたあとは一定時間、無敵時間になります。

  --  ダメージを受けた際の無敵時間の計算
  if damage_muteki == 1 then
    damage_muteki_time_remaing = damage_muteki_time_remaing - dt
    if damage_muteki_time_remaing <= 0 then
      damage_muteki = 0
      damage_muteki_time_remaing = damage_muteki_time
    end
  end

と、まあ、こんな感じです。

目的はあくまでもまずは形にすることなので、汎用化や記述の整理などは後日ひとつずつグルーピングする感じで解決していきます!

個人的なメモと、おそらく素人の過程は誰かのタメになるのではということで...

左上の二段目の数字がダメージを受けた回数。
三段目の数字が無敵状態か否かの変数の数値です。
1のときは猫ちゃんも半透明で無敵状態になってます。

GIF動画がたまにアスペクト比がイカれる...
ちなみにmacのQuickTimeの部分画面収録したMOVファイルをGiftedっていうアプリで簡単にGIFアニメーション化しています。

今、解決したいことは、

【直近にやりたいこと】
・タイトルページをつくる
・制限時間を設定する
・スコアやHPの表示をできるようにする
・画面外に猫ちゃんが出ていかないようにする

【おおよそ見えてきたらやりたいこと】
・石ころをランダムに配置して、それに対する当たり判定をつくる
・ゆくゆくはネズミとかにして敵が動き回るようにする
・コードの記述を整理する
・見た目が複雑なので変数に置き換えれるものを変数にする
・テキストメッセージのコードを解読してテキストメッセージを自由に出せるようにする
・ストーリーを組み込む
・マウス操作ができるようにする(実は本来作りたいゲームがマウス操作必須なのでこれが最終目標でもある)

【本当は優先順位は低いけどやりたいこと】
・猫ちゃんがダメージ受けたときのアニメーション
・背景のイラスト
・無敵時間終了が近づくと透明度が変わって点滅する
・画面が少しずつスクロールしてステージが進んでいく

といった感じでしょうか。

次回はタイトルページを作りたいと思います。

この記事が気に入ったらサポートをしてみませんか?