I have a Laser class that is used in the following two places in my game.
- As a property of the Base class
- As a property of the Enemy class
How can these two class properties communicate to do a proper collision check while remaining encapsulated?
One idea is to run the collision check from within PlayState which is a class and has self.base = Base() and self.enemyFormation = EnemyFormation() which has Enemy class as a property. That would work fine. However, I'm trying really hard to keep such logic out the PlayState.lua file. It seems reasonable to me to think the Base class laser instance should do the collision check on all enemy laser instances vs. pushing it up to the PlayState.
Thoughts on this? Mostly I'm interested in how I'm thinking about things. I can make the collision detection work easily enough, but my goal with this effort is to avoid hacking my way through the build. I want to learn how to develop games well.
Thanks!
PlayState.lua file
Code: Select all
PlayState = Class{includes = BaseState}
function PlayState:enter(params)
self.starfield = Starfield()
self.base = Base()
self.shelters = Shelters()
self.enemyFormation = EnemyFormation()
end
function PlayState:update(dt)
self.base:update(dt)
if self.base.laser.isVisible then
self.base.laser:update(dt)
end
self.enemyFormation:update(dt)
for key, enemy in ipairs(self.enemyFormation.enemy) do
if self.base.laser:collision(self.enemyFormation.x + enemy.xOffset, self.enemyFormation.x + enemy.xOffset + enemy.width, self.enemyFormation.y + enemy.yOffset, self.enemyFormation.y + enemy.yOffset + enemy.height) and enemy.isActive and self.base.laser.isVisible then
enemy.isActive = false
self.base.laser:resetLaser()
end
end
for key, laser in ipairs(self.enemyFormation.enemyLasers) do
if laser:collision(self.base.x, self.base.x + self.base.width, self.base.y, self.base.y + self.base.height) then
love.event.quit()
end
if laser:collision(self.base.laser.x, self.base.laser.x + self.base.laser.width, self.base.laser.y, self.base.laser.y + self.base.laser.height) then
love.event.quit()
end
end
end
function PlayState:render()
self.base:render()
self.enemyFormation:render()
self.starfield:render()
end
Code: Select all
Laser = Class{}
function Laser:init(x, y, direction, dy)
self.width = 2
self.height = 10
self.x = x
self.y = y
self.dy = dy or 150
self.direction = direction
self.isVisible = false
end
function Laser:update(dt)
self.y = self.y + self.dy * dt * self.direction
if self.y < 0 then
self:resetLaser()
end
end
function Laser:render()
if self.isVisible then
love.graphics.rectangle("fill", self.x, self.y, self.width, self.height)
end
end
function Laser:collision(xMin, xMax, yMin, yMax)
if self.y + self.height < yMin then
return false
elseif self.y > yMax then
return false
elseif self.x + self.width < xMin then
return false
elseif self.x > xMax then
return false
end
return true
end
function Laser:fireLaser(x, y)
self.isVisible = true
self.x = x
self.y = y
end
function Laser:resetLaser()
self.isVisible = false
self.x = 0
self.y = 0
end
Code: Select all
EnemyFormation = Class{}
function EnemyFormation:init()
self.x = 10
self.y = 10
self.rows = 4
self.cols = 8
self.xMin = 0
self.xMax = self.cols * 40 - 20
self.yMin = 0
self.yMax = self.rows * 30 - 10
self.dx = 30
self.spacing = 2
self.enemy = self:generateEnemyFormation(self.cols, self.rows, self.spacing)
self.timer = 0
self.stepTime = 1
self.accelerator = 0.9
self.xStep = (VIRTUAL_WIDTH - (ENEMY_WIDTH * (self.spacing * (self.cols - 1) + 1))) / 20
self.yStep = 10
self.edgeFlag = false
self.stepFlag = true
self.width = self.cols * 40 - 20
self.enemyLasers = {}
end
function EnemyFormation:update(dt)
self.timer = self.timer + dt
self.xMin = self:xMinCheck()
self.xMax = self:xMaxCheck()
-- Simple timer to "step" the enemyFormation across screen and toggle edgeFlag
if self.timer > self.stepTime then
if not self.edgeFlag then
self.x = self.x + self.xStep
elseif self.edgeFlag then
self.y = self.y + self.yStep
self.stepTime = self.stepTime * self.accelerator
self.edgeFlag = false
end
self:fireEnemyLaser()
self.timer = 0
self.stepFlag = not self.stepFlag
end
for key, laser in pairs(self.enemyLasers) do
laser:update(dt)
end
-- Track position of bounding box to move Enemy instances as a group vs. individually
if self.x + self.xMax >= VIRTUAL_WIDTH then
self.edgeFlag = true
self.xStep = -self.xStep
self.x = VIRTUAL_WIDTH - self.xMax - 1
elseif self.x + self.xMin < 0 then
self.edgeFlag = true
self.xStep = -self.xStep
self.x = 1 - self.xMin
end
end
-- Pass x, y coordinates of the enemyFormation box for Enemy instances to use as a reference point.
function EnemyFormation:render()
for key, enemy in ipairs(self.enemy) do
if enemy.isActive then
enemy:render(self.x, self.y, self.stepFlag)
end
end
for key, laser in pairs(self.enemyLasers) do
if laser.y > VIRTUAL_HEIGHT then
table.remove(self.enemyLasers, key)
end
laser:render()
end
end
-- Function to generate all enemy instances within the formation
function EnemyFormation:generateEnemyFormation()
local enemies = {}
for row=1, self.rows do
for col=1, self.cols do
table.insert(enemies, Enemy(12, 12, row, col, self.spacing))
end
end
return enemies
end
-- Function to determine the leftmost x position of enemy formation
function EnemyFormation:xMinCheck()
for col=1, self.cols do
for row=1, self.rows do
if self.enemy[(col - 1) + (row - 1) * self.cols + 1].isActive then
return ((col - 1) * 40)
end
end
end
end
-- Function to determine the rightmost x position of enemy formation
function EnemyFormation:xMaxCheck()
for col=self.cols, 1, -1 do
for row=self.rows, 1, -1 do
if self.enemy[(col - 1) + (row - 1) * self.cols + 1].isActive then
return (col * 40) - 20
end
end
end
end
function EnemyFormation:fireEnemyLaser()
local shooter = self:selectShooter()
local laser = Laser(shooter.xOffset + shooter.width * 0.5 + self.x, shooter.yOffset + shooter.height + self.y, 1)
laser.isVisible = true
table.insert(self.enemyLasers, laser)
end
function EnemyFormation:selectShooter()
while true do
local col = math.random(self.cols)
for row = self.rows, 1, -1 do
if self.enemy[(row-1) * self.cols + col].isActive then
return self.enemy[(row-1) * self.cols + col]
end
end
end
end