Drawing the Cannon Bullet

We want to create a new cannon bullet everytime we press the space bar. Since we can have more than one bullet at a time, we'll use a list to hold all our bullets. Start by creating an empty list...

bullets = []

Next, in update(), check if the space key is pressed and add a bullet if it is.

if keyboard.space:
    bullet = Actor('bulletblue2')
    bullet.angle = tank.angle
    bullet.x = tank.x
    bullet.y = tank.y
    bullets.append(bullet)

We set the bullet angle to be the same as the tank so that they'll face the same direction. We also set the x and y of the bullet to be the same as the tank, so that the bullet will start from the tank.

Don't forget to draw the bullets in the draw() function...

for bullet in bullets:
    bullet.draw()

When you try out the program, you'll see a bullet created each time you press the space bar. But the bullet just stays there and don't do anything, so next, we'll make the bullet fly.

Moving the Bullet

In update, we can move the bullet by changing x or y. To decide which direction to move towards, we can look at the bullet's angle.

Angle Direction x or y
0 Right x+
90 Up y-
180 Left x-
270 Down y+

We should move the bullet in the update() function. Using a for loop.

for bullet in bullets:
    if bullet.angle == 0:
        bullet.x = bullet.x + 5
    elif bullet.angle == 90:
        bullet.y = bullet.y - 5
    elif bullet.angle == 180:
        bullet.x = bullet.x - 5
    elif bullet.angle == 270:
        bullet.y = bullet.y + 5

Hold-Off

Now our bullet looks more like a laser! That's because when we press the space bar, it creates a LOT of bullets very rapidly. We should add a hold-off. This is some code that stops the tank for shooting for a while after each bullet. We'll start by creating a new variable.

bullet_holdoff = 0

Then modify our shoot code...

if bullet_holdoff == 0:
    if keyboard.space:
        bullet = Actor('bulletblue2')
        bullet.angle = tank.angle
        bullet.x = tank.x
        bullet.y = tank.y
        bullets.append(bullet)
        bullet_holdoff = 100
else:
    bullet_holdoff = bullet_holdoff - 1

If you try to run the code now, you'll get an error. Do you know why? Look at the Score section in Gem Catcher and see if you can figure out what's missing! (Answer is at the bottom of this page, but try to figure it out on your own!)

What the above code does is...

if bullet_holdoff == 0: Only allow firing if bullet_holdoff is 0.

bullet_holdoff = 100 Set the bullet_holdoff to 100. This will prevent firing since it is no longer zero. You can try changing this value and see what effects it has.

else:
    bullet_holdoff = bullet_holdoff - 1

If the bullet_holdoff is not zero, we'll decrease it by one. This will gradually reduce it until it reaches zero again.

Your code should now look like this...

import pgzrun
import random

WIDTH=800
HEIGHT=600

tank = Actor('tank_blue')
tank.y = 575
tank.x = 400
tank.angle = 90

background = Actor('grass')

walls = []
for x in range(16):
    for y in range(10):
        if random.randint(0, 100) < 50:
            wall = Actor('wall')
            wall.x = x * 50 + 25
            wall.y = y * 50 + 25 + 50
            walls.append(wall)

bullets = []
bullet_holdoff = 0

def update():
    global bullet_holdoff

    # This part is for the tank
    original_x = tank.x
    original_y = tank.y

    if keyboard.left:
        tank.x = tank.x - 2
        tank.angle = 180
    elif keyboard.right:
        tank.x = tank.x + 2
        tank.angle = 0
    elif keyboard.up:
        tank.y = tank.y - 2
        tank.angle = 90
    elif keyboard.down:
        tank.y = tank.y + 2
        tank.angle = 270

    if tank.collidelist(walls) != -1:
        tank.x = original_x
        tank.y = original_y

    if tank.x < 0 or tank.x > 800 or tank.y < 0 or tank.y > 600:
        tank.x = original_x
        tank.y = original_y

    # This part is for the bullet
    if bullet_holdoff == 0:
        if keyboard.space:
            bullet = Actor('bulletblue2')
            bullet.angle = tank.angle
            bullet.x = tank.x
            bullet.y = tank.y
            bullets.append(bullet)
            bullet_holdoff = 100
    else:
        bullet_holdoff = bullet_holdoff - 1

    for bullet in bullets:
        if bullet.angle == 0:
            bullet.x = bullet.x + 5
        elif bullet.angle == 90:
            bullet.y = bullet.y - 5
        elif bullet.angle == 180:
            bullet.x = bullet.x - 5
        elif bullet.angle == 270:
            bullet.y = bullet.y + 5

def draw():
    screen.fill((0,0,0))
    background.draw()
    tank.draw()
    for bullet in bullets:
        bullet.draw()
    for wall in walls:
        wall.draw()

pgzrun.go() # Must be last line

What's missing?

We need to add global bullet_holdoff.

The bullet_holdoff variable is declared outside of the update() function, but we are trying to write it from inside the update() function. In Python, we can read variables that are outside a function (...these are called global variables), but cannot write to them unless we declare them as global inside the function.