ruck 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,307 @@
1
+ ## Notes from the original file:
2
+ ## Author: Dirk Johnson
3
+ ## Version: 1.0.0
4
+ ## Date: 2007-10-05
5
+ ## License: Same as for Gosu (MIT)
6
+ ## Comments: Based on the Gosu Ruby Tutorial, but incorporating the Chipmunk Physics Engine
7
+ ## See http://code.google.com/p/gosu/wiki/RubyChipmunkIntegration for the accompanying text.
8
+ ## originally http://gosu.googlecode.com/svn/trunk/examples/ChipmunkIntegration.rb
9
+
10
+ ## ruck adaptation by Tom Lieber, 2010-08-01
11
+ ##
12
+ ## all of these are now handled by shreds in a GameShreduler:
13
+ ## - physics updates
14
+ ## - star animation
15
+ ## - adding stars
16
+ ## - drawing
17
+ ## - framerate calculation
18
+
19
+ require "rubygems"
20
+ require "gosu"
21
+ require "chipmunk"
22
+ require "ruck"
23
+
24
+ include Ruck
25
+
26
+ SCREEN_WIDTH = 640
27
+ SCREEN_HEIGHT = 480
28
+
29
+ # Amount of time in seconds between physics steps.
30
+ # (6 times per frame at 60fps)
31
+ PHYSICS_STEP_SIZE = (1.0 / 60.0) / 6.0
32
+
33
+ # a real-time shreduler that expects you to wake it up every now
34
+ # and then with GameShreduler#catch_up, when it'll run all the
35
+ # shreds that were scheduled for the time period between the last
36
+ # call and now. it's not very accurate unless you're calling it often.
37
+ class GameShreduler < Ruck::Shreduler
38
+ def start_time
39
+ @start_time ||= Time.now
40
+ end
41
+
42
+ def actual_now
43
+ Time.now - start_time
44
+ end
45
+
46
+ def catch_up
47
+ run_until actual_now
48
+ end
49
+ end
50
+
51
+ class Numeric
52
+ # convenience method for converting from radians to a unit Vec2
53
+ def radians_to_vec2
54
+ CP::Vec2.new(Math::cos(self), Math::sin(self))
55
+ end
56
+ end
57
+
58
+ module ZOrder
59
+ # sprite layers
60
+ Background, Stars, Player, UI = *0..3
61
+ end
62
+
63
+ class Ship
64
+ attr_reader :shape
65
+
66
+ def initialize(window, shape)
67
+ @image = Gosu::Image.new(window, "media/Starfighter.bmp", false)
68
+ @shape = shape
69
+ @shape.body.p = CP::Vec2.new(0.0, 0.0) # position
70
+ @shape.body.v = CP::Vec2.new(0.0, 0.0) # velocity
71
+
72
+ # Keep in mind that down the screen is positive y, which means that PI/2 radians,
73
+ # which you might consider the top in the traditional Trig unit circle sense is actually
74
+ # the bottom; thus 3PI/2 is the top
75
+ @shape.body.a = (3*Math::PI/2.0) # angle in radians; faces towards top of screen
76
+ end
77
+
78
+ # Directly set the position of our ship
79
+ def warp(vect)
80
+ @shape.body.p = vect
81
+ end
82
+
83
+ # Apply negative torque
84
+ def turn_left
85
+ @shape.body.t -= 900.0
86
+ end
87
+
88
+ # Apply positive torque
89
+ def turn_right
90
+ @shape.body.t += 900.0
91
+ end
92
+
93
+ # Apply forward force
94
+ def accelerate
95
+ @shape.body.apply_force((@shape.body.a.radians_to_vec2 * 3000.0), CP::Vec2.new(0.0, 0.0))
96
+ end
97
+
98
+ # Apply even more forward force
99
+ def boost
100
+ @shape.body.apply_force((@shape.body.a.radians_to_vec2 * 6000.0), CP::Vec2.new(0.0, 0.0))
101
+ end
102
+
103
+ # Apply reverse force
104
+ def reverse
105
+ @shape.body.apply_force(-(@shape.body.a.radians_to_vec2 * 3000.0), CP::Vec2.new(0.0, 0.0))
106
+ end
107
+
108
+ # Wrap to the other side of the screen when we fly off the edge
109
+ def validate_position
110
+ @shape.body.p = CP::Vec2.new(@shape.body.p.x % SCREEN_WIDTH, @shape.body.p.y % SCREEN_HEIGHT)
111
+ end
112
+
113
+ def draw
114
+ @image.draw_rot(@shape.body.p.x, @shape.body.p.y, ZOrder::Player, @shape.body.a.radians_to_gosu)
115
+ end
116
+ end
117
+
118
+ # animated star sprite
119
+ class Star
120
+ attr_reader :shape
121
+
122
+ def initialize(animation, shape)
123
+ @animation = animation
124
+ @color = Gosu::Color.new(0xff000000)
125
+ @color.red = rand(255 - 40) + 40
126
+ @color.green = rand(255 - 40) + 40
127
+ @color.blue = rand(255 - 40) + 40
128
+ @shape = shape
129
+ @shape.body.p = CP::Vec2.new(rand * SCREEN_WIDTH, rand * SCREEN_HEIGHT) # position
130
+ @shape.body.v = CP::Vec2.new(0.0, 0.0) # velocity
131
+ @shape.body.a = (3*Math::PI/2.0) # angle in radians; faces towards top of screen
132
+
133
+ @frame = 0
134
+ @anim_shred = spork_loop do
135
+ @frame = (@frame + 1) % @animation.size
136
+ Shred.yield(0.1)
137
+ end
138
+ end
139
+
140
+ def draw
141
+ img = @animation[@frame]
142
+ img.draw(@shape.body.p.x - img.width / 2.0,
143
+ @shape.body.p.y - img.height / 2.0,
144
+ ZOrder::Stars, 1, 1, @color, :additive)
145
+ end
146
+
147
+ def kill
148
+ @anim_shred.kill
149
+ end
150
+ end
151
+
152
+ class GameWindow < Gosu::Window
153
+ def initialize
154
+ super(SCREEN_WIDTH, SCREEN_HEIGHT, false, 16)
155
+ self.caption = "Gosu & Chipmunk & ruck Integration Demo"
156
+
157
+ @score = 0
158
+ @stars = Array.new
159
+ @dead_star_shapes = []
160
+
161
+ ruck_start
162
+
163
+ load_resources
164
+
165
+ setup_physics
166
+ end
167
+
168
+ def ruck_start
169
+ @shreduler = GameShreduler.new
170
+ @shreduler.make_convenient
171
+
172
+ spork_loop(PHYSICS_STEP_SIZE) do
173
+ physics_update
174
+ end
175
+
176
+ spork_loop do
177
+ if @stars.length < 25
178
+ body = CP::Body.new(0.0001, 0.0001)
179
+ shape = CP::Shape::Circle.new(body, 25/2, CP::Vec2.new(0.0, 0.0))
180
+ shape.collision_type = :star
181
+
182
+ @space.add_body(body)
183
+ @space.add_shape(shape)
184
+
185
+ @stars.push(Star.new(@star_animation_frames, shape))
186
+ end
187
+
188
+ Shred.yield(rand * 1)
189
+ end
190
+
191
+ spork_loop do
192
+ Shred.wait_on(:frame)
193
+
194
+ @background_image.draw(0, 0, ZOrder::Background)
195
+ @ship.draw
196
+ @stars.each { |star| star.draw }
197
+ @font.draw("Score: #{@score}", 10, 10, ZOrder::UI, 1.0, 1.0, 0xffffff00)
198
+ @font.draw("Framerate: #{@framerate}", 10, 33, ZOrder::UI, 1.0, 1.0, 0xffffff00)
199
+ end
200
+
201
+ @framerate = 0.0
202
+ @frames = 0
203
+ spork_loop do
204
+ Shred.wait_on(:frame)
205
+ @frames += 1
206
+ @framerate = @frames / @shreduler.actual_now
207
+ end
208
+ end
209
+
210
+ def load_resources
211
+ @background_image = Gosu::Image.new(self, "media/Space.png", true)
212
+ @beep = Gosu::Sample.new(self, "media/Beep.wav")
213
+ @font = Gosu::Font.new(self, Gosu::default_font_name, 20)
214
+ @star_animation_frames = Gosu::Image::load_tiles(self, "media/Star.png", 25, 25, false)
215
+ end
216
+
217
+ def setup_physics
218
+ @space = CP::Space.new
219
+ @space.damping = 0.2
220
+
221
+ # create the body and shape for the ship
222
+ body = CP::Body.new(10.0, 150.0) # mass, inertia
223
+ shape_array = [CP::Vec2.new(-25.0, -25.0), # the "top" is towards 0 radians (the right)
224
+ CP::Vec2.new(-25.0, 25.0),
225
+ CP::Vec2.new(25.0, 1.0),
226
+ CP::Vec2.new(25.0, -1.0)]
227
+ shape = CP::Shape::Poly.new(body, shape_array, CP::Vec2.new(0,0))
228
+ shape.collision_type = :ship
229
+ @space.add_body(body)
230
+ @space.add_shape(shape)
231
+
232
+ @ship = Ship.new(self, shape)
233
+ @ship.warp(CP::Vec2.new(SCREEN_WIDTH/2, SCREEN_HEIGHT/2))
234
+
235
+ # we cannot remove shapes or bodies from space within a collision closure,
236
+ # so we save them in @dead_star_shapes to be removed in physics_update
237
+ @space.add_collision_func(:ship, :star) do |ship_shape, star_shape|
238
+ @score += 10
239
+ @beep.play
240
+ @dead_star_shapes << star_shape
241
+ end
242
+
243
+ # prevent stars from bumping into one another
244
+ @space.add_collision_func(:star, :star, &nil)
245
+ end
246
+
247
+ def update
248
+ GC.enable
249
+ GC.start
250
+ GC.disable
251
+ end
252
+
253
+ def draw
254
+ @shreduler.catch_up
255
+ @shreduler.raise_all :frame
256
+ @shreduler.catch_up
257
+ end
258
+
259
+ def physics_update
260
+ @dead_star_shapes.each do |shape|
261
+ @stars.delete_if do |star|
262
+ if star.shape == shape
263
+ star.kill
264
+ true
265
+ end
266
+ end
267
+ @space.remove_body(shape.body)
268
+ @space.remove_shape(shape)
269
+ end
270
+ @dead_star_shapes.clear
271
+
272
+ # forces are cumulative, so reset them for this update
273
+ @ship.shape.body.reset_forces
274
+
275
+ # Wrap around the screen to the other side
276
+ @ship.validate_position
277
+
278
+ # apply forces to ship based on keyboard state
279
+ if button_down? Gosu::KbLeft
280
+ @ship.turn_left
281
+ end
282
+ if button_down? Gosu::KbRight
283
+ @ship.turn_right
284
+ end
285
+
286
+ if button_down? Gosu::KbUp
287
+ if ( (button_down? Gosu::KbRightShift) || (button_down? Gosu::KbLeftShift) )
288
+ @ship.boost
289
+ else
290
+ @ship.accelerate
291
+ end
292
+ elsif button_down? Gosu::KbDown
293
+ @ship.reverse
294
+ end
295
+
296
+ @space.step(PHYSICS_STEP_SIZE)
297
+ end
298
+
299
+ def button_down(id)
300
+ if id == Gosu::KbEscape
301
+ close
302
+ end
303
+ end
304
+ end
305
+
306
+ window = GameWindow.new
307
+ window.show
@@ -1,8 +1,8 @@
1
1
 
2
2
  module Ruck
3
- require "logger"
4
- LOG = Logger.new(STDOUT)
5
- LOG.level = Logger::WARN
6
3
  end
7
4
 
8
- require File.join(File.dirname(__FILE__), "ruck", "shreduling")
5
+ require File.join(File.dirname(__FILE__), "ruck", "clock")
6
+ require File.join(File.dirname(__FILE__), "ruck", "event_clock")
7
+ require File.join(File.dirname(__FILE__), "ruck", "shreduler")
8
+ require File.join(File.dirname(__FILE__), "ruck", "shred")
@@ -0,0 +1,137 @@
1
+
2
+ require "rubygems"
3
+ require "priority_queue"
4
+
5
+ module Ruck
6
+
7
+ # Clock keeps track of occurrences on a virtual timeline. Clocks
8
+ # can be configured to run fast or slow relative to another clock
9
+ # by changing their relative_rate and providing them a parent via
10
+ # add_child_clock.
11
+ #
12
+ # Clocks and their sub-clocks always tell the same time. When
13
+ # fast_forward is called, they advance in lock-step. You should only
14
+ # call fast_forward on the root of any tree of Clocks.
15
+ #
16
+ # = A warning about fast_forward and time travel
17
+ #
18
+ # When using a Clock with no children, there's little reason to ever
19
+ # call fast_forward because in that case Clock is little more than
20
+ # a priority queue. When using a Clock with children, before ever
21
+ # changing a Clock's relative_rate, you should fast_forward to the
22
+ # VIRTUAL instant that change is meant to take place. This ensures
23
+ # that the change happens at that time and future occurrences are
24
+ # scheduled correctly.
25
+ #
26
+ # (For an example of why this is important, consider two connected
27
+ # clocks, where the child's relative_rate is 1.0. If 5 time units in,
28
+ # the relative_rate is changed to 5,000 and fast_forward(5) isn't
29
+ # called, the first 5 time units of the child's clock are also
30
+ # affected by the change, and some occurrences could afterward take
31
+ # place at t < 5.)
32
+
33
+ class Clock
34
+ attr_reader :now # current time in this clock's units
35
+ attr_accessor :relative_rate # rate relative to parent clock
36
+
37
+ def initialize(relative_rate = 1.0)
38
+ @relative_rate = relative_rate
39
+ @now = 0
40
+ @children = []
41
+ @occurrences = PriorityQueue.new
42
+ end
43
+
44
+ # fast-forward this clock and all children clocks by the given time delta
45
+ def fast_forward(dt)
46
+ adjusted_dt = dt * @relative_rate
47
+ @now += adjusted_dt
48
+ @children.each { |sub_clock| sub_clock.fast_forward(adjusted_dt) }
49
+ end
50
+
51
+ # adds the given clock as a child of this one. a clock should only be
52
+ # the child of one other clock, please.
53
+ def add_child_clock(clock)
54
+ @children << clock
55
+ clock
56
+ end
57
+
58
+ # schedules an occurrence at the given time with the given object,
59
+ # defaulting to the current time
60
+ def schedule(obj, time = nil)
61
+ @occurrences[obj] = time || now
62
+ end
63
+
64
+ # dequeues the earliest occurrence from this clock or any child clocks.
65
+ # returns nil if it wasn't there, or its relative_time otherwise
66
+ def unschedule(obj)
67
+ if @occurrences[obj]
68
+ obj, time = @occurrences.delete obj
69
+ unscale_time(time)
70
+ else
71
+ relative_time = @children.first_non_nil { |clock| clock.unschedule(obj) }
72
+ unscale_relative_time(relative_time) if relative_time
73
+ end
74
+ end
75
+
76
+ # returns [obj, relative_time], where relative_time is the offset from
77
+ # now in parent's time units
78
+ def next
79
+ clock, (obj, relative_time) = next_with_clock
80
+ [obj, relative_time] if obj
81
+ end
82
+
83
+ # unschedules and returns the next object as [obj, relative_time],
84
+ # where relative_time is the offset from now in parent's time units
85
+ def unschedule_next
86
+ clock, (obj, relative_time) = next_with_clock
87
+ if obj
88
+ clock.unschedule(obj)
89
+ [obj, relative_time]
90
+ end
91
+ end
92
+
93
+ protected
94
+
95
+ # returns [clock, [obj, relative_time]]
96
+ def next_with_clock
97
+ min = nil
98
+
99
+ if @occurrences.length > 0
100
+ obj, time = @occurrences.min
101
+ min = [self, [obj, unscale_time(time)]]
102
+ end
103
+
104
+ # earliest occurrence of each child, converting to absolute time
105
+ @children.each do |child_clock|
106
+ obj, relative_time = child_clock.next
107
+ next unless obj
108
+ relative_time = unscale_relative_time(relative_time)
109
+ min = [child_clock, [obj, relative_time]] if min.nil? || relative_time < min[1][1]
110
+ end
111
+
112
+ min
113
+ end
114
+
115
+ # convert an absolute time in this clock's units to an offset from
116
+ # now in parent clock's units
117
+ def unscale_time(time)
118
+ unscale_relative_time(time - now)
119
+ end
120
+
121
+ # convert an offset from now in this clock's units to a time
122
+ # delta from now in parent clock's units
123
+ def unscale_relative_time(relative_time)
124
+ relative_time / @relative_rate.to_f
125
+ end
126
+ end
127
+ end
128
+
129
+ module Enumerable
130
+ def first_non_nil
131
+ each do |o|
132
+ val = yield o
133
+ return val if val
134
+ end
135
+ nil
136
+ end
137
+ end