gosu 0.8.7.2-x86-mingw32 → 0.9.0-x86-mingw32

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,417 +0,0 @@
1
- # A (too) simple Gorilla-style shooter for two players.
2
- # Shows how Gosu and RMagick can be used together to generate a map, implement
3
- # a dynamic landscape and generally look great.
4
- # Also shows a very minimal, yet effective way of designing a game's object system.
5
-
6
- # Doesn't make use of Gosu's Z-ordering. Not many different things to draw, it's
7
- # easy to get the order right without it.
8
-
9
- # Known issues:
10
- # * Collision detection of the missiles is lazy, allows shooting through thin walls.
11
- # * The look of dead soldiers is, err, by accident. Soldier.png needs to be
12
- # designed in a less obfuscated way :)
13
-
14
- require 'rubygems'
15
- require 'gosu'
16
- require 'RMagick'
17
-
18
- NULL_PIXEL = Magick::Pixel.from_color('none')
19
-
20
- # The class for this game's map.
21
- # Design:
22
- # * Dynamic map creation at startup, holding it as RMagick Image in @image
23
- # * Testing for solidity by testing @image's pixel values
24
- # * Drawing from a Gosu::Image instance
25
- # * Blasting holes into the map is implemented by drawing and erasing portions
26
- # of @image, then recreating the corresponding area in the Gosu::Image
27
-
28
- class Map
29
- WIDTH, HEIGHT = 800, 600
30
-
31
- def initialize window
32
- # We'll need the window later for re-creating Gosu images.
33
- @window = window
34
-
35
- # Let's start with something simple and load the sky via RMagick.
36
- # Loading SVG files isn't possible with Gosu, so say wow!
37
- # (Seems to take a while though)
38
- sky = Magick::Image.read("media/Landscape.svg").first
39
- @sky = Gosu::Image.new(window, sky, true)
40
-
41
- # Create the map an stores the RMagick image in @image
42
- create_rmagick_map
43
-
44
- # Copy the RMagick Image to a Gosu Image (still unchanged)
45
- @gosu_image = Gosu::Image.new(window, @image, true)
46
- end
47
-
48
- def solid? x, y
49
- # Map is open at the top.
50
- return false if y < 0
51
- # Map is closed on all other sides.
52
- return true if x < 0 or x >= 800 or y >= 600
53
- # Inside of the map, determine solidity from the map image.
54
- @image.pixel_color(x, y) != NULL_PIXEL
55
- end
56
-
57
- def draw
58
- # Sky background.
59
- @sky.draw(0, 0, 0)
60
- # The landscape.
61
- @gosu_image.draw 0, 0, 0
62
- end
63
-
64
- # Radius of a crater.
65
- RADIUS = 25
66
- # Radius of a crater, Shadow included.
67
- SH_RADIUS = 45
68
-
69
- # Create the crater image (basically a circle shape that is used to erase
70
- # parts of the map) and the crater shadow image.
71
- CRATER_IMAGE = begin
72
- crater = Magick::Image.new(2 * RADIUS, 2 * RADIUS) { self.background_color = 'none' }
73
- gc = Magick::Draw.new
74
- gc.fill('black').circle(RADIUS, RADIUS, RADIUS, 0)
75
- gc.draw crater
76
- crater
77
- end
78
- CRATER_SHADOW = CRATER_IMAGE.shadow(0, 0, (SH_RADIUS - RADIUS) / 2, 1)
79
-
80
- def blast x, y
81
- # Draw the shadow (twice for more intensity), then erase a circle from the map.
82
- @image.composite!(CRATER_SHADOW, x - SH_RADIUS, y - SH_RADIUS, Magick::AtopCompositeOp)
83
- @image.composite!(CRATER_SHADOW, x - SH_RADIUS, y - SH_RADIUS, Magick::AtopCompositeOp)
84
- @image.composite!(CRATER_IMAGE, x - RADIUS, y - RADIUS, Magick::DstOutCompositeOp)
85
-
86
- # Isolate the affected portion of the RMagick image.
87
- dirty_portion = @image.crop(x - SH_RADIUS, y - SH_RADIUS, SH_RADIUS * 2, SH_RADIUS * 2)
88
- # Overwrite this part of the Gosu image. If the crater begins outside of the map, still
89
- # just update the inner part.
90
- @gosu_image.insert dirty_portion, [x - SH_RADIUS, 0].max, [y - SH_RADIUS, 0].max
91
- end
92
-
93
- private
94
-
95
- def create_rmagick_map
96
- # This is the one large RMagick image that represents the map.
97
- @image = Magick::Image.new(WIDTH, HEIGHT) { self.background_color = 'none' }
98
-
99
- # Set up a Draw object that fills with an earth texture.
100
- earth = Magick::Image.read('media/Earth.png').first.resize(1.5)
101
- gc = Magick::Draw.new
102
- gc.pattern('earth', 0, 0, earth.columns, earth.rows) { gc.composite(0, 0, 0, 0, earth) }
103
- gc.fill('earth')
104
- gc.stroke('#603000').stroke_width(1.5)
105
- # Draw a smooth bezier island onto the map!
106
- polypoints = [0, HEIGHT]
107
- 0.upto(8) do |x|
108
- polypoints += [x * 100, HEIGHT * 0.2 + rand(HEIGHT * 0.8)]
109
- end
110
- polypoints += [WIDTH, HEIGHT]
111
- gc.bezier(*polypoints)
112
- gc.draw(@image)
113
-
114
- # Create a bright-dark gradient fill, an image from it and change the map's
115
- # brightness with it.
116
- fill = Magick::GradientFill.new(0, HEIGHT * 0.4, WIDTH, HEIGHT * 0.4, '#fff', '#666')
117
- gradient = Magick::Image.new(WIDTH, HEIGHT, fill)
118
- gradient = @image.composite(gradient, 0, 0, Magick::InCompositeOp)
119
- @image.composite!(gradient, 0, 0, Magick::MultiplyCompositeOp)
120
-
121
- # Finally, place the star in the middle of the map, just onto the ground.
122
- star = Magick::Image.read('media/LargeStar.png').first
123
- star_y = 0
124
- star_y += 20 until solid?(WIDTH / 2, star_y)
125
- @image.composite!(star, (WIDTH - star.columns) / 2, star_y - star.rows * 0.85,
126
- Magick::DstOverCompositeOp)
127
- end
128
- end
129
-
130
- # Player class.
131
- # Note that applies to the whole game:
132
- # All objects implement an informal interface.
133
- # draw: Draws the object (obviously)
134
- # update: Moves the object etc., returns false if the object is to be deleted
135
- # hit_by?(missile): Returns true if an object is hit by the missile, causing
136
- # it to explode on this object.
137
-
138
- class Player
139
- # Magic numbers considered harmful! This is the height of the
140
- # player as used for collision detection.
141
- HEIGHT = 14
142
-
143
- attr_reader :x, :y, :dead
144
-
145
- def initialize(window, x, y, color)
146
- # Only load the images once for all instances of this class.
147
- @@images ||= Gosu::Image.load_tiles(window, "media/Soldier.png", 40, 50, false)
148
-
149
- @window, @x, @y, @color = window, x, y, color
150
- @vy = 0
151
-
152
- # -1: left, +1: right
153
- @dir = -1
154
-
155
- # Aiming angle.
156
- @angle = 90
157
- end
158
-
159
- def draw
160
- if dead then
161
- # Poor, broken soldier.
162
- @@images[0].draw_rot(x, y, 0, 290 * @dir, 0.5, 0.65, @dir * 0.5, 0.5, @color)
163
- @@images[2].draw_rot(x, y, 0, 160 * @dir, 0.95, 0.5, 0.5, @dir * 0.5, @color)
164
- else
165
- # Was moved last frame?
166
- if @show_walk_anim
167
- # Yes: Display walking animation.
168
- frame = Gosu::milliseconds / 200 % 2
169
- else
170
- # No: Stand around (boring).
171
- frame = 0
172
- end
173
-
174
- # Draw feet, then chest.
175
- @@images[frame].draw(x - 10 * @dir, y - 20, 0, @dir * 0.5, 0.5, @color)
176
- angle = @angle
177
- angle = 180 - angle if @dir == -1
178
- @@images[2].draw_rot(x, y - 5, 0, angle, 1, 0.5, 0.5, @dir * 0.5, @color)
179
- end
180
- end
181
-
182
- def update
183
- # First, assume that no walking happened this frame.
184
- @show_walk_anim = false
185
-
186
- # Gravity.
187
- @vy += 1
188
-
189
- if @vy > 1 then
190
- # Move upwards until hitting something.
191
- @vy.times do
192
- if @window.map.solid?(x, y + 1)
193
- @vy = 0
194
- break
195
- else
196
- @y += 1
197
- end
198
- end
199
- else
200
- # Move downwards until hitting something.
201
- (-@vy).times do
202
- if @window.map.solid?(x, y - HEIGHT - 1)
203
- @vy = 0
204
- break
205
- else
206
- @y -= 1
207
- end
208
- end
209
- end
210
-
211
- # Soldiers are never deleted (they may die, though, but that is a different
212
- # concept).
213
- true
214
- end
215
-
216
- def aim_up
217
- @angle -= 2 unless @angle < 10
218
- end
219
-
220
- def aim_down
221
- @angle += 2 unless @angle > 170
222
- end
223
-
224
- def try_walk(dir)
225
- @show_walk_anim = true
226
- @dir = dir
227
- # First, magically move up (so soldiers can run up hills)
228
- 2.times { @y -= 1 unless @window.map.solid?(x, y - HEIGHT - 1) }
229
- # Now move into the desired direction.
230
- @x += dir unless @window.map.solid?(x + dir, y) or
231
- @window.map.solid?(x + dir, y - HEIGHT)
232
- # To make up for unnecessary movement upwards, sink downward again.
233
- 2.times { @y += 1 unless @window.map.solid?(x, y + 1) }
234
- end
235
-
236
- def try_jump
237
- @vy = -12 if @window.map.solid?(x, y + 1)
238
- end
239
-
240
- def shoot
241
- @window.objects << Missile.new(@window, x + 10 * @dir, y - 10, @angle * @dir)
242
- end
243
-
244
- def hit_by? missile
245
- if Gosu::distance(missile.x, missile.y, x, y) < 30 then
246
- # Was hit :(
247
- @dead = true
248
- return true
249
- else
250
- return false
251
- end
252
- end
253
- end
254
-
255
- # Implements the same interface as Player, except it'S a missile!
256
-
257
- class Missile
258
- attr_reader :x, :y, :vx, :vy
259
-
260
- # All missile instances use the same sound.
261
- EXPLOSION = Gosu::Sample.new("media/Explosion.wav")
262
-
263
- def initialize(window, x, y, angle)
264
- # Horizontal/vertical velocity.
265
- @vx, @vy = Gosu::offset_x(angle, 20).to_i, Gosu::offset_y(angle, 20).to_i
266
-
267
- @window, @x, @y = window, x + @vx, y + @vy
268
- end
269
-
270
- def update
271
- # Movement, gravity
272
- @x += @vx
273
- @y += @vy
274
- @vy += 1
275
- # Hit anything?
276
- if @window.map.solid?(x, y) or @window.objects.any? { |o| o.hit_by?(self) } then
277
- # Create great particles.
278
- 5.times { @window.objects << Particle.new(@window, x - 25 + rand(51), y - 25 + rand(51)) }
279
- @window.map.blast(x, y)
280
- # Weeee, stereo sound!
281
- EXPLOSION.play_pan((2 * @x - Map::WIDTH) / Map::WIDTH)
282
- return false
283
- else
284
- return true
285
- end
286
- end
287
-
288
- def draw
289
- # Just draw a small quad.
290
- @window.draw_quad(x-2, y-2, 0xff800000, x+2, y-2, 0xff800000,
291
- x-2, y+2, 0xff800000, x+2, y+2, 0xff800000, 0)
292
- end
293
-
294
- def hit_by?(missile)
295
- # Missiles can't be hit by other missiles!
296
- false
297
- end
298
- end
299
-
300
- # Very minimal object that just draws a fading particle.
301
-
302
- class Particle
303
- def initialize(window, x, y)
304
- # All Particle instances use the same image
305
- @@image ||= Gosu::Image.new(window, 'media/Smoke.png', false)
306
-
307
- @x, @y = x, y
308
- @color = Gosu::Color.new(255, 255, 255, 255)
309
- end
310
-
311
- def update
312
- @y -= 5
313
- @x = @x - 1 + rand(3)
314
- @color.alpha -= 5
315
-
316
- # Remove if faded completely.
317
- @color.alpha > 0
318
- end
319
-
320
- def draw
321
- @@image.draw(@x - 25, @y - 25, 0, 1, 1, @color)
322
- end
323
-
324
- def hit_by?(missile)
325
- # Smoke can't be hit!
326
- false
327
- end
328
- end
329
-
330
- # Finally, the class that ties it all together.
331
- # Very straightforward implementation.
332
-
333
- class GameWindow < Gosu::Window
334
- attr_reader :map, :objects
335
-
336
- def initialize()
337
- super(800, 600, false)
338
- self.caption = "Gosu & RMagick Integration Demo"
339
-
340
- # Texts to display in the appropriate situations.
341
- @player_instructions = []
342
- @player_won_messages = []
343
- 2.times do |plr|
344
- @player_instructions << Gosu::Image.from_text(self,
345
- "It is the #{ plr == 0 ? 'green' : 'red' } toy soldier's turn.\n" +
346
- "(Arrow keys to walk and aim, Return to jump, Space to shoot)",
347
- Gosu::default_font_name, 30, 0, width, :center)
348
- @player_won_messages << Gosu::Image.from_text(self,
349
- "The #{ plr == 0 ? 'green' : 'red' } toy soldier has won!",
350
- Gosu::default_font_name, 30, 5, width, :center)
351
- end
352
-
353
- # Create everything!
354
- @map = Map.new(self)
355
- @players = [Player.new(self, 200, 40, 0xff308000), Player.new(self, 600, 40, 0xff803000)]
356
- @objects = @players.dup
357
-
358
- # Let any player start.
359
- @current_player = rand(2)
360
- # Currently not waiting for a missile to hit something.
361
- @waiting = false
362
- end
363
-
364
- def draw
365
- # Draw the main game.
366
- @map.draw
367
- @objects.each { |o| o.draw }
368
-
369
- # If any text should be displayed, draw it - and add a nice black border around it
370
- # by drawing it four times, with a little offset in each direction.
371
-
372
- cur_text = @player_instructions[@current_player] if not @waiting
373
- cur_text = @player_won_messages[1 - @current_player] if @players[@current_player].dead
374
-
375
- if cur_text then
376
- x, y = 0, 30
377
- cur_text.draw(x - 1, y, 0, 1, 1, 0xff000000)
378
- cur_text.draw(x + 1, y, 0, 1, 1, 0xff000000)
379
- cur_text.draw(x, y - 1, 0, 1, 1, 0xff000000)
380
- cur_text.draw(x, y + 1, 0, 1, 1, 0xff000000)
381
- cur_text.draw(x, y, 0, 1, 1, 0xffffffff)
382
- end
383
- end
384
-
385
- def update
386
- # if waiting for the next player's turn, continue to do so until the missile has
387
- # hit something.
388
- @waiting &&= !@objects.grep(Missile).empty?
389
-
390
- # Remove all objects whose update method returns false.
391
- @objects.reject! { |o| o.update == false }
392
-
393
- # If it's a player's turn, forward controls.
394
- if not @waiting and not @players[@current_player].dead then
395
- player = @players[@current_player]
396
- player.aim_up if button_down? Gosu::KbUp
397
- player.aim_down if button_down? Gosu::KbDown
398
- player.try_walk(-1) if button_down? Gosu::KbLeft
399
- player.try_walk(+1) if button_down? Gosu::KbRight
400
- player.try_jump if button_down? Gosu::KbReturn
401
- end
402
- end
403
-
404
- def button_down(id)
405
- if id == Gosu::KbSpace and not @waiting and not @players[@current_player].dead then
406
- # Shoot! This is handled in button_down because holding space shouldn't auto-fire.
407
- @players[@current_player].shoot
408
- @current_player = 1 - @current_player
409
- @waiting = true
410
- end
411
- # Very important feature! ;)
412
- close if id == Gosu::KbEscape
413
- end
414
- end
415
-
416
- # So far we have only defined how everything *should* work - now set it up and run it!
417
- GameWindow.new.show
@@ -1,154 +0,0 @@
1
- # This example demonstrates the use of the TextInput functionality.
2
- # One can tab through, or click into the text fields and change it's contents.
3
-
4
- # At its most basic form, you only need to create a new TextInput instance and
5
- # set the text_input attribute of your window to it. Until you set this
6
- # attribute to nil again, the TextInput object will build a text that can be
7
- # accessed via TextInput#text.
8
-
9
- # The TextInput object also maintains the position of the caret as the index
10
- # of the character that it's left to via the caret_pos attribute. Furthermore,
11
- # if there is a selection, the selection_start attribute yields its beginning,
12
- # using the same indexing scheme. If there is no selection, selection_start
13
- # is equal to caret_pos.
14
-
15
- # A TextInput object is purely abstract, though; drawing the input field is left
16
- # to the user. In this case, we are subclassing TextInput to add this code.
17
- # As with most of Gosu, how this is handled is completely left open; the scheme
18
- # presented here is not mandatory! Gosu only aims to provide enough code for
19
- # games (or intermediate UI toolkits) to be built upon it.
20
-
21
- require 'rubygems'
22
- require 'gosu'
23
-
24
- class TextField < Gosu::TextInput
25
- # Some constants that define our appearance.
26
- INACTIVE_COLOR = 0xcc666666
27
- ACTIVE_COLOR = 0xccff6666
28
- SELECTION_COLOR = 0xcc0000ff
29
- CARET_COLOR = 0xffffffff
30
- PADDING = 5
31
-
32
- attr_reader :x, :y
33
-
34
- def initialize(window, font, x, y)
35
- # TextInput's constructor doesn't expect any arguments.
36
- super()
37
-
38
- @window, @font, @x, @y = window, font, x, y
39
-
40
- # Start with a self-explanatory text in each field.
41
- self.text = "Click to change text"
42
- end
43
-
44
- # Example filter method. You can truncate the text to employ a length limit (watch out
45
- # with Ruby 1.8 and UTF-8!), limit the text to certain characters etc.
46
- def filter text
47
- text.upcase
48
- end
49
-
50
- def draw
51
- # Depending on whether this is the currently selected input or not, change the
52
- # background's color.
53
- if @window.text_input == self then
54
- background_color = ACTIVE_COLOR
55
- else
56
- background_color = INACTIVE_COLOR
57
- end
58
- @window.draw_quad(x - PADDING, y - PADDING, background_color,
59
- x + width + PADDING, y - PADDING, background_color,
60
- x - PADDING, y + height + PADDING, background_color,
61
- x + width + PADDING, y + height + PADDING, background_color, 0)
62
-
63
- # Calculate the position of the caret and the selection start.
64
- pos_x = x + @font.text_width(self.text[0...self.caret_pos])
65
- sel_x = x + @font.text_width(self.text[0...self.selection_start])
66
-
67
- # Draw the selection background, if any; if not, sel_x and pos_x will be
68
- # the same value, making this quad empty.
69
- @window.draw_quad(sel_x, y, SELECTION_COLOR,
70
- pos_x, y, SELECTION_COLOR,
71
- sel_x, y + height, SELECTION_COLOR,
72
- pos_x, y + height, SELECTION_COLOR, 0)
73
-
74
- # Draw the caret; again, only if this is the currently selected field.
75
- if @window.text_input == self then
76
- @window.draw_line(pos_x, y, CARET_COLOR,
77
- pos_x, y + height, CARET_COLOR, 0)
78
- end
79
-
80
- # Finally, draw the text itself!
81
- @font.draw(self.text, x, y, 0)
82
- end
83
-
84
- # This text field grows with the text that's being entered.
85
- # (Usually one would use clip_to and scroll around on the text field.)
86
- def width
87
- @font.text_width(self.text)
88
- end
89
-
90
- def height
91
- @font.height
92
- end
93
-
94
- # Hit-test for selecting a text field with the mouse.
95
- def under_point?(mouse_x, mouse_y)
96
- mouse_x > x - PADDING and mouse_x < x + width + PADDING and
97
- mouse_y > y - PADDING and mouse_y < y + height + PADDING
98
- end
99
-
100
- # Tries to move the caret to the position specifies by mouse_x
101
- def move_caret(mouse_x)
102
- # Test character by character
103
- 1.upto(self.text.length) do |i|
104
- if mouse_x < x + @font.text_width(text[0...i]) then
105
- self.caret_pos = self.selection_start = i - 1;
106
- return
107
- end
108
- end
109
- # Default case: user must have clicked the right edge
110
- self.caret_pos = self.selection_start = self.text.length
111
- end
112
- end
113
-
114
- class TextInputWindow < Gosu::Window
115
- def initialize
116
- super(300, 200, false)
117
- self.caption = "Text Input Example"
118
-
119
- font = Gosu::Font.new(self, Gosu::default_font_name, 20)
120
-
121
- # Set up an array of three text fields.
122
- @text_fields = Array.new(3) { |index| TextField.new(self, font, 50, 30 + index * 50) }
123
-
124
- @cursor = Gosu::Image.new(self, "media/Cursor.png", false)
125
- end
126
-
127
- def draw
128
- @text_fields.each { |tf| tf.draw }
129
- @cursor.draw(mouse_x, mouse_y, 0)
130
- end
131
-
132
- def button_down(id)
133
- if id == Gosu::KbTab then
134
- # Tab key will not be 'eaten' by text fields; use for switching through
135
- # text fields.
136
- index = @text_fields.index(self.text_input) || -1
137
- self.text_input = @text_fields[(index + 1) % @text_fields.size]
138
- elsif id == Gosu::KbEscape then
139
- # Escape key will not be 'eaten' by text fields; use for deselecting.
140
- if self.text_input then
141
- self.text_input = nil
142
- else
143
- close
144
- end
145
- elsif id == Gosu::MsLeft then
146
- # Mouse click: Select text field based on mouse position.
147
- self.text_input = @text_fields.find { |tf| tf.under_point?(mouse_x, mouse_y) }
148
- # Advanced: Move caret to clicked position
149
- self.text_input.move_caret(mouse_x) unless self.text_input.nil?
150
- end
151
- end
152
- end
153
-
154
- TextInputWindow.new.show