rubygame 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # This program is released to the PUBLIC DOMAIN.
4
+ # It is distributed in the hope that it will be useful,
5
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
6
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7
+
8
+ require "rubygame"
9
+ include Rubygame
10
+
11
+ $stdout.sync = true
12
+
13
+ Rubygame.init()
14
+
15
+
16
+ # SDL_gfx is required for drawing shapes and rotating/zooming Surfaces.
17
+ $gfx_ok = (VERSIONS[:sdl_gfx] != nil)
18
+
19
+ unless ( $gfx_ok )
20
+ raise "You must have SDL_gfx support to run this demo!"
21
+ end
22
+
23
+
24
+
25
+ # Set up autoloading for Surfaces. Surfaces will be loaded automatically
26
+ # the first time you use Surface["filename"]. Check out the docs for
27
+ # Rubygame::NamedResource for more info about that.
28
+ #
29
+ Surface.autoload_dirs = [ File.dirname(__FILE__) ]
30
+
31
+
32
+
33
+ screen = Screen.set_mode([320,240])
34
+ screen.title = "Drawing test"
35
+
36
+
37
+
38
+ # Make the background surface. We'll draw on this, then blit (copy) it
39
+ # onto the screen. We'll also use it as the background for "erasing"
40
+ # the pandas from their old positions each frame.
41
+ background = Surface.new( screen.size )
42
+
43
+
44
+ # Filling with colors in a variety of ways.
45
+ # See the Rubygame::Color module for more info.
46
+ def fill_background( background )
47
+ background.fill( Color::ColorRGB.new([0.1, 0.2, 0.35]) )
48
+ background.fill( :black, [70,120,80,80] )
49
+ background.fill( "dark red", [80,110,80,80] )
50
+ end
51
+
52
+
53
+ # Draw a bunch of shapes to try out the drawing methods
54
+ def draw_some_shapes( background )
55
+ # Create a new surface
56
+ a = Surface.new([100,100])
57
+
58
+ # Fill it with blue
59
+ a.fill([70,70,255])
60
+
61
+ # Fill a specific part of it with a different blue
62
+ rect1 = Rect.new([3,3,94,94])
63
+ a.fill([40,40,150],rect1)
64
+
65
+ # Draw a black box with white almost at the edges
66
+ a.draw_box_s( [30,30], [70,70], [0,0,0] )
67
+ a.draw_box( [31,31], [69,69], [255,255,255] )
68
+
69
+ # Draw a circle in the box
70
+ a.draw_circle_s( [50,50], 10, [100,150,200] )
71
+
72
+ # Two diagonal white lines, the right anti-aliased, the left not
73
+ a.draw_line([31,69],[49,31],[255,255,255])
74
+ a.draw_line_a([49,31],[69,69],[255,255,255])
75
+
76
+ # Finally, blit (copy) this interesting surface onto
77
+ # the background image
78
+ a.blit(background,[50,50],[0,0,90,80])
79
+ end
80
+
81
+
82
+ # Draw a filled pentagon with a lighter border
83
+ def draw_pentagon( background )
84
+ points = [ [50,150], [100,140], [150,160], [120,180], [60,170] ]
85
+ background.draw_polygon_s( points, [100,100,100] )
86
+ background.draw_polygon_a( points, [200,200,200] )
87
+ end
88
+
89
+
90
+ # Draw a pepperoni pizza! (Use your imagination...)
91
+ def draw_pizza( background )
92
+ # Crust
93
+ background.draw_arc_s( [250,200], 34, [210,150], [180,130,50])
94
+
95
+ # Cheese -- similar to the crust, but different radius and color
96
+ background.draw_arc_s( [250,200], 30, [210,150], [230,180,80])
97
+
98
+ # Pepperonis
99
+ background.draw_circle_s( [240,180], 4, :dark_red )
100
+ background.draw_circle_s( [265,185], 4, :dark_red )
101
+ background.draw_circle_s( [258,200], 4, :dark_red )
102
+ background.draw_circle_s( [240,215], 4, :dark_red )
103
+ background.draw_circle_s( [260,220], 4, :dark_red )
104
+ end
105
+
106
+
107
+ # _Try_ to draw an anti-aliased, solid ellipse, but it doesn't work
108
+ # well. If you look closely at the white ellipse, you can see that it
109
+ # isn't anti-aliased on the left and right side, and there are some
110
+ # black specks on the top and bottom where the two ellipses don't
111
+ # quite match.
112
+ #
113
+ # It sure would be nice if SDL_gfx had anti-aliased solid shapes...
114
+ #
115
+ def draw_antialiased_filled_ellipse( background )
116
+ background.draw_ellipse_s([200,150],[30,25], :beige )
117
+ background.draw_ellipse_a([200,150],[30,25], :beige )
118
+ end
119
+
120
+
121
+ # Render some text with SFont (image-based font)
122
+ def render_sfont_text( background )
123
+ require "rubygame/sfont"
124
+ sfont = SFont.new( Surface["term16.png"] )
125
+ result = sfont.render( "This is some SFont text!" )
126
+ result.blit( background, [10,10] )
127
+ end
128
+
129
+
130
+ # Render some text with TTF (vector-based font)
131
+ def render_ttf_text( background )
132
+ TTF.setup()
133
+ ttfont_path = File.join(File.dirname(__FILE__),"FreeSans.ttf")
134
+ ttfont = TTF.new( ttfont_path, 20 )
135
+
136
+ result = ttfont.render( "This is some TTF text!", true, [250,250,250] )
137
+ result.blit( background, [20,200] )
138
+ end
139
+
140
+
141
+ # Create another surface to test transparency blitting
142
+ def do_transparent_blit( background )
143
+ b = Surface.new([200,50])
144
+ b.fill([150,20,40])
145
+ b.set_alpha(123)# approx. half transparent
146
+ b.blit(background,[20,40])
147
+ end
148
+
149
+
150
+ # Call all those functions to draw on the background.
151
+ # Try commenting some of these out or reordering them to
152
+ # see what happens!
153
+
154
+ fill_background( background )
155
+ draw_some_shapes( background )
156
+ draw_pentagon( background )
157
+ draw_pizza( background )
158
+ draw_antialiased_filled_ellipse( background )
159
+ render_sfont_text( background )
160
+ render_ttf_text( background )
161
+ do_transparent_blit( background )
162
+
163
+
164
+ # Now blit the background onto the screen and update the screen once.
165
+ # During the loop, we'll use 'dirty rect' updating to refresh only the
166
+ # parts of the screen that have changed.
167
+ background.blit(screen,[0,0])
168
+
169
+ screen.update()
170
+
171
+
172
+
173
+ queue = EventQueue.new()
174
+ queue.ignore = [ActiveEvent,MouseMotionEvent,MouseUpEvent,MouseDownEvent]
175
+ queue.wait()
@@ -5,30 +5,101 @@
5
5
  # but WITHOUT ANY WARRANTY; without even the implied warranty of
6
6
  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7
7
 
8
- # This script is messy, but it demonstrates almost all of
9
- # Rubygame's features, so it acts as a test program to see
10
- # whether your installation of Rubygame is working.
8
+
9
+ #
10
+ # This demo shows a simple example of game structure.
11
+ # It demonstrates how to use (or, one of many ways to use):
12
+ #
13
+ # * Clock to limit the maximum framerate (to keep CPU usage low)
14
+ #
15
+ # * Sprites to display player characters on the screen
16
+ #
17
+ # * EventQueue, EventHandler, and the HasEventHandler mixin to
18
+ # receive events from the keyboard, joystick, etc.
19
+ #
20
+ # * A custom Game class to integrate it all and provide the game
21
+ # structure and main loop.
22
+ #
23
+
11
24
 
12
25
  require "rubygame"
26
+
27
+
28
+ # Include these modules so we can type "Surface" instead of
29
+ # "Rubygame::Surface", etc. Purely for convenience/readability.
30
+
13
31
  include Rubygame
32
+ include Rubygame::Events
33
+ include Rubygame::EventActions
34
+ include Rubygame::EventTriggers
35
+
14
36
 
37
+ # Make text we output appear on the console right away.
15
38
  $stdout.sync = true
16
39
 
40
+
17
41
  # Use smooth scaling/rotating? You can toggle this with S key
18
- $smooth = false
42
+ $smooth = false
43
+
19
44
 
45
+ # Make sure everything is set up properly.
20
46
  Rubygame.init()
21
47
 
22
- queue = EventQueue.new() # new EventQueue with autofetch
23
- queue.ignore = [MouseMotionEvent]
24
- clock = Clock.new()
25
- clock.target_framerate = 50
26
48
 
27
- unless ($gfx_ok = (VERSIONS[:sdl_gfx] != nil))
28
- raise "SDL_gfx is not available. Bailing out."
49
+ # SDL_gfx is required for drawing shapes and rotating/zooming Surfaces.
50
+ $gfx_ok = (VERSIONS[:sdl_gfx] != nil)
51
+
52
+ unless ( $gfx_ok )
53
+ raise "You must have SDL_gfx support to run this demo!"
54
+ end
55
+
56
+
57
+ # Activate all joysticks so that their button press
58
+ # events, etc. appear in the event queue.
59
+ Joystick.activate_all
60
+
61
+
62
+
63
+
64
+ ########################
65
+ # CUSTOM EVENT CLASSES #
66
+ ########################
67
+
68
+
69
+ # Holds information about the clock, created each frame.
70
+ class ClockTicked
71
+ attr_reader :time, :framerate
72
+
73
+ def initialize( ms, framerate )
74
+ @time = ms / 1000.0
75
+ @framerate = framerate
76
+ end
77
+ end
78
+
79
+ # Signals sprites to draw themselves on the screen
80
+ class DrawSprites
81
+ attr_accessor :screen
82
+ def initialize( screen )
83
+ @screen = screen
84
+ end
85
+ end
86
+
87
+ # Signals sprites to erase themselves from the screen
88
+ class UndrawSprites
89
+ attr_accessor :screen, :background
90
+ def initialize( screen, background )
91
+ @screen, @background = screen, background
92
+ end
29
93
  end
30
94
 
31
95
 
96
+
97
+
98
+ ######################
99
+ # AUTOLOADING IMAGES #
100
+ ######################
101
+
102
+
32
103
  # Set up autoloading for Surfaces. Surfaces will be loaded automatically
33
104
  # the first time you use Surface["filename"]. Check out the docs for
34
105
  # Rubygame::NamedResource for more info about that.
@@ -36,13 +107,23 @@ end
36
107
  Surface.autoload_dirs = [ File.dirname(__FILE__) ]
37
108
 
38
109
 
110
+
111
+
112
+ #################
113
+ # PANDA CLASSES #
114
+ #################
115
+
116
+
117
+ # Base class for our panda sprites. This provides the core
118
+ # logic for initialization and movement of the sprites.
39
119
  class Panda
40
120
  include Sprites::Sprite
41
-
42
- # Autoload the "panda.png" image and set its colorkey
121
+ include EventHandler::HasEventHandler
122
+
123
+ # Autoload the "panda.png" image and set its colorkey
43
124
  @@pandapic = Surface["panda.png"]
44
125
  @@pandapic.set_colorkey(@@pandapic.get_at(0,0))
45
-
126
+
46
127
  attr_accessor :vx, :vy, :speed
47
128
  def initialize(x,y)
48
129
  super()
@@ -50,24 +131,29 @@ class Panda
50
131
  @speed = 40
51
132
  @image = @@pandapic
52
133
  @rect = Rect.new(x,y,*@@pandapic.size)
134
+
53
135
  end
54
136
 
55
137
  def update_image(time)
56
138
  # do nothing in base class, rotate/zoom image in subs
57
139
  end
58
140
 
59
- def update(time)
141
+ def update( event )
60
142
  x,y = @rect.center
61
- self.update_image(time)
143
+ self.update_image( event.time * 1000.0 )
62
144
  @rect.size = @image.size
63
-
64
- base = @speed * time/1000.0
145
+
146
+ base = @speed * event.time
65
147
  @rect.centerx = x + @vx * base
66
148
  @rect.centery = y + @vy * base
67
149
  end
68
150
 
69
151
  end
70
152
 
153
+
154
+ # A panda that spins around and around. The update_image
155
+ # method is called once per frame to generate the new
156
+ # image (in this case by rotating the original image).
71
157
  class SpinnyPanda < Panda
72
158
  attr_accessor :rate
73
159
  def initialize(x,y,rate=0.1)
@@ -82,6 +168,9 @@ class SpinnyPanda < Panda
82
168
  end
83
169
  end
84
170
 
171
+
172
+ # A panda that grows and shrinks in size. Like the other
173
+ # panda classes, it updates its image every frame.
85
174
  class ExpandaPanda < Panda
86
175
  attr_accessor :rate
87
176
  def initialize(x,y,rate=0.1)
@@ -97,6 +186,9 @@ class ExpandaPanda < Panda
97
186
  end
98
187
  end
99
188
 
189
+
190
+ # A panda that wobbles and jiggles. Like the other
191
+ # panda classes, it updates its image every frame.
100
192
  class WobblyPanda < Panda
101
193
  attr_accessor :rate
102
194
  def initialize(x,y,rate=0.1)
@@ -113,184 +205,348 @@ class WobblyPanda < Panda
113
205
  end
114
206
  end
115
207
 
116
- pandas = Sprites::Group.new
117
- pandas.extend(Sprites::UpdateGroup)
118
- pandas.extend(Sprites::DepthSortGroup)
119
-
120
- # Create the SDL window
121
- screen = Screen.set_mode([320,240])
122
- screen.title = "Rubygame test"
123
- screen.show_cursor = false;
124
208
 
125
209
  # Create the very cute panda objects!
126
210
  panda1 = SpinnyPanda.new(100,50)
127
211
  panda2 = ExpandaPanda.new(150,50)
128
212
  panda3 = WobblyPanda.new(200,50,0.5)
129
213
 
214
+ # Set their depths. This affects which one appears in front
215
+ # of the other in case they overlap.
130
216
  panda1.depth = 0 # in between the others
131
217
  panda2.depth = 10 # behind both of the others
132
218
  panda3.depth = -10 # in front of both of the others
133
219
 
134
- # Put the pandas in a sprite group
220
+
221
+
222
+
223
+ ###############
224
+ # PANDA GROUP #
225
+ ###############
226
+
227
+
228
+ # Create a spritegroup to manage the pandas.
229
+ pandas = Sprites::Group.new
230
+ pandas.extend(Sprites::UpdateGroup)
231
+ pandas.extend(Sprites::DepthSortGroup)
232
+
233
+ # Add the pandas to the group.
135
234
  pandas.push(panda1,panda2,panda3)
136
235
 
137
- # Make the background surface
138
- background = Surface.new(screen.size)
139
236
 
140
- # Filling with colors in a variety of ways
237
+ # Extend the pandas group with event hooks.
238
+ class << pandas
239
+ include EventHandler::HasEventHandler
240
+
241
+ # Draw all the sprites and refresh
242
+ # those parts of the screen
243
+ def do_draw( event )
244
+ dirty_rects = draw( event.screen )
245
+ event.screen.update_rects(dirty_rects)
246
+ end
247
+
248
+ # Erase the sprites from the screen by
249
+ # drawing over them with the background.
250
+ def do_undraw( event )
251
+ undraw( event.screen, event.background )
252
+ end
253
+ end
254
+
255
+ pandas.make_magic_hooks( ClockTicked => :update,
256
+ DrawSprites => :do_draw,
257
+ UndrawSprites => :do_undraw )
258
+
259
+
260
+
261
+ ##########
262
+ # SCREEN #
263
+ ##########
264
+
265
+
266
+ # Create the SDL window
267
+ screen = Screen.set_mode([320,240])
268
+ screen.title = "Rubygame test"
269
+ screen.show_cursor = false;
270
+
271
+
272
+
273
+
274
+ ###############
275
+ # BACKGROUND #
276
+ ###############
277
+
278
+
279
+ # Make the background surface. We'll draw on this, then blit (copy) it
280
+ # onto the screen. We'll also use it as the background for "erasing"
281
+ # the pandas from their old positions each frame.
282
+ background = Surface.new( screen.size )
283
+
284
+ # Fill the background with a nice blue color.
141
285
  background.fill( Color::ColorRGB.new([0.1, 0.2, 0.35]) )
142
- background.fill( :black, [70,120,80,80] )
143
- background.fill( "dark red", [80,110,80,80] )
144
-
145
- # Create and test a new surface
146
- a = Surface.new([100,100])
147
-
148
- # Draw a bunch of shapes on the new surface to try out the drawing module
149
- a.fill([70,70,255])
150
- rect1 = Rect.new([3,3,94,94])
151
- a.fill([40,40,1500],rect1)
152
- a.draw_box_s([30,30],[70,70],[0,0,0])
153
- a.draw_box([31,31],[69,69],[255,255,255])
154
- a.draw_circle_s([50,50],10,[100,150,200])
155
- # Two diagonal white lines, the right anti-aliased, the left not.
156
- a.draw_line([31,69],[49,31],[255,255,255])
157
- a.draw_line_a([49,31],[69,69],[255,255,255])
158
- # Finally, copy this interesting surface onto the background image
159
- a.blit(background,[50,50],[0,0,90,80])
160
-
161
- # Draw some shapes on the background for fun
162
- # ... a filled pentagon with a lighter border
163
- background.draw_polygon_s(\
164
- [[50,150],[100,140],[150,160],[120,180],[60,170]],\
165
- [100,100,100])
166
- background.draw_polygon_a(\
167
- [[50,150],[100,140],[150,160],[120,180],[60,170]],\
168
- [200,200,200])
169
- # ... a pepperoni pizza!! (if you use your imagination...)
170
- background.draw_arc_s([250,200],34,[210,150],[180,130,50])
171
- background.draw_arc_s([250,200],30,[210,150],[230,180,80])
172
- background.draw_circle_s( [240,180], 4, :dark_red )
173
- background.draw_circle_s( [265,185], 4, :dark_red )
174
- background.draw_circle_s( [258,200], 4, :dark_red )
175
- background.draw_circle_s( [240,215], 4, :dark_red )
176
- background.draw_circle_s( [260,220], 4, :dark_red )
177
-
178
- # _Try_ to make an anti-aliased, filled ellipse, but it doesn't work well.
179
- # If you look closely at the white ellipse, you can see that it isn't
180
- # AA on the left and right side, and there are some black specks on the top
181
- # and bottom where the two ellipses don't quite match.
182
- background.draw_ellipse_s([200,150],[30,25], :beige )
183
- background.draw_ellipse_a([200,150],[30,25], :beige )
184
-
185
- # Let's make some labels
186
- require "rubygame/sfont"
187
- sfont = SFont.new( Surface["term16.png"] )
188
- sfont.render("Arrow keys move the spinning panda!").blit(background,[10,10])
189
286
 
287
+
288
+ # Render instructions with TTF (TrueType Font)
190
289
  TTF.setup()
191
290
  ttfont_path = File.join(File.dirname(__FILE__),"FreeSans.ttf")
192
- ttfont = TTF.new( ttfont_path, 20 )
193
- ttfont.render("This is some TTF text!",true,[250,250,250]).blit(background,[20,200])
291
+ ttfont = TTF.new( ttfont_path, 14 )
194
292
 
293
+ ttfont.render( "Use arrow keys or joystick to move pandas.",
294
+ true, [250,250,250] ).blit( background, [20,160] )
295
+
296
+ ttfont.render( "Press escape or q to quit.",
297
+ true, [250,250,250] ).blit( background, [20,180] )
195
298
 
196
- # Create another surface to test transparency blitting
197
- b = Surface.new([200,50])
198
- b.fill([150,20,40])
199
- b.set_alpha(123)# approx. half transparent
200
- b.blit(background,[20,40])
201
- background.blit(screen,[0,0])
202
299
 
203
- # Refresh the screen once. During the loop, we'll use 'dirty rect' updating
204
- # to refresh only the parts of the screen that have changed.
300
+ # Now blit the background onto the screen and update the screen once.
301
+ # During the loop, we'll use 'dirty rect' updating to refresh only the
302
+ # parts of the screen that have changed.
303
+ background.blit(screen,[0,0])
205
304
  screen.update()
206
305
 
207
- if Joystick.num_joysticks > 0
208
- Joystick.new(0) # So that joystick events will appear on the queue
306
+
307
+
308
+
309
+ ############################
310
+ # EVENT HOOKS AND HANDLING #
311
+ ############################
312
+
313
+
314
+ # Factory methods for creating event triggers
315
+
316
+ # Returns a trigger that matches the released key event
317
+ def released( key )
318
+ return KeyReleaseTrigger.new( key )
209
319
  end
210
320
 
211
- update_time = 0
212
- framerate = 0
213
-
214
- catch(:rubygame_quit) do
215
- loop do
216
- queue.each do |event|
217
- case event
218
- when KeyDownEvent
219
- case event.key
220
- when K_ESCAPE
221
- throw :rubygame_quit
222
- when K_Q
223
- throw :rubygame_quit
224
- when K_UP
225
- panda1.vy = -1
226
- when K_DOWN
227
- panda1.vy = 1
228
- when K_LEFT
229
- panda1.vx = -1
230
- when K_RIGHT
231
- panda1.vx = 1
232
- when K_S
233
- $smooth = !$smooth
234
- puts "#{$smooth?'En':'Dis'}abling smooth scale/rotate."
235
- else
236
- print "%s"%[event.string]
237
- end
238
- when KeyUpEvent
239
- case event.key
240
- when K_UP
241
- panda1.vy = 0
242
- when K_DOWN
243
- panda1.vy = 0
244
- when K_LEFT
245
- panda1.vx = 0
246
- when K_RIGHT
247
- panda1.vx = 0
248
- end
249
- when ActiveEvent
250
- # ActiveEvent appears when the window gains or loses focus.
251
- # This helps to ensure everything is refreshed after the Rubygame
252
- # window has been covered up by a different window.
253
- screen.update()
254
- when QuitEvent
255
- throw :rubygame_quit
256
- when MouseDownEvent
257
- puts "click: [%d,%d]"%event.pos
258
- when JoyDownEvent
259
- case event.button
260
- when 4; panda1.speed = 80
261
- when 5; panda2.speed = 80
262
- end
263
- #puts "jdown: %d"%[event.button]
264
- when JoyUpEvent
265
- case event.button
266
- when 4; panda1.speed = 40
267
- when 5; panda2.speed = 40
268
- end
269
- #puts "jup: %d"%[event.button]
270
- when JoyAxisEvent
271
- # max = 32767
272
- case(event.axis)
273
- when 0; panda1.vx = event.value / 32767.0
274
- when 1; panda1.vy = event.value / 32767.0
275
- when 2; panda2.vx = event.value / 32767.0
276
- when 3; panda2.vy = event.value / 32767.0
277
- end
278
- #puts "jaxis: %d %d"%[event.axis,event.value]
321
+
322
+ # Returns a trigger that matches the joystick axis event.
323
+ # There are no built-in joystick event triggers in Rubygame
324
+ # yet, sorry.
325
+ def joyaxis( axis )
326
+ return AndTrigger.new( InstanceOfTrigger.new( JoystickAxisMoved ),
327
+ AttrTrigger.new(:joystick_id => 0,
328
+ :axis => axis))
329
+ end
330
+
331
+
332
+ # Returns a trigger that matches the joystick button press event.
333
+ def joypressed( button )
334
+ return AndTrigger.new( InstanceOfTrigger.new( JoystickButtonPressed ),
335
+ AttrTrigger.new(:joystick_id => 0,
336
+ :button => button))
337
+ end
338
+
339
+
340
+ # Returns a trigger that matches the joystick button press event.
341
+ def joyreleased( button )
342
+ return AndTrigger.new( InstanceOfTrigger.new( JoystickButtonReleased ),
343
+ AttrTrigger.new(:joystick_id => 0,
344
+ :button => button))
345
+ end
346
+
347
+
348
+ #######################
349
+ # PANDA 1 EVENT HOOKS #
350
+ #######################
351
+
352
+ hooks = {
353
+ # Start moving when an arrow key is pressed
354
+ :up => proc { |owner, event| owner.vy = -1 },
355
+ :down => proc { |owner, event| owner.vy = 1 },
356
+ :left => proc { |owner, event| owner.vx = -1 },
357
+ :right => proc { |owner, event| owner.vx = 1 },
358
+
359
+ # Stop moving when the arrow key is released
360
+ released( :up ) => proc { |owner, event| owner.vy = 0 },
361
+ released( :down ) => proc { |owner, event| owner.vy = 0 },
362
+ released( :left ) => proc { |owner, event| owner.vx = 0 },
363
+ released( :right ) => proc { |owner, event| owner.vx = 0 },
364
+
365
+ # Move according to how far the joystick axis is moved
366
+ joyaxis( 0 ) => proc { |owner, event| owner.vx = event.value },
367
+ joyaxis( 1 ) => proc { |owner, event| owner.vy = event.value },
368
+
369
+ # Fast speed when button is pressed, normal speed when released
370
+ joypressed( 4 ) => proc { |owner, event| owner.speed *= 2.0 },
371
+ joyreleased( 4 ) => proc { |owner, event| owner.speed *= 0.5 }
372
+ }
373
+
374
+ panda1.make_magic_hooks( hooks )
375
+
376
+
377
+ #######################
378
+ # PANDA 2 EVENT HOOKS #
379
+ #######################
380
+
381
+ hooks = {
382
+ # Move according to how far the joystick axis is moved
383
+ joyaxis( 2 ) => proc { |owner, event| owner.vx = event.value },
384
+ joyaxis( 3 ) => proc { |owner, event| owner.vy = event.value },
385
+
386
+ # Fast speed when button is pressed, normal speed when released
387
+ joypressed( 5 ) => proc { |owner, event| owner.speed *= 2.0 },
388
+ joyreleased( 5 ) => proc { |owner, event| owner.speed *= 0.5 }
389
+ }
390
+
391
+ panda2.make_magic_hooks( hooks )
392
+
393
+
394
+
395
+
396
+ ##############
397
+ # GAME CLASS #
398
+ ##############
399
+
400
+
401
+ # The Game class helps organize thing. It takes events
402
+ # from the queue and handles them, sometimes performing
403
+ # its own action (e.g. Escape key = quit), but also
404
+ # passing the events to the pandas to handle.
405
+ #
406
+ class Game
407
+ include EventHandler::HasEventHandler
408
+
409
+ attr_reader :clock, :queue
410
+
411
+ def initialize( screen, background )
412
+
413
+ @screen = screen
414
+ @background = background
415
+
416
+ setup_clock()
417
+ setup_queue()
418
+ setup_event_hooks()
419
+
420
+ end
421
+
422
+
423
+ # The "main loop". Repeat the #step method
424
+ # over and over and over until the user quits.
425
+ def go
426
+ catch(:quit) do
427
+ loop do
428
+ step
279
429
  end
280
430
  end
431
+ end
281
432
 
282
- pandas.undraw(screen,background)
283
- pandas.update(update_time)
284
- dirty_rects = pandas.draw(screen)
285
- screen.update_rects(dirty_rects)
286
433
 
287
- update_time = clock.tick()
288
- unless framerate == clock.framerate
289
- framerate = clock.framerate
290
- screen.title = "Rubygame test [%d fps]"%framerate
434
+ # Register the object to receive all events.
435
+ # Events will be passed to the object's #handle method.
436
+ def register( *objects )
437
+ objects.each do |object|
438
+ append_hook( :owner => object,
439
+ :trigger => YesTrigger.new,
440
+ :action => MethodAction.new(:handle) )
291
441
  end
292
442
  end
443
+
444
+
445
+ private
446
+
447
+
448
+ # Quit the game
449
+ def quit
450
+ puts "Quitting!"
451
+ throw :quit
452
+ end
453
+
454
+
455
+ # Create a new Clock to manage the game framerate
456
+ # so it doesn't use 100% of the CPU
457
+ def setup_clock
458
+ @clock = Clock.new()
459
+ @clock.target_framerate = 50
460
+ end
461
+
462
+
463
+ # Set up the event hooks to perform actions in
464
+ # response to certain events.
465
+ def setup_event_hooks
466
+ hooks = {
467
+ :escape => :quit,
468
+ :q => :quit,
469
+ :s => :toggle_smooth,
470
+
471
+ QuitRequested => :quit,
472
+
473
+ # Tell the user where they clicked.
474
+ MousePressed => proc { |owner, event|
475
+ puts "click: [%d,%d]"%event.pos
476
+ },
477
+
478
+ # These help to ensure everything is refreshed after the
479
+ # Rubygame window has been covered up by a different window.
480
+ InputFocusGained => :update_screen,
481
+ WindowUnminimized => :update_screen,
482
+ WindowExposed => :update_screen,
483
+
484
+ # Refresh the window title.
485
+ ClockTicked => :update_framerate
486
+ }
487
+
488
+ make_magic_hooks( hooks )
489
+ end
490
+
491
+
492
+ # Create an EventQueue to take events from the keyboard, etc.
493
+ # The events are taken from the queue and passed to objects
494
+ # as part of the main loop.
495
+ def setup_queue
496
+ # Create EventQueue with new-style events (added in Rubygame 2.4)
497
+ @queue = EventQueue.new()
498
+ @queue.enable_new_style_events
499
+
500
+ # Don't care about mouse movement, so let's ignore it.
501
+ @queue.ignore = [MouseMoved]
502
+ end
503
+
504
+
505
+ # Do everything needed for one frame.
506
+ def step
507
+ @queue << UndrawSprites.new( @screen, @background )
508
+ @queue.fetch_sdl_events
509
+ @queue << DrawSprites.new( @screen )
510
+ @queue << ClockTicked.new( $game.clock.tick,
511
+ $game.clock.framerate )
512
+ @queue.each do |event|
513
+ handle( event )
514
+ end
515
+ end
516
+
517
+
518
+ # Toggle smooth effects
519
+ def toggle_smooth
520
+ $smooth = !$smooth
521
+ puts "#{$smooth?'En':'Dis'}abling smooth scale/rotate."
522
+ end
523
+
524
+
525
+ # Update the window title to display the current framerate.
526
+ def update_framerate( event )
527
+ unless @old_framerate == event.framerate
528
+ @screen.title = "Rubygame test [%d fps]"%event.framerate
529
+ @old_framerate = event.framerate
530
+ end
531
+ end
532
+
533
+
534
+ # Refresh the whole screen.
535
+ def update_screen
536
+ @screen.update()
537
+ end
538
+
293
539
  end
294
540
 
295
- puts "Quitting!"
541
+
542
+ $game = Game.new( screen, background )
543
+
544
+ # Register the pandas to receive events.
545
+ $game.register( pandas, panda1, panda2 )
546
+
547
+ # Start the main game loop. It will repeat forever
548
+ # until the user quits the game!
549
+ $game.go
550
+
551
+ # Make sure everything is cleaned up properly.
296
552
  Rubygame.quit()