Commit | Line | Data |
9000d229 |
1 | =head1 NAME |
2 | |
3 | Lunar Lander - a small tutorial on Perl SDL |
4 | |
5 | =head1 INTRODUCTION |
6 | |
b17615ea |
7 | This is a quick introduction to Games, Perl, and SDL (Simple |
8 | DirectMedia Layer, a cross-platform multimedia programming |
9 | library). We'll write a small game -- Lunar Lander -- in 100 |
10 | lines of code, or less. |
9000d229 |
11 | |
9000d229 |
12 | |
13 | =head2 FIRST VERSION |
14 | |
15 | We'll start with a text version of the game. |
16 | |
17 | "What?", you may ask. "I thought it was a SDL tutorial". |
18 | |
19 | Yes, it is -- thank you for reminding me. But we'll leave the SDL part for |
20 | later. We must build the game logic first! |
21 | |
22 | One of the traps of game programming is focusing too much on the interface. |
23 | If we start with a simpler simulation, we can worry with the presentation |
24 | later. |
25 | |
26 | So, here's the initial code: |
27 | |
28 | #!/usr/bin/perl |
29 | |
30 | use strict; |
31 | use warnings; |
32 | |
33 | my $height = 1000; # m |
34 | my $velocity = 0; # m/s |
35 | my $gravity = 1; # m/s^2 |
36 | |
37 | my $t = 0; |
38 | |
39 | while ( $height > 0 ) { |
40 | print "at $t s height = $height m, velocity = $velocity m/s\n"; |
41 | |
42 | $height = $height - $velocity; |
43 | $velocity = $velocity + $gravity; |
44 | $t = $t + 1; |
45 | } |
46 | |
47 | if ( $velocity > 10 ) { |
48 | print "CRASH!!!\n"; |
49 | } else { |
50 | print "You landed on the surface safely! :-D\n"; |
51 | } |
52 | |
53 | Run the code and you'll see something like this: |
54 | |
55 | at 0 s height = 1000 m, velocity = 0 m/s |
56 | at 1 s height = 1000 m, velocity = 1 m/s |
57 | at 2 s height = 999 m, velocity = 2 m/s |
58 | at 3 s height = 997 m, velocity = 3 m/s |
59 | at 4 s height = 994 m, velocity = 4 m/s |
60 | at 5 s height = 990 m, velocity = 5 m/s |
61 | ... |
62 | at 43 s height = 97 m, velocity = 43 m/s |
63 | at 44 s height = 54 m, velocity = 44 m/s |
64 | at 45 s height = 10 m, velocity = 45 m/s |
65 | |
66 | CRASH!!! |
67 | |
68 | "What happened? How do I control the ship???" |
69 | |
70 | =head2 CONTROLLING THE SHIP |
71 | |
72 | The problem with our first spaceship is that it had no controls! |
73 | |
74 | So, let's fix this problem, making the spaceship scriptable. (We |
75 | could write some code to handle keyboard and joysticks now, but |
76 | an scriptable spaceship will be easier to start. Remember, focus |
77 | on the game logic!) |
78 | |
79 | So, create add this simple script to the end of your file: |
80 | |
81 | __DATA__ |
82 | at 41s, accelerate 10 m/s^2 up |
83 | at 43s, 10 m/s^2 |
84 | at 45s, 10 |
85 | at 47s, 10 |
86 | at 49s, 10 |
87 | |
88 | The script is straightforward: it simply states a time when we |
89 | will push the spaceship up with a given acceleration. It accepts |
90 | free text: any two numbers you type will work. |
91 | |
92 | We can parse the script using this regular expression: |
93 | |
94 | my $script_re = qr/(\d+) \D+ (\d+)/x; |
95 | |
96 | And we can build a hash of ( time => acceleration ) with: |
97 | |
98 | my %up = map { $_ =~ $script_re } <DATA>; |
99 | |
100 | So the middle section of the program will become: |
101 | |
102 | my $script_re = qr/(\d+) \D+ (\d+)/x; |
103 | my %up = map { $_ =~ $script_re } <DATA>; |
104 | |
105 | while ( $height > 0 ) { |
106 | print "at $t s height = $height m, velocity = $velocity m/s\n"; |
107 | |
108 | if ( $up{$t} ) { |
109 | my $a = $up{$t}; |
110 | print "(accellerating $a m/s^2)\n"; |
111 | $velocity = $velocity - $a; |
112 | } |
113 | |
114 | $height = $height - $velocity; |
115 | $velocity = $velocity + $gravity; |
116 | $t = $t + 1; |
117 | } |
118 | |
119 | That's it! |
120 | |
121 | Try to run the program, and the ship should land safely: |
122 | |
123 | ./lunar.pl autopilot.txt |
124 | at 0 s height = 1000 m, velocity = 0 m/s |
125 | at 1 s height = 1000 m, velocity = 1 m/s |
126 | at 2 s height = 999 m, velocity = 2 m/s |
127 | at 3 s height = 997 m, velocity = 3 m/s |
128 | at 4 s height = 994 m, velocity = 4 m/s |
129 | at 5 s height = 990 m, velocity = 5 m/s |
130 | ... |
131 | at 54 s height = 19 m, velocity = 4 m/s |
132 | at 55 s height = 15 m, velocity = 5 m/s |
133 | at 56 s height = 10 m, velocity = 6 m/s |
134 | at 57 s height = 4 m, velocity = 7 m/s |
135 | |
136 | You landed on the surface safely! :-D |
137 | |
138 | Cool, but... |
139 | |
140 | =head2 HOW ABOUT THE GRAPHICS? |
141 | |
142 | Okay, okay... now that we have a working prototype, we can work on |
143 | the graphics. But, first of all, we'll need... |
144 | |
145 | =head3 THE GRAPHICS |
146 | |
147 | Yes, the graphics. |
148 | |
149 | We won't use anything fancy here, just two images: a large one, for |
150 | the background, and a smaller one for the spaceship. |
151 | |
152 | Create the images using the Gimp, or use the images provided by |
153 | this tutorial; Save these images in a subdirectory called "images": |
154 | ("C<images/background.jpg>" and "C<images/ship.png>"). |
155 | |
156 | =head2 USING SDL |
157 | |
158 | First step: use the required libraries: |
159 | |
160 | use SDL; #needed to get all constants |
161 | use SDL::App; |
162 | use SDL::Surface; |
163 | use SDL::Rect; |
164 | |
165 | Second step: initialize C<SDL::App>: |
166 | |
167 | my $app = SDL::App->new( |
168 | -title => "Lunar Lander", |
169 | -width => 800, |
170 | -height => 600, |
171 | -depth => 32, |
172 | ); |
173 | |
174 | Third step: load the images and create the necessary "rectangles": |
175 | |
176 | my $background = SDL::Surface->new( -name => 'images/background.jpg', ); |
177 | my $ship = SDL::Surface->new( -name => 'images/ship.png', ); |
178 | |
179 | my $background_rect = SDL::Rect->new( |
180 | -height => $background->height(), |
181 | -width => $background->width(), |
182 | ); |
183 | |
184 | my $ship_rect = SDL::Rect->new( |
185 | -height => $ship->height(), |
186 | -width => $ship->width(), |
187 | ); |
188 | |
189 | Fourth step: create a sub to draw the spaceship and background: |
190 | |
191 | sub draw { |
192 | my ( $x, $y ) = @_; # spaceship position |
193 | |
194 | # fix $y for screen resolution |
195 | $y = 450 * ( 1000 - $y ) / 1000; |
196 | |
197 | # background |
198 | $background->blit( $background_rect, $app, $background_rect ); |
199 | |
200 | # ship |
201 | my $ship_dest_rect = SDL::Rect->new( |
202 | -height => $ship->height(), |
203 | -width => $ship->width(), |
204 | -x => $x, |
205 | -y => $y, |
206 | ); |
207 | |
208 | $ship->blit( $ship_rect, $app, $ship_dest_rect ); |
209 | |
210 | $app->update($background_rect); |
211 | } |
212 | |
213 | Note that this sub first combines all the bitmaps, using a blit |
214 | ("Block Image Transfer") operation -- which is quite fast, but does |
215 | not update the display. |
216 | |
217 | The combined image is displayed in the last line. This process of |
218 | combining first, and displaying later, avoids that annoying fading |
219 | between cycles ("flickering"). |
220 | |
221 | Finally, add the following lines to the end of the main loop, so that |
222 | we call the C<draw()> function with the correct spaceship |
223 | coordinates: |
224 | |
225 | while ( $height > 0 ) { |
226 | |
227 | # ... |
228 | |
229 | draw( 100, $height ); |
230 | $app->delay(10); |
231 | } |
232 | |
233 | That's it! |
234 | |
235 | Run the program and watch the spaceship landing safely on the surface |
236 | of the moon. |
237 | |
238 | =head1 COPYRIGHT & LICENSE |
239 | |
240 | Copyright 2009 Nelson Ferraz, all rights reserved. |
241 | |
242 | This program is free software; you can redistribute it and/or modify it |
243 | under the same terms as Perl itself. |
244 | |