Commit 0a4402566502167108c2e4a28ec3ca5534e249c5

Authored by Marius Hanne
1 parent 9bee4b715e
Exists in master

use chipmunk library for physics simulation

Showing 7 changed files with 238 additions and 195 deletions Side-by-side Diff

... ... @@ -3,6 +3,7 @@
3 3 $: << File.join(File.dirname(__FILE__), "lib")
4 4  
5 5 require 'rubygame'
  6 +require 'chipmunk'
6 7  
7 8 require 'drawable'
8 9 require 'explosion'
9 10  
10 11  
11 12  
12 13  
13 14  
14 15  
15 16  
16 17  
17 18  
18 19  
19 20  
20 21  
21 22  
22 23  
23 24  
24 25  
25 26  
26 27  
27 28  
28 29  
29 30  
... ... @@ -14,93 +15,177 @@
14 15  
15 16  
16 17 Rubygame.init
  18 +class Array
  19 + def add(o); calc(o, :+); end
  20 + def sub(o); calc(o, :-); end
  21 + def mul(o); calc(o, :*); end
  22 + def div(o); calc(o, :/); end
  23 + def calc(other, op)
  24 + other = [other, other] unless other.is_a?(Array)
  25 + zip(other).map {|v| v.inject(op) }
  26 + end
  27 +end
17 28  
18 29 class Game
19 30  
20 31 include Rubygame
21 32  
22 33 attr_reader :screen, :queue, :clock, :objects, :player, :enemies, :level, :opts
23   - attr_accessor :state
24   - def initialize opts
  34 + attr_accessor :state, :space, :options
  35 + def initialize options
  36 + @options = options
25 37 flags = [HWSURFACE, DOUBLEBUF]
26   - flags << FULLSCREEN if opts[:fullscreen]
27   - @screen = Screen.new opts[:size], 0, flags
  38 + flags << FULLSCREEN if options[:fullscreen]
  39 + @screen = Screen.new options[:size], 0, flags
28 40 @screen.title = "test-game"
  41 + @space = CP::Space.new
  42 + setup_collisions
29 43 @objects = []
30   - @player = Player.new(self, [@screen.size[0] / 2, @screen.size[1] - 100], :fighter)
31 44 @enemies = []
32 45 @queue = EventQueue.new
33 46 @clock = Clock.new
34   - @clock.target_framerate = 60
  47 + @clock.target_framerate = 600
35 48 @keys_pressed = []
36 49 @background = Surface.load(File.join(data_dir, "images", "background.png")).zoom_to(@screen.size[0], @screen.size[1] * 2).to_display
37 50 @bg_pos = @background.size[1] / 2
38   - @level = opts[:level]
39   - @debug = opts[:debug]
  51 + @level = options[:level]
40 52 @state = :ready
41 53 @opts = opts
42 54 Music.autoload_dirs = [ File.join(data_dir, "sound") ]
43 55 end
44 56  
  57 + def setup_collisions
  58 + @space.add_collision_func(:ship, :ship) do |ship1_shape, ship2_shape|
  59 + ship1 = self.objects.find {|o| o.shape == ship1_shape }
  60 + ship2 = self.objects.find {|o| o.shape == ship2_shape }
  61 + next unless ship1 && ship2
  62 + power1 = ship1.power
  63 + ship1.take_damage(ship2.power / 10)
  64 + ship2.take_damage(power1 / 10)
  65 + end
  66 + @space.add_collision_func(:ship, :bullet) do |ship_shape, bullet_shape|
  67 + bullet = self.objects.find {|o| o.shape == bullet_shape }
  68 + ship = self.objects.find {|o| o.shape == ship_shape }
  69 + next unless bullet && ship
  70 + bullet.hit(ship)
  71 + @space.remove_body(bullet_shape.body)
  72 + @space.remove_shape(bullet_shape)
  73 + end
  74 + @space.add_collision_func(:bullet, :bullet, &nil)
  75 + @space.add_collision_func(:explosion, :explosion, &nil)
  76 + @space.add_collision_func(:explosion, :bullet, &nil)
  77 + @space.add_collision_func(:explosion, :ship, &nil)
  78 + end
  79 +
  80 + def run
  81 + @player = Player.new(self, p: [@screen.size[0] / 2, @screen.size[1] - 100])
  82 + @state = :running
  83 + @player.show
  84 +
  85 + flash_text(text: "Level #{@level}", alpha: 150)
  86 + loop do
  87 +
  88 + @queue.enable_new_style_events
  89 + @queue.each do |event|
  90 +
  91 + @keys_pressed << event.key if event.is_a?(Events::KeyPressed)
  92 + @keys_pressed.delete(event.key) if event.is_a?(Events::KeyReleased)
  93 + @keys_pressed << :space if event.is_a?(Events::MousePressed)
  94 + @keys_pressed.delete(:space) if event.is_a?(Events::MouseReleased)
  95 + @player.body.p = CP::Vec2.new(*event.pos) if event.is_a?(Events::MouseMoved)
  96 +
  97 + exit if event.is_a?(Rubygame::Events::QuitRequested) ||
  98 + exit if event.is_a?(Events::KeyPressed) && [:escape, :q].include?(event.key)
  99 + end
  100 +
  101 + # TODO
  102 + if @keys_pressed.include?(:space) && @state == :running
  103 + @bullet_lock = 0 if @bullet_lock > 10
  104 + if @bullet_lock == 0
  105 + Bullet.new(self, p: @player.pos.sub([0.0, @player.shape.r]),
  106 + dir: :up, type: :torpedo, shooter: @player).show
  107 + end
  108 + @bullet_lock += 1
  109 + else
  110 + @bullet_lock = 0
  111 + end
  112 +
  113 + Enemy.generate(self) if @enemies.size < @level
  114 +
  115 + [:up, :down, :left, :right].each {|d|
  116 + @player.move(d) if @keys_pressed.include?(d) }
  117 +
  118 + draw
  119 +
  120 + # TODO
  121 + if @bg_pos.abs >= (@background.size[1] * 2 * @level) && @state == :running
  122 + @level += 1
  123 + flash_text(text: "Level #{@level}")
  124 + @bg_pos = @background.size[1] / 2
  125 + end
  126 +
  127 + @space.step(1)
  128 + Clock.wait(0.1 - @clock.tick / 1000) # TODO
  129 + end
  130 + end
  131 +
45 132 def draw
46 133 @bg_pos -= 1
47 134 @background.blit(@screen, [0, 0], [0, @bg_pos % @background.size[1] / 2, *@screen.size])
48 135  
49   - @objects.each(&:blit)
50   -
  136 + @objects.each {|o| o.draw(@screen) }
51 137 draw_hud
52   - draw_debug if @debug
53   -
  138 + draw_debug if @options[:debug]
54 139 @screen.flip
55 140 end
56 141  
57 142 def draw_debug
58   - text = "[ fps: %.2f | " % @clock.framerate
59   - text += "objects: #{@objects.count} | pos: #{0 - @bg_pos} | level: #{@level} | power: #{@player.power} ]"
  143 + text = "[ fps: #{"%.2f" % @clock.framerate} | " +
  144 + "objects: #{@objects.count} | pos: #{0 - @bg_pos} | "+
  145 + "level: #{@level} | power: #{@player.power} ]"
60 146 text += "[ #{@keys_pressed.join(' | ')} ]" if @keys_pressed.any?
  147 + font("FreeSans").render(text, false, :white)
  148 + .blit(@screen, [10, @screen.size[1] - 20])
61 149  
62   - @font_text = font("FreeSans", 10)
63   - @font_text.render(text, false, :white).blit(@screen, [10, @screen.size[1] - 20])
64   -
65 150 ([@player] + @enemies).each do |ship|
66   - pos = [ship.pos[0] + ship.size[0], ship.pos[1] + ship.size[1]]
67   - ship.debug.blit(@screen, pos)
  151 + ship.debug.blit(@screen, ship.pos.add(ship.shape.r))
68 152 end
69   -
70 153 end
71 154  
72 155 def draw_hud
73   - @font_hud ||= font("captain", 50)
74   -
75 156 if @state == :lost
76 157 text = "Game Over"
77 158 surface = font("captain", 150).render(text, false, :red)
78 159 surface.alpha = 150
79   - surface.blit(@screen, [@screen.size[0] / 2 - surface.size[0] / 2,
80   - @screen.size[1] / 2 - surface.size[1] / 2])
  160 + surface.blit(@screen, @screen.size.div(2).sub(surface.size.div(2)))
81 161 end
82 162  
83 163 if @flash
84 164 begin
85 165 surface = font("captain", @flash[:size]).render(@flash[:text], false, @flash[:color])
86 166 surface.alpha = @flash[:alpha]
87   - surface.blit(@screen, [@screen.size[0] / 2 - surface.size[0] / 2,
88   - @screen.size[1] / 2 - surface.size[1] / 2])
  167 + surface.blit(@screen, @screen.size.div(2).sub(surface.size.div(2)))
89 168 rescue
90 169 end
91 170 end
92 171  
93   - if @player.power > 1
94   - surface = Surface.new([(@screen.size[0] / 100 * @player.power) - 10, 20])
95   - surface.fill :blue
96   - surface.blit(@screen, [5, @screen.size[1] - 25])
97   - end
  172 + draw_power_bar
  173 + draw_score
  174 + end
98 175  
  176 + def draw_power_bar
  177 + return unless @player.power > 1
  178 + surface = Surface.new([(@screen.size[0] / 100 * @player.power) - 10, 20])
  179 + surface.fill :blue
  180 + surface.blit(@screen, [5, @screen.size[1] - 25])
  181 + end
  182 +
  183 + def draw_score
99 184 text = @player.score.to_s
100 185 color = @flash_hud || :white
101   - surface = @font_hud.render(text, false, color)
  186 + surface = font("captain", 50).render(text, false, color)
102 187 surface.alpha = 150
103   - surface.blit(@screen, [@screen.size[0] - 10 - surface.size[0], @screen.size[1] - 50])
  188 + surface.blit(@screen, @screen.size.sub([surface.size[0] + 10, 50]))
104 189 end
105 190  
106 191 def flash_hud color, duration = 1
107 192  
... ... @@ -113,88 +198,13 @@
113 198 Thread.start { sleep @flash[:duration]; @flash = nil }
114 199 end
115 200  
116   - def run
117   - @state = :running
118   - @player.show
119   -
120   - flash_text(text: "Level #{@level}", alpha: 150)
121   - loop do
122   -
123   - @queue.enable_new_style_events
124   - @queue.each do |event|
125   -
126   - if event.is_a?(Events::KeyPressed)
127   - @keys_pressed << event.key
128   - end
129   -
130   - if event.is_a?(Events::KeyReleased)
131   - @keys_pressed.delete(event.key)
132   - end
133   -
134   - if event.is_a?(Events::MousePressed)
135   - @keys_pressed << :space
136   - end
137   -
138   - if event.is_a?(Events::MouseReleased)
139   - @keys_pressed.delete(:space)
140   - end
141   -
142   - if event.is_a?(Events::MouseMoved)
143   - @player.pos = [event.pos[0] - @player.size[0] / 2, event.pos[1] - @player.size[1] / 2]
144   - end
145   -
146   - if event.is_a?(Rubygame::Events::QuitRequested) ||
147   - (event.is_a?(Events::KeyPressed) && (event.key == :escape || event.key == :q))
148   - puts "Bye"
149   - exit 1
150   - end
151   -
152   - end
153   -
154   - if @keys_pressed.include?(:space) && @state == :running
155   - @bullet_lock = 0 if @bullet_lock > 10
156   - if @bullet_lock == 0
157   - pos = @player.pos.dup
158   - pos[0] += @player.size[0] / 2
159   - pos[1] -= 5
160   - Bullet.new(self, pos, :up, :torpedo, @player).show
161   - end
162   - @bullet_lock += 1
163   - else
164   - @bullet_lock = 0
165   - end
166   -
167   -
168   - if @enemies.size < @level
169   - generate_enemy
170   - end
171   -
172   - [:up, :down, :left, :right].each {|d| @player.move(d) if @keys_pressed.include?(d) }
173   -
174   - draw
175   -
176   - if @bg_pos.abs >= (@background.size[1] * 2 * @level) && @state == :running
177   - @level += 1
178   - flash_text(text: "Level #{@level}")
179   - @bg_pos = @background.size[1] / 2
180   - end
181   -
182   - @clock.tick
183   - end
184   - end
185   -
186   - def generate_enemy
187   - e = Enemy.new(self, [0, 0], [:glider, :drone, :fighter].sample)
188   - e.show
189   - @enemies << e
190   - end
191   -
192 201 def data_dir
193 202 File.join(File.dirname(__FILE__), "data")
194 203 end
195 204  
196   - def font name, size
197   - TTF.new(File.join(data_dir, "fonts", "#{name}.ttf"), size)
  205 + def font name, size = 10
  206 + @fonts ||= {}
  207 + @fonts[name.to_sym] = TTF.new(File.join(data_dir, "fonts", "#{name}.ttf"), size)
198 208 end
199 209 end
200 210  
... ... @@ -228,6 +238,11 @@
228 238 end
229 239 end.parse!
230 240  
231   -p @opts
232   -Game.new(@opts).run
  241 +
  242 +#require 'ruby-prof'
  243 +#results = RubyProf.profile do
  244 + Game.new(@opts).run
  245 +#end
  246 +#printer = RubyProf::GraphHtmlPrinter.new(results)
  247 +#printer.print(File.new("report.html","w"))
... ... @@ -11,40 +11,29 @@
11 11  
12 12 attr_reader :damage, :image
13 13  
14   - def initialize game, pos, dir, type, ship
15   - opts = WEAPONS[type][:image]
16   - opts[:rot] = dir == :down ? 180.0 : 0.0
17   - super(game, pos, opts)
18   - @pos, @dir, @type, @ship = pos, dir, type, ship
19   - @pos[0] -= size[0] / 2
20   - @damage = WEAPONS[type][:damage]
21   - @speed = WEAPONS[type][:speed]
22   - @freq = WEAPONS[type][:freq]
  14 + def initialize game, opts = {}
  15 + @params = WEAPONS[opts[:type]]
  16 + opts = @params[:image].merge(opts)
  17 + opts[:rot] = opts[:dir] == :down ? 180.0 : 0.0
  18 + opts.merge!(m: 10.0, ctype: :bullet)
  19 + super(game, opts)
  20 + p = opts[:dir] == :down ? pos.add(@shape.r) : pos.sub(@shape.r)
  21 + @body.p = CP::Vec2.new(*p)
  22 + v = options[:shooter].body.v.to_a[1].abs + @params[:speed]
  23 + @body.v += CP::Vec2.new(0.0, (options[:dir] == :down ? v : 0-v))
23 24 end
24 25  
25   - def blit
26   - if @dir == :up
27   - @pos[1] -= 1 + @speed
28   - else
29   - @pos[1] += 1 + @speed
30   - end
31   - return hide if @pos[0] < (0 - size[0]) || @pos[1] < (0 - size[1]) ||
32   - @pos[0] > @game.screen.size[0] || @pos[1] > @game.screen.size[1]
33   - if @ship.is_a?(Player)
34   - @game.enemies.each do |ship|
35   - hit(ship) if ship.collides?(self)
36   - end
37   - else
38   - hit(@game.player) if @game.player.collides?(self)
39   - end
40   - super
  26 + def draw screen
  27 + return hide if pos[0] < (0 - size[0]) || pos[1] < (0 - size[1]) ||
  28 + pos[0] > @game.screen.size[0] || pos[1] > @game.screen.size[1]
  29 + super screen
41 30 end
42 31  
43 32 def hit ship
44   - pos = [@pos[0] + size[0] / 2, @pos[1] + size[1] / 2]
45   - Explosion.new(@game, pos, image: "explosion.png", zoom: 1.0).show
  33 + p = pos; @options[:dir] == :up ? p[1] -= @shape.r : p[1] += @shape.r
  34 + Explosion.new(@game, p: p).show
46 35 ship.take_damage(damage)
47   - @ship.hit(ship, damage)
  36 + @options[:shooter].hit(ship, damage)
48 37 hide
49 38 end
50 39 end
1 1 class Drawable
2 2  
3 3 include Rubygame
  4 + include Sprites::Sprite
4 5  
5   -
6 6 DEFAULTS = {
7 7 image: "placeholder.png",
8 8 zoom: 1.0,
  9 + m: 1000.0,
  10 + p: [0.0, 0.0],
  11 + v: [0.0, 0.0],
  12 + v_limit: 1.0,
9 13 rot: 0.0,
  14 + ctype: :object,
10 15 }
11 16  
12   - attr_accessor :pos, :size
13   - def initialize game, pos, opts = {}
14   - @game, @pos, @opts = game, pos, DEFAULTS.merge(opts)
15   - @surface = Surface.load(File.join(@game.data_dir, "images", @opts[:image]))
16   - .rotozoom(@opts[:rot], @opts[:zoom]).to_display_alpha
  17 + attr_accessor :size, :shape, :body, :options
  18 + def initialize game, opts = {}
  19 + @game, @options = game, DEFAULTS.merge(opts)
  20 + @surface = Surface.load(File.join(@game.data_dir, "images", options[:image]))
  21 + .rotozoom(options[:rot], options[:zoom]).to_display_alpha
  22 + @image = @surface
  23 + @body = CP::Body.new(options[:m], 1.0)
  24 +
  25 + @shape = CP::Shape::Circle.new(@body, @surface.size[0] / 2, CP::Vec2.new(0.0, 0.0))
  26 + @shape.collision_type = options[:ctype]
  27 +
  28 + @body.p = CP::Vec2.new(*options[:p])
  29 + @body.v = CP::Vec2.new(*options[:v])
  30 + @body.v_limit = options[:v_limit]
  31 +
  32 + game.space.add_body(@body)
  33 + game.space.add_shape(@shape)
17 34 end
18 35  
  36 + def pos
  37 + @body.p.to_a
  38 + end
  39 +
  40 + def pos= p
  41 + @body.p = CP::Vec2.new(*p)
  42 + end
  43 +
  44 + def rect
  45 + Rect.new(@body.p.to_a, @surface.size)
  46 + end
  47 +
19 48 def screen
20 49 @game.screen
21 50 end
... ... @@ -24,8 +53,9 @@
24 53 @surface.size
25 54 end
26 55  
27   - def blit
28   - @surface.blit(screen, @pos)
  56 + def draw screen
  57 + @surface.blit(screen, pos.sub(@shape.r))
  58 + screen.draw_circle(pos, @shape.r, :red) if @game.options[:debug]
29 59 end
30 60  
31 61 def show
... ... @@ -43,5 +73,11 @@
43 73 ((pos[0]..pos[0]+size[0]).to_a & (other.pos[0]..other.pos[0]+other.size[0]).to_a).any? &&
44 74 ((pos[1]..pos[1]+size[1]).to_a & (other.pos[1]..other.pos[1]+other.size[1]).to_a).any?
45 75 end
  76 +
  77 + def debug
  78 + @font ||= @game.font("FreeSans", 10)
  79 + @debug = @font.render("[ #{self.class}: v: #{@body.v}, m: #{@body.m} ]", false, :white).to_display
  80 + end
  81 +
46 82 end
1 1 class Enemy < Ship
2 2  
3   - def initialize game, pos, type
4   - super game, pos, type
  3 + def initialize game, options
  4 + super game, options
  5 +
5 6 @pos = [rand(game.screen.size[0] - size[0]), 0 - size[1]]
6   - @thread =Thread.start do
  7 + @body.p = CP::Vec2.new(*@pos)
  8 + @body.v += CP::Vec2.new(0.0, @params[:speed])
  9 + @thread = Thread.start do
7 10 loop do
8   - pos = @pos.dup
9   - pos[1] += size[1]
10   - pos[0] += 10
11   - Bullet.new(@game, pos, :down, :torpedo, self).show
  11 + break unless @visible
  12 + Bullet.new(@game, p: pos.add([0.0, @shape.r * 2]), dir: :down,
  13 + type: :torpedo, shooter: self).show
12 14 sleep rand(3) + 1
13 15 end
14 16 end
15 17 end
16 18  
17   - def blit
18   - return passed if @pos[1] > @game.screen.size[1]
19   - @pos[1] += (1 + @speed).floor
20   - super
  19 + def draw screen
  20 + return passed if pos[1] > @game.screen.size[1]
  21 + super screen
21 22 end
22 23  
23 24 def passed
24 25  
25 26  
... ... @@ -27,13 +28,20 @@
27 28 @game.player.score -= @power * @game.level
28 29 @game.flash_hud :red
29 30 @game.player.destroy if @game.player.score < 0
  31 + @thread.kill
30 32 end
31 33  
32 34 def destroy
33 35 super
34   - @game.player.score += SHIPS[@type][:power]
  36 + @game.player.score += @params[:power]
35 37 @game.flash_hud :green
36 38 @thread.kill
  39 + end
  40 +
  41 + def self.generate game
  42 + e = new(game, p: [0, 0], type: [:glider, :drone, :fighter].sample)
  43 + e.show
  44 + game.enemies << e
37 45 end
38 46  
39 47 end
... ... @@ -2,29 +2,27 @@
2 2  
3 3 include Rubygame
4 4  
5   - DEFAULTS = { sound: "explosion.wav", volume: 0.5 }
6   -
7   - attr_accessor :image, :size, :pos
8   - def initialize game, pos, opts = {}, &block
  5 + DEFAULTS = { image: "explosion.png", zoom: 0.5, sound: "explosion.wav", volume: 0.5, ctype: :explosion}
  6 +
  7 + attr_accessor :image, :size
  8 + def initialize game, opts = {}, &block
9 9 opts = DEFAULTS.merge(opts)
10   - super(game, pos, opts)
  10 + super(game, opts)
11 11 @frame = 0
12 12 @block = block
13 13 @length = 20
14   - @zoom = 0.0
15   - unless game.opts[:mute]
16   - @sound = Music[@opts[:sound]]
17   - @sound.volume = @opts[:volume]
  14 + unless game.options[:mute]
  15 + @sound = Music[options[:sound]]
  16 + @sound.volume = options[:volume]
18 17 @sound.play(fade_in: 0.0, repeats: 0)
19 18 end
20 19 end
21 20  
22   - def blit
  21 + def draw screen
23 22 destroy if @frame >= @length - 2
24   - surface = @surface.zoom(@zoom)
25   - @zoom += 0.025
26   - pos = [@pos[0] - surface.size[0] / 2, @pos[1] - surface.size[1] / 2]
27   - surface.blit(screen, pos)
  23 + s = @surface.zoom(options[:zoom])
  24 + s.blit(screen, pos.sub(s.size.div(2)))
  25 + options[:zoom] += 0.025
28 26 @frame += 1
29 27 end
30 28  
... ... @@ -2,8 +2,9 @@
2 2  
3 3 attr_accessor :score
4 4  
5   - def initialize *args
6   - super *args
  5 + def initialize game, options
  6 + options[:type] = :fighter
  7 + super game, options
7 8 @score = 0
8 9 end
9 10  
1 1 class Ship < Movable
2 2  
3 3 SHIPS = {
4   - glider: { power: 50, shield: 20, speed: 2, weapons: [:laser],
  4 + glider: { power: 50, shield: 20, speed: 3, weapons: [:laser],
5 5 image: { image: "ship.png", zoom: 0.5 }},
6   - drone: { power: 100, shield: 50, speed: 0.8, weapons: [:missile],
  6 + drone: { power: 75, shield: 50, speed: 2, weapons: [:missile],
7 7 image: { image: "ship.png", zoom: 0.7 }},
8 8 fighter: { power: 100, shield: 100, speed: 1, weapons: [:laser, :torpedo, :missile],
9 9 image: { image: "ship.png", zoom: 1.0 }},
10 10 }
11 11  
12 12 attr_accessor :power, :image, :type, :debug
13   - def initialize *a, type
14   - image = SHIPS[type][:image]
  13 + def initialize game, options
  14 + @params = SHIPS[options[:type]]
  15 + options.merge!(m: 1000.0, ctype: :ship)
  16 + image = @params[:image]
15 17 image[:rot] = is_a?(Enemy) ? 0.0 : 180.0
16   - super(*a, SHIPS[type][:image])
17   - @max_power = SHIPS[type][:power]
  18 + super game, options.merge(image)
  19 + @max_power = @params[:power]
18 20 @power = @max_power
19   - @speed = SHIPS[type][:speed]
20   - @type = type
21 21 end
22 22  
23   - def blit
24   - super
25   - end
26   -
27 23 def take_damage damage
28   - @power -= damage
  24 + @power -= 20
29 25 if @power <= 0
30 26 @power = 1
31 27 destroy
32 28  
33 29  
34 30  
... ... @@ -38,22 +34,22 @@
38 34 end
39 35  
40 36 def hit other, damage
41   - @power += damage / 2
  37 + @power += 20 / 2
42 38 @power = @max_power if @power > @max_power
43 39 @debug = nil
44 40 end
45 41  
46 42 def destroy
47   - pos = [@pos[0] + size[0] / 2, @pos[1] + size[1] / 2]
48 43 @game.enemies.delete(self)
49   - Explosion.new(@game, pos, image: "explosion.png", zoom: 3.0 * @opts[:zoom], volume: @opts[:zoom]) do
  44 + Explosion.new(@game, p: pos.add(size.div(2)), image: "explosion.png",
  45 + zoom: options[:zoom] * 1.5, volume: options[:zoom]) do
50 46 hide
51 47 end.show
52 48 end
53 49  
54 50 def debug
55   - @font ||= @game.font("FreeSans", 10)
56   - @debug ||= @font.render("[ #{@type}: #{@power} ]", false, :white).to_display
  51 + text = "[ #{@type}: #{@power}, v: #{@body.v}, m: #{@body.m} ]"
  52 + @debug = @game.font("FreeSans").render(text, false, :white).to_display
57 53 end
58 54  
59 55 end