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.
- data/CREDITS +5 -0
- data/NEWS +88 -0
- data/README +8 -4
- data/ROADMAP +19 -43
- data/Rakefile +94 -10
- data/doc/macosx_install.rdoc +2 -2
- data/doc/windows_install.rdoc +2 -2
- data/ext/rubygame/rubygame_event.c +116 -0
- data/ext/rubygame/rubygame_event2.c +661 -0
- data/ext/rubygame/rubygame_event2.h +29 -0
- data/ext/rubygame/rubygame_joystick.c +106 -17
- data/ext/rubygame/rubygame_main.c +3 -0
- data/ext/rubygame/rubygame_shared.c +2 -5
- data/ext/rubygame/rubygame_shared.h +1 -0
- data/ext/rubygame/rubygame_surface.c +11 -9
- data/lib/rubygame.rb +14 -3
- data/lib/rubygame/event_actions.rb +203 -0
- data/lib/rubygame/event_handler.rb +454 -0
- data/lib/rubygame/event_hook.rb +112 -0
- data/lib/rubygame/event_triggers.rb +692 -0
- data/lib/rubygame/events.rb +44 -0
- data/lib/rubygame/events/joystick_events.rb +342 -0
- data/lib/rubygame/events/keyboard_events.rb +132 -0
- data/lib/rubygame/events/misc_events.rb +144 -0
- data/lib/rubygame/events/mouse_events.rb +155 -0
- data/lib/rubygame/ftor.rb +2 -2
- data/lib/rubygame/queue.rb +50 -29
- data/samples/demo_draw.rb +175 -0
- data/samples/demo_rubygame.rb +421 -165
- metadata +18 -5
@@ -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()
|
data/samples/demo_rubygame.rb
CHANGED
@@ -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
|
-
|
9
|
-
#
|
10
|
-
#
|
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
|
-
|
28
|
-
|
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
|
-
|
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(
|
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
|
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
|
-
|
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
|
-
#
|
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,
|
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
|
-
#
|
204
|
-
#
|
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
|
-
|
208
|
-
|
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
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
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
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
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
|
-
|
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()
|