gosu_extensions 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gemspec|
4
+ gemspec.name = "gosu_extensions"
5
+ gemspec.summary = ""
6
+ gemspec.email = "florian.hanke@gmail.com"
7
+ gemspec.homepage = "http://www.github.com/floere/gosu_extensions"
8
+ gemspec.description = ""
9
+ gemspec.authors = ["Florian Hanke"]
10
+ gemspec.rdoc_options = ["--inline-source", "--charset=UTF-8"]
11
+ gemspec.files = FileList["[A-Z]*", "{generators,lib,rails,spec}/**/*"]
12
+ gemspec.add_dependency 'gosu'
13
+ gemspec.add_dependency 'chipmunk'
14
+ end
15
+ Jeweler::GemcutterTasks.new
16
+ rescue LoadError => e
17
+ puts "Jeweler not available (#{e}). Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
18
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/lib/controls.rb ADDED
@@ -0,0 +1,35 @@
1
+ # Controls for a controllable.
2
+ # Example:
3
+ # # Note: left, right, full_speed_ahead, reverse, revive are
4
+ # # methods on the @player.
5
+ # #
6
+ # @controls << Controls.new(self, @player,
7
+ # Gosu::Button::KbA => :left,
8
+ # Gosu::Button::KbD => :right,
9
+ # Gosu::Button::KbW => :full_speed_ahead,
10
+ # Gosu::Button::KbS => :reverse,
11
+ # Gosu::Button::Kb1 => :revive
12
+ # )
13
+ #
14
+ #
15
+ #
16
+ class Controls
17
+
18
+ #
19
+ #
20
+ def initialize window, controllable
21
+ @window = window
22
+ @controllable = controllable
23
+ @mapping = controllable.controls_mapping
24
+ end
25
+
26
+ #
27
+ #
28
+ def handle
29
+ return if @controllable.destroyed?
30
+ @mapping.each do |key, command|
31
+ @controllable.send(command) if @window.button_down? key
32
+ end
33
+ end
34
+
35
+ end
@@ -0,0 +1,15 @@
1
+ class Module
2
+
3
+ def manual text
4
+ metaclass.send :define_method, :manual! do
5
+ puts <<-MANUAL
6
+ MANUAL FOR #{self}
7
+ #{text}
8
+ Change #{self}.manual! -> #{self}, to not show the manual anymore.
9
+
10
+ MANUAL
11
+ self
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,9 @@
1
+ class Numeric
2
+
3
+ # Convenience method for converting from radians to a Vec2 vector.
4
+ #
5
+ def radians_to_vec2
6
+ CP::Vec2.new Math::cos(self), Math::sin(self)
7
+ end
8
+
9
+ end
@@ -0,0 +1,381 @@
1
+ # Extend this class for your game.
2
+ #
3
+ # Example:
4
+ # class MyGreatGame < GameWindow
5
+ #
6
+ class GameWindow < Gosu::Window
7
+
8
+ include InitializerHooks
9
+
10
+ attr_writer :full_screen,
11
+ :font_name,
12
+ :font_size,
13
+ :damping,
14
+ :caption,
15
+ :screen_width,
16
+ :screen_height,
17
+ :gravity
18
+ attr_reader :environment,
19
+ :moveables,
20
+ :font
21
+ attr_accessor :background_path,
22
+ :background_hard_borders
23
+
24
+ def initialize
25
+ after_initialize
26
+
27
+ super self.screen_width, self.screen_height, self.full_screen, 16
28
+
29
+ setup_window
30
+ setup_background
31
+ setup_containers
32
+ setup_steps
33
+ setup_waves
34
+ setup_scheduling
35
+ setup_font
36
+
37
+ setup_environment
38
+ setup_enemies
39
+ setup_players
40
+ setup_collisions
41
+ end
42
+
43
+ def media_path
44
+ @media_path || 'media'
45
+ end
46
+ def full_screen
47
+ @full_screen || false
48
+ end
49
+ def font_name
50
+ @font_name || Gosu::default_font_name
51
+ end
52
+ def font_size
53
+ @font_size || 20
54
+ end
55
+ def damping
56
+ @damping || 0.001
57
+ end
58
+ def caption
59
+ @caption || ""
60
+ end
61
+ def screen_width
62
+ @screen_width || DEFAULT_SCREEN_WIDTH
63
+ end
64
+ def screen_height
65
+ @screen_height || DEFAULT_SCREEN_HEIGHT
66
+ end
67
+ def gravity_vector
68
+ @gravity || @gravity = CP::Vec2.new(0, 0.98/SUBSTEPS)
69
+ end
70
+
71
+ class << self
72
+ def gravity amount = 0.98
73
+ InitializerHooks.register self do
74
+ self.gravity = CP::Vec2.new 0, amount.to_f/SUBSTEPS
75
+ end
76
+ end
77
+ def width value = DEFAULT_SCREEN_WIDTH
78
+ InitializerHooks.register self do
79
+ self.screen_width = value
80
+ end
81
+ end
82
+ def height value = DEFAULT_SCREEN_HEIGHT
83
+ InitializerHooks.register self do
84
+ self.screen_height = value
85
+ end
86
+ end
87
+ def caption text = ""
88
+ InitializerHooks.register self do
89
+ self.caption = text
90
+ end
91
+ end
92
+ def damping amount = 0.0
93
+ InitializerHooks.register self do
94
+ self.damping = amount
95
+ end
96
+ end
97
+ def font name = Gosu::default_font_name, size = 20
98
+ InitializerHooks.register self do
99
+ self.font_name = name
100
+ self.font_size = size
101
+ end
102
+ end
103
+ def background path, options = {}
104
+ InitializerHooks.register self do
105
+ self.background_path = path
106
+ self.background_hard_borders = options[:hard_borders] || false
107
+ end
108
+ end
109
+ def full_screen
110
+ InitializerHooks.register self do
111
+ self.full_screen = true
112
+ end
113
+ end
114
+ def collisions &block
115
+ raise "collisions are defined in a block" unless block_given?
116
+ InitializerHooks.register self do
117
+ @collision_definitions = block
118
+ end
119
+ end
120
+ end
121
+
122
+ def setup_window
123
+ self.caption = self.class.caption || ""
124
+ end
125
+ def setup_background
126
+ @background_image = Gosu::Image.new self, File.join(Resources.root, self.background_path), self.background_hard_borders
127
+ end
128
+ def setup_containers
129
+ @moveables = []
130
+ @controls = []
131
+ @remove_shapes = []
132
+ @players = []
133
+ end
134
+ def setup_steps
135
+ @step = 0
136
+ @dt = 1.0 / 60.0
137
+ end
138
+ def setup_waves
139
+ @waves = Waves.new self
140
+ end
141
+ def setup_scheduling
142
+ @scheduling = Scheduling.new
143
+ end
144
+ def setup_font
145
+ @font = Gosu::Font.new self, self.font_name, self.font_size
146
+ end
147
+ def setup_environment
148
+ @environment = CP::Space.new
149
+ class << @environment
150
+ attr_accessor :window
151
+ end
152
+ @environment.window = self
153
+ @environment.damping = -self.damping + 1 # recalculate the damping such that 0.0 has no damping.
154
+ end
155
+
156
+ # Override.
157
+ #
158
+ def setup_players; end
159
+ def setup_enemies; end
160
+
161
+ #
162
+ #
163
+ # Example:
164
+ # collisions do
165
+ # add_collision_func ...
166
+ #
167
+ def setup_collisions
168
+ @environment.instance_eval &@collision_definitions
169
+ end
170
+
171
+ # Add controls for a player.
172
+ #
173
+ # Example:
174
+ # add_controls_for @player1, Gosu::Button::KbA => :left,
175
+ # Gosu::Button::KbD => :right,
176
+ # Gosu::Button::KbW => :full_speed_ahead,
177
+ # Gosu::Button::KbS => :reverse,
178
+ # Gosu::Button::Kb1 => :revive
179
+ #
180
+ def add_controls_for object
181
+ @controls << Controls.new(self, object)
182
+ end
183
+
184
+
185
+ # Core methods used by the extensions "framework"
186
+ #
187
+
188
+ # The main loop.
189
+ #
190
+ # TODO implement hooks.
191
+ #
192
+ def update
193
+ @step += 1
194
+ # Step the physics environment SUBSTEPS times each update.
195
+ #
196
+ SUBSTEPS.times do
197
+ remove_shapes!
198
+ reset_forces
199
+ move_all
200
+ targeting
201
+ handle_input
202
+ step_once
203
+ end
204
+ @waves.check @step
205
+ @scheduling.step
206
+ end
207
+ # Each step, this is called to handle any input.
208
+ #
209
+ def handle_input
210
+ @controls.each &:handle
211
+ end
212
+ # Does a single step.
213
+ #
214
+ def step_once
215
+ # Perform the step over @dt period of time
216
+ # For best performance @dt should remain consistent for the game
217
+ @environment.step @dt
218
+ end
219
+
220
+ # Things unregister themselves here.
221
+ #
222
+ # Note: Use as follows in a Thing.
223
+ #
224
+ # def destroy
225
+ # threaded do
226
+ # 5.times { sleep 0.1; animate_explosion }
227
+ # @window.unregister self
228
+ # end
229
+ # end
230
+ #
231
+ def unregister thing
232
+ remove thing.shape
233
+ end
234
+
235
+ # Remove this shape the next turn.
236
+ #
237
+ # Note: Internal use. Use unregister to properly remove a moveable.
238
+ #
239
+ def remove shape
240
+ @remove_shapes << shape
241
+ end
242
+
243
+ # Run some code at relative time <time>.
244
+ #
245
+ # Example:
246
+ # # Will destroy the object that calls this method
247
+ # # in 20 steps.
248
+ # #
249
+ # window.threaded 20 do
250
+ # destroy!
251
+ # end
252
+ #
253
+ def threaded time = 1, &code
254
+ @scheduling.add time, &code
255
+ end
256
+
257
+ # Moves each moveable.
258
+ #
259
+ def move_all
260
+ @moveables.each &:move
261
+ end
262
+
263
+ # Handles the targeting process.
264
+ #
265
+ def targeting
266
+ @moveables.select { |m| m.respond_to? :target }.each do |gun|
267
+ gun.target *@moveables.select { |m| m.kind_of? Enemy }
268
+ end
269
+ end
270
+
271
+
272
+
273
+
274
+ # Utility Methods
275
+ #
276
+
277
+ #
278
+ #
279
+ # Example:
280
+ # * x, y = uniform_random_position
281
+ #
282
+ def uniform_random_position
283
+ [rand(self.width), rand(self.height)]
284
+ end
285
+
286
+ #
287
+ #
288
+ # Example:
289
+ # imprint do
290
+ # circle x, y, radius, :fill => true, :color => :black
291
+ # end
292
+ #
293
+ def imprint &block
294
+ @background_image.paint &block
295
+ end
296
+
297
+ # Randomly adds a Thing to a uniform random position.
298
+ #
299
+ def randomly_add type
300
+ thing = type.new self
301
+ thing.warp_to *uniform_random_position
302
+ register thing
303
+ end
304
+
305
+ # Moveables register themselves here.
306
+ #
307
+ def register moveable
308
+ @moveables << moveable
309
+ moveable.add_to @environment
310
+ end
311
+
312
+ def remove_shapes!
313
+ # This iterator makes an assumption of one Shape per Star making it safe to remove
314
+ # each Shape's Body as it comes up
315
+ # If our Stars had multiple Shapes, as would be required if we were to meticulously
316
+ # define their true boundaries, we couldn't do this as we would remove the Body
317
+ # multiple times
318
+ # We would probably solve this by creating a separate @remove_bodies array to remove the Bodies
319
+ # of the Stars that were gathered by the Player
320
+ #
321
+ # p @remove_shapes unless @remove_shapes.empty?
322
+ @remove_shapes.each do |shape|
323
+ @environment.remove_body shape.body
324
+ @environment.remove_shape shape
325
+ @moveables.delete_if { |moveable| moveable.shape == shape }
326
+ end
327
+ @remove_shapes.clear
328
+ end
329
+
330
+ def reset_forces
331
+ # When a force or torque is set on a Body, it is cumulative
332
+ # This means that the force you applied last SUBSTEP will compound with the
333
+ # force applied this SUBSTEP; which is probably not the behavior you want
334
+ # We reset the forces on the Player each SUBSTEP for this reason
335
+ #
336
+ # @player1.shape.body.reset_forces
337
+ # @player2.shape.body.reset_forces
338
+ # @player3.shape.body.reset_forces
339
+ # @players.each { |player| player.shape.body.reset_forces }
340
+ end
341
+
342
+ # def revive player
343
+ # return if @moveables.find { |moveable| moveable == player }
344
+ # register player
345
+ # end
346
+
347
+ # Drawing methods
348
+ #
349
+
350
+ def draw
351
+ draw_background
352
+ draw_ambient
353
+ draw_moveables
354
+ draw_ui
355
+ end
356
+ def draw_background
357
+ @background_image.draw 0, 0, Layer::Background, 1.0, 1.0 if @background_image
358
+ end
359
+ def draw_ambient
360
+
361
+ end
362
+ def draw_moveables
363
+ @moveables.each &:draw
364
+ end
365
+ def draw_ui
366
+ # @font.draw "P1 Score: ", 10, 10, ZOrder::UI, 1.0, 1.0, 0xffff0000
367
+ end
368
+
369
+ # Escape exits by default.
370
+ #
371
+ def button_down id
372
+ close if exit?(id)
373
+ end
374
+
375
+ # Override exit? if you want to define another exit rule.
376
+ #
377
+ def exit? id = nil
378
+ id == Gosu::Button::KbEscape
379
+ end
380
+
381
+ end
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'active_support'
3
+ # require 'texplay'
4
+ begin
5
+ require 'gosu'
6
+ rescue LoadError => e
7
+ puts "Couldn't find gem gosu. Install using:\n\nsudo gem install gosu.\n\n"
8
+ raise e
9
+ end
10
+ begin
11
+ require 'chipmunk' # A physics framework.
12
+ rescue LoadError => e
13
+ puts "Couldn't find gem chipmunk. Install using:\n\nsudo gem install chipmunk.\n\n"
14
+ raise e
15
+ end
16
+
17
+ require 'resources'
18
+ require 'vector_utilities'
19
+
20
+ $:.unshift File.join(File.dirname(__FILE__), '/extensions')
21
+ require 'module'
22
+ require 'numeric'
23
+
24
+ $:.unshift File.join(File.dirname(__FILE__), '/traits')
25
+ require 'it_is_a'
26
+ require 'pod'
27
+ require 'attachable'
28
+ require 'damaging'
29
+ require 'generator'
30
+ require 'initializer_hooks'
31
+ require 'lives'
32
+ require 'targeting'
33
+ require 'targeting/closest'
34
+ require 'shooter'
35
+ require 'shot'
36
+ require 'targetable'
37
+ require 'turnable'
38
+ require 'controllable'
39
+
40
+ $:.unshift File.join(File.dirname(__FILE__), '/units')
41
+ require 'thing'
42
+ require 'moveable'
43
+ require 'short_lived'
44
+
45
+ require 'controls'
46
+ require 'game_window'
47
+ require 'scheduling'
48
+ require 'waves'
49
+ require 'layer'
50
+
51
+ DEFAULT_SCREEN_WIDTH = 1200
52
+ DEFAULT_SCREEN_HEIGHT = 700
53
+ SUBSTEPS = 10
data/lib/layer.rb ADDED
@@ -0,0 +1,11 @@
1
+ # Layering of sprites.
2
+ #
3
+ # 4 Layers:
4
+ # * UI
5
+ # * Players (Controlled and AI Elements)
6
+ # * Ambient (Moveable Background)
7
+ # * Background (Fixed, true Background)
8
+ #
9
+ module Layer
10
+ Background, Ambient, Players, UI = 0, 1, 2, 3
11
+ end