Raspberry Pi’s own Rik Cross shows you how to hit enemies with your mouse pointer as they move around the screen.
Clicky Clicky Bang Bang
Shooting galleries have always been a part of gaming, from the Seeburg Ray-O-Lite in the 1930s to the light gun video games of the past 40 years. Nintendo’s Duck Hunt — played with the NES Zapper — was a popular console shooting game in the early eighties, while titles such as Time Crisis and The House of the Dead kept the genre alive in the 1990s and 2000s.
Here, I’ll show you how to use a mouse to fire bullets at moving targets. Code written to instead make use of a light gun and a CRT TV (as with Duck Hunt) would look very different. In these games, pressing the light gun’s trigger would cause the entire screen to go black and an enemy sprite to become bright white. A light sensor at the end of the gun would then check whether the gun is pointed at the white sprite, and if so, would register a hit. If more than one enemy was on the screen when the trigger was pressed, each enemy would flash white for one frame in turn, so that the gun would know which enemy had been hit.
I’ve used two Pygame Zero event hooks for dealing with mouse input. Firstly, the
on_mouse_move() function updates the position of the crosshair sprite whenever the mouse is moved. The
on_mouse_down() function reacts to mouse button presses, with the left button being pressed to fire a bullet (if
numberofbullets > 0) and the right button to reload (setting
numberofbullets to MAXBULLETS).
Each time a bullet is fired, a check is made to see whether any enemy sprites are colliding with the crosshair — a collision means that an enemy has been hit. Luckily, Pygame Zero has a
colliderect() function to tell us whether the rectangular boundary around two sprites intersects.
If this helper function wasn’t available, we’d instead need to use sprites’ x and y coordinates, along with width and height data (w and h below) to check whether the two sprites intersect both horizontally and vertically. This is achieved by coding the following algorithm:
- Is the left-hand edge of sprite 1 further left than the right-hand edge of sprite 2
(x1 < x2+w2)?
- Is the right-hand edge of sprite 1 further right than the left-hand edge of sprite 2
(x1+w1 > x2)?
- Is the top edge of sprite 1 higher up than the bottom edge of sprite 2
(y1 < y2+h2)?
- Is the bottom edge of sprite 1 lower down than the top edge of sprite 2
(y1+h1 > y2)?
If the answer to the four questions above is
True, then the two sprites intersect (see Figure 1). To give visual feedback, hit enemies briefly remain on the screen (in this case, 50 frames). This is achieved by setting a
hit variable to
True, and then decrementing a timer once this variable has been set. The enemy’s deleted when the timer reaches 0.
As well as showing an enemy for a short time after being hit, successful shots are also shown. A problem that needs to be overcome is how to modify an enemy sprite to show bullet holes. A
hits list for each enemy stores bullet sprites, which are then drawn over enemy sprites.
Storing hits against an enemy allows us to easily stop drawing these hits once the enemy is removed. In the example code, an enemy stops moving once it has been hit.
If you don’t want this behaviour, then you’ll also need to update the position of the bullets in an enemy’s
hits list to match the enemy movement pattern.
When decrementing the number of bullets, the
max() function is used to ensure that the bullet count never falls below 0. The
max() function returns the highest of the numbers passed to it, and as the maximum of 0 and any negative number is 0, the number of bullets always stays within range.
There are a couple of ways in which the example code could be improved. Currently, a hit is registered when the crosshair intersects with an enemy — even if they are barely touching. This means that often part of the bullet is drawn outside of the enemy sprite boundary. This could be solved by creating a clipping mask around an enemy before drawing a bullet. More visual feedback could also be given by drawing missed shots, stored in a separate list.
Get your copy of Wireframe issue 20
You can read more features like this one in Wireframe issue 20, available now at Tesco, WHSmith, and all good independent UK newsagents.
Or you can buy Wireframe directly from Raspberry Pi Press — delivery is available worldwide. And if you’d like a handy digital version of the magazine, you can also download issue 20 for free in PDF format.