gosu 0.7.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|
+
}
|