gosu-examples 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +14 -0
  4. data/bin/gosu-examples +97 -0
  5. data/examples/chipmunk_and_rmagick.rb +152 -0
  6. data/examples/chipmunk_integration.rb +278 -0
  7. data/examples/cptn_ruby.rb +229 -0
  8. data/examples/media/BrokenPNG.png +0 -0
  9. data/examples/media/Cursor.png +0 -0
  10. data/examples/media/JingleBells.mp3 +0 -0
  11. data/examples/media/JingleBells.ogg +0 -0
  12. data/examples/media/Loop.wav +0 -0
  13. data/examples/media/Sample.wav +0 -0
  14. data/examples/media/SquareTexture.png +0 -0
  15. data/examples/media/Wallpaper.png +0 -0
  16. data/examples/media/WallpaperXXL.png +0 -0
  17. data/examples/media/WhiteAlpha.png +0 -0
  18. data/examples/media/audio_formats/aiff_32bit_float.aiff +0 -0
  19. data/examples/media/audio_formats/au_16bit_pcm.au +0 -0
  20. data/examples/media/audio_formats/caf_be_16bit_44khz.caf +0 -0
  21. data/examples/media/audio_formats/caf_le_16bit_44khz.caf +0 -0
  22. data/examples/media/audio_formats/caf_le_8bit_44khz.caf +0 -0
  23. data/examples/media/audio_formats/general_midi.mid +0 -0
  24. data/examples/media/audio_formats/impulse_tracker.it +0 -0
  25. data/examples/media/audio_formats/mp3_128k_stereo.mp3 +0 -0
  26. data/examples/media/audio_formats/mp3_avg_96kbit_jointstereo.mp3 +0 -0
  27. data/examples/media/audio_formats/ogg_vorbis.ogg +0 -0
  28. data/examples/media/audio_formats/wav_16bit_pcm.wav +0 -0
  29. data/examples/media/audio_formats/wav_32bit_pcm.wav +0 -0
  30. data/examples/media/audio_formats/wav_4bit_ms_adpcm.wav +0 -0
  31. data/examples/media/beep.wav +0 -0
  32. data/examples/media/cptn_ruby.png +0 -0
  33. data/examples/media/cptn_ruby_map.txt +25 -0
  34. data/examples/media/earth.png +0 -0
  35. data/examples/media/explosion.wav +0 -0
  36. data/examples/media/gem.png +0 -0
  37. data/examples/media/header.psd +0 -0
  38. data/examples/media/image_formats/test.jpg +0 -0
  39. data/examples/media/image_formats/test.psd +0 -0
  40. data/examples/media/landscape.svg +10 -0
  41. data/examples/media/large_star.png +0 -0
  42. data/examples/media/smoke.png +0 -0
  43. data/examples/media/soldier.png +0 -0
  44. data/examples/media/space.png +0 -0
  45. data/examples/media/star.png +0 -0
  46. data/examples/media/starfighter.bmp +0 -0
  47. data/examples/media/tileset.png +0 -0
  48. data/examples/media/vera.ttf +0 -0
  49. data/examples/opengl_integration.rb +224 -0
  50. data/examples/rmagick_integration.rb +413 -0
  51. data/examples/tutorial.rb +129 -0
  52. data/examples/welcome.rb +59 -0
  53. data/lib/gosu-examples/example.rb +82 -0
  54. data/lib/gosu-examples/sidebar.rb +60 -0
  55. 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