In the next version of the program, I get the ship moving. The player can press the Up Arrow key to engage the ships engines. This applies thrust to the ship in the direction the ship is facing. Since there's no friction in this simple game, the ship keeps moving based on all of the thrust the player applies to it.
When the player engages the ship's engines, the Astrocrash03 program changes the velocity of the ship based on its angle (and produces an appropriate sound effect too). Figure 12.10 illustrates the program.
Figure 12.10: The ship can now move around the screen.
The first thing I do is import a new module at the top of the program:
import math
The math module contains a bunch of mathematical functions and constants. But don't let that scare you. I use only a few in this program.
I create a class constant, VELOCITY_STEP, for altering the ship's velocity:
VELOCITY_STEP = .03
A higher number would make the ship accelerate faster, a lower number would make the ship accelerate more slowly.
I also add a new class variable, sound, for the thrusting sound of the ship:
sound = games.load_sound("thrust.wav")
Next, I add code to the end of Ship's moved() method to get the ship moving. I check to see if the player is pressing the Up Arrow key. If so, I play the thrusting sound:
# apply thrust based on up arrow key if self.screen.is_pressed(games.K_UP): Ship.sound.play()
Now, when the player presses the Up Arrow key, I need to alter the ship's velocity components (the Ship object's dx and dy attributes) based on the angle of the ship. For example, if the ship's angle is 0 degrees (it's facing straight up), then I need to decrease the object's dy attribute. Conversely, if the ship's angle is 90 degrees (it's facing to the right), then I need to increase the object's dx attribute. And if the ship is at 45 degrees (it's facing diagonally up and to the right), then I need to decrease the object's dy attribute and increase it's dx attribute equally. Of course, every angle requires its own adjustments. So, how can I figure out how much to change each velocity component based on the angle of the ship? Well, the answer is trigonometry. Wait, don't slam this book shut and run as fast as your legs can carry you, screaming incoherently. As promised, I use only two mathematical functions in a few lines of code to figure this out.
To start the process, I get the angle of the ship, converted to radians:
# get velocity component changes based on ship's angle angle = self.get_angle() * math.pi / 180 # convert to radians
A radian is just a measure of rotation, like a degree. Python's math module expects angles in radians (while livewires works with degrees) so that's why I need to make the conversion. In the calculation, I use the math module constant pi, which represents the number pi.
Now that I've got the ship's angle in radians, I can figure out how much to change each velocity component using the math module's sin() and cos() functions, which calculate an angle's sine and cosine. The following lines calculate how much the object's dx and dy attribute values should change based on the ship's angle and VELOCITY_STEP:
add_dx = Ship.VELOCITY_STEP * math.sin(angle) add_dy = -Ship.VELOCITY_STEP * math.cos(angle)
Next, I calculate the object's new dx and dy values using add_dx and add_dy:
# add current velocity and velocity change to get new velocity dx, dy = self.get_velocity() new_dx = dx + add_dx new_dy = dy + add_dy
Then, I set the object's velocity with these new values:
# set new velocity self.set_velocity(new_dx, new_dy)
All that's left to do is handle the screen boundaries. I use the same strategy as I did with the asteroids: the ship should wrap around the screen. In fact, I copy and paste the code from Asteroid's moved() method to the end of Ship's moved() method:
# wrap the ship around screen if self.get_top() > SCREEN_HEIGHT: self.set_bottom(0) if self.get_bottom() < 0: self.set_top(SCREEN_HEIGHT) if self.get_left() > SCREEN_WIDTH: self.set_right(0) if self.get_right() < 0: self.set_left(SCREEN_WIDTH)
Although this works, copying and pasting large portions of code is usually a sign of poor design. I'll revisit this code later and find a more elegant solution.
TRAP | Repeated chunks of code bloat programs and make them harder to maintain. When you see repeated code, it's often time for a new function or class. Think about how you might consolidate the code into one place and call or invoke it from the parts of your program where the repeated code currently lives. |