gosu 0.8.6-x64-mingw32
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.
- checksums.yaml +7 -0
- data/Gosu/Audio.hpp +171 -0
- data/Gosu/AutoLink.hpp +16 -0
- data/Gosu/Bitmap.hpp +96 -0
- data/Gosu/Buttons.hpp +265 -0
- data/Gosu/Color.hpp +204 -0
- data/Gosu/Directories.hpp +36 -0
- data/Gosu/Font.hpp +83 -0
- data/Gosu/Fwd.hpp +31 -0
- data/Gosu/Gosu.hpp +34 -0
- data/Gosu/Graphics.hpp +115 -0
- data/Gosu/GraphicsBase.hpp +110 -0
- data/Gosu/IO.hpp +269 -0
- data/Gosu/Image.hpp +122 -0
- data/Gosu/ImageData.hpp +61 -0
- data/Gosu/Input.hpp +149 -0
- data/Gosu/Inspection.hpp +14 -0
- data/Gosu/Math.hpp +135 -0
- data/Gosu/Platform.hpp +93 -0
- data/Gosu/Sockets.hpp +156 -0
- data/Gosu/TR1.hpp +56 -0
- data/Gosu/Text.hpp +71 -0
- data/Gosu/TextInput.hpp +70 -0
- data/Gosu/Timing.hpp +16 -0
- data/Gosu/Utility.hpp +28 -0
- data/Gosu/Version.hpp +19 -0
- data/Gosu/WinUtility.hpp +75 -0
- data/Gosu/Window.hpp +145 -0
- data/examples/ChipmunkIntegration.rb +275 -0
- data/examples/CptnRuby.rb +223 -0
- data/examples/GosuZen.rb +68 -0
- data/examples/MoreChipmunkAndRMagick.rb +155 -0
- data/examples/OpenGLIntegration.rb +226 -0
- data/examples/RMagickIntegration.rb +417 -0
- data/examples/TextInput.rb +154 -0
- data/examples/Tutorial.rb +131 -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/Landscape.svg +10 -0
- data/examples/media/LargeStar.png +0 -0
- data/examples/media/Smoke.png +0 -0
- data/examples/media/Soldier.png +0 -0
- data/examples/media/Space.png +0 -0
- data/examples/media/Star.png +0 -0
- data/examples/media/Starfighter.bmp +0 -0
- data/lib/gosu.rb +20 -0
- data/lib/gosu/patches.rb +81 -0
- data/lib/gosu/preview.rb +139 -0
- data/lib/gosu/run.rb +11 -0
- data/lib/gosu/swig_patches.rb +60 -0
- data/lib/gosu/zen.rb +89 -0
- data/lib64/2.1/gosu.so +0 -0
- data/lib64/FreeImage.dll +0 -0
- data/lib64/OpenAL32.dll +0 -0
- data/lib64/SDL2.dll +0 -0
- data/lib64/libsndfile.dll +0 -0
- metadata +110 -0
data/Gosu/Window.hpp
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
//! \file Window.hpp
|
2
|
+
//! Interface of the Window class.
|
3
|
+
|
4
|
+
#ifndef GOSU_WINDOW_HPP
|
5
|
+
#define GOSU_WINDOW_HPP
|
6
|
+
|
7
|
+
#include <Gosu/Fwd.hpp>
|
8
|
+
#include <Gosu/Platform.hpp>
|
9
|
+
#include <Gosu/Input.hpp>
|
10
|
+
#include <Gosu/TR1.hpp>
|
11
|
+
#include <memory>
|
12
|
+
#include <string>
|
13
|
+
|
14
|
+
#ifdef GOSU_IS_WIN
|
15
|
+
#ifndef NOMINMAX
|
16
|
+
#define NOMINMAX
|
17
|
+
#endif
|
18
|
+
#include <windows.h>
|
19
|
+
#endif
|
20
|
+
|
21
|
+
namespace Gosu
|
22
|
+
{
|
23
|
+
//! Returns the width (in pixels) of the user's primary screen.
|
24
|
+
unsigned screenWidth();
|
25
|
+
|
26
|
+
//! Returns the height (in pixels) of the user's primary screen.
|
27
|
+
unsigned screenHeight();
|
28
|
+
|
29
|
+
//! Returns the maximum width (in 'points') that is available for a non-fullscreen Window.
|
30
|
+
//! All windows larger than this size will automatically be shrunk to fit.
|
31
|
+
unsigned availableWidth();
|
32
|
+
|
33
|
+
//! Returns the maximum height (in 'points') that is available for a non-fullscreen Window.
|
34
|
+
//! All windows larger than this size will automatically be shrunk to fit.
|
35
|
+
unsigned availableHeight();
|
36
|
+
|
37
|
+
//! Convenient all-in-one class that serves as the foundation of a standard
|
38
|
+
//! Gosu application. Manages initialization of all of Gosu's core components
|
39
|
+
//! and provides timing functionality.
|
40
|
+
//! Note that you should really only use one instance of this class at the same time.
|
41
|
+
//! This may or may not change later.
|
42
|
+
class Window
|
43
|
+
{
|
44
|
+
struct Impl;
|
45
|
+
const GOSU_UNIQUE_PTR<Impl> pimpl;
|
46
|
+
#if defined(GOSU_CPP11_ENABLED)
|
47
|
+
Window(Window&&) = delete;
|
48
|
+
Window& operator=(Window&&) = delete;
|
49
|
+
Window(const Window&) = delete;
|
50
|
+
Window& operator=(const Window&) = delete;
|
51
|
+
#endif
|
52
|
+
|
53
|
+
public:
|
54
|
+
//! Constructs a Window.
|
55
|
+
//! \param width Width of the window in points; that is, pixels on a normal display, and 'virtual pixels' on a
|
56
|
+
//! high-resolution display.
|
57
|
+
//! \param height See width.
|
58
|
+
//! \param updateInterval Interval in milliseconds between two calls
|
59
|
+
//! to the update member function.
|
60
|
+
Window(unsigned width, unsigned height, bool fullscreen,
|
61
|
+
double updateInterval = 16.666666);
|
62
|
+
virtual ~Window();
|
63
|
+
|
64
|
+
std::wstring caption() const;
|
65
|
+
void setCaption(const std::wstring& caption);
|
66
|
+
|
67
|
+
double updateInterval() const;
|
68
|
+
|
69
|
+
//! Enters a modal loop where the Window is visible on screen and
|
70
|
+
//! receives calls to draw, update etc.
|
71
|
+
void show();
|
72
|
+
//! Closes the window if it is currently shown.
|
73
|
+
void close();
|
74
|
+
|
75
|
+
//! Called every updateInterval milliseconds while the window is being
|
76
|
+
//! shown. Your application's main game logic goes here.
|
77
|
+
virtual void update() {}
|
78
|
+
//! Called after every update and when the OS wants the window to
|
79
|
+
//! repaint itself. Your application's rendering code goes here.
|
80
|
+
virtual void draw() {}
|
81
|
+
|
82
|
+
//! Gives the game a chance to say no to being redrawn.
|
83
|
+
//! This is not a definitive answer. The operating system can still force
|
84
|
+
//! the window to redraw itself.
|
85
|
+
//! By default, the window is redrawn all the time.
|
86
|
+
virtual bool needsRedraw() const { return true; }
|
87
|
+
|
88
|
+
//! If this function returns true, the system arrow cursor is drawn while
|
89
|
+
//! over the window.
|
90
|
+
virtual bool needsCursor() const { return false; }
|
91
|
+
|
92
|
+
//! This function is called when the window loses focus on some platforms.
|
93
|
+
//! Most importantly, it is called on the iPhone or iPad when the user
|
94
|
+
//! locks the screen.
|
95
|
+
virtual void loseFocus() {}
|
96
|
+
|
97
|
+
//! This function is called when the operating system's memory is low.
|
98
|
+
//! So far, it is only called in iOS applications.
|
99
|
+
virtual void releaseMemory() {}
|
100
|
+
|
101
|
+
//! Called before update when the user pressed a button while the
|
102
|
+
//! window had the focus.
|
103
|
+
virtual void buttonDown(Gosu::Button) {}
|
104
|
+
//! Same as buttonDown. Called then the user released a button.
|
105
|
+
virtual void buttonUp(Gosu::Button) {}
|
106
|
+
|
107
|
+
// Ignore when SWIG is wrapping this class for Ruby/Gosu.
|
108
|
+
#ifndef SWIG
|
109
|
+
|
110
|
+
const Graphics& graphics() const;
|
111
|
+
Graphics& graphics();
|
112
|
+
|
113
|
+
const Input& input() const;
|
114
|
+
Input& input();
|
115
|
+
|
116
|
+
#ifdef GOSU_IS_UNIX
|
117
|
+
// Context for creating shared contexts.
|
118
|
+
// Only on Unices (so far).
|
119
|
+
typedef std::tr1::shared_ptr<std::tr1::function<void()> > SharedContext;
|
120
|
+
SharedContext createSharedContext();
|
121
|
+
#endif
|
122
|
+
|
123
|
+
#ifdef GOSU_IS_IPHONE
|
124
|
+
void* rootViewController() const;
|
125
|
+
// iPhone-only callbacks for touch events.
|
126
|
+
// Note that it does not hurt to override them even if you compile
|
127
|
+
// for another platform; if you don't specify "virtual" the code
|
128
|
+
// should even be stripped away cleanly.
|
129
|
+
virtual void touchBegan(Touch touch) {}
|
130
|
+
virtual void touchMoved(Touch touch) {}
|
131
|
+
virtual void touchEnded(Touch touch) {}
|
132
|
+
#endif
|
133
|
+
|
134
|
+
const Audio& audio() const;
|
135
|
+
Audio& audio();
|
136
|
+
|
137
|
+
#endif
|
138
|
+
};
|
139
|
+
}
|
140
|
+
|
141
|
+
#ifdef GOSU_IS_IPHONE
|
142
|
+
Gosu::Window& windowInstance();
|
143
|
+
#endif
|
144
|
+
|
145
|
+
#endif
|
@@ -0,0 +1,275 @@
|
|
1
|
+
## File: ChipmunkIntegration.rb
|
2
|
+
## Author: Dirk Johnson
|
3
|
+
## Version: 1.0.0
|
4
|
+
## Date: 2007-10-05
|
5
|
+
## License: Same as for Gosu (MIT)
|
6
|
+
## Comments: Based on the Gosu Ruby Tutorial, but incorporating the Chipmunk Physics Engine
|
7
|
+
## See https://github.com/jlnr/gosu/wiki/Ruby-Chipmunk-Integration for the accompanying text.
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
require 'gosu'
|
11
|
+
require 'chipmunk'
|
12
|
+
|
13
|
+
SCREEN_WIDTH = 640
|
14
|
+
SCREEN_HEIGHT = 480
|
15
|
+
|
16
|
+
# The number of steps to process every Gosu update
|
17
|
+
# The Player ship can get going so fast as to "move through" a
|
18
|
+
# star without triggering a collision; an increased number of
|
19
|
+
# Chipmunk step calls per update will effectively avoid this issue
|
20
|
+
SUBSTEPS = 6
|
21
|
+
|
22
|
+
# Convenience method for converting from radians to a Vec2 vector.
|
23
|
+
class Numeric
|
24
|
+
def radians_to_vec2
|
25
|
+
CP::Vec2.new(Math::cos(self), Math::sin(self))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Layering of sprites
|
30
|
+
module ZOrder
|
31
|
+
Background, Stars, Player, UI = *0..3
|
32
|
+
end
|
33
|
+
|
34
|
+
# This game will have one Player in the form of a ship
|
35
|
+
class Player
|
36
|
+
attr_reader :shape
|
37
|
+
|
38
|
+
def initialize(window, shape)
|
39
|
+
@image = Gosu::Image.new(window, "media/Starfighter.bmp", false)
|
40
|
+
@shape = shape
|
41
|
+
@shape.body.p = CP::Vec2.new(0.0, 0.0) # position
|
42
|
+
@shape.body.v = CP::Vec2.new(0.0, 0.0) # velocity
|
43
|
+
|
44
|
+
# Keep in mind that down the screen is positive y, which means that PI/2 radians,
|
45
|
+
# which you might consider the top in the traditional Trig unit circle sense is actually
|
46
|
+
# the bottom; thus 3PI/2 is the top
|
47
|
+
@shape.body.a = (3*Math::PI/2.0) # angle in radians; faces towards top of screen
|
48
|
+
end
|
49
|
+
|
50
|
+
# Directly set the position of our Player
|
51
|
+
def warp(vect)
|
52
|
+
@shape.body.p = vect
|
53
|
+
end
|
54
|
+
|
55
|
+
# Apply negative Torque; Chipmunk will do the rest
|
56
|
+
# SUBSTEPS is used as a divisor to keep turning rate constant
|
57
|
+
# even if the number of steps per update are adjusted
|
58
|
+
def turn_left
|
59
|
+
@shape.body.t -= 400.0/SUBSTEPS
|
60
|
+
end
|
61
|
+
|
62
|
+
# Apply positive Torque; Chipmunk will do the rest
|
63
|
+
# SUBSTEPS is used as a divisor to keep turning rate constant
|
64
|
+
# even if the number of steps per update are adjusted
|
65
|
+
def turn_right
|
66
|
+
@shape.body.t += 400.0/SUBSTEPS
|
67
|
+
end
|
68
|
+
|
69
|
+
# Apply forward force; Chipmunk will do the rest
|
70
|
+
# SUBSTEPS is used as a divisor to keep acceleration rate constant
|
71
|
+
# even if the number of steps per update are adjusted
|
72
|
+
# Here we must convert the angle (facing) of the body into
|
73
|
+
# forward momentum by creating a vector in the direction of the facing
|
74
|
+
# and with a magnitude representing the force we want to apply
|
75
|
+
def accelerate
|
76
|
+
@shape.body.apply_force((@shape.body.a.radians_to_vec2 * (3000.0/SUBSTEPS)), CP::Vec2.new(0.0, 0.0))
|
77
|
+
end
|
78
|
+
|
79
|
+
# Apply even more forward force
|
80
|
+
# See accelerate for more details
|
81
|
+
def boost
|
82
|
+
@shape.body.apply_force((@shape.body.a.radians_to_vec2 * (3000.0)), CP::Vec2.new(0.0, 0.0))
|
83
|
+
end
|
84
|
+
|
85
|
+
# Apply reverse force
|
86
|
+
# See accelerate for more details
|
87
|
+
def reverse
|
88
|
+
@shape.body.apply_force(-(@shape.body.a.radians_to_vec2 * (1000.0/SUBSTEPS)), CP::Vec2.new(0.0, 0.0))
|
89
|
+
end
|
90
|
+
|
91
|
+
# Wrap to the other side of the screen when we fly off the edge
|
92
|
+
def validate_position
|
93
|
+
l_position = CP::Vec2.new(@shape.body.p.x % SCREEN_WIDTH, @shape.body.p.y % SCREEN_HEIGHT)
|
94
|
+
@shape.body.p = l_position
|
95
|
+
end
|
96
|
+
|
97
|
+
def draw
|
98
|
+
@image.draw_rot(@shape.body.p.x, @shape.body.p.y, ZOrder::Player, @shape.body.a.radians_to_gosu)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# See how simple our Star is?
|
103
|
+
# Of course... it just sits around and looks good...
|
104
|
+
class Star
|
105
|
+
attr_reader :shape
|
106
|
+
|
107
|
+
def initialize(animation, shape)
|
108
|
+
@animation = animation
|
109
|
+
@color = Gosu::Color.new(0xff000000)
|
110
|
+
@color.red = rand(255 - 40) + 40
|
111
|
+
@color.green = rand(255 - 40) + 40
|
112
|
+
@color.blue = rand(255 - 40) + 40
|
113
|
+
@shape = shape
|
114
|
+
@shape.body.p = CP::Vec2.new(rand * SCREEN_WIDTH, rand * SCREEN_HEIGHT) # position
|
115
|
+
@shape.body.v = CP::Vec2.new(0.0, 0.0) # velocity
|
116
|
+
@shape.body.a = (3*Math::PI/2.0) # angle in radians; faces towards top of screen
|
117
|
+
end
|
118
|
+
|
119
|
+
def draw
|
120
|
+
img = @animation[Gosu::milliseconds / 100 % @animation.size];
|
121
|
+
img.draw(@shape.body.p.x - img.width / 2.0, @shape.body.p.y - img.height / 2.0, ZOrder::Stars, 1, 1, @color, :add)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# The Gosu::Window is always the "environment" of our game
|
126
|
+
# It also provides the pulse of our game
|
127
|
+
class GameWindow < Gosu::Window
|
128
|
+
def initialize
|
129
|
+
super(SCREEN_WIDTH, SCREEN_HEIGHT, false, 16)
|
130
|
+
self.caption = "Gosu & Chipmunk Integration Demo"
|
131
|
+
@background_image = Gosu::Image.new(self, "media/Space.png", true)
|
132
|
+
|
133
|
+
# Put the beep here, as it is the environment now that determines collision
|
134
|
+
@beep = Gosu::Sample.new(self, "media/Beep.wav")
|
135
|
+
|
136
|
+
# Put the score here, as it is the environment that tracks this now
|
137
|
+
@score = 0
|
138
|
+
@font = Gosu::Font.new(self, Gosu::default_font_name, 20)
|
139
|
+
|
140
|
+
# Time increment over which to apply a physics "step" ("delta t")
|
141
|
+
@dt = (1.0/60.0)
|
142
|
+
|
143
|
+
# Create our Space and set its damping
|
144
|
+
# A damping of 0.8 causes the ship bleed off its force and torque over time
|
145
|
+
# This is not realistic behavior in a vacuum of space, but it gives the game
|
146
|
+
# the feel I'd like in this situation
|
147
|
+
@space = CP::Space.new
|
148
|
+
@space.damping = 0.8
|
149
|
+
|
150
|
+
# Create the Body for the Player
|
151
|
+
body = CP::Body.new(10.0, 150.0)
|
152
|
+
|
153
|
+
# In order to create a shape, we must first define it
|
154
|
+
# Chipmunk defines 3 types of Shapes: Segments, Circles and Polys
|
155
|
+
# We'll use s simple, 4 sided Poly for our Player (ship)
|
156
|
+
# You need to define the vectors so that the "top" of the Shape is towards 0 radians (the right)
|
157
|
+
shape_array = [CP::Vec2.new(-25.0, -25.0), CP::Vec2.new(-25.0, 25.0), CP::Vec2.new(25.0, 1.0), CP::Vec2.new(25.0, -1.0)]
|
158
|
+
shape = CP::Shape::Poly.new(body, shape_array, CP::Vec2.new(0,0))
|
159
|
+
|
160
|
+
# The collision_type of a shape allows us to set up special collision behavior
|
161
|
+
# based on these types. The actual value for the collision_type is arbitrary
|
162
|
+
# and, as long as it is consistent, will work for us; of course, it helps to have it make sense
|
163
|
+
shape.collision_type = :ship
|
164
|
+
|
165
|
+
@space.add_body(body)
|
166
|
+
@space.add_shape(shape)
|
167
|
+
|
168
|
+
@player = Player.new(self, shape)
|
169
|
+
@player.warp(CP::Vec2.new(320, 240)) # move to the center of the window
|
170
|
+
|
171
|
+
@star_anim = Gosu::Image::load_tiles(self, "media/Star.png", 25, 25, false)
|
172
|
+
@stars = Array.new
|
173
|
+
|
174
|
+
# Here we define what is supposed to happen when a Player (ship) collides with a Star
|
175
|
+
# I create a @remove_shapes array because we cannot remove either Shapes or Bodies
|
176
|
+
# from Space within a collision closure, rather, we have to wait till the closure
|
177
|
+
# is through executing, then we can remove the Shapes and Bodies
|
178
|
+
# In this case, the Shapes and the Bodies they own are removed in the Gosu::Window.update phase
|
179
|
+
# by iterating over the @remove_shapes array
|
180
|
+
# Also note that both Shapes involved in the collision are passed into the closure
|
181
|
+
# in the same order that their collision_types are defined in the add_collision_func call
|
182
|
+
@remove_shapes = []
|
183
|
+
@space.add_collision_func(:ship, :star) do |ship_shape, star_shape|
|
184
|
+
@score += 10
|
185
|
+
@beep.play
|
186
|
+
@remove_shapes << star_shape
|
187
|
+
end
|
188
|
+
|
189
|
+
# Here we tell Space that we don't want one star bumping into another
|
190
|
+
# The reason we need to do this is because when the Player hits a Star,
|
191
|
+
# the Star will travel until it is removed in the update cycle below
|
192
|
+
# which means it may collide and therefore push other Stars
|
193
|
+
# To see the effect, remove this line and play the game, every once in a while
|
194
|
+
# you'll see a Star moving
|
195
|
+
@space.add_collision_func(:star, :star, &nil)
|
196
|
+
end
|
197
|
+
|
198
|
+
def update
|
199
|
+
# Step the physics environment SUBSTEPS times each update
|
200
|
+
SUBSTEPS.times do
|
201
|
+
# This iterator makes an assumption of one Shape per Star making it safe to remove
|
202
|
+
# each Shape's Body as it comes up
|
203
|
+
# If our Stars had multiple Shapes, as would be required if we were to meticulously
|
204
|
+
# define their true boundaries, we couldn't do this as we would remove the Body
|
205
|
+
# multiple times
|
206
|
+
# We would probably solve this by creating a separate @remove_bodies array to remove the Bodies
|
207
|
+
# of the Stars that were gathered by the Player
|
208
|
+
@remove_shapes.each do |shape|
|
209
|
+
@stars.delete_if { |star| star.shape == shape }
|
210
|
+
@space.remove_body(shape.body)
|
211
|
+
@space.remove_shape(shape)
|
212
|
+
end
|
213
|
+
@remove_shapes.clear # clear out the shapes for next pass
|
214
|
+
|
215
|
+
# When a force or torque is set on a Body, it is cumulative
|
216
|
+
# This means that the force you applied last SUBSTEP will compound with the
|
217
|
+
# force applied this SUBSTEP; which is probably not the behavior you want
|
218
|
+
# We reset the forces on the Player each SUBSTEP for this reason
|
219
|
+
@player.shape.body.reset_forces
|
220
|
+
|
221
|
+
# Wrap around the screen to the other side
|
222
|
+
@player.validate_position
|
223
|
+
|
224
|
+
# Check keyboard
|
225
|
+
if button_down? Gosu::KbLeft
|
226
|
+
@player.turn_left
|
227
|
+
end
|
228
|
+
if button_down? Gosu::KbRight
|
229
|
+
@player.turn_right
|
230
|
+
end
|
231
|
+
|
232
|
+
if button_down? Gosu::KbUp
|
233
|
+
if ( (button_down? Gosu::KbRightShift) || (button_down? Gosu::KbLeftShift) )
|
234
|
+
@player.boost
|
235
|
+
else
|
236
|
+
@player.accelerate
|
237
|
+
end
|
238
|
+
elsif button_down? Gosu::KbDown
|
239
|
+
@player.reverse
|
240
|
+
end
|
241
|
+
|
242
|
+
# Perform the step over @dt period of time
|
243
|
+
# For best performance @dt should remain consistent for the game
|
244
|
+
@space.step(@dt)
|
245
|
+
end
|
246
|
+
|
247
|
+
# Each update (not SUBSTEP) we see if we need to add more Stars
|
248
|
+
if rand(100) < 4 and @stars.size < 25 then
|
249
|
+
body = CP::Body.new(0.0001, 0.0001)
|
250
|
+
shape = CP::Shape::Circle.new(body, 25/2, CP::Vec2.new(0.0, 0.0))
|
251
|
+
shape.collision_type = :star
|
252
|
+
|
253
|
+
@space.add_body(body)
|
254
|
+
@space.add_shape(shape)
|
255
|
+
|
256
|
+
@stars.push(Star.new(@star_anim, shape))
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def draw
|
261
|
+
@background_image.draw(0, 0, ZOrder::Background)
|
262
|
+
@player.draw
|
263
|
+
@stars.each { |star| star.draw }
|
264
|
+
@font.draw("Score: #{@score}", 10, 10, ZOrder::UI, 1.0, 1.0, 0xffffff00)
|
265
|
+
end
|
266
|
+
|
267
|
+
def button_down(id)
|
268
|
+
if id == Gosu::KbEscape
|
269
|
+
close
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
window = GameWindow.new
|
275
|
+
window.show
|
@@ -0,0 +1,223 @@
|
|
1
|
+
# Basically, the tutorial game taken to a jump'n'run perspective.
|
2
|
+
|
3
|
+
# Shows how to
|
4
|
+
# * implement jumping/gravity
|
5
|
+
# * implement scrolling using Window#translate
|
6
|
+
# * implement a simple tile-based map
|
7
|
+
# * load levels from primitive text files
|
8
|
+
|
9
|
+
# Some exercises, starting at the real basics:
|
10
|
+
# 0) understand the existing code!
|
11
|
+
# As shown in the tutorial:
|
12
|
+
# 1) change it use Gosu's Z-ordering
|
13
|
+
# 2) add gamepad support
|
14
|
+
# 3) add a score as in the tutorial game
|
15
|
+
# 4) similarly, add sound effects for various events
|
16
|
+
# Exploring this game's code and Gosu:
|
17
|
+
# 5) make the player wider, so he doesn't fall off edges as easily
|
18
|
+
# 6) add background music (check if playing in Window#update to implement
|
19
|
+
# looping)
|
20
|
+
# 7) implement parallax scrolling for the star background!
|
21
|
+
# Getting tricky:
|
22
|
+
# 8) optimize Map#draw so only tiles on screen are drawn (needs modulo, a pen
|
23
|
+
# and paper to figure out)
|
24
|
+
# 9) add loading of next level when all gems are collected
|
25
|
+
# ...Enemies, a more sophisticated object system, weapons, title and credits
|
26
|
+
# screens...
|
27
|
+
|
28
|
+
require 'rubygems'
|
29
|
+
require 'gosu'
|
30
|
+
include Gosu
|
31
|
+
|
32
|
+
module Tiles
|
33
|
+
Grass = 0
|
34
|
+
Earth = 1
|
35
|
+
end
|
36
|
+
|
37
|
+
class CollectibleGem
|
38
|
+
attr_reader :x, :y
|
39
|
+
|
40
|
+
def initialize(image, x, y)
|
41
|
+
@image = image
|
42
|
+
@x, @y = x, y
|
43
|
+
end
|
44
|
+
|
45
|
+
def draw
|
46
|
+
# Draw, slowly rotating
|
47
|
+
@image.draw_rot(@x, @y, 0, 25 * Math.sin(milliseconds / 133.7))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Player class.
|
52
|
+
class CptnRuby
|
53
|
+
attr_reader :x, :y
|
54
|
+
|
55
|
+
def initialize(window, x, y)
|
56
|
+
@x, @y = x, y
|
57
|
+
@dir = :left
|
58
|
+
@vy = 0 # Vertical velocity
|
59
|
+
@map = window.map
|
60
|
+
# Load all animation frames
|
61
|
+
@standing, @walk1, @walk2, @jump =
|
62
|
+
*Image.load_tiles(window, "media/CptnRuby.png", 50, 50, false)
|
63
|
+
# This always points to the frame that is currently drawn.
|
64
|
+
# This is set in update, and used in draw.
|
65
|
+
@cur_image = @standing
|
66
|
+
end
|
67
|
+
|
68
|
+
def draw
|
69
|
+
# Flip vertically when facing to the left.
|
70
|
+
if @dir == :left then
|
71
|
+
offs_x = -25
|
72
|
+
factor = 1.0
|
73
|
+
else
|
74
|
+
offs_x = 25
|
75
|
+
factor = -1.0
|
76
|
+
end
|
77
|
+
@cur_image.draw(@x + offs_x, @y - 49, 0, factor, 1.0)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Could the object be placed at x + offs_x/y + offs_y without being stuck?
|
81
|
+
def would_fit(offs_x, offs_y)
|
82
|
+
# Check at the center/top and center/bottom for map collisions
|
83
|
+
not @map.solid?(@x + offs_x, @y + offs_y) and
|
84
|
+
not @map.solid?(@x + offs_x, @y + offs_y - 45)
|
85
|
+
end
|
86
|
+
|
87
|
+
def update(move_x)
|
88
|
+
# Select image depending on action
|
89
|
+
if (move_x == 0)
|
90
|
+
@cur_image = @standing
|
91
|
+
else
|
92
|
+
@cur_image = (milliseconds / 175 % 2 == 0) ? @walk1 : @walk2
|
93
|
+
end
|
94
|
+
if (@vy < 0)
|
95
|
+
@cur_image = @jump
|
96
|
+
end
|
97
|
+
|
98
|
+
# Directional walking, horizontal movement
|
99
|
+
if move_x > 0 then
|
100
|
+
@dir = :right
|
101
|
+
move_x.times { if would_fit(1, 0) then @x += 1 end }
|
102
|
+
end
|
103
|
+
if move_x < 0 then
|
104
|
+
@dir = :left
|
105
|
+
(-move_x).times { if would_fit(-1, 0) then @x -= 1 end }
|
106
|
+
end
|
107
|
+
|
108
|
+
# Acceleration/gravity
|
109
|
+
# By adding 1 each frame, and (ideally) adding vy to y, the player's
|
110
|
+
# jumping curve will be the parabole we want it to be.
|
111
|
+
@vy += 1
|
112
|
+
# Vertical movement
|
113
|
+
if @vy > 0 then
|
114
|
+
@vy.times { if would_fit(0, 1) then @y += 1 else @vy = 0 end }
|
115
|
+
end
|
116
|
+
if @vy < 0 then
|
117
|
+
(-@vy).times { if would_fit(0, -1) then @y -= 1 else @vy = 0 end }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def try_to_jump
|
122
|
+
if @map.solid?(@x, @y + 1) then
|
123
|
+
@vy = -20
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def collect_gems(gems)
|
128
|
+
# Same as in the tutorial game.
|
129
|
+
gems.reject! do |c|
|
130
|
+
(c.x - @x).abs < 50 and (c.y - @y).abs < 50
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Map class holds and draws tiles and gems.
|
136
|
+
class Map
|
137
|
+
attr_reader :width, :height, :gems
|
138
|
+
|
139
|
+
def initialize(window, filename)
|
140
|
+
# Load 60x60 tiles, 5px overlap in all four directions.
|
141
|
+
@tileset = Image.load_tiles(window, "media/CptnRuby Tileset.png", 60, 60, true)
|
142
|
+
|
143
|
+
gem_img = Image.new(window, "media/CptnRuby Gem.png", false)
|
144
|
+
@gems = []
|
145
|
+
|
146
|
+
lines = File.readlines(filename).map { |line| line.chomp }
|
147
|
+
@height = lines.size
|
148
|
+
@width = lines[0].size
|
149
|
+
@tiles = Array.new(@width) do |x|
|
150
|
+
Array.new(@height) do |y|
|
151
|
+
case lines[y][x, 1]
|
152
|
+
when '"'
|
153
|
+
Tiles::Grass
|
154
|
+
when '#'
|
155
|
+
Tiles::Earth
|
156
|
+
when 'x'
|
157
|
+
@gems.push(CollectibleGem.new(gem_img, x * 50 + 25, y * 50 + 25))
|
158
|
+
nil
|
159
|
+
else
|
160
|
+
nil
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def draw
|
167
|
+
# Very primitive drawing function:
|
168
|
+
# Draws all the tiles, some off-screen, some on-screen.
|
169
|
+
@height.times do |y|
|
170
|
+
@width.times do |x|
|
171
|
+
tile = @tiles[x][y]
|
172
|
+
if tile
|
173
|
+
# Draw the tile with an offset (tile images have some overlap)
|
174
|
+
# Scrolling is implemented here just as in the game objects.
|
175
|
+
@tileset[tile].draw(x * 50 - 5, y * 50 - 5, 0)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
@gems.each { |c| c.draw }
|
180
|
+
end
|
181
|
+
|
182
|
+
# Solid at a given pixel position?
|
183
|
+
def solid?(x, y)
|
184
|
+
y < 0 || @tiles[x / 50][y / 50]
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
class Game < Window
|
189
|
+
attr_reader :map
|
190
|
+
|
191
|
+
def initialize
|
192
|
+
super(640, 480, false)
|
193
|
+
self.caption = "Cptn. Ruby"
|
194
|
+
@sky = Image.new(self, "media/Space.png", true)
|
195
|
+
@map = Map.new(self, "media/CptnRuby Map.txt")
|
196
|
+
@cptn = CptnRuby.new(self, 400, 100)
|
197
|
+
# The scrolling position is stored as top left corner of the screen.
|
198
|
+
@camera_x = @camera_y = 0
|
199
|
+
end
|
200
|
+
def update
|
201
|
+
move_x = 0
|
202
|
+
move_x -= 5 if button_down? KbLeft
|
203
|
+
move_x += 5 if button_down? KbRight
|
204
|
+
@cptn.update(move_x)
|
205
|
+
@cptn.collect_gems(@map.gems)
|
206
|
+
# Scrolling follows player
|
207
|
+
@camera_x = [[@cptn.x - 320, 0].max, @map.width * 50 - 640].min
|
208
|
+
@camera_y = [[@cptn.y - 240, 0].max, @map.height * 50 - 480].min
|
209
|
+
end
|
210
|
+
def draw
|
211
|
+
@sky.draw 0, 0, 0
|
212
|
+
translate(-@camera_x, -@camera_y) do
|
213
|
+
@map.draw
|
214
|
+
@cptn.draw
|
215
|
+
end
|
216
|
+
end
|
217
|
+
def button_down(id)
|
218
|
+
if id == KbUp then @cptn.try_to_jump end
|
219
|
+
if id == KbEscape then close end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
Game.new.show
|