gosu 0.7.10.1
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING.txt +29 -0
- data/Gosu/Async.hpp +48 -0
- data/Gosu/Audio.hpp +145 -0
- data/Gosu/AutoLink.hpp +16 -0
- data/Gosu/Bitmap.hpp +85 -0
- data/Gosu/ButtonsMac.hpp +114 -0
- data/Gosu/ButtonsWin.hpp +111 -0
- data/Gosu/ButtonsX.hpp +115 -0
- data/Gosu/Color.hpp +172 -0
- data/Gosu/Directories.hpp +36 -0
- data/Gosu/Font.hpp +59 -0
- data/Gosu/Fwd.hpp +31 -0
- data/Gosu/Gosu.hpp +26 -0
- data/Gosu/Graphics.hpp +86 -0
- data/Gosu/GraphicsBase.hpp +45 -0
- data/Gosu/IO.hpp +255 -0
- data/Gosu/Image.hpp +148 -0
- data/Gosu/ImageData.hpp +45 -0
- data/Gosu/Input.hpp +116 -0
- data/Gosu/Math.hpp +95 -0
- data/Gosu/Platform.hpp +61 -0
- data/Gosu/RotFlip.hpp +116 -0
- data/Gosu/Sockets.hpp +129 -0
- data/Gosu/Text.hpp +47 -0
- data/Gosu/TextInput.hpp +57 -0
- data/Gosu/Timing.hpp +16 -0
- data/Gosu/Utility.hpp +24 -0
- data/Gosu/WinUtility.hpp +76 -0
- data/Gosu/Window.hpp +84 -0
- data/GosuImpl/Async.cpp +37 -0
- data/GosuImpl/AudioFmod.cpp +417 -0
- data/GosuImpl/AudioSDL.cpp +255 -0
- data/GosuImpl/DirectoriesMac.mm +38 -0
- data/GosuImpl/DirectoriesUnix.cpp +48 -0
- data/GosuImpl/DirectoriesWin.cpp +42 -0
- data/GosuImpl/FileUnix.cpp +100 -0
- data/GosuImpl/FileWin.cpp +83 -0
- data/GosuImpl/Graphics/Bitmap.cpp +116 -0
- data/GosuImpl/Graphics/BitmapBMP.cpp +232 -0
- data/GosuImpl/Graphics/BitmapColorKey.cpp +39 -0
- data/GosuImpl/Graphics/BitmapPNG.cpp +276 -0
- data/GosuImpl/Graphics/BitmapUtils.cpp +67 -0
- data/GosuImpl/Graphics/BlockAllocator.cpp +127 -0
- data/GosuImpl/Graphics/BlockAllocator.hpp +34 -0
- data/GosuImpl/Graphics/Color.cpp +126 -0
- data/GosuImpl/Graphics/Common.hpp +21 -0
- data/GosuImpl/Graphics/DrawOp.hpp +154 -0
- data/GosuImpl/Graphics/Font.cpp +110 -0
- data/GosuImpl/Graphics/Graphics.cpp +295 -0
- data/GosuImpl/Graphics/Image.cpp +159 -0
- data/GosuImpl/Graphics/LargeImageData.cpp +115 -0
- data/GosuImpl/Graphics/LargeImageData.hpp +37 -0
- data/GosuImpl/Graphics/RotFlip.cpp +184 -0
- data/GosuImpl/Graphics/TexChunk.cpp +77 -0
- data/GosuImpl/Graphics/TexChunk.hpp +40 -0
- data/GosuImpl/Graphics/Text.cpp +223 -0
- data/GosuImpl/Graphics/TextMac.cpp +242 -0
- data/GosuImpl/Graphics/TextPangoFT.cpp +186 -0
- data/GosuImpl/Graphics/TextWin.cpp +172 -0
- data/GosuImpl/Graphics/Texture.cpp +104 -0
- data/GosuImpl/Graphics/Texture.hpp +34 -0
- data/GosuImpl/IO.cpp +48 -0
- data/GosuImpl/InputMac.mm +677 -0
- data/GosuImpl/InputWin.cpp +444 -0
- data/GosuImpl/InputX.cpp +158 -0
- data/GosuImpl/MacUtility.hpp +48 -0
- data/GosuImpl/Math.cpp +49 -0
- data/GosuImpl/RubyGosu.swg +474 -0
- data/GosuImpl/RubyGosuStub.mm +17 -0
- data/GosuImpl/RubyGosu_DllMain.cxx +30 -0
- data/GosuImpl/RubyGosu_wrap.cxx +8521 -0
- data/GosuImpl/RubyGosu_wrap.h +31 -0
- data/GosuImpl/Sockets/CommSocket.cpp +304 -0
- data/GosuImpl/Sockets/ListenerSocket.cpp +60 -0
- data/GosuImpl/Sockets/MessageSocket.cpp +136 -0
- data/GosuImpl/Sockets/Socket.cpp +145 -0
- data/GosuImpl/Sockets/Sockets.hpp +66 -0
- data/GosuImpl/TextInputMac.mm +207 -0
- data/GosuImpl/TextInputWin.cpp +197 -0
- data/GosuImpl/TextInputX.cpp +201 -0
- data/GosuImpl/TextTTFWin.cpp +247 -0
- data/GosuImpl/TimingUnix.cpp +17 -0
- data/GosuImpl/TimingWin.cpp +28 -0
- data/GosuImpl/Utility.cpp +140 -0
- data/GosuImpl/WinMain.cpp +69 -0
- data/GosuImpl/WinUtility.cpp +137 -0
- data/GosuImpl/WindowMac.mm +466 -0
- data/GosuImpl/WindowWin.cpp +447 -0
- data/GosuImpl/WindowX.cpp +392 -0
- data/GosuImpl/X11vroot.h +118 -0
- data/README.txt +13 -0
- data/Rakefile +178 -0
- data/examples/ChipmunkIntegration.rb +275 -0
- data/examples/CptnRuby.rb +231 -0
- data/examples/MoreChipmunkAndRMagick.rb +155 -0
- data/examples/OpenGLIntegration.rb +232 -0
- data/examples/RMagickIntegration.rb +449 -0
- data/examples/TextInput.cpp +170 -0
- data/examples/TextInput.rb +139 -0
- data/examples/Tutorial.cpp +215 -0
- data/examples/Tutorial.rb +137 -0
- data/examples/media/Beep.wav +0 -0
- data/examples/media/CptnRuby Gem.png +0 -0
- data/examples/media/CptnRuby Map.txt +25 -0
- data/examples/media/CptnRuby Tileset.png +0 -0
- data/examples/media/CptnRuby.png +0 -0
- data/examples/media/Cursor.png +0 -0
- data/examples/media/Earth.png +0 -0
- data/examples/media/Explosion.wav +0 -0
- data/examples/media/LargeStar.png +0 -0
- data/examples/media/Sky.jpg +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/linux/Makefile.in +98 -0
- data/linux/configure +5658 -0
- data/linux/configure.ac +126 -0
- data/linux/extconf.rb +11 -0
- data/mac/English.lproj/InfoPlist.strings +0 -0
- data/mac/Gosu-Info.plist +26 -0
- data/mac/Gosu.xcodeproj/project.pbxproj +1194 -0
- data/mac/RubyGosu Template-Info.plist +26 -0
- data/mac/libboost_thread_1_34_1_universal.a +0 -0
- data/mac/libboost_thread_d_1_34_1_universal.a +0 -0
- data/mac/libfmod_universal.a +0 -0
- data/mac/libpng_universal.a +0 -0
- data/mac/libz_universal.a +0 -0
- data/reference/Async_8hpp-source.html +70 -0
- data/reference/Audio_8hpp-source.html +114 -0
- data/reference/Audio_8hpp.html +50 -0
- data/reference/AutoLink_8hpp-source.html +38 -0
- data/reference/AutoLink_8hpp.html +34 -0
- data/reference/Bitmap_8hpp-source.html +85 -0
- data/reference/Bitmap_8hpp.html +58 -0
- data/reference/ButtonsMac_8hpp-source.html +133 -0
- data/reference/ButtonsWin_8hpp-source.html +133 -0
- data/reference/ButtonsX_8hpp-source.html +134 -0
- data/reference/Color_8hpp-source.html +169 -0
- data/reference/Color_8hpp.html +85 -0
- data/reference/Directories_8hpp-source.html +42 -0
- data/reference/Directories_8hpp.html +46 -0
- data/reference/Font_8hpp-source.html +65 -0
- data/reference/Font_8hpp.html +41 -0
- data/reference/Fwd_8hpp-source.html +52 -0
- data/reference/Fwd_8hpp.html +37 -0
- data/reference/Gosu_8hpp-source.html +48 -0
- data/reference/Gosu_8hpp.html +34 -0
- data/reference/GraphicsBase_8hpp-source.html +57 -0
- data/reference/GraphicsBase_8hpp.html +56 -0
- data/reference/Graphics_8hpp-source.html +96 -0
- data/reference/Graphics_8hpp.html +53 -0
- data/reference/IO_8hpp-source.html +255 -0
- data/reference/IO_8hpp.html +74 -0
- data/reference/ImageData_8hpp-source.html +62 -0
- data/reference/ImageData_8hpp.html +43 -0
- data/reference/Image_8hpp-source.html +126 -0
- data/reference/Image_8hpp.html +48 -0
- data/reference/Input_8hpp-source.html +118 -0
- data/reference/Input_8hpp.html +50 -0
- data/reference/Math_8hpp-source.html +92 -0
- data/reference/Math_8hpp.html +74 -0
- data/reference/Platform_8hpp-source.html +83 -0
- data/reference/Platform_8hpp.html +73 -0
- data/reference/RotFlip_8hpp-source.html +138 -0
- data/reference/RotFlip_8hpp.html +77 -0
- data/reference/Sockets_8hpp-source.html +130 -0
- data/reference/Sockets_8hpp.html +66 -0
- data/reference/TextInput_8hpp-source.html +64 -0
- data/reference/TextInput_8hpp.html +41 -0
- data/reference/Text_8hpp-source.html +51 -0
- data/reference/Text_8hpp.html +46 -0
- data/reference/Timing_8hpp-source.html +36 -0
- data/reference/Timing_8hpp.html +42 -0
- data/reference/Utility_8hpp-source.html +44 -0
- data/reference/Utility_8hpp.html +48 -0
- data/reference/WinUtility_8hpp-source.html +79 -0
- data/reference/WinUtility_8hpp.html +64 -0
- data/reference/Window_8hpp-source.html +91 -0
- data/reference/Window_8hpp.html +41 -0
- data/reference/annotated.html +51 -0
- data/reference/classGosu_1_1Audio-members.html +34 -0
- data/reference/classGosu_1_1Audio.html +46 -0
- data/reference/classGosu_1_1Bitmap-members.html +44 -0
- data/reference/classGosu_1_1Bitmap.html +263 -0
- data/reference/classGosu_1_1Buffer-members.html +44 -0
- data/reference/classGosu_1_1Buffer.html +78 -0
- data/reference/classGosu_1_1Buffer.png +0 -0
- data/reference/classGosu_1_1Button-members.html +36 -0
- data/reference/classGosu_1_1Button.html +143 -0
- data/reference/classGosu_1_1Color-members.html +56 -0
- data/reference/classGosu_1_1Color.html +387 -0
- data/reference/classGosu_1_1File-members.html +41 -0
- data/reference/classGosu_1_1File.html +69 -0
- data/reference/classGosu_1_1File.png +0 -0
- data/reference/classGosu_1_1Font-members.html +39 -0
- data/reference/classGosu_1_1Font.html +309 -0
- data/reference/classGosu_1_1Graphics-members.html +50 -0
- data/reference/classGosu_1_1Graphics.html +234 -0
- data/reference/classGosu_1_1Image-members.html +45 -0
- data/reference/classGosu_1_1Image.html +518 -0
- data/reference/classGosu_1_1ImageData-members.html +37 -0
- data/reference/classGosu_1_1ImageData.html +60 -0
- data/reference/classGosu_1_1Input-members.html +44 -0
- data/reference/classGosu_1_1Input.html +223 -0
- data/reference/classGosu_1_1MessageSocket-members.html +40 -0
- data/reference/classGosu_1_1MessageSocket.html +233 -0
- data/reference/classGosu_1_1Resource-members.html +39 -0
- data/reference/classGosu_1_1Resource.html +116 -0
- data/reference/classGosu_1_1Resource.png +0 -0
- data/reference/classGosu_1_1Sample-members.html +37 -0
- data/reference/classGosu_1_1Sample.html +200 -0
- data/reference/classGosu_1_1SampleInstance-members.html +38 -0
- data/reference/classGosu_1_1SampleInstance.html +169 -0
- data/reference/classGosu_1_1Song-members.html +43 -0
- data/reference/classGosu_1_1Song.html +260 -0
- data/reference/classGosu_1_1TextInput-members.html +38 -0
- data/reference/classGosu_1_1TextInput.html +121 -0
- data/reference/classGosu_1_1Window-members.html +50 -0
- data/reference/classGosu_1_1Window.html +271 -0
- data/reference/doxyfile +233 -0
- data/reference/doxygen.css +433 -0
- data/reference/doxygen.png +0 -0
- data/reference/files.html +54 -0
- data/reference/functions.html +236 -0
- data/reference/functions_enum.html +45 -0
- data/reference/functions_func.html +227 -0
- data/reference/functions_vars.html +47 -0
- data/reference/hierarchy.html +53 -0
- data/reference/index.html +26 -0
- data/reference/namespaceGosu.html +2890 -0
- data/reference/namespaceGosu_1_1Colors.html +70 -0
- data/reference/namespaceGosu_1_1Win.html +275 -0
- data/reference/namespacemembers.html +216 -0
- data/reference/namespacemembers_enum.html +52 -0
- data/reference/namespacemembers_eval.html +54 -0
- data/reference/namespacemembers_func.html +185 -0
- data/reference/namespacemembers_type.html +46 -0
- data/reference/namespacemembers_vars.html +46 -0
- data/reference/namespaces.html +35 -0
- data/reference/tab_b.gif +0 -0
- data/reference/tab_l.gif +0 -0
- data/reference/tab_r.gif +0 -0
- data/reference/tabs.css +102 -0
- data/windows/Gosu.sln +29 -0
- data/windows/Gosu.vcproj +553 -0
- data/windows/RubyGosu.vcproj +138 -0
- metadata +305 -0
@@ -0,0 +1,449 @@
|
|
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
|
+
begin
|
15
|
+
# In case you use Gosu via RubyGems.
|
16
|
+
require 'rubygems'
|
17
|
+
rescue LoadError
|
18
|
+
# In case you don't.
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'gosu'
|
22
|
+
require 'RMagick'
|
23
|
+
|
24
|
+
NULL_PIXEL = Magick::Pixel.from_color('none')
|
25
|
+
|
26
|
+
# The class for this game's map.
|
27
|
+
# Design:
|
28
|
+
# * Dynamic map creation at startup, holding it as RMagick Image in @image
|
29
|
+
# * Testing for solidity by testing @image's pixel values
|
30
|
+
# * Drawing by (re)creating an array of Gosu::Image instances, each representing
|
31
|
+
# a part of the large @image
|
32
|
+
# * Blasting holes into the map is implemented by drawing and erasing portions
|
33
|
+
# of @image, then setting the corresponding Gosu::Image instances to nil, so
|
34
|
+
# they will be recreated in Map#draw
|
35
|
+
# Note: The splitting is done because recreating such a large Gosu::Image for
|
36
|
+
# every map change would be a very noticeable delay!
|
37
|
+
|
38
|
+
class Map
|
39
|
+
WIDTH, HEIGHT = 800, 600
|
40
|
+
TILE_SIZE = 100
|
41
|
+
TILES_X = WIDTH / TILE_SIZE
|
42
|
+
TILES_Y = HEIGHT / TILE_SIZE
|
43
|
+
|
44
|
+
def initialize(window)
|
45
|
+
# We'll need the window later for re-creating Gosu images.
|
46
|
+
@window = window
|
47
|
+
|
48
|
+
# Let's start with something simple and load the sky via RMagick.
|
49
|
+
# Loading JPEG files isn't possible with Gosu, so say wow!
|
50
|
+
sky = Magick::Image.read("media/Sky.jpg").first
|
51
|
+
@sky = Gosu::Image.new(window, sky, true)
|
52
|
+
|
53
|
+
# This is the one large RMagick image that represents the map.
|
54
|
+
@image = Magick::Image.new(WIDTH, HEIGHT) { self.background_color = 'none' }
|
55
|
+
|
56
|
+
# Set up a Draw object that fills with an earth texture.
|
57
|
+
earth = Magick::Image.read('media/Earth.png').first.resize(1.5)
|
58
|
+
gc = Magick::Draw.new
|
59
|
+
gc.pattern('earth', 0, 0, earth.columns, earth.rows) { gc.composite(0, 0, 0, 0, earth) }
|
60
|
+
gc.fill('earth')
|
61
|
+
gc.stroke('#603000').stroke_width(1.5)
|
62
|
+
# Draw a smooth bezier island onto the map!
|
63
|
+
polypoints = [0, HEIGHT]
|
64
|
+
0.upto(TILES_X) do |x|
|
65
|
+
polypoints += [x * TILE_SIZE, HEIGHT * 0.2 + rand(HEIGHT * 0.8)]
|
66
|
+
end
|
67
|
+
polypoints += [WIDTH, HEIGHT]
|
68
|
+
gc.bezier(*polypoints)
|
69
|
+
gc.draw(@image)
|
70
|
+
|
71
|
+
# Create a bright-dark gradient fill, an image from it and change the map's
|
72
|
+
# brightness with it.
|
73
|
+
fill = Magick::GradientFill.new(0, HEIGHT * 0.4, WIDTH, HEIGHT * 0.4, '#fff', '#666')
|
74
|
+
gradient = Magick::Image.new(WIDTH, HEIGHT, fill)
|
75
|
+
gradient = @image.composite(gradient, 0, 0, Magick::InCompositeOp)
|
76
|
+
@image.composite!(gradient, 0, 0, Magick::MultiplyCompositeOp)
|
77
|
+
|
78
|
+
# Finally, place the star in the middle of the map, just onto the ground.
|
79
|
+
star = Magick::Image.read('media/LargeStar.png').first
|
80
|
+
star_y = 0
|
81
|
+
star_y += 20 until solid?(WIDTH / 2, star_y)
|
82
|
+
@image.composite!(star, (WIDTH - star.columns) / 2, star_y - star.rows * 0.85,
|
83
|
+
Magick::DstOverCompositeOp)
|
84
|
+
|
85
|
+
# Creates an X*Y array for the Gosu images.
|
86
|
+
# (Initialized to nil automatically).
|
87
|
+
@gosu_images = Array.new(TILES_X) { Array.new(TILES_Y) }
|
88
|
+
end
|
89
|
+
|
90
|
+
def solid?(x, y)
|
91
|
+
# Map is open at the top.
|
92
|
+
return false if y < 0
|
93
|
+
# Map is closed on all other sides.
|
94
|
+
return true if x < 0 or x >= 800 or y >= 600
|
95
|
+
# Inside of the map, determine solidity from the map image.
|
96
|
+
@image.pixel_color(x, y) != NULL_PIXEL
|
97
|
+
end
|
98
|
+
|
99
|
+
def draw
|
100
|
+
# Sky background.
|
101
|
+
@sky.draw(0, 0, 0)
|
102
|
+
# All the tiles.
|
103
|
+
TILES_Y.times do |y|
|
104
|
+
TILES_X.times do |x|
|
105
|
+
# Recreate images that haven't been created yet, or need to be recreated
|
106
|
+
# due to map changes.
|
107
|
+
if @gosu_images[x][y].nil? then
|
108
|
+
part = @image.crop(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE)
|
109
|
+
@gosu_images[x][y] = Gosu::Image.new(@window, part, true)
|
110
|
+
end
|
111
|
+
# At last - draw it!
|
112
|
+
@gosu_images[x][y].draw(x * TILE_SIZE, y * TILE_SIZE, 0)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Radius of a crater.
|
118
|
+
RADIUS = 25
|
119
|
+
# Radius of a crater, SHadow included.
|
120
|
+
SH_RADIUS = 45
|
121
|
+
|
122
|
+
def blast(x, y)
|
123
|
+
# This code assumes at most 2x2 tiles are affected by a blast, so
|
124
|
+
# don't change the RADIUS to 200 or something ;)
|
125
|
+
# Calculate the x/y indices of the two to four affected tiles.
|
126
|
+
# (left/right and top/bottom might be the same).
|
127
|
+
|
128
|
+
left = (x - SH_RADIUS) / TILE_SIZE
|
129
|
+
right = (x + SH_RADIUS) / TILE_SIZE
|
130
|
+
top = (y - SH_RADIUS) / TILE_SIZE
|
131
|
+
bottom = (y + SH_RADIUS) / TILE_SIZE
|
132
|
+
|
133
|
+
# Set affected images to nil.
|
134
|
+
# A 'double-free' doesn't hurt if e.g. left == right! However, we have to watch out
|
135
|
+
# for out-of-bounds errors.
|
136
|
+
|
137
|
+
@gosu_images[left][top] = nil unless left < 0 or top < 0
|
138
|
+
@gosu_images[right][top] = nil unless right >= TILES_X or top < 0
|
139
|
+
@gosu_images[left][bottom] = nil unless left < 0 or bottom >= TILES_Y
|
140
|
+
@gosu_images[right][bottom] = nil unless right >= TILES_X or bottom >= TILES_Y
|
141
|
+
|
142
|
+
# Create the crater image (basically a circle shape that is used to erase
|
143
|
+
# parts of the map) and the crater shadow image, if they don't exist
|
144
|
+
# already.
|
145
|
+
|
146
|
+
if @crater_image.nil? then
|
147
|
+
@crater_image = Magick::Image.new(2 * RADIUS, 2 * RADIUS) { self.background_color = 'none' }
|
148
|
+
gc = Magick::Draw.new
|
149
|
+
gc.fill('black').circle(RADIUS, RADIUS, RADIUS, 0)
|
150
|
+
gc.draw(@crater_image)
|
151
|
+
@crater_shadow = @crater_image.shadow(0, 0, (SH_RADIUS - RADIUS) / 2, 1)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Draw the shadow (twice for more intensity), then erase a circle from the map.
|
155
|
+
@image.composite!(@crater_shadow, x - SH_RADIUS, y - SH_RADIUS, Magick::AtopCompositeOp)
|
156
|
+
@image.composite!(@crater_shadow, x - SH_RADIUS, y - SH_RADIUS, Magick::AtopCompositeOp)
|
157
|
+
@image.composite!(@crater_image, x - RADIUS, y - RADIUS, Magick::DstOutCompositeOp)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Player class.
|
162
|
+
# Note that applies to the whole game:
|
163
|
+
# All objects implement an informal interface.
|
164
|
+
# draw: Draws the object (obviously)
|
165
|
+
# update: Moves the object etc., returns false if the object is to be deleted
|
166
|
+
# hit_by?(missile): Returns true if an object is hit by the missile, causing
|
167
|
+
# it to explode on this object.
|
168
|
+
|
169
|
+
class Player
|
170
|
+
# Magic numbers considered harmful! This is the height of the
|
171
|
+
# player as used for collision detection.
|
172
|
+
HEIGHT = 14
|
173
|
+
|
174
|
+
attr_reader :x, :y, :dead
|
175
|
+
|
176
|
+
def initialize(window, x, y, color)
|
177
|
+
# Only load the images once for all instances of this class.
|
178
|
+
@@images ||= Gosu::Image.load_tiles(window, "media/Soldier.png", 40, 50, false)
|
179
|
+
|
180
|
+
@window, @x, @y, @color = window, x, y, color
|
181
|
+
@vy = 0
|
182
|
+
|
183
|
+
# -1: left, +1: right
|
184
|
+
@dir = -1
|
185
|
+
|
186
|
+
# Aiming angle.
|
187
|
+
@angle = 90
|
188
|
+
end
|
189
|
+
|
190
|
+
def draw
|
191
|
+
if dead then
|
192
|
+
# Poor, broken soldier.
|
193
|
+
@@images[0].draw_rot(x, y, 0, 290 * @dir, 0.5, 0.65, @dir * 0.5, 0.5, @color)
|
194
|
+
@@images[2].draw_rot(x, y, 0, 160 * @dir, 0.95, 0.5, 0.5, @dir * 0.5, @color)
|
195
|
+
else
|
196
|
+
# Was moved last frame?
|
197
|
+
if @show_walk_anim
|
198
|
+
# Yes: Display walking animation.
|
199
|
+
frame = Gosu::milliseconds / 200 % 2
|
200
|
+
else
|
201
|
+
# No: Stand around (boring).
|
202
|
+
frame = 0
|
203
|
+
end
|
204
|
+
|
205
|
+
# Draw feet, then chest.
|
206
|
+
@@images[frame].draw(x - 10 * @dir, y - 20, 0, @dir * 0.5, 0.5, @color)
|
207
|
+
angle = @angle
|
208
|
+
angle = 180 - angle if @dir == -1
|
209
|
+
@@images[2].draw_rot(x, y - 5, 0, angle, 1, 0.5, 0.5, @dir * 0.5, @color)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def update
|
214
|
+
# First, assume that no walking happened this frame.
|
215
|
+
@show_walk_anim = false
|
216
|
+
|
217
|
+
# Gravity.
|
218
|
+
@vy += 1
|
219
|
+
|
220
|
+
if @vy > 1 then
|
221
|
+
# Move upwards until hitting something.
|
222
|
+
@vy.times do
|
223
|
+
if @window.map.solid?(x, y + 1)
|
224
|
+
@vy = 0
|
225
|
+
break
|
226
|
+
else
|
227
|
+
@y += 1
|
228
|
+
end
|
229
|
+
end
|
230
|
+
else
|
231
|
+
# Move downwards until hitting something.
|
232
|
+
(-@vy).times do
|
233
|
+
if @window.map.solid?(x, y - HEIGHT - 1)
|
234
|
+
@vy = 0
|
235
|
+
break
|
236
|
+
else
|
237
|
+
@y -= 1
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
# Soldiers are never deleted (they may die, though, but that is a different
|
243
|
+
# concept).
|
244
|
+
true
|
245
|
+
end
|
246
|
+
|
247
|
+
def aim_up
|
248
|
+
@angle -= 2 unless @angle < 10
|
249
|
+
end
|
250
|
+
|
251
|
+
def aim_down
|
252
|
+
@angle += 2 unless @angle > 170
|
253
|
+
end
|
254
|
+
|
255
|
+
def try_walk(dir)
|
256
|
+
@show_walk_anim = true
|
257
|
+
@dir = dir
|
258
|
+
# First, magically move up (so soldiers can run up hills)
|
259
|
+
2.times { @y -= 1 unless @window.map.solid?(x, y - HEIGHT - 1) }
|
260
|
+
# Now move into the desired direction.
|
261
|
+
@x += dir unless @window.map.solid?(x + dir, y) or
|
262
|
+
@window.map.solid?(x + dir, y - HEIGHT)
|
263
|
+
# To make up for unnecessary movement upwards, sink downward again.
|
264
|
+
2.times { @y += 1 unless @window.map.solid?(x, y + 1) }
|
265
|
+
end
|
266
|
+
|
267
|
+
def try_jump
|
268
|
+
@vy = -12 if @window.map.solid?(x, y + 1)
|
269
|
+
end
|
270
|
+
|
271
|
+
def shoot
|
272
|
+
@window.objects << Missile.new(@window, x + 10 * @dir, y - 10, @angle * @dir)
|
273
|
+
end
|
274
|
+
|
275
|
+
def hit_by?(missile)
|
276
|
+
if Gosu::distance(missile.x, missile.y, x, y) < 30 then
|
277
|
+
# Was hit :(
|
278
|
+
@dead = true
|
279
|
+
return true
|
280
|
+
else
|
281
|
+
return false
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
# Implements the same interface as Player, except it'S a missile!
|
287
|
+
|
288
|
+
class Missile
|
289
|
+
attr_reader :x, :y, :vx, :vy
|
290
|
+
|
291
|
+
def initialize(window, x, y, angle)
|
292
|
+
# All missile instances use the same sound.
|
293
|
+
@@explosion_sound ||= Gosu::Sample.new(window, "media/Explosion.wav")
|
294
|
+
|
295
|
+
# Horizontal/vertical velocity.
|
296
|
+
@vx, @vy = Gosu::offset_x(angle, 20).to_i, Gosu::offset_y(angle, 20).to_i
|
297
|
+
|
298
|
+
@window, @x, @y = window, x + @vx, y + @vy
|
299
|
+
end
|
300
|
+
|
301
|
+
def update
|
302
|
+
# Movement, gravity
|
303
|
+
@x += @vx
|
304
|
+
@y += @vy
|
305
|
+
@vy += 1
|
306
|
+
# Hit anything?
|
307
|
+
if @window.map.solid?(x, y) or @window.objects.any? { |o| o.hit_by?(self) } then
|
308
|
+
# Create great particles.
|
309
|
+
5.times { @window.objects << Particle.new(@window, x - 25 + rand(51), y - 25 + rand(51)) }
|
310
|
+
@window.map.blast(x, y)
|
311
|
+
# Weeee, stereo sound!
|
312
|
+
@@explosion_sound.play_pan((2 * @x - Map::WIDTH) / Map::WIDTH)
|
313
|
+
return false
|
314
|
+
else
|
315
|
+
return true
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def draw
|
320
|
+
# Just draw a small quad.
|
321
|
+
@window.draw_quad(x-2, y-2, 0xff800000, x+2, y-2, 0xff800000,
|
322
|
+
x-2, y+2, 0xff800000, x+2, y+2, 0xff800000, 0)
|
323
|
+
end
|
324
|
+
|
325
|
+
def hit_by?(missile)
|
326
|
+
# Missiles can't be hit by other missiles!
|
327
|
+
false
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
# Very minimal object that just draws a fading particle.
|
332
|
+
|
333
|
+
class Particle
|
334
|
+
def initialize(window, x, y)
|
335
|
+
# All Particle instances use the same image
|
336
|
+
@@image ||= Gosu::Image.new(window, 'media/Smoke.png', false)
|
337
|
+
|
338
|
+
@x, @y = x, y
|
339
|
+
@color = Gosu::Color.new(255, 255, 255, 255)
|
340
|
+
end
|
341
|
+
|
342
|
+
def update
|
343
|
+
@y -= 5
|
344
|
+
@x = @x - 1 + rand(3)
|
345
|
+
@color.alpha -= 5
|
346
|
+
|
347
|
+
# Remove if faded completely.
|
348
|
+
@color.alpha > 0
|
349
|
+
end
|
350
|
+
|
351
|
+
def draw
|
352
|
+
@@image.draw(@x - 25, @y - 25, 0, 1, 1, @color)
|
353
|
+
end
|
354
|
+
|
355
|
+
def hit_by?(missile)
|
356
|
+
# Smoke can't be hit!
|
357
|
+
false
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
# Finally, the class that ties it all together.
|
362
|
+
# Very straightforward implementation.
|
363
|
+
|
364
|
+
class GameWindow < Gosu::Window
|
365
|
+
attr_reader :map, :objects
|
366
|
+
|
367
|
+
def initialize()
|
368
|
+
super(800, 600, false)
|
369
|
+
self.caption = "Medal of Anna - Gosu & RMagick Integration Demo"
|
370
|
+
|
371
|
+
# Texts to display in the appropriate situations.
|
372
|
+
@player_instructions = []
|
373
|
+
@player_won_messages = []
|
374
|
+
2.times do |plr|
|
375
|
+
@player_instructions << Gosu::Image.from_text(self,
|
376
|
+
"It is the #{ plr == 0 ? 'green' : 'red' } toy soldier's turn.\n" +
|
377
|
+
"(Arrow keys to walk and aim, Return to jump, Space to shoot)",
|
378
|
+
Gosu::default_font_name, 25, 0, width, :center)
|
379
|
+
@player_won_messages << Gosu::Image.from_text(self,
|
380
|
+
"The #{ plr == 0 ? 'green' : 'red' } toy soldier has won!",
|
381
|
+
Gosu::default_font_name, 25, 5, width, :center)
|
382
|
+
end
|
383
|
+
|
384
|
+
# Create everything!
|
385
|
+
@map = Map.new(self)
|
386
|
+
@players = [Player.new(self, 200, 40, 0xff308000), Player.new(self, 600, 40, 0xff803000)]
|
387
|
+
@objects = @players.dup
|
388
|
+
|
389
|
+
# Let any player start.
|
390
|
+
@current_player = rand(2)
|
391
|
+
# Currently not waiting for a missile to hit something.
|
392
|
+
@waiting = false
|
393
|
+
end
|
394
|
+
|
395
|
+
def draw
|
396
|
+
# Draw the main game.
|
397
|
+
@map.draw
|
398
|
+
@objects.each { |o| o.draw }
|
399
|
+
|
400
|
+
# If any text should be displayed, draw it - and add a nice black border around it
|
401
|
+
# by drawing it four times, with a little offset in each direction.
|
402
|
+
|
403
|
+
cur_text = @player_instructions[@current_player] if not @waiting
|
404
|
+
cur_text = @player_won_messages[1 - @current_player] if @players[@current_player].dead
|
405
|
+
|
406
|
+
if cur_text then
|
407
|
+
x, y = 0, 30
|
408
|
+
cur_text.draw(x - 1, y, 0, 1, 1, 0xff000000)
|
409
|
+
cur_text.draw(x + 1, y, 0, 1, 1, 0xff000000)
|
410
|
+
cur_text.draw(x, y - 1, 0, 1, 1, 0xff000000)
|
411
|
+
cur_text.draw(x, y + 1, 0, 1, 1, 0xff000000)
|
412
|
+
cur_text.draw(x, y, 0, 1, 1, 0xffffffff)
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
def update
|
417
|
+
# if waiting for the next player's turn, continue to do so until the missile has
|
418
|
+
# hit something.
|
419
|
+
@waiting &&= !@objects.grep(Missile).empty?
|
420
|
+
|
421
|
+
# Remove all objects whose update method returns false.
|
422
|
+
@objects.reject! { |o| o.update == false }
|
423
|
+
|
424
|
+
# If it's a player's turn, forward controls.
|
425
|
+
if not @waiting and not @players[@current_player].dead then
|
426
|
+
player = @players[@current_player]
|
427
|
+
player.aim_up if button_down? Gosu::KbUp
|
428
|
+
player.aim_down if button_down? Gosu::KbDown
|
429
|
+
player.try_walk(-1) if button_down? Gosu::KbLeft
|
430
|
+
player.try_walk(1) if button_down? Gosu::KbRight
|
431
|
+
player.try_jump if button_down? Gosu::KbReturn
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
def button_down(id)
|
436
|
+
if id == Gosu::KbSpace and not @waiting and not @players[@current_player].dead then
|
437
|
+
# Shoot! This is handled in button_down because holding space shouldn't
|
438
|
+
# auto-fire - the shots would come from different players.
|
439
|
+
@players[@current_player].shoot
|
440
|
+
@current_player = 1 - @current_player
|
441
|
+
@waiting = true
|
442
|
+
end
|
443
|
+
# Very important feature! ;)
|
444
|
+
close if id == Gosu::KbEscape
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
# So far we have only defined how everything *should* work - now set it up and run it!
|
449
|
+
GameWindow.new.show
|
@@ -0,0 +1,170 @@
|
|
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
|
+
// pass it to your window via setTextInput. Until you call this function again,
|
6
|
+
// passing 0, the TextInput object will build a text that can be accessed via
|
7
|
+
// 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 caretPos() member function.
|
11
|
+
// Furthermore, if there is a selection, the selectionStart() member yields its
|
12
|
+
// beginning, using the same indexing scheme. If there is no selection,
|
13
|
+
// selectionStart() is equal to caretPos().
|
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
|
+
#include <Gosu/Gosu.hpp>
|
22
|
+
#include <boost/scoped_ptr.hpp>
|
23
|
+
|
24
|
+
class TextField : public Gosu::TextInput
|
25
|
+
{
|
26
|
+
Gosu::Window& window;
|
27
|
+
Gosu::Font& font;
|
28
|
+
double x, y;
|
29
|
+
|
30
|
+
public:
|
31
|
+
// Some constants that define our appearance.
|
32
|
+
// (Can't use Gosu::Color that easily as a class constant, thanks to C++.)
|
33
|
+
static const unsigned long INACTIVE_COLOR = 0xcc666666;
|
34
|
+
static const unsigned long ACTIVE_COLOR = 0xccff6666;
|
35
|
+
static const unsigned long SELECTION_COLOR = 0xcc0000ff;
|
36
|
+
static const unsigned long CARET_COLOR = 0xffffffff;
|
37
|
+
static const int PADDING = 5;
|
38
|
+
|
39
|
+
TextField(Gosu::Window& window, Gosu::Font& font, double x, double y)
|
40
|
+
: window(window), font(font), x(x), y(y)
|
41
|
+
{
|
42
|
+
// Start with a self-explanatory text in each field.
|
43
|
+
setText(L"Click to change text");
|
44
|
+
}
|
45
|
+
|
46
|
+
void draw() const
|
47
|
+
{
|
48
|
+
// Depending on whether this is the currently selected input or not, change the
|
49
|
+
// background's color.
|
50
|
+
Gosu::Color backgroundColor;
|
51
|
+
if (window.input().textInput() == this)
|
52
|
+
backgroundColor = ACTIVE_COLOR;
|
53
|
+
else
|
54
|
+
backgroundColor = INACTIVE_COLOR;
|
55
|
+
window.graphics().drawQuad(x - PADDING, y - PADDING, backgroundColor,
|
56
|
+
x + width() + PADDING, y - PADDING, backgroundColor,
|
57
|
+
x - PADDING, y + height() + PADDING, backgroundColor,
|
58
|
+
x + width() + PADDING, y + height() + PADDING, backgroundColor, 0);
|
59
|
+
|
60
|
+
// Calculate the position of the caret and the selection start.
|
61
|
+
double posX = x + font.textWidth(text().substr(0, caretPos()));
|
62
|
+
double selX = x + font.textWidth(text().substr(0, selectionStart()));
|
63
|
+
|
64
|
+
// Draw the selection background, if any; if not, sel_x and pos_x will be
|
65
|
+
// the same value, making this quad empty.
|
66
|
+
window.graphics().drawQuad(selX, y, SELECTION_COLOR,
|
67
|
+
posX, y, SELECTION_COLOR,
|
68
|
+
selX, y + height(), SELECTION_COLOR,
|
69
|
+
posX, y + height(), SELECTION_COLOR, 0);
|
70
|
+
|
71
|
+
// Draw the caret; again, only if this is the currently selected field.
|
72
|
+
if (window.input().textInput() == this)
|
73
|
+
window.graphics().drawLine(posX, y, CARET_COLOR,
|
74
|
+
posX, y + height(), CARET_COLOR, 0);
|
75
|
+
|
76
|
+
// Finally, draw the text itself!
|
77
|
+
font.draw(text(), x, y, 0);
|
78
|
+
}
|
79
|
+
|
80
|
+
// This text field grows with the text that's being entered.
|
81
|
+
// (Without clipping, one has to be a bit creative about this ;) )
|
82
|
+
double width() const
|
83
|
+
{
|
84
|
+
return font.textWidth(text());
|
85
|
+
}
|
86
|
+
|
87
|
+
double height() const
|
88
|
+
{
|
89
|
+
return font.height();
|
90
|
+
}
|
91
|
+
|
92
|
+
bool isUnderPoint(double mouseX, double mouseY)
|
93
|
+
{
|
94
|
+
return mouseX > x - PADDING && mouseX < x + width() + PADDING &&
|
95
|
+
mouseY > y - PADDING and mouseY < y + height() + PADDING;
|
96
|
+
}
|
97
|
+
};
|
98
|
+
|
99
|
+
// Helper magic to get size of static array, MUCH safer than sizeof hackery.
|
100
|
+
template<typename T, std::size_t Len>
|
101
|
+
std::size_t lengthof(const T(&) [Len])
|
102
|
+
{
|
103
|
+
return Len;
|
104
|
+
}
|
105
|
+
|
106
|
+
class TextInputWindow : public Gosu::Window
|
107
|
+
{
|
108
|
+
boost::scoped_ptr<Gosu::Font> font;
|
109
|
+
boost::scoped_ptr<TextField> textFields[3];
|
110
|
+
boost::scoped_ptr<Gosu::Image> cursor;
|
111
|
+
|
112
|
+
public:
|
113
|
+
TextInputWindow()
|
114
|
+
: Gosu::Window(300, 200, false)
|
115
|
+
{
|
116
|
+
setCaption(L"Text Input Example");
|
117
|
+
|
118
|
+
font.reset(new Gosu::Font(graphics(), Gosu::defaultFontName(), 20));
|
119
|
+
|
120
|
+
for (int index = 0; index < lengthof(textFields); ++index)
|
121
|
+
textFields[index].reset(new TextField(*this, *font, 50, 30 + index * 50));
|
122
|
+
|
123
|
+
cursor.reset(new Gosu::Image(graphics(), L"media/Cursor.png", false));
|
124
|
+
}
|
125
|
+
|
126
|
+
void draw()
|
127
|
+
{
|
128
|
+
for (int i = 0; i < lengthof(textFields); ++i)
|
129
|
+
textFields[i]->draw();
|
130
|
+
|
131
|
+
cursor->draw(input().mouseX(), input().mouseY(), 0);
|
132
|
+
}
|
133
|
+
|
134
|
+
void buttonDown(Gosu::Button btn)
|
135
|
+
{
|
136
|
+
if (btn == Gosu::kbTab)
|
137
|
+
{
|
138
|
+
// Tab key will not be 'eaten' by text fields; use for switching through
|
139
|
+
// text fields.
|
140
|
+
int index = -1;
|
141
|
+
for (int i = 0; i < lengthof(textFields); ++i)
|
142
|
+
if (input().textInput() == textFields[i].get())
|
143
|
+
index = i;
|
144
|
+
input().setTextInput(textFields[(index + 1) % lengthof(textFields)].get());
|
145
|
+
}
|
146
|
+
else if (btn == Gosu::kbEscape)
|
147
|
+
{
|
148
|
+
// Escape key will not be 'eaten' by text fields; use for deselecting.
|
149
|
+
if (input().textInput())
|
150
|
+
input().setTextInput(0);
|
151
|
+
else
|
152
|
+
close();
|
153
|
+
}
|
154
|
+
else if (btn == Gosu::msLeft)
|
155
|
+
{
|
156
|
+
// Mouse click: Select text field based on mouse position.
|
157
|
+
input().setTextInput(0);
|
158
|
+
for (int i = 0; i < lengthof(textFields); ++i)
|
159
|
+
if (textFields[i]->isUnderPoint(input().mouseX(), input().mouseY()))
|
160
|
+
input().setTextInput(textFields[i].get());
|
161
|
+
}
|
162
|
+
}
|
163
|
+
};
|
164
|
+
|
165
|
+
int main(int argc, char* argv[])
|
166
|
+
{
|
167
|
+
TextInputWindow win;
|
168
|
+
win.show();
|
169
|
+
return 0;
|
170
|
+
}
|