tank_island 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +58 -0
- data/Rakefile +2 -0
- data/bin/tank_island +30 -0
- data/lib/entities/box.rb +28 -0
- data/lib/entities/bullet.rb +26 -0
- data/lib/entities/camera.rb +113 -0
- data/lib/entities/components/ai/gun.rb +114 -0
- data/lib/entities/components/ai/tank_chasing_state.rb +30 -0
- data/lib/entities/components/ai/tank_fighting_state.rb +47 -0
- data/lib/entities/components/ai/tank_fleeing_state.rb +50 -0
- data/lib/entities/components/ai/tank_motion_fsm.rb +102 -0
- data/lib/entities/components/ai/tank_motion_state.rb +84 -0
- data/lib/entities/components/ai/tank_navigating_state.rb +34 -0
- data/lib/entities/components/ai/tank_roaming_state.rb +83 -0
- data/lib/entities/components/ai/tank_stuck_state.rb +45 -0
- data/lib/entities/components/ai/vision.rb +109 -0
- data/lib/entities/components/ai_input.rb +70 -0
- data/lib/entities/components/box_graphics.rb +39 -0
- data/lib/entities/components/bullet_graphics.rb +13 -0
- data/lib/entities/components/bullet_physics.rb +65 -0
- data/lib/entities/components/bullet_sounds.rb +15 -0
- data/lib/entities/components/component.rb +32 -0
- data/lib/entities/components/damage_graphics.rb +20 -0
- data/lib/entities/components/explosion_graphics.rb +43 -0
- data/lib/entities/components/explosion_sounds.rb +16 -0
- data/lib/entities/components/health.rb +87 -0
- data/lib/entities/components/player_input.rb +100 -0
- data/lib/entities/components/player_sounds.rb +16 -0
- data/lib/entities/components/powerup_graphics.rb +22 -0
- data/lib/entities/components/powerup_sounds.rb +15 -0
- data/lib/entities/components/tank_graphics.rb +46 -0
- data/lib/entities/components/tank_health.rb +32 -0
- data/lib/entities/components/tank_physics.rb +179 -0
- data/lib/entities/components/tank_sounds.rb +43 -0
- data/lib/entities/components/tree_graphics.rb +69 -0
- data/lib/entities/damage.rb +26 -0
- data/lib/entities/explosion.rb +34 -0
- data/lib/entities/game_object.rb +54 -0
- data/lib/entities/hud.rb +79 -0
- data/lib/entities/map.rb +183 -0
- data/lib/entities/object_pool.rb +59 -0
- data/lib/entities/powerups/fire_rate_powerup.rb +14 -0
- data/lib/entities/powerups/health_powerup.rb +12 -0
- data/lib/entities/powerups/powerup.rb +35 -0
- data/lib/entities/powerups/powerup_respawn_queue.rb +23 -0
- data/lib/entities/powerups/repair_powerup.rb +14 -0
- data/lib/entities/powerups/tank_speed_powerup.rb +14 -0
- data/lib/entities/radar.rb +62 -0
- data/lib/entities/score_display.rb +35 -0
- data/lib/entities/tank.rb +64 -0
- data/lib/entities/tree.rb +18 -0
- data/lib/game_states/demo_state.rb +49 -0
- data/lib/game_states/game_state.rb +27 -0
- data/lib/game_states/menu_state.rb +60 -0
- data/lib/game_states/pause_state.rb +61 -0
- data/lib/game_states/play_state.rb +119 -0
- data/lib/misc/axis_aligned_bounding_box.rb +33 -0
- data/lib/misc/game_window.rb +30 -0
- data/lib/misc/names.rb +13 -0
- data/lib/misc/quad_tree.rb +91 -0
- data/lib/misc/stats.rb +55 -0
- data/lib/misc/stereo_sample.rb +96 -0
- data/lib/misc/utils.rb +145 -0
- data/media/armalite_rifle.ttf +0 -0
- data/media/boxes_barrels.json +60 -0
- data/media/boxes_barrels.png +0 -0
- data/media/bullet.png +0 -0
- data/media/c_dot.png +0 -0
- data/media/country_field.png +0 -0
- data/media/crash.ogg +0 -0
- data/media/damage1.png +0 -0
- data/media/damage2.png +0 -0
- data/media/damage3.png +0 -0
- data/media/damage4.png +0 -0
- data/media/decor.json +516 -0
- data/media/decor.png +0 -0
- data/media/decor.psd +0 -0
- data/media/explosion.mp3 +0 -0
- data/media/explosion.png +0 -0
- data/media/fire.mp3 +0 -0
- data/media/ground.json +492 -0
- data/media/ground.png +0 -0
- data/media/ground_units.json +900 -0
- data/media/ground_units.png +0 -0
- data/media/menu_music.mp3 +0 -0
- data/media/metal_interaction2.wav +0 -0
- data/media/names.txt +279 -0
- data/media/pickups.json +68 -0
- data/media/pickups.png +0 -0
- data/media/powerup.mp3 +0 -0
- data/media/respawn.wav +0 -0
- data/media/tank_driving.mp3 +0 -0
- data/media/top_secret.ttf +0 -0
- data/media/trees.png +0 -0
- data/media/trees_packed.json +388 -0
- data/media/trees_packed.png +0 -0
- data/media/water.png +0 -0
- data/spec/misc/aabb_spec.rb +85 -0
- data/spec/misc/quad_tree_spec.rb +137 -0
- data/tank_island.gemspec +29 -0
- metadata +223 -0
@@ -0,0 +1,109 @@
|
|
1
|
+
class AiVision
|
2
|
+
CACHE_TIMEOUT = 500
|
3
|
+
POWERUP_CACHE_TIMEOUT = 50
|
4
|
+
attr_reader :in_sight
|
5
|
+
|
6
|
+
def initialize(viewer, object_pool, distance)
|
7
|
+
@viewer = viewer
|
8
|
+
@object_pool = object_pool
|
9
|
+
@distance = distance
|
10
|
+
end
|
11
|
+
|
12
|
+
def can_go_forward?
|
13
|
+
in_front = Utils.point_at_distance(
|
14
|
+
*@viewer.location, @viewer.direction, 40)
|
15
|
+
@object_pool.map.can_move_to?(*in_front) &&
|
16
|
+
@object_pool.nearby_point(*in_front, 40, @viewer)
|
17
|
+
.reject { |o| o.is_a? Powerup }.empty?
|
18
|
+
end
|
19
|
+
|
20
|
+
def update
|
21
|
+
@in_sight = @object_pool.nearby(@viewer, @distance)
|
22
|
+
end
|
23
|
+
|
24
|
+
def closest_free_path(away_from = nil)
|
25
|
+
paths = []
|
26
|
+
5.times do |i|
|
27
|
+
if paths.any?
|
28
|
+
return farthest_from(paths, away_from)
|
29
|
+
end
|
30
|
+
radius = 55 - i * 5
|
31
|
+
range_x = range_y = [-radius, 0, radius]
|
32
|
+
range_x.shuffle.each do |x|
|
33
|
+
range_y.shuffle.each do |y|
|
34
|
+
x = @viewer.x + x
|
35
|
+
y = @viewer.y + y
|
36
|
+
if @object_pool.map.can_move_to?(x, y) &&
|
37
|
+
@object_pool.nearby_point(x, y, radius, @viewer)
|
38
|
+
.reject { |o| o.is_a? Powerup }.empty?
|
39
|
+
if away_from
|
40
|
+
paths << [x, y]
|
41
|
+
else
|
42
|
+
return [x, y]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
false
|
49
|
+
end
|
50
|
+
|
51
|
+
alias :closest_free_path_away_from :closest_free_path
|
52
|
+
|
53
|
+
def closest_tank
|
54
|
+
now = Gosu.milliseconds
|
55
|
+
@closest_tank = nil
|
56
|
+
if now - (@cache_updated_at ||= 0) > CACHE_TIMEOUT
|
57
|
+
@closest_tank = nil
|
58
|
+
@cache_updated_at = now
|
59
|
+
end
|
60
|
+
@closest_tank ||= find_closest_tank
|
61
|
+
end
|
62
|
+
|
63
|
+
def closest_powerup(*suitable)
|
64
|
+
now = Gosu.milliseconds
|
65
|
+
@closest_powerup = nil
|
66
|
+
if now - (@powerup_cache_updated_at ||= 0) > POWERUP_CACHE_TIMEOUT
|
67
|
+
@closest_powerup = nil
|
68
|
+
@powerup_cache_updated_at = now
|
69
|
+
end
|
70
|
+
@closest_powerup ||= find_closest_powerup(*suitable)
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def farthest_from(paths, away_from)
|
76
|
+
paths.sort do |p1, p2|
|
77
|
+
Utils.distance_between(*p1, *away_from) <=>
|
78
|
+
Utils.distance_between(*p2, *away_from)
|
79
|
+
end.first
|
80
|
+
end
|
81
|
+
|
82
|
+
def find_closest_powerup(*suitable)
|
83
|
+
if suitable.empty?
|
84
|
+
suitable = [FireRatePowerup,
|
85
|
+
HealthPowerup,
|
86
|
+
RepairPowerup,
|
87
|
+
TankSpeedPowerup]
|
88
|
+
end
|
89
|
+
@in_sight.select do |o|
|
90
|
+
suitable.include?(o.class)
|
91
|
+
end.sort do |a, b|
|
92
|
+
x, y = @viewer.x, @viewer.y
|
93
|
+
d1 = Utils.distance_between(x, y, a.x, a.y)
|
94
|
+
d2 = Utils.distance_between(x, y, b.x, b.y)
|
95
|
+
d1 <=> d2
|
96
|
+
end.first
|
97
|
+
end
|
98
|
+
|
99
|
+
def find_closest_tank
|
100
|
+
@in_sight.select do |o|
|
101
|
+
o.class == Tank && !o.health.dead?
|
102
|
+
end.sort do |a, b|
|
103
|
+
x, y = @viewer.x, @viewer.y
|
104
|
+
d1 = Utils.distance_between(x, y, a.x, a.y)
|
105
|
+
d2 = Utils.distance_between(x, y, b.x, b.y)
|
106
|
+
d1 <=> d2
|
107
|
+
end.first
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
class AiInput < Component
|
2
|
+
# Dark red
|
3
|
+
NAME_COLOR = Gosu::Color.argb(0xeeb10000)
|
4
|
+
UPDATE_RATE = 10 # ms
|
5
|
+
attr_reader :name
|
6
|
+
attr_reader :stats
|
7
|
+
|
8
|
+
def initialize(name, object_pool)
|
9
|
+
super(nil)
|
10
|
+
@object_pool = object_pool
|
11
|
+
@stats = Stats.new(name)
|
12
|
+
@name = name
|
13
|
+
@last_update = Gosu.milliseconds
|
14
|
+
end
|
15
|
+
|
16
|
+
def control(obj)
|
17
|
+
self.object = obj
|
18
|
+
object.components << self
|
19
|
+
@vision = AiVision.new(obj, @object_pool,
|
20
|
+
rand(700..1200))
|
21
|
+
@gun = AiGun.new(obj, @vision)
|
22
|
+
@motion = TankMotionFSM.new(obj, @vision, @gun)
|
23
|
+
end
|
24
|
+
|
25
|
+
def on_collision(with)
|
26
|
+
return if object.health.dead?
|
27
|
+
@motion.on_collision(with)
|
28
|
+
end
|
29
|
+
|
30
|
+
def on_damage(amount)
|
31
|
+
@motion.on_damage(amount)
|
32
|
+
@stats.add_damage(amount)
|
33
|
+
end
|
34
|
+
|
35
|
+
def update
|
36
|
+
return respawn if object.health.dead?
|
37
|
+
@gun.adjust_angle
|
38
|
+
now = Gosu.milliseconds
|
39
|
+
return if now - @last_update < UPDATE_RATE
|
40
|
+
@last_update = now
|
41
|
+
@vision.update
|
42
|
+
@gun.update
|
43
|
+
@motion.update
|
44
|
+
end
|
45
|
+
|
46
|
+
def draw(viewport)
|
47
|
+
@motion.draw(viewport)
|
48
|
+
@gun.draw(viewport)
|
49
|
+
@name_image ||= Gosu::Image.from_text(
|
50
|
+
$window, @name, Gosu.default_font_name, 20)
|
51
|
+
@name_image.draw(
|
52
|
+
x - @name_image.width / 2 - 1,
|
53
|
+
y + object.graphics.height / 2, 100,
|
54
|
+
1, 1, Gosu::Color::WHITE)
|
55
|
+
@name_image.draw(
|
56
|
+
x - @name_image.width / 2,
|
57
|
+
y + object.graphics.height / 2, 100,
|
58
|
+
1, 1, NAME_COLOR)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def respawn
|
64
|
+
if object.health.should_respawn?
|
65
|
+
object.health.restore
|
66
|
+
object.move(*@object_pool.map.spawn_point)
|
67
|
+
PlayerSounds.respawn(object, @object_pool.camera)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class BoxGraphics < Component
|
2
|
+
def initialize(object)
|
3
|
+
super(object)
|
4
|
+
load_sprite
|
5
|
+
end
|
6
|
+
|
7
|
+
def draw(viewport)
|
8
|
+
@box.draw_rot(x, y, 0, object.angle)
|
9
|
+
Utils.mark_corners(object.box) if $debug
|
10
|
+
end
|
11
|
+
|
12
|
+
def height
|
13
|
+
@box.height
|
14
|
+
end
|
15
|
+
|
16
|
+
def width
|
17
|
+
@box.width
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def load_sprite
|
23
|
+
frame = boxes.frame_list.sample
|
24
|
+
@box = boxes.frame(frame)
|
25
|
+
end
|
26
|
+
|
27
|
+
def center_x
|
28
|
+
@center_x ||= x - width / 2
|
29
|
+
end
|
30
|
+
|
31
|
+
def center_y
|
32
|
+
@center_y ||= y - height / 2
|
33
|
+
end
|
34
|
+
|
35
|
+
def boxes
|
36
|
+
@@boxes ||= Gosu::TexturePacker.load_json($window,
|
37
|
+
Utils.media_path('boxes_barrels.json'))
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class BulletGraphics < Component
|
2
|
+
def draw(viewport)
|
3
|
+
image.draw(x - 8, y - 8, 1)
|
4
|
+
Utils.mark_corners(object.box) if $debug
|
5
|
+
end
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def image
|
10
|
+
@@bullet ||= Gosu::Image.new(
|
11
|
+
$window, Utils.media_path('bullet.png'), false)
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
class BulletPhysics < Component
|
2
|
+
START_DIST = 20
|
3
|
+
MAX_DIST = 500
|
4
|
+
|
5
|
+
def initialize(game_object, object_pool)
|
6
|
+
super(game_object)
|
7
|
+
x, y = point_at_distance(START_DIST)
|
8
|
+
object.move(x, y)
|
9
|
+
@object_pool = object_pool
|
10
|
+
if trajectory_length > MAX_DIST
|
11
|
+
object.target_x, object.target_y = point_at_distance(MAX_DIST)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def update
|
16
|
+
fly_speed = Utils.adjust_speed(object.speed)
|
17
|
+
now = Gosu.milliseconds
|
18
|
+
@last_update ||= object.fired_at
|
19
|
+
fly_distance = (now - @last_update) * 0.001 * fly_speed
|
20
|
+
object.move(*point_at_distance(fly_distance))
|
21
|
+
@last_update = now
|
22
|
+
check_hit
|
23
|
+
object.explode if arrived?
|
24
|
+
end
|
25
|
+
|
26
|
+
def trajectory_length
|
27
|
+
Utils.distance_between(object.target_x, object.target_y, x, y)
|
28
|
+
end
|
29
|
+
|
30
|
+
def point_at_distance(distance)
|
31
|
+
if distance > trajectory_length
|
32
|
+
return [object.target_x, object.target_y]
|
33
|
+
end
|
34
|
+
distance_factor = distance.to_f / trajectory_length
|
35
|
+
p_x = x + (object.target_x - x) * distance_factor
|
36
|
+
p_y = y + (object.target_y - y) * distance_factor
|
37
|
+
[p_x, p_y]
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def check_hit
|
43
|
+
@object_pool.nearby(object, 50).each do |obj|
|
44
|
+
next if obj == object.source # Don't hit source tank
|
45
|
+
if obj.class == Tree
|
46
|
+
if Utils.distance_between(x, y, obj.x, obj.y) < 10
|
47
|
+
return do_hit(obj) if obj.respond_to?(:health)
|
48
|
+
end
|
49
|
+
elsif Utils.point_in_poly(x, y, *obj.box)
|
50
|
+
# Direct hit - extra damage
|
51
|
+
return do_hit(obj) if obj.respond_to?(:health)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def do_hit(obj)
|
57
|
+
obj.health.inflict_damage(20, object.source)
|
58
|
+
object.target_x = x
|
59
|
+
object.target_y = y
|
60
|
+
end
|
61
|
+
|
62
|
+
def arrived?
|
63
|
+
x == object.target_x && y == object.target_y
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class BulletSounds
|
2
|
+
class << self
|
3
|
+
def play(object, camera)
|
4
|
+
volume, pan = Utils.volume_and_pan(object, camera)
|
5
|
+
sound.play(object.object_id, pan, volume)
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def sound
|
11
|
+
@@sound ||= StereoSample.new(
|
12
|
+
$window, Utils.media_path('fire.mp3'))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class Component
|
2
|
+
attr_reader :object # better performance
|
3
|
+
|
4
|
+
def initialize(game_object = nil)
|
5
|
+
self.object = game_object
|
6
|
+
end
|
7
|
+
|
8
|
+
def update
|
9
|
+
# override
|
10
|
+
end
|
11
|
+
|
12
|
+
def draw(viewport)
|
13
|
+
# override
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def object=(obj)
|
19
|
+
if obj
|
20
|
+
@object = obj
|
21
|
+
obj.components << self
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def x
|
26
|
+
@object.x
|
27
|
+
end
|
28
|
+
|
29
|
+
def y
|
30
|
+
@object.y
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class DamageGraphics < Component
|
2
|
+
def initialize(object_pool)
|
3
|
+
super
|
4
|
+
@image = images.sample
|
5
|
+
@angle = rand(0..360)
|
6
|
+
end
|
7
|
+
|
8
|
+
def draw(viewport)
|
9
|
+
@image.draw_rot(x, y, 0, @angle)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def images
|
15
|
+
@@images ||= (1..4).map do |i|
|
16
|
+
Gosu::Image.new($window,
|
17
|
+
Utils.media_path("damage#{i}.png"), false)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class ExplosionGraphics < Component
|
2
|
+
FRAME_DELAY = 16.66 # ms
|
3
|
+
|
4
|
+
def initialize(game_object)
|
5
|
+
super
|
6
|
+
@current_frame = 0
|
7
|
+
end
|
8
|
+
|
9
|
+
def draw(viewport)
|
10
|
+
image = current_frame
|
11
|
+
image.draw(
|
12
|
+
x - image.width / 2 + 3,
|
13
|
+
y - image.height / 2 - 35,
|
14
|
+
20)
|
15
|
+
end
|
16
|
+
|
17
|
+
def update
|
18
|
+
now = Gosu.milliseconds
|
19
|
+
delta = now - (@last_frame ||= now)
|
20
|
+
if delta > FRAME_DELAY
|
21
|
+
@last_frame = now
|
22
|
+
end
|
23
|
+
@current_frame += (delta / FRAME_DELAY).floor
|
24
|
+
object.mark_for_removal if done?
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def current_frame
|
30
|
+
animation[@current_frame % animation.size]
|
31
|
+
end
|
32
|
+
|
33
|
+
def done?
|
34
|
+
@done ||= @current_frame >= animation.size
|
35
|
+
end
|
36
|
+
|
37
|
+
def animation
|
38
|
+
@@animation ||=
|
39
|
+
Gosu::Image.load_tiles(
|
40
|
+
$window, Utils.media_path('explosion.png'),
|
41
|
+
128, 128, false)
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class ExplosionSounds
|
2
|
+
class << self
|
3
|
+
def play(object, camera)
|
4
|
+
volume, pan = Utils.volume_and_pan(object, camera)
|
5
|
+
sound.play(object.object_id, pan, volume)
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def sound
|
11
|
+
@@sound ||= StereoSample.new(
|
12
|
+
$window, Utils.media_path('explosion.mp3'))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
@@ -0,0 +1,87 @@
|
|
1
|
+
class Health < Component
|
2
|
+
attr_accessor :health
|
3
|
+
|
4
|
+
def initialize(object, object_pool, health, explodes)
|
5
|
+
super(object)
|
6
|
+
@explodes = explodes
|
7
|
+
@object_pool = object_pool
|
8
|
+
@initial_health = @health = health
|
9
|
+
@health_updated = true
|
10
|
+
end
|
11
|
+
|
12
|
+
def restore
|
13
|
+
@health = @initial_health
|
14
|
+
@health_updated = true
|
15
|
+
end
|
16
|
+
|
17
|
+
def increase(amount)
|
18
|
+
@health = [@health + 25, @initial_health * 2].min
|
19
|
+
@health_updated = true
|
20
|
+
end
|
21
|
+
|
22
|
+
def damaged?
|
23
|
+
@health < @initial_health
|
24
|
+
end
|
25
|
+
|
26
|
+
def dead?
|
27
|
+
@health < 1
|
28
|
+
end
|
29
|
+
|
30
|
+
def update
|
31
|
+
update_image
|
32
|
+
end
|
33
|
+
|
34
|
+
def inflict_damage(amount, cause)
|
35
|
+
if @health > 0
|
36
|
+
@health_updated = true
|
37
|
+
if object.respond_to?(:input)
|
38
|
+
object.input.stats.add_damage(amount)
|
39
|
+
# Don't count damage to trees and boxes
|
40
|
+
if cause.respond_to?(:input) && cause != object
|
41
|
+
cause.input.stats.add_damage_dealt(amount)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
@health = [@health - amount.to_i, 0].max
|
45
|
+
after_death(cause) if dead?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def draw(viewport)
|
50
|
+
return unless draw?
|
51
|
+
@image && @image.draw(
|
52
|
+
x - @image.width / 2,
|
53
|
+
y - object.graphics.height / 2 -
|
54
|
+
@image.height, 100)
|
55
|
+
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
|
59
|
+
def draw?
|
60
|
+
$debug
|
61
|
+
end
|
62
|
+
|
63
|
+
def update_image
|
64
|
+
return unless draw?
|
65
|
+
if @health_updated
|
66
|
+
text = @health.to_s
|
67
|
+
font_size = 18
|
68
|
+
@image = Gosu::Image.from_text(
|
69
|
+
$window, text,
|
70
|
+
Gosu.default_font_name, font_size)
|
71
|
+
@health_updated = false
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def after_death(cause)
|
76
|
+
if @explodes
|
77
|
+
Thread.new do
|
78
|
+
sleep(rand(0.1..0.3))
|
79
|
+
Explosion.new(@object_pool, x, y, cause)
|
80
|
+
sleep 0.3
|
81
|
+
object.mark_for_removal
|
82
|
+
end
|
83
|
+
else
|
84
|
+
object.mark_for_removal
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|