Shooting Bullets
In this chapter, we will implement bullet firing from the player sprite.
Here, we will build the core mechanics of a shooting game, including:
- Adding a new sprite (bullets)
- Managing multiple bullets using a list
- Removing bullets that are no longer needed
The complete code is shown at the end of this article.
1. Creating a Bullet Sprite
As in the previous chapter, add a new BulletSprite class to sprite.py.
This time, instead of using an image, we will draw a simple 2×2 square using pyxel.rect().
This is more than enough to represent small bullets.
# sprite.py (add this class)
class BulletSprite(BaseSprite):
def __init__(self, x, y):
""" Constructor """
super().__init__(x, y)
self.x += self.w / 2 - 1 # Adjust to center
def draw(self):
""" Draw processing """
pyxel.rect(self.x, self.y, 2, 2, 12)
2. Defining Constants
Next, add a constant for bullet speed in main.py.
By defining it as a constant, you can easily tweak the bullet speed later.
# main.py (add a constant)
BULLET_SPD = 3 # Bullet speed
3. Firing Bullets
Now, let’s add the logic for firing bullets.
In this sample, bullets are fired when the player presses the space key—the same timing used to flip the player’s direction left and right.
Bullets will move straight upward (270 degrees).
# main.py (add to the Game class method `control_ship()`)
def control_ship(self):
""" Action """
if pyxel.btnp(pyxel.KEY_SPACE):
self.ship.flip_x() # Reverse movement
# Fire a bullet
bullet = sprite.BulletSprite(self.ship.x, self.ship.y)
bullet.move(BULLET_SPD, 270)
self.bullets.append(bullet)
4. Updating and Drawing Bullets
Next, add the update logic for bullets.
In the update() method of the Game class, update all bullets and remove those that go off-screen.
Because elements are removed during iteration, the list is processed in reverse order.
This prevents bugs caused by index shifting.
# main.py (add to the Game class `update()` method)
# Update bullets (reverse order)
for bullet in self.bullets[::-1]:
bullet.update()
# Remove bullets that go off-screen
if bullet.y < 0:
self.bullets.remove(bullet)
continue
Then, draw all bullets together in the draw() method.
# main.py (add to the Game class `draw()` method)
# Draw bullets
for bullet in self.bullets:
bullet.draw()
With this, the player can now shoot bullets.
Complete Code
Below is the complete code with all features implemented so far.
# sprite.py
import pyxel
import math
import random
class BaseSprite:
def __init__(self, x, y, w=8, h=8):
""" Constructor """
self.x = x
self.y = y
self.w = w
self.h = h
self.vx = 0
self.vy = 0
def update(self):
""" Update processing """
self.x += self.vx
self.y += self.vy
def draw(self):
""" Draw processing (implemented in subclasses) """
pass
def move(self, spd, deg):
""" Move """
rad = deg * math.pi / 180
self.vx = spd * math.cos(rad) # X-axis speed
self.vy = spd * math.sin(rad) # Y-axis speed
def flip_x(self):
""" Flip movement on the X-axis """
self.vx *= -1
class ShipSprite(BaseSprite):
def __init__(self, x, y):
""" Constructor """
super().__init__(x, y)
def draw(self):
""" Draw processing """
pyxel.blt(
self.x, self.y, 0,
0, 0,
self.w, self.h, 0
) # Ship
class AsteroidSprite(BaseSprite):
def __init__(self, x, y):
""" Constructor """
super().__init__(x, y)
self.index = random.randint(2, 7) # Asteroid image index
def draw(self):
""" Draw processing """
pyxel.blt(
self.x, self.y, 0,
self.w * self.index, 0,
self.w, self.h, 0
) # Asteroid
class BulletSprite(BaseSprite):
def __init__(self, x, y):
""" Constructor """
super().__init__(x, y)
self.x += self.w / 2 - 1 # Adjust to center
def draw(self):
""" Draw processing """
pyxel.rect(self.x, self.y, 2, 2, 12)
# main.py
import pyxel
import math
import random
import sprite
W, H = 160, 120
SHIP_SPD = 1.4
ASTEROID_INTERVAL = 20
ASTEROID_LIMIT = 30
ASTEROID_SPD_MIN = 1.0
ASTEROID_SPD_MAX = 2.0
ASTEROID_DEG_MIN = 30
ASTEROID_DEG_MAX = 150
BULLET_SPD = 3 # Bullet speed
# Game
class Game:
def __init__(self):
""" Constructor """
# Initialize score
self.score = 0
# Initialize player
self.ship = sprite.ShipSprite(W / 2, H - 40)
deg = 0 if random.random() < 0.5 else 180
self.ship.move(SHIP_SPD, deg)
# Asteroids
self.asteroid_time = 0
self.asteroids = []
# Bullets
self.bullets = []
# Start Pyxel
pyxel.init(W, H, title="Hello, Pyxel!!")
pyxel.load("shooter.pyxres")
pyxel.run(self.update, self.draw)
def update(self):
""" Update processing """
# Update player
self.ship.update()
self.control_ship()
self.overlap_spr(self.ship)
self.check_interval() # Add asteroids
# Update asteroids
for asteroid in self.asteroids:
asteroid.update()
self.overlap_spr(asteroid)
# Update bullets (reverse order)
for bullet in self.bullets[::-1]:
bullet.update()
if bullet.y < 0:
self.bullets.remove(bullet)
continue
def draw(self):
""" Draw processing """
pyxel.cls(0)
# Draw score
pyxel.text(
10, 10,
"SCORE:{:04}".format(self.score), 12
)
# Draw player
self.ship.draw()
# Draw asteroids
for asteroid in self.asteroids:
asteroid.draw()
# Draw bullets
for bullet in self.bullets:
bullet.draw()
def control_ship(self):
""" Action """
if pyxel.btnp(pyxel.KEY_SPACE):
self.ship.flip_x() # Reverse movement
# Fire a bullet
bullet = sprite.BulletSprite(self.ship.x, self.ship.y)
bullet.move(BULLET_SPD, 270)
self.bullets.append(bullet)
def overlap_spr(self, spr):
""" Wrap around the screen """
if spr.x < -spr.w:
spr.x = W
return
if W < spr.x:
spr.x = -spr.w
return
if spr.y < -spr.h:
spr.y = H
return
if H < spr.y:
spr.y = -spr.h
return
def check_interval(self):
# Asteroid spawn interval
self.asteroid_time += 1
if self.asteroid_time < ASTEROID_INTERVAL:
return
self.asteroid_time = 0
# Limit the number of asteroids
if ASTEROID_LIMIT < len(self.asteroids):
return
# Add an asteroid
x = random.random() * W
y = 0
spd = random.uniform(ASTEROID_SPD_MIN, ASTEROID_SPD_MAX)
deg = random.uniform(ASTEROID_DEG_MIN, ASTEROID_DEG_MAX)
asteroid = sprite.AsteroidSprite(x, y)
asteroid.move(spd, deg)
self.asteroids.append(asteroid)
def main():
""" Main entry point """
Game()
if __name__ == "__main__":
main()
When you run this program, the result will look like this:
Coming Up Next...
Thank you for reading!
The next chapter is titled “Implementing Collision Detection.”
Stay tuned!!

Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.