Developing Games With Ruby (2014)
Implementing Health And Damage
I know you have been waiting for this. We will be implementing health system and most importantly, damage. Soo we will be ready to blow things up.
To implement this, we need to:
1. Add TankHealth component. Start with 100 health.
2. Render tank health next to tank itself.
3. Inflict damage to tank when it is in explosion zone
4. Render different sprite for dead tank.
5. Cut off player input when tank is dead.
Adding Health Component
If we didn’t have Component system in place, it would be way more difficult. Now we just kick in a new class:
1 class TankHealth < Component
2 attr_accessor :health
4 def initialize(object, object_pool)
6 @object_pool = object_pool
7 @health = 100
8 @health_updated = true
9 @last_damage = Gosu.milliseconds
12 def update
16 def update_image
17 if @health_updated
18 if dead?
19 text = '✝'
20 font_size = 25
22 text = @health.to_s
23 font_size = 18
25 @image = Gosu::Image.from_text(
26 $window, text,
27 Gosu.default_font_name, font_size)
28 @health_updated = false
32 def dead?
33 @health < 1
36 def inflict_damage(amount)
37 if @health > 0
38 @health_updated = true
39 @health = [@health - amount.to_i, 0].max
40 if @health < 1
41 Explosion.new(@object_pool, x, y)
46 def draw(viewport)
48 x - @image.width / 2,
49 y - object.graphics.height / 2 -
50 @image.height, 100)
It hooks itself into the game right away, after we initialize it in Tank class:
class Tank < GameObject
def initialize(object_pool, input)
@health = TankHealth.new(self, object_pool)
Inflicting Damage With Bullets
There are two ways to inflict damage - directly and indirectly. When bullet hits enemy tank (collides with tank bounding box), we should inflict direct damage. It can be done in BulletPhysics#check_hit method that we already had:
class BulletPhysics < Component
@object_pool.nearby(object, 50).each do |obj|
next if obj == object.source # Don't hit source tank
if Utils.point_in_poly(x, y, *obj.box)
# Direct hit - extra damage
object.target_x = x
object.target_y = y
Finally, Explosion itself should inflict additional damage to anything that are nearby. The effect will be diminishing and it will be determined by object distance.
class Explosion < GameObject
def initialize(object_pool, x, y)
object_pool.nearby(self, 100).each do |obj|
if obj.class == Tank
Math.sqrt(3 * 100 - Utils.distance_between(
obj.x, obj.y, x, y)))
This is it, we are ready to deal damage. But we want to see if we actually killed somebody, so TankGraphics should be aware of health and should draw different set of sprites when tank is dead. Here is what we need to change in our current TankGraphics to achieve the result:
class TankGraphics < Component
@body_normal = units.frame('tank1_body.png')
@shadow_normal = units.frame('tank1_body_shadow.png')
@gun_normal = units.frame('tank1_dualgun.png')
@body_dead = units.frame('tank1_body_destroyed.png')
@shadow_dead = units.frame('tank1_body_destroyed_shadow.png')
@gun_dead = nil
@body = @body_dead
@gun = @gun_dead
@shadow = @shadow_dead
@body = @body_normal
@gun = @gun_normal
@shadow = @shadow_normal
@shadow.draw_rot(x - 1, y - 1, 0, object.direction)
@body.draw_rot(x, y, 1, object.direction)
@gun.draw_rot(x, y, 2, object.gun_angle) if @gun
Now we can blow them up and enjoy the view:
But what if we blow ourselves up by shooting nearby? We would still be able to move around. To fix this, we will simply cut out player input when we are dead:
class PlayerInput < Component
return if object.health.dead?
And to prevent tank from throttling forever if the pedal was down before it got killed:
class TankPhysics < Component
if object.throttle_down && !object.health.dead?
That’s it. All we need right now is some resistance from those brain dead enemies. We will spark some life into them in next chapter.