Table of Contents
Step 020 - Shooting bullets from the end of a cannon barrel
In this example, two tanks can be controlled by the players (using both hands), moving forward and backward and rotating. Additionally, the turrets can rotate also. The turrets can shoot bullets out of the tanks's main cannon (please admire the recoil effect) and the tanks can fire tracer rounds from machine guns. Each gun has a machine gun at the turret and at its bow (see graphic at the right).
This source code examples teaches nothing new but demonstrate how to solve a specific problem: Creating bullet-sprites not at the center of it's launcher, but at the end (and some space away) from it. The most obvious solution for such a problem would be to create the bullet at the center of it's launcher (the Tank) and use the Layer system to make sure the Tank is drawn on top of the bullet.
But if you have a fine eye you will notice some ugly effects: if you rotate a cannon fast enough, it will look like the bullet exits at the side of the cannon instead of at it's end. Also creating one Sprite at the exact position of another sprite will trigger a collision detection, needing more code to make sure that a tank cannot shoot itself.
source code discussion
To deal with the problem of creating a bullet sprite at the exact end of a rotated cannon sprite, see the source code below. All you need is a little knowledge of the math.sin and math.cos function (remember to transform grad into radiant), like explained in step017 - rotating, shooting, inheritance.
If you like complicated explanations: What you do here is creating a vector and rotating it to find the coordinate of a point (the musszle). This is done in the methods calculate_origin of the Bullet and the Tracer class:
For the Bullet, shooting out of the muzzle of the tanks main gun the problem is this: It's boss sprite, the Tank turret, is rotated by turretAngle. Also the cannon is very long, nearly as long as the side of the Tank.
def calculate_origin(self): # - spawn bullet at end of turret barrel instead tank center - # cannon is around Tank.side long, calculatet from Tank center # later subtracted 20 pixel from this distance # so that bullet spawns closer to tank muzzle self.pos += math.cos(degrees_to_radians(self.boss.turretAngle)) * (Tank.side-20) self.pos += math.sin(degrees_to_radians(-self.boss.turretAngle)) * (Tank.side-20)
For the Tracer (shooting out of the red bow rectangle of the tank) the point of launching is the little red rectangle in the front of the Tank. Because the Tank can rotate it's turret independent of the tank's own rotation, the important variable here is tankangle. The bow machine gun rectangle is not so much distanced from the Tank's center (side/2), but it is a bit on the left side. De facto i created here an 2D-Vector, 30° from the Tank's center and with the lenght of Tank.side/2. This vector is rotated with the tankAngle to find the coordinates of the point of origin for the Tracer round.
def calculate_origin(self): """overwriting because another point of origin is needed""" # - spawn bullet at end of bow rect (and some extra distance) # the bow rect is in the middle -left from the tank center # calculatet by going -30° from the Tank center for the half tank side self.pos += math.cos(degrees_to_radians(30+self.boss.tankAngle)) * (Tank.side/2) self.pos += math.sin(degrees_to_radians(-30-self.boss.tankAngle)) * (Tank.side/2)
keyboard overflow problem
While playing you will notice that sometimes some keyboard command seem to be ignored when you press several keys at once. It also can happen that if you press some combinations of more than 2 keys at the same time, it seems like a different key was pressed (ghost)
To test it out:
- run example tankdemo03.py from below.
- press W and S to move the tank forward and back (works)
- press W and S at the same time, to stop the tank movement (works also)
- if you press D and A at the same time, the tank does not rotate (correctly)
- while pressing D and A, also press W or S. This time, it works (the tank does not rotate, but moves forward & back). In this case, 3 keys are pressed simultaneously and interpreted correctly.
- the combinations W and A , S and A, W and D, S and D all work, letting the tank rotate and move at the same time
- while pressing W and S at the same time (no movement), also press D to rotate the tank. This does not work ! Worse, the bow mg start firing, but you have not pressed LCTRL ! (ghost key effect)
This phenomenon is well known among game designer. Ultimately it is caused by the way how keyboards are constructed.
A possible solution is to design games with fewer keys to be pressed and use keys like SHIFT, ALT and CTRL because those keys are better recognized by design of the hardware. Also think about accepting input from Mouse and Joysticks (see pygame documentation) or writing network-games where each player has his own keyboard.
no additional resources necessary.
source code on github
To run this example you need:
|020_shooting_from_tank.py|| || Download the whole Archive with all files from Github:
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/020_shooting_from_tank.py
click reload in your browser if you see no code here: