110. In order that the tasks are described:
function Rock:new(...) ... self.hp = 100end xxxxxxxxxxfunction 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) endendSo 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:
xxxxxxxxxxfunction 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)endHere 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:
xxxxxxxxxxfunction Rock:hit(damage) ... if self.hp <= 0 then ... self.area:addGameObject('EnemyDeathEffect', self.x, self.y, {color = hp_color, w = 3*self.w}) ...endA 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:
xxxxxxxxxxfunction Projectile:update(dt) ... if self.collider:enter('Enemy') then local collision_data = self.collider:getEnterCollisionData('Enemy') local object = collision_data.collider:getObject() endendAt 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 endendHere 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
hitto the Player class
xxxxxxxxxxfunction Player:hit(damage) endIt should receive a
damageargument, and in case it isn't defined it should default to 10
xxxxxxxxxxfunction Player:hit(damage) damage = damage or 10endThis function should not do anything whenever the
invincibleattribute is true
xxxxxxxxxxfunction Player:hit(damage) if self.invincible then return end damage = damage or 10endIn 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
ExplodeParticleobjects should be spawned
xxxxxxxxxxfunction 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) endendThe
addHP(orremoveHPfunction if you decided to add this one) should take thedamageattribute and use it to remove HP from the Player. Inside theaddHP(orremoveHP) function there should be a way to deal with the case wherehphits or goes below 0 and the player dies.
xxxxxxxxxxfunction 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)endAnd the removeHP function looks like this:
xxxxxxxxxxfunction Player:removeHP(amount) self.hp = self.hp - (amount or 5) if self.hp <= 0 then self.hp = 0 self:die() endendIt 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
invincibleattribute 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, aninvisibleattribute should alternate between true and false every 0.04 for the duration thatinvincibleis set to true, and additionally the Player's draw function shouldn't draw anything wheneverinvisibleis set to true.
xxxxxxxxxxfunction 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) endendNote 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.
xxxxxxxxxxfunction Player:hit(damage) ... else camera:shake(3, 60, 0.1) flash(2) slow(0.75, 0.25) endendThis
hitfunction should be called whenever the Player collides with an Enemy. The player should be hit for 30 damage on enemy collision.
xxxxxxxxxxfunction 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 endendHere 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:
xxxxxxxxxxfunction 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 endendThere'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.