【Playdate】種類ごとにコライダーの衝突処理を変更する

## 前口上

[前回の外部クラス化](https://qiita.com/sada_lp1/items/e3e7ef6ea80e4609d24c)を用いて、spriteの種類ごとにコライダーの衝突処理を変更してみます。
本記事は以下リポジトリを参考に制作しています。

https://github.com/SquidGodDev/PlaydateSDKArrowCollisionsExample

## 実装

プレイヤーが矢を射出し、壁に矢がぶつかると反射していくという内容のものを作ります。
プレイヤー、壁、弓矢の3種類のクラスを用意し、クラスを判別してそれぞれのコライダーの衝突処理を実装します。
それぞれ `source` ディレクトリ直下に配置しています。

```player.lua
local gfx <const> = playdate.graphics

class('Player').extends(gfx.sprite)

function Player:init(x, y)
	Player.super.init(self)
	local playerImage = gfx.image.new("Images/playerImage")
	assert( playerImage )
	self:setImage( playerImage )
	self:moveTo( x, y )
	self:setCollideRect(0, 0, self:getSize())
	self.collisionResponse = playdate.graphics.sprite.kCollisionTypeBounce
end

function Player:update()
	local offsetX = 0
	local offsetY = 0
	if playdate.buttonIsPressed( playdate.kButtonUp ) then
		offsetY = -2
	end
	if playdate.buttonIsPressed( playdate.kButtonRight ) then
		offsetX = 2
	end
	if playdate.buttonIsPressed( playdate.kButtonDown ) then
		offsetY = 2
	end
	if playdate.buttonIsPressed( playdate.kButtonLeft ) then
		offsetX = -2
	end
	self:moveWithCollisions(self.x + offsetX, self.y + offsetY)
end


function Player:collisionResponse(other)
	if other:isa(Wall) then
		return playdate.graphics.sprite.kCollisionTypeFreeze
	else
		return playdate.graphics.sprite.kCollisionTypeOverlap
	end
end
```

```wall.lua
local gfx <const> = playdate.graphics

class('Wall').extends(gfx.sprite)

function Wall:init(x, y, w, h)
	Wall.super.init(self)
	local wallImage = gfx.image.new(w, h)
	gfx.pushContext(wallImage)
		gfx.setColor(gfx.kColorWhite)
		gfx.fillRect(0, 0, w, h)
	gfx.popContext()
	self:setImage(wallImage)
	self:moveTo(x, y)
	self:setCollideRect(0, 0, self:getSize())
end
```

```arrow.lua
import "CoreLibs/object"
import "CoreLibs/sprites"

local gfx <const> = playdate.graphics

class('Arrow').extends(gfx.sprite)

function Arrow:init(x, y, speed, accel)
	Arrow.super.init(self)
	local arrowImage = gfx.image.new("Images/arrow")
	self:setImage(arrowImage)
	self:moveTo(x, y)
	self:setCollideRect(0, 4, 12, 5)
	self.speed = speed
	self.accel = accel
	self.currentSpeed = speed
	self.minSpeed = speed / 2
	self.yOffset = math.random(-10,10) / 10
	self.dir = 1
end

function Arrow:update()
	Arrow.super.update(self)
	
	self:moveWithCollisions(self.x + self.dir * self.currentSpeed, self.y + self.yOffset)
	self.currentSpeed -= self.dir * self.accel
	
	if math.abs(self.currentSpeed) < self.minSpeed then
		self.currentSpeed = self.dir * self.minSpeed
	end
end

function Arrow:collisionResponse(other)
	if other:isa(Wall) then
		self.dir *= -1
		self:setRotation(180)
		return playdate.graphics.sprite.kCollisionTypeBounce
	else
		return playdate.graphics.sprite.kCollisionTypeOverlap
	end
end
```

上記を`main.lua`で読み込みます。
矢の射出はAボタンを押すと実行されるようにしました。

```main.lua
import "CoreLibs/object"
import "CoreLibs/graphics"
import "CoreLibs/sprites"
import "CoreLibs/timer"

import "arrow"
import "wall"
import "player"

local gfx <const> = playdate.graphics

local playerSprite = nil
local wallSprite = nil

function myGameSetUp()

	playerSprite = Player(200, 120)
	playerSprite:add()
	
	wallSprite = Wall(300, 120, 20, 80)
	wallSprite:add()

	local backgroundImage = gfx.image.new( "Images/background" )
	assert( backgroundImage )

	gfx.sprite.setBackgroundDrawingCallback(
		function( x, y, width, height )
			backgroundImage:draw( 0, 0 )
		end
	)

end

myGameSetUp()

function playdate.update()
	-- 矢を射出
	if playdate.buttonIsPressed(playdate.kButtonA) then
		local newArrow = Arrow(playerSprite.x, playerSprite.y, 10, .1)
		newArrow:add()
	end

	gfx.sprite.update()
	playdate.timer.updateTimers()

end
```

## 解説

ポイントとしては動くスプライト(player、arrow)の`sprite:collisionResponse(other)`で、衝突時の処理を実行します。
`other`が衝突したオブジェクトで、`other:isa([クラス名])`で指定したクラス名に衝突したかを調べています。
collisionResponseの返り値は衝突したときの振る舞いの定数が返ってきています。

あとは`main.lua`で初期状態でplayerとwallを出し、update関数の`playdate.buttonIsPressed(playdate.kButtonA)`の中でAボタンが押されたとき、矢を射出するよう処理を記述してます。