gosu-examples 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +14 -0
- data/bin/gosu-examples +97 -0
- data/examples/chipmunk_and_rmagick.rb +152 -0
- data/examples/chipmunk_integration.rb +278 -0
- data/examples/cptn_ruby.rb +229 -0
- data/examples/media/BrokenPNG.png +0 -0
- data/examples/media/Cursor.png +0 -0
- data/examples/media/JingleBells.mp3 +0 -0
- data/examples/media/JingleBells.ogg +0 -0
- data/examples/media/Loop.wav +0 -0
- data/examples/media/Sample.wav +0 -0
- data/examples/media/SquareTexture.png +0 -0
- data/examples/media/Wallpaper.png +0 -0
- data/examples/media/WallpaperXXL.png +0 -0
- data/examples/media/WhiteAlpha.png +0 -0
- data/examples/media/audio_formats/aiff_32bit_float.aiff +0 -0
- data/examples/media/audio_formats/au_16bit_pcm.au +0 -0
- data/examples/media/audio_formats/caf_be_16bit_44khz.caf +0 -0
- data/examples/media/audio_formats/caf_le_16bit_44khz.caf +0 -0
- data/examples/media/audio_formats/caf_le_8bit_44khz.caf +0 -0
- data/examples/media/audio_formats/general_midi.mid +0 -0
- data/examples/media/audio_formats/impulse_tracker.it +0 -0
- data/examples/media/audio_formats/mp3_128k_stereo.mp3 +0 -0
- data/examples/media/audio_formats/mp3_avg_96kbit_jointstereo.mp3 +0 -0
- data/examples/media/audio_formats/ogg_vorbis.ogg +0 -0
- data/examples/media/audio_formats/wav_16bit_pcm.wav +0 -0
- data/examples/media/audio_formats/wav_32bit_pcm.wav +0 -0
- data/examples/media/audio_formats/wav_4bit_ms_adpcm.wav +0 -0
- data/examples/media/beep.wav +0 -0
- data/examples/media/cptn_ruby.png +0 -0
- data/examples/media/cptn_ruby_map.txt +25 -0
- data/examples/media/earth.png +0 -0
- data/examples/media/explosion.wav +0 -0
- data/examples/media/gem.png +0 -0
- data/examples/media/header.psd +0 -0
- data/examples/media/image_formats/test.jpg +0 -0
- data/examples/media/image_formats/test.psd +0 -0
- data/examples/media/landscape.svg +10 -0
- data/examples/media/large_star.png +0 -0
- data/examples/media/smoke.png +0 -0
- data/examples/media/soldier.png +0 -0
- data/examples/media/space.png +0 -0
- data/examples/media/star.png +0 -0
- data/examples/media/starfighter.bmp +0 -0
- data/examples/media/tileset.png +0 -0
- data/examples/media/vera.ttf +0 -0
- data/examples/opengl_integration.rb +224 -0
- data/examples/rmagick_integration.rb +413 -0
- data/examples/tutorial.rb +129 -0
- data/examples/welcome.rb +59 -0
- data/lib/gosu-examples/example.rb +82 -0
- data/lib/gosu-examples/sidebar.rb +60 -0
- metadata +112 -0
@@ -0,0 +1,413 @@
|
|
1
|
+
# Encoding: UTF-8
|
2
|
+
|
3
|
+
# A (too) simple Gorilla-style shooter for two players.
|
4
|
+
# Shows how Gosu and RMagick can be used together to generate a map, implement
|
5
|
+
# a dynamic landscape and generally look great.
|
6
|
+
# Also shows a very minimal, yet effective way of designing a game's object system.
|
7
|
+
|
8
|
+
# Doesn't make use of Gosu's Z-ordering. Not many different things to draw, it's
|
9
|
+
# easy to get the order right without it.
|
10
|
+
|
11
|
+
# Known issues:
|
12
|
+
# * Collision detection of the missiles is lazy, allows shooting through thin walls.
|
13
|
+
# * The look of dead soldiers is, err, by accident. Soldier.png needs to be
|
14
|
+
# designed in a less obfuscated way :)
|
15
|
+
|
16
|
+
require 'rubygems'
|
17
|
+
require 'gosu'
|
18
|
+
require 'rmagick'
|
19
|
+
|
20
|
+
WIDTH, HEIGHT = 600, 600
|
21
|
+
|
22
|
+
NULL_PIXEL = Magick::Pixel.from_color('none')
|
23
|
+
|
24
|
+
# The class for this game's map.
|
25
|
+
# Design:
|
26
|
+
# * Dynamic map creation at startup, holding it as RMagick Image in @image
|
27
|
+
# * Testing for solidity by testing @image's pixel values
|
28
|
+
# * Drawing from a Gosu::Image instance
|
29
|
+
# * Blasting holes into the map is implemented by drawing and erasing portions
|
30
|
+
# of @image, then recreating the corresponding area in the Gosu::Image
|
31
|
+
|
32
|
+
class Map
|
33
|
+
def initialize
|
34
|
+
# Let's start with something simple and load the sky via RMagick.
|
35
|
+
# Loading SVG files isn't possible with Gosu, so say wow!
|
36
|
+
# (Seems to take a while though)
|
37
|
+
sky = Magick::Image.read("media/landscape.svg").first
|
38
|
+
@sky = Gosu::Image.new(sky, :tileable => true)
|
39
|
+
|
40
|
+
# Create the map an stores the RMagick image in @image
|
41
|
+
create_rmagick_map
|
42
|
+
|
43
|
+
# Copy the RMagick Image to a Gosu Image (still unchanged)
|
44
|
+
@gosu_image = Gosu::Image.new(@image, :tileable => true)
|
45
|
+
end
|
46
|
+
|
47
|
+
def solid? x, y
|
48
|
+
# Map is open at the top.
|
49
|
+
return false if y < 0
|
50
|
+
# Map is closed on all other sides.
|
51
|
+
return true if x < 0 or x >= WIDTH or y >= HEIGHT
|
52
|
+
# Inside of the map, determine solidity from the map image.
|
53
|
+
@image.pixel_color(x, y) != NULL_PIXEL
|
54
|
+
end
|
55
|
+
|
56
|
+
def draw
|
57
|
+
# Sky background.
|
58
|
+
@sky.draw 0, 0, 0
|
59
|
+
# The landscape.
|
60
|
+
@gosu_image.draw 0, 0, 0
|
61
|
+
end
|
62
|
+
|
63
|
+
# Radius of a crater.
|
64
|
+
RADIUS = 25
|
65
|
+
# Radius of a crater, Shadow included.
|
66
|
+
SH_RADIUS = 45
|
67
|
+
|
68
|
+
# Create the crater image (basically a circle shape that is used to erase
|
69
|
+
# parts of the map) and the crater shadow image.
|
70
|
+
CRATER_IMAGE = begin
|
71
|
+
crater = Magick::Image.new(2 * RADIUS, 2 * RADIUS) { self.background_color = 'none' }
|
72
|
+
gc = Magick::Draw.new
|
73
|
+
gc.fill('black').circle(RADIUS, RADIUS, RADIUS, 0)
|
74
|
+
gc.draw crater
|
75
|
+
crater
|
76
|
+
end
|
77
|
+
CRATER_SHADOW = CRATER_IMAGE.shadow(0, 0, (SH_RADIUS - RADIUS) / 2, 1)
|
78
|
+
|
79
|
+
def blast x, y
|
80
|
+
# Draw the shadow (twice for more intensity), then erase a circle from the map.
|
81
|
+
@image.composite! CRATER_SHADOW, x - SH_RADIUS, y - SH_RADIUS, Magick::AtopCompositeOp
|
82
|
+
@image.composite! CRATER_SHADOW, x - SH_RADIUS, y - SH_RADIUS, Magick::AtopCompositeOp
|
83
|
+
@image.composite! CRATER_IMAGE, x - RADIUS, y - RADIUS, Magick::DstOutCompositeOp
|
84
|
+
|
85
|
+
# Isolate the affected portion of the RMagick image.
|
86
|
+
dirty_portion = @image.crop(x - SH_RADIUS, y - SH_RADIUS, SH_RADIUS * 2, SH_RADIUS * 2)
|
87
|
+
# Overwrite this part of the Gosu image. If the crater begins outside of the map, still
|
88
|
+
# just update the inner part.
|
89
|
+
@gosu_image.insert dirty_portion, [x - SH_RADIUS, 0].max, [y - SH_RADIUS, 0].max
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def create_rmagick_map
|
95
|
+
# This is the one large RMagick image that represents the map.
|
96
|
+
@image = Magick::Image.new(WIDTH, HEIGHT) { self.background_color = 'none' }
|
97
|
+
|
98
|
+
# Set up a Draw object that fills with an earth texture.
|
99
|
+
earth = Magick::Image.read('media/earth.png').first.resize(1.5)
|
100
|
+
gc = Magick::Draw.new
|
101
|
+
gc.pattern('earth', 0, 0, earth.columns, earth.rows) { gc.composite(0, 0, 0, 0, earth) }
|
102
|
+
gc.fill('earth')
|
103
|
+
gc.stroke('#603000').stroke_width(1.5)
|
104
|
+
# Draw a smooth bezier island onto the map!
|
105
|
+
polypoints = [0, HEIGHT]
|
106
|
+
0.upto(8) do |x|
|
107
|
+
polypoints += [x * 100, HEIGHT * 0.2 + rand(HEIGHT * 0.8)]
|
108
|
+
end
|
109
|
+
polypoints += [WIDTH, HEIGHT]
|
110
|
+
gc.bezier(*polypoints)
|
111
|
+
gc.draw(@image)
|
112
|
+
|
113
|
+
# Create a bright-dark gradient fill, an image from it and change the map's
|
114
|
+
# brightness with it.
|
115
|
+
fill = Magick::GradientFill.new(0, HEIGHT * 0.4, WIDTH, HEIGHT * 0.4, '#fff', '#666')
|
116
|
+
gradient = Magick::Image.new(WIDTH, HEIGHT, fill)
|
117
|
+
gradient = @image.composite(gradient, 0, 0, Magick::InCompositeOp)
|
118
|
+
@image.composite!(gradient, 0, 0, Magick::MultiplyCompositeOp)
|
119
|
+
|
120
|
+
# Finally, place the star in the middle of the map, just onto the ground.
|
121
|
+
star = Magick::Image.read('media/large_star.png').first
|
122
|
+
star_y = 0
|
123
|
+
star_y += 20 until solid?(WIDTH / 2, star_y)
|
124
|
+
@image.composite!(star, (WIDTH - star.columns) / 2, star_y - star.rows * 0.85,
|
125
|
+
Magick::DstOverCompositeOp)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Player class.
|
130
|
+
# Note that applies to the whole game:
|
131
|
+
# All objects implement an informal interface.
|
132
|
+
# draw: Draws the object (obviously)
|
133
|
+
# update: Moves the object etc., returns false if the object is to be deleted
|
134
|
+
# hit_by?(missile): Returns true if an object is hit by the missile, causing
|
135
|
+
# it to explode on this object.
|
136
|
+
|
137
|
+
class Player
|
138
|
+
# Magic numbers considered harmful! This is the height of the
|
139
|
+
# player as used for collision detection.
|
140
|
+
HEIGHT = 14
|
141
|
+
|
142
|
+
attr_reader :x, :y, :dead
|
143
|
+
|
144
|
+
def initialize(window, x, y, color)
|
145
|
+
# Only load the images once for all instances of this class.
|
146
|
+
@@images ||= Gosu::Image.load_tiles("media/soldier.png", 40, 50)
|
147
|
+
|
148
|
+
@window, @x, @y, @color = window, x, y, color
|
149
|
+
@vy = 0
|
150
|
+
|
151
|
+
# -1: left, +1: right
|
152
|
+
@dir = -1
|
153
|
+
|
154
|
+
# Aiming angle.
|
155
|
+
@angle = 90
|
156
|
+
end
|
157
|
+
|
158
|
+
def draw
|
159
|
+
if dead then
|
160
|
+
# Poor, broken soldier.
|
161
|
+
@@images[0].draw_rot(x, y, 0, 290 * @dir, 0.5, 0.65, @dir * 0.5, 0.5, @color)
|
162
|
+
@@images[2].draw_rot(x, y, 0, 160 * @dir, 0.95, 0.5, 0.5, @dir * 0.5, @color)
|
163
|
+
else
|
164
|
+
# Was moved last frame?
|
165
|
+
if @show_walk_anim
|
166
|
+
# Yes: Display walking animation.
|
167
|
+
frame = Gosu::milliseconds / 200 % 2
|
168
|
+
else
|
169
|
+
# No: Stand around (boring).
|
170
|
+
frame = 0
|
171
|
+
end
|
172
|
+
|
173
|
+
# Draw feet, then chest.
|
174
|
+
@@images[frame].draw(x - 10 * @dir, y - 20, 0, @dir * 0.5, 0.5, @color)
|
175
|
+
angle = @angle
|
176
|
+
angle = 180 - angle if @dir == -1
|
177
|
+
@@images[2].draw_rot(x, y - 5, 0, angle, 1, 0.5, 0.5, @dir * 0.5, @color)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def update
|
182
|
+
# First, assume that no walking happened this frame.
|
183
|
+
@show_walk_anim = false
|
184
|
+
|
185
|
+
# Gravity.
|
186
|
+
@vy += 1
|
187
|
+
|
188
|
+
if @vy > 1 then
|
189
|
+
# Move upwards until hitting something.
|
190
|
+
@vy.times do
|
191
|
+
if @window.map.solid?(x, y + 1)
|
192
|
+
@vy = 0
|
193
|
+
break
|
194
|
+
else
|
195
|
+
@y += 1
|
196
|
+
end
|
197
|
+
end
|
198
|
+
else
|
199
|
+
# Move downwards until hitting something.
|
200
|
+
(-@vy).times do
|
201
|
+
if @window.map.solid?(x, y - HEIGHT - 1)
|
202
|
+
@vy = 0
|
203
|
+
break
|
204
|
+
else
|
205
|
+
@y -= 1
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Soldiers are never deleted (they may die, but that is a different thing).
|
211
|
+
true
|
212
|
+
end
|
213
|
+
|
214
|
+
def aim_up
|
215
|
+
@angle -= 2 unless @angle < 10
|
216
|
+
end
|
217
|
+
|
218
|
+
def aim_down
|
219
|
+
@angle += 2 unless @angle > 170
|
220
|
+
end
|
221
|
+
|
222
|
+
def try_walk(dir)
|
223
|
+
@show_walk_anim = true
|
224
|
+
@dir = dir
|
225
|
+
# First, magically move up (so soldiers can run up hills)
|
226
|
+
2.times { @y -= 1 unless @window.map.solid?(x, y - HEIGHT - 1) }
|
227
|
+
# Now move into the desired direction.
|
228
|
+
@x += dir unless @window.map.solid?(x + dir, y) or
|
229
|
+
@window.map.solid?(x + dir, y - HEIGHT)
|
230
|
+
# To make up for unnecessary movement upwards, sink downward again.
|
231
|
+
2.times { @y += 1 unless @window.map.solid?(x, y + 1) }
|
232
|
+
end
|
233
|
+
|
234
|
+
def try_jump
|
235
|
+
@vy = -12 if @window.map.solid?(x, y + 1)
|
236
|
+
end
|
237
|
+
|
238
|
+
def shoot
|
239
|
+
@window.objects << Missile.new(@window, x + 10 * @dir, y - 10, @angle * @dir)
|
240
|
+
end
|
241
|
+
|
242
|
+
def hit_by? missile
|
243
|
+
if Gosu::distance(missile.x, missile.y, x, y) < 30 then
|
244
|
+
# Was hit :(
|
245
|
+
@dead = true
|
246
|
+
return true
|
247
|
+
else
|
248
|
+
return false
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# Implements the same interface as Player, except it's a missile!
|
254
|
+
|
255
|
+
class Missile
|
256
|
+
attr_reader :x, :y, :vx, :vy
|
257
|
+
|
258
|
+
# All missile instances use the same sound.
|
259
|
+
EXPLOSION = Gosu::Sample.new("media/explosion.wav")
|
260
|
+
|
261
|
+
def initialize(window, x, y, angle)
|
262
|
+
# Horizontal/vertical velocity.
|
263
|
+
@vx, @vy = Gosu::offset_x(angle, 20).to_i, Gosu::offset_y(angle, 20).to_i
|
264
|
+
|
265
|
+
@window, @x, @y = window, x + @vx, y + @vy
|
266
|
+
end
|
267
|
+
|
268
|
+
def update
|
269
|
+
# Movement, gravity
|
270
|
+
@x += @vx
|
271
|
+
@y += @vy
|
272
|
+
@vy += 1
|
273
|
+
# Hit anything?
|
274
|
+
if @window.map.solid?(x, y) or @window.objects.any? { |o| o.hit_by?(self) } then
|
275
|
+
# Create great particles.
|
276
|
+
5.times { @window.objects << Particle.new(@window, x - 25 + rand(51), y - 25 + rand(51)) }
|
277
|
+
@window.map.blast(x, y)
|
278
|
+
# Weeee, stereo sound!
|
279
|
+
EXPLOSION.play_pan((1.0 * @x / WIDTH) * 2 - 1)
|
280
|
+
return false
|
281
|
+
else
|
282
|
+
return true
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def draw
|
287
|
+
# Just draw a small rectangle.
|
288
|
+
Gosu::draw_rect x-2, y-2, 4, 4, 0xff_800000
|
289
|
+
end
|
290
|
+
|
291
|
+
def hit_by?(missile)
|
292
|
+
# Missiles can't be hit by other missiles!
|
293
|
+
false
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
# Very minimal object that just draws a fading particle.
|
298
|
+
|
299
|
+
class Particle
|
300
|
+
def initialize(window, x, y)
|
301
|
+
# All Particle instances use the same image
|
302
|
+
@@image ||= Gosu::Image.new('media/smoke.png')
|
303
|
+
|
304
|
+
@x, @y = x, y
|
305
|
+
@color = Gosu::Color.new(255, 255, 255, 255)
|
306
|
+
end
|
307
|
+
|
308
|
+
def update
|
309
|
+
@y -= 5
|
310
|
+
@x = @x - 1 + rand(3)
|
311
|
+
@color.alpha -= 5
|
312
|
+
|
313
|
+
# Remove if faded completely.
|
314
|
+
@color.alpha > 0
|
315
|
+
end
|
316
|
+
|
317
|
+
def draw
|
318
|
+
@@image.draw(@x - 25, @y - 25, 0, 1, 1, @color)
|
319
|
+
end
|
320
|
+
|
321
|
+
def hit_by?(missile)
|
322
|
+
# Smoke can't be hit!
|
323
|
+
false
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
# Finally, the class that ties it all together.
|
328
|
+
# Very straightforward implementation.
|
329
|
+
|
330
|
+
class RMagickIntegration < (Example rescue Gosu::Window)
|
331
|
+
attr_reader :map, :objects
|
332
|
+
|
333
|
+
def initialize
|
334
|
+
super WIDTH, HEIGHT
|
335
|
+
|
336
|
+
self.caption = "RMagick Integration Demo"
|
337
|
+
|
338
|
+
# Texts to display in the appropriate situations.
|
339
|
+
@player_instructions = []
|
340
|
+
@player_won_messages = []
|
341
|
+
2.times do |plr|
|
342
|
+
@player_instructions << Gosu::Image.from_text(
|
343
|
+
"It is the #{ plr == 0 ? 'green' : 'red' } toy soldier's turn.\n" +
|
344
|
+
"(Arrow keys to walk and aim, Return to jump, Space to shoot)",
|
345
|
+
30, :width => width, :align => :center)
|
346
|
+
@player_won_messages << Gosu::Image.from_text(
|
347
|
+
"The #{ plr == 0 ? 'green' : 'red' } toy soldier has won!",
|
348
|
+
30, :width => width, :align => :center)
|
349
|
+
end
|
350
|
+
|
351
|
+
# Create everything!
|
352
|
+
@map = Map.new
|
353
|
+
@players = [Player.new(self, 100, 40, 0xff_308000), Player.new(self, WIDTH - 100, 40, 0xff_803000)]
|
354
|
+
@objects = @players.dup
|
355
|
+
|
356
|
+
# Let any player start.
|
357
|
+
@current_player = rand(2)
|
358
|
+
# Currently not waiting for a missile to hit something.
|
359
|
+
@waiting = false
|
360
|
+
end
|
361
|
+
|
362
|
+
def draw
|
363
|
+
# Draw the main game.
|
364
|
+
@map.draw
|
365
|
+
@objects.each { |o| o.draw }
|
366
|
+
|
367
|
+
# If any text should be displayed, draw it - and add a nice black border around it
|
368
|
+
# by drawing it four times, with a little offset in each direction.
|
369
|
+
|
370
|
+
cur_text = @player_instructions[@current_player] if not @waiting
|
371
|
+
cur_text = @player_won_messages[1 - @current_player] if @players[@current_player].dead
|
372
|
+
|
373
|
+
if cur_text then
|
374
|
+
x, y = 0, 30
|
375
|
+
cur_text.draw(x - 1, y, 0, 1, 1, 0xff_000000)
|
376
|
+
cur_text.draw(x + 1, y, 0, 1, 1, 0xff_000000)
|
377
|
+
cur_text.draw(x, y - 1, 0, 1, 1, 0xff_000000)
|
378
|
+
cur_text.draw(x, y + 1, 0, 1, 1, 0xff_000000)
|
379
|
+
cur_text.draw(x, y, 0, 1, 1, 0xff_ffffff)
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
def update
|
384
|
+
# if waiting for the next player's turn, continue to do so until the missile has
|
385
|
+
# hit something.
|
386
|
+
@waiting &&= !@objects.grep(Missile).empty?
|
387
|
+
|
388
|
+
# Remove all objects whose update method returns false.
|
389
|
+
@objects.reject! { |o| o.update == false }
|
390
|
+
|
391
|
+
# If it's a player's turn, forward controls.
|
392
|
+
if not @waiting and not @players[@current_player].dead then
|
393
|
+
player = @players[@current_player]
|
394
|
+
player.aim_up if Gosu::button_down? Gosu::KbUp
|
395
|
+
player.aim_down if Gosu::button_down? Gosu::KbDown
|
396
|
+
player.try_walk(-1) if Gosu::button_down? Gosu::KbLeft
|
397
|
+
player.try_walk(+1) if Gosu::button_down? Gosu::KbRight
|
398
|
+
player.try_jump if Gosu::button_down? Gosu::KbReturn
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
def button_down(id)
|
403
|
+
if id == Gosu::KbSpace and not @waiting and not @players[@current_player].dead then
|
404
|
+
# Shoot! This is handled in button_down because holding space shouldn't auto-fire.
|
405
|
+
@players[@current_player].shoot
|
406
|
+
@current_player = 1 - @current_player
|
407
|
+
@waiting = true
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
# So far we have only defined how everything *should* work - now set it up and run it!
|
413
|
+
RMagickIntegration.new.show if __FILE__ == $0
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# Encoding: UTF-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'gosu'
|
5
|
+
|
6
|
+
WIDTH, HEIGHT = 600, 600
|
7
|
+
|
8
|
+
module ZOrder
|
9
|
+
Background, Stars, Player, UI = *0..3
|
10
|
+
end
|
11
|
+
|
12
|
+
class Player
|
13
|
+
attr_reader :score
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@image = Gosu::Image.new("media/starfighter.bmp")
|
17
|
+
@beep = Gosu::Sample.new("media/beep.wav")
|
18
|
+
@x = @y = @vel_x = @vel_y = @angle = 0.0
|
19
|
+
@score = 0
|
20
|
+
end
|
21
|
+
|
22
|
+
def warp(x, y)
|
23
|
+
@x, @y = x, y
|
24
|
+
end
|
25
|
+
|
26
|
+
def turn_left
|
27
|
+
@angle -= 4.5
|
28
|
+
end
|
29
|
+
|
30
|
+
def turn_right
|
31
|
+
@angle += 4.5
|
32
|
+
end
|
33
|
+
|
34
|
+
def accelerate
|
35
|
+
@vel_x += Gosu::offset_x(@angle, 0.5)
|
36
|
+
@vel_y += Gosu::offset_y(@angle, 0.5)
|
37
|
+
end
|
38
|
+
|
39
|
+
def move
|
40
|
+
@x += @vel_x
|
41
|
+
@y += @vel_y
|
42
|
+
@x %= WIDTH
|
43
|
+
@y %= HEIGHT
|
44
|
+
|
45
|
+
@vel_x *= 0.95
|
46
|
+
@vel_y *= 0.95
|
47
|
+
end
|
48
|
+
|
49
|
+
def draw
|
50
|
+
@image.draw_rot(@x, @y, ZOrder::Player, @angle)
|
51
|
+
end
|
52
|
+
|
53
|
+
def collect_stars(stars)
|
54
|
+
stars.reject! do |star|
|
55
|
+
if Gosu::distance(@x, @y, star.x, star.y) < 35 then
|
56
|
+
@score += 10
|
57
|
+
@beep.play
|
58
|
+
true
|
59
|
+
else
|
60
|
+
false
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Star
|
67
|
+
attr_reader :x, :y
|
68
|
+
|
69
|
+
def initialize(animation)
|
70
|
+
@animation = animation
|
71
|
+
@color = Gosu::Color.new(0xff_000000)
|
72
|
+
@color.red = rand(256 - 40) + 40
|
73
|
+
@color.green = rand(256 - 40) + 40
|
74
|
+
@color.blue = rand(256 - 40) + 40
|
75
|
+
@x = rand * WIDTH
|
76
|
+
@y = rand * HEIGHT
|
77
|
+
end
|
78
|
+
|
79
|
+
def draw
|
80
|
+
img = @animation[Gosu::milliseconds / 100 % @animation.size]
|
81
|
+
img.draw(@x - img.width / 2.0, @y - img.height / 2.0,
|
82
|
+
ZOrder::Stars, 1, 1, @color, :add)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class Tutorial < (Example rescue Gosu::Window)
|
87
|
+
def initialize
|
88
|
+
super WIDTH, HEIGHT
|
89
|
+
|
90
|
+
self.caption = "Tutorial Game"
|
91
|
+
|
92
|
+
@background_image = Gosu::Image.new("media/space.png", :tileable => true)
|
93
|
+
|
94
|
+
@player = Player.new
|
95
|
+
@player.warp(320, 240)
|
96
|
+
|
97
|
+
@star_anim = Gosu::Image::load_tiles("media/star.png", 25, 25)
|
98
|
+
@stars = Array.new
|
99
|
+
|
100
|
+
@font = Gosu::Font.new(20)
|
101
|
+
end
|
102
|
+
|
103
|
+
def update
|
104
|
+
if Gosu::button_down? Gosu::KbLeft or Gosu::button_down? Gosu::GpLeft then
|
105
|
+
@player.turn_left
|
106
|
+
end
|
107
|
+
if Gosu::button_down? Gosu::KbRight or Gosu::button_down? Gosu::GpRight then
|
108
|
+
@player.turn_right
|
109
|
+
end
|
110
|
+
if Gosu::button_down? Gosu::KbUp or Gosu::button_down? Gosu::GpButton0 then
|
111
|
+
@player.accelerate
|
112
|
+
end
|
113
|
+
@player.move
|
114
|
+
@player.collect_stars(@stars)
|
115
|
+
|
116
|
+
if rand(100) < 4 and @stars.size < 25 then
|
117
|
+
@stars.push(Star.new(@star_anim))
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def draw
|
122
|
+
@background_image.draw(0, 0, ZOrder::Background)
|
123
|
+
@player.draw
|
124
|
+
@stars.each { |star| star.draw }
|
125
|
+
@font.draw("Score: #{@player.score}", 10, 10, ZOrder::UI, 1.0, 1.0, 0xff_ffff00)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
Tutorial.new.show if __FILE__ == $0
|