Game Design: Aiming
Game Design: Aiming
In this chapter we are going to let BB8 fly around the screen and aim and shoot at the Troopers. We also will let the Troopers wander around and shoot randomly in any one of the four directions. This will involve some advanced math.
Duplicate your BB8 Levels Game and call it BB8 Aiming Game. We will be updating that code as you go through this chapter.
In the constant section we are going to add a few more constants.
#13-14: I decided to add some scoring constants. Firing a bullet still costs 1 point but shooting a Trooper now gets you five points. I used constants at the top so if I ever decide to change the scoring again, I don't have to go looking through my code for all of the places that I add or subtract points from the score.
#22-25: Here we add some speed constants. MOVEMENT_SPEED is how fast BB8 will be moving forward or reverse when the UP or DOWN keys are pressed. ANGLE_SPEED is how fast BB8 will rotate if the LEFT or RIGHT keys are pressed. BULLET_SPEED is how fast the bullet will be traveling. E_BULLET_SPEED is how fast the enemy bullet will be traveling. Obviously, we made that constant higher than BB8's speed.
In the Setup() function we want to now place BB8 in the center of the window and the Troopers at random locations anywhere within the screen.
#157-160: This spawns BB8 to the center of the screen instead of at the center bottom.
#163-171: This code puts half of the spawned Troopers in the left 1/3 of the screen and the other half in the right 1/3 of the screen. Why did I do this? Originally, I was trying to keep it simple and just spawn them anywhere in the screen but sometimes the game would end instantly if a Trooper spawned touching BB8. To solve this problem I used an if-else statement. I performed the modulus calculation on the variable i. If you remember, modulus(%) divides i by a number and returns the remainder instead of the answer. So on #165, the code "if i%2 == 0" will work for even values of i. Odd values of i will be implemented in the else: section.
#166-167: For even values of i, the center_x is given a random value between the width of the Trooper and the screen width divided by 3. The center_y is given a random value between the height of the Trooper and the screen height minus the Trooper height. The screen width divided by 3 could give us a float number which would give an error. This is why we converted it to an integer.
#169-170: For odd values of i, the center_x is given a random value between 2/3 of the screen width and the screen width minus the Trooper width. The center_y is again given a random value between the height of the Trooper and the screen height minus the Trooper height.
We now need to update the Troopers so that they randomly move in any direction and bounce off of the walls instead of just always moving down the screen.
#74-75: We created self.dx and self.dy variables. These variable stand for change in x and change in y. These were assigned a random integer from -1 up to 2 incrementing by 2. This actually means that self.dx and self.dy will be assigned a value of either -1 or +1.
#78-79: In the update() section of the Trooper Sprite we simply add the self.dx and self.dy to the self.center_x and self.center_y which will move the Trooper in a random direction.
#80-83: Finally we want to bounce the Troopers off of the side walls of the screen. If the bottom of the Trooper is less than zero or the top of the Trooper is greater than the screen height, we simply multiply self.dy by -1 which changes the sign of self.dy so it will go the other direction. We also check to see if the left side of the Trooper is less than zero or the right side of the Trooper is greater than the screen width. If either of these are true we multiply self.dx by -1 to change it's direction as well.
Every sprite that we create has a built in variable called self.angle. This tells the orientation of the sprite and is a little different than what we are used to in math. With normal graphing 0˚ is to the right on the x-axis and the angle rotates counter-clockwise so 90˚would be straight up and so on. With sprites the angle still rotates counterclockwise but 0˚ is straight up. Whenever you open up an image it is in it's 0˚ position. Look at our BB8 sprite below. When we open BB8 it looks like the first frame which is it's 0˚ angle. Notice rotating left is the positive direction and rotating right is the negative direction. The other three frames show BB8 in it's 90˚, 180˚ and 270˚ orientations.
This can be kind of confusing depending on how a sprite is originally oriented in an image. Remember, whichever way an image opens up is in it's 0˚ orientation. If we open up the bullet image we see it is pointed to the right. However, the self.angle variable for this bullet is 0˚. If we rotate the bullet counterclockwise 90˚it will be pointing straight up. So when BB8 is pointing up it's self.angle = 0˚ but when the bullet is pointing straight up it's self.angle = 90˚. As you create games, a way to avoid this confusion is to manipulate the image ahead of time. We could open the bullet in some image editor and rotate it to point straight up and save it. Then both the BB8 and the bullet pointing straight up would be their 0˚ orientations. This would probably make it easier to understand when we get to the code where BB8 shoots a bullet. However, I did not manipulate the bullet image in this chapter.
#49-50: In the initialization of our Player() class we are going to declare two variables: self.speed=0 and self.change_angle = 0. These variables will change as a user presses the arrow keys.
#268-271: We now need to update the on_key_press() section to control BB8. If the LEFT arrow is pressed we want to rotate BB8 left so we set self.BB8.change_angle to ANGLE_SPEED. If the RIGHT arrow is pressed we want to rotate BB8 right so we set self.BB8.change_angle to negative ANGLE_SPEED.
#272-275: If the UP arrow is pressed we want to make BB8 move forward so we set self.BB8.speed to MOVEMENT_SPEED. If the DOWN arrow is pressed we want to reverse BB8 so we set self.BB8.speed to negative MOVEMENT_SPEED.
#305-309: We also need to update the on_key_release() section. If the LEFT or RIGHT arrows are released we want to stop BB8 from rotating so we set self.BB8.change_angle to 0. If the UP or DOWN arrows are released we want to make BB8 stop moving, so we set self.BB8.speed to 0.
#55: The first thing we want to do is update the self.angle of BB8 by adding on the self.change_angle. This will rotate BB8 when we are pressing the LEFT or RIGHT arrow keys.
This is the only easy thing to understand in this section. We now have to figure out how to move BB8 in the direction that he is angled. This will be difficult for you if you haven't yet studied trigonometry. We will be using the sine and cosine functions. In Python your angle must be in radians instead of degrees to use these functions.
#3: At the top of your program you need to import the math module.
#56: Create a variable called angle_rad and use math.radians() to convert self.angle from degrees to radians.
When a sprite moves in one of the four cardinal directions this is easy to update. You simply add or subtract an amount from either center_x or center_y. However, to get a sprite to move at an angle you have to add a certain amount to both center_x and center_y at the same time. We need to understand right-angle Trigonometry to solve this problem.
Look at BB8 rotated to 60˚ in the image below. The origin is BB8's current center_x and center_y. We need to determine the ∆x and ∆y components of the speed vector to determine the new center_x and center_y. We will use A below as the angle in radians.
sin(A) = opposite / hypotenuse = ∆x / speed
Therefore, ∆x = speed * sin(A)
You will notice that ∆x is pointing to the left so we will put a negative sign in front of the speed. ∆x = -speed * sin(A)
Likewise,
cos(A) = adjacent / hypotenuse = ∆y / speed
Therefore, ∆y = speed * sin(A)
Here ∆y is pointing up so this equation will remain as is.
#61-62: We add ∆x to center_x and ∆y to center_y.
#64-71: We use a series of if statements to not let BB8 go beyond the side walls.
The bullets are still fired when we press the SPACE key. There are just a few adjustments we have to make to this code.
#279: Instead of lining up the bullet.bottom and BB8.top like we did previously, we need to line up the bullet.center_y and the BB8.center_y.
#281: Instead of setting the bullet.angle = 90 to point it straight up we need it to point the same direction as BB8. If you remember, BB8 is pointing up at 0˚ and the bullet is pointing up at 90˚. Therefore we need to set the bullet.angle equal to the BB8.angle + 90. Again, it might have been easier to originally rotate the bullet image to point straight up.
#282: We will now set our bullet.speed to the constant BULLET_SPEED.
#284: Subtract the BULLET_PTS constant from the score.
Instead of having the bullets just shoot straight up the screen we want them to shoot whichever way BB8 was pointing.
#112: Create a variable called angle_shoot and set it to the angle of BB8 in radians. Remember, if we get self.angle of the bullet we need to now subtract 90 to have the angle of BB8. Use math.radians() to convert it to radians.
#113-114: We use the same trigonometry we used with moving BB8 to change the center_x and center_y of the bullet.
#115-116: Kill the bullet if it goes outside the screen.
#235: When we create an enemy bullet, the only change we will make is instead of lining up the ebullet.top and trooper.bottom like we did previously, we need to line up the ebullet.center_y and the trooper.center_y.
Instead of just having enemy bullets shoot down the screen, we will have them be shooting in one of the four cardinal directions.
#90: Create an angle_list=[0,90,180,270]
#91: Set the angle of the enemy bullet randomly to one of the angles in angle_list.
#94-101: Depending on which angle was chosen move the bullet in that direction
#103-104: Kill the enemy bullet if it flies off of the screen.
Finally, we will run the main function. We can change the game title to "BB8 Aiming".
When you get this game to work, demonstrate it to your instructor. Feel free to implement any new knowledge into your game project.