110. In order that the tasks are described:
function Rock:new(...)
...
self.hp = 100
end
xxxxxxxxxx
function Rock:hit(damage)
self.hp = self.hp - (damage or 100)
if self.hp <= 0 then
self.dead = true
self.area:addGameObject('Ammo', self.x, self.y)
else
self.hit_flash = true
self.timer:after('hit_flash', 0.2, function() self.hit_flash = false end)
end
end
So here we first subtract damage
from hp
, while accounting for the possibility that damage is nil and in that case defaulting to 100 (damage or 100
). After that we check to see if HP is below 0 or not, if it is we kill the object by setting dead
to true and then spawn an Ammo resource (as all enemies should do when killed). If it isn't less than 0 then we set hit_flash
to true and set it back to false 0.2 seconds later using timer:after
.
The final thing we should do is change the Rock:draw
function to take into account the hit_flash
attribute:
xxxxxxxxxx
function Rock:draw()
love.graphics.setColor(hp_color)
if self.hit_flash then love.graphics.setColor(default_color) end
local points = {self.collider:getWorldPoints(self.collider.shapes.main:getPoints())}
love.graphics.polygon('line', points)
love.graphics.setColor(default_color)
end
Here we just added the second line as the 4 others were already there.
111. With this question we can literally just copy the code from ProjectileDeathEffect
in its entirety and just change the name to EnemyDeathEffect
. The only thing we have to do to make it look like it does in the gifs is make sure that when calling it, we pass the appropriate w
value, otherwise it will look too small:
xxxxxxxxxx
function Rock:hit(damage)
...
if self.hp <= 0 then
...
self.area:addGameObject('EnemyDeathEffect', self.x, self.y,
{color = hp_color, w = 3*self.w})
...
end
A good question to ask here would be why not just use the ProjectileDeathEffect object instead of creating a new one? And I really don't have a good answer for that. I personally prefer to create different objects if they are serving different purposes, even though they might share the same code (and in this extreme case, their code is actually exactly the same). This is just my preference though and I have no real rational explanation to it. It's perfectly reasonable to just skip creating this new EnemyDeathEffect object and just make use of the effect that already exists.
112. We want to capture collision events between objects of the Projectile collision class and the Enemy collision class. The only object of the Projectile collision class we have in the game right now is the Projectile object itself, and so we can start placing our code there:
xxxxxxxxxx
function Projectile:update(dt)
...
if self.collider:enter('Enemy') then
local collision_data = self.collider:getEnterCollisionData('Enemy')
local object = collision_data.collider:getObject()
end
end
At first we have the default collision capturing code. The object
variable here will contain a reference to the enemy that we collided with. First we want to check if this variable actually exists (it isn't nil), and then we want to do what the exercise asks us to do (call hit
on it and then call die
on self):
xfunction Projectile:update(dt)
...
if self.collider:enter('Enemy') then
local collision_data = self.collider:getEnterCollisionData('Enemy')
local object = collision_data.collider:getObject()
if object then
object:hit(self.damage)
self:die()
end
end
end
Here we use self.damage
which the exercise asks us to define as 100 in the Projectile object's constructor.
113. In order that the tasks are described:
Add a function named
hit
to the Player class
xxxxxxxxxx
function Player:hit(damage)
end
It should receive a
damage
argument, and in case it isn't defined it should default to 10
xxxxxxxxxx
function Player:hit(damage)
damage = damage or 10
end
This function should not do anything whenever the
invincible
attribute is true
xxxxxxxxxx
function Player:hit(damage)
if self.invincible then return end
damage = damage or 10
end
In general whenever you want to not operate on some condition, place that condition at the top of function and return if its fulfilled.
Between 4 and 8
ExplodeParticle
objects should be spawned
xxxxxxxxxx
function Player:hit(damage)
if self.invincible then return end
damage = damage or 10
for i = 1, love.math.random(4, 8) do
self.area:addGameObject('ExplodeParticle', self.x, self.y)
end
end
The
addHP
(orremoveHP
function if you decided to add this one) should take thedamage
attribute and use it to remove HP from the Player. Inside theaddHP
(orremoveHP
) function there should be a way to deal with the case wherehp
hits or goes below 0 and the player dies.
xxxxxxxxxx
function Player:hit(damage)
if self.invincible then return end
damage = damage or 10
for i = 1, love.math.random(4, 8) do
self.area:addGameObject('ExplodeParticle', self.x, self.y)
end
self:removeHP(damage)
end
And the removeHP
function looks like this:
xxxxxxxxxx
function Player:removeHP(amount)
self.hp = self.hp - (amount or 5)
if self.hp <= 0 then
self.hp = 0
self:die()
end
end
It simply removes the amount of HP passed in and then calls die
whenever the hp
attribute goes below 0.
If the damage received is equal to or above 30, then the
invincible
attribute should be set to true and 2 seconds later it should be set to false. On top of that, the camera should shake with intensity 6 for 0.2 seconds, the screen should flash for 3 frames, and the game should be slowed to 0.25 for 0.5 seconds. Finally, aninvisible
attribute should alternate between true and false every 0.04 for the duration thatinvincible
is set to true, and additionally the Player's draw function shouldn't draw anything wheneverinvisible
is set to true.
xxxxxxxxxx
function Player:hit(damage)
...
if damage >= 30 then
self.invincible = true
self.timer:after('invincibility', 2, function() self.invincible = false end)
for i = 1, 50 do
self.timer:after((i-1)*0.04, function() self.invisible = not self.invisible end)
end
self.timer:after(51*0.04, function() self.invisible = false end)
camera:shake(6, 60, 0.2)
flash(3)
slow(0.25, 0.5)
end
end
Note that the for loop that calls timer:after
a bunch of times could have been swapped for an every
call instead with the for loop removed. Both are valid ways of achieving the same goal.
If the damage received is below 30, then the camera should shake with intensity 6 for 0.1 seconds, the screen should flash for 2 frames, and the game should be slowed to 0.75 for 0.25 seconds.
xxxxxxxxxx
function Player:hit(damage)
...
else
camera:shake(3, 60, 0.1)
flash(2)
slow(0.75, 0.25)
end
end
This
hit
function should be called whenever the Player collides with an Enemy. The player should be hit for 30 damage on enemy collision.
xxxxxxxxxx
function Player:update(dt)
...
if self.collider:enter('Enemy') then
local collision_data = self.collider:getEnterCollisionData('Enemy')
local object = collision_data.collider:getObject()
if object then
self:hit(30)
end
end
end
Here we use the same code as we did for the projectile whenever if hits an enemy. In general whenever we want to capture collision events between two entities the code will look kinda like this.
114. This one is pretty straightforward and it just follows from the previous exercises:
xxxxxxxxxx
function EnemyProjectile:update(dt)
...
if self.collider:enter('Projectile') then
local collision_data = self.collider:getEnterCollisionData('Projectile')
local object = collision_data.collider:getObject()
if object then
object:die()
self:die()
end
end
end
There's really no mystery to it. We can just use the general collision event template and then replace the meaningful part with what we want to do, which in this case is calling die
on both objects.
115. The direction
attribute is named improperly because it doesn't refer to the direction that the object is moving towards, which is what one would naturally assume. It refers to the direction in which the object is spawned (left or right), and so when we want to use it thinking it would be referring to the direction of movement of the object it might be confusing. A better name for it would be spawn_direction
, and then it follows that direction of movement of the object would always be -spawn_direction
.