Getting Started with 2D Games Using Pyxel (Part 8): Spawning Asteroids
Source: Dev.to
1. Create an Asteroid Sprite
First, let’s prepare a sprite class for asteroids. Add a new AsteroidSprite class to sprite.py. In its constructor we use a random value to select which asteroid image to display.
# sprite.py
# add this class
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"""
pyxel.blt(
self.x, self.y, 0,
self.w * self.index, 0,
self.w, self.h, 0
) # Asteroid
With this setup, each asteroid will have a different appearance every time it is generated.
2. Prepare Constants and Variables
Next, add asteroid‑related constants to main.py. By grouping these values as constants, it becomes much easier to adjust the game difficulty later.
# main.py
# add constants
ASTEROID_INTERVAL = 20 # Spawn interval (frames)
ASTEROID_LIMIT = 30 # Maximum number of asteroids
ASTEROID_SPD_MIN = 1.0 # Minimum speed
ASTEROID_SPD_MAX = 2.0 # Maximum speed
ASTEROID_DEG_MIN = 30 # Minimum angle
ASTEROID_DEG_MAX = 150 # Maximum angle
Add variables for managing asteroids to the constructor of the Game class.
# main.py
# add to Game.__init__()
# Asteroids
self.asteroid_time = 0 # Spawn interval counter
self.asteroids = [] # List of asteroids
3. Asteroid Spawning Logic
Add a check_interval() method to the Game class. This method spawns asteroids at fixed intervals (every ASTEROID_INTERVAL frames). The total number of asteroids is limited by ASTEROID_LIMIT. The spawn position is randomly chosen along the top edge of the screen, and speed/angle are also randomly selected within predefined ranges.
# main.py
# add to Game class
def check_interval(self):
# Asteroid spawn interval
self.asteroid_time += 1
if self.asteroid_time = ASTEROID_LIMIT:
return
# Add a new 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)
4. Updating and Drawing Asteroids
Update
In the update() method of the Game class, handle asteroid spawning and updating.
# main.py
# add to Game.update()
self.check_interval() # Spawn asteroids
# Update asteroids
for asteroid in self.asteroids:
asteroid.update()
self.overlap_spr(asteroid)
Draw
In the draw() method, draw all asteroids.
# main.py
# add to Game.draw()
# Draw asteroids
for asteroid in self.asteroids:
asteroid.draw()
Now, multiple asteroids will flow across the screen.
Complete Code
Below is the complete code implementing all the features in this chapter.
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"""
self.x += self.vx
self.y += self.vy
def draw(self):
"""Draw (implemented in subclasses)"""
pass
def move(self, spd, deg):
"""Move"""
rad = deg * math.pi / 180
self.vx = spd * math.cos(rad) # x‑axis velocity
self.vy = spd * math.sin(rad) # y‑axis velocity
def flip_x(self):
"""Flip movement in the x direction"""
self.vx *= -1
class ShipSprite(BaseSprite):
def __init__(self, x, y):
"""Constructor"""
super().__init__(x, y)
def draw(self):
"""Draw"""
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"""
pyxel.blt(
self.x, self.y, 0,
self.w * self.index, 0,
self.w, self.h, 0
) # Asteroid
main.py
import pyxel
import math
import random
import sprite
W, H = 160, 120
SHIP_SPD = 1.4
ASTEROID_INTERVAL = 20 # Spawn interval
ASTEROID_LIMIT = 30 # Maximum number of asteroids
ASTEROID_SPD_MIN = 1.0 # Minimum speed
ASTEROID_SPD_MAX = 2.0 # Maximum speed
ASTEROID_DEG_MIN = 30 # Minimum angle
ASTEROID_DEG_MAX = 150 # Maximum angle
class Game:
def __init__(self):
pyxel.init(W, H, caption="Asteroid Shooter")
self.ship = sprite.ShipSprite(W // 2, H - 20)
# Asteroids
self.asteroid_time = 0
self.asteroids = []
def check_interval(self):
# Asteroid spawn interval
self.asteroid_time += 1
if self.asteroid_time = ASTEROID_LIMIT:
return
# Add a new 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 overlap_spr(self, asteroid):
# Placeholder for collision detection logic
pass
def update(self):
# Ship movement (example)
if pyxel.btn(pyxel.KEY_LEFT):
self.ship.x -= SHIP_SPD
if pyxel.btn(pyxel.KEY_RIGHT):
self.ship.x += SHIP_SPD
self.check_interval() # Spawn asteroids
# Update asteroids
for asteroid in self.asteroids:
asteroid.update()
self.overlap_spr(asteroid)
def draw(self):
pyxel.cls(0)
# Draw ship
self.ship.draw()
# Draw asteroids
for asteroid in self.asteroids:
asteroid.draw()
Game()
pyxel.run(Game().update, Game().draw)
Maximum Angle
# 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 # Spawn interval counter
self.asteroids = [] # Asteroid list
# Start Pyxel
pyxel.init(W, H, title="Hello, Pyxel!!")
pyxel.load("shooter.pyxres")
pyxel.run(self.update, self.draw)
def update(self):
"""Update"""
# Update player
self.ship.update()
self.control_ship()
self.overlap_spr(self.ship)
self.check_interval() # Spawn asteroids
# Update asteroids
for asteroid in self.asteroids:
asteroid.update()
self.overlap_spr(asteroid)
def draw(self):
"""Draw"""
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()
def control_ship(self):
"""Action"""
if pyxel.btnp(pyxel.KEY_SPACE):
self.ship.flip_x() # Reverse movement
def overlap_spr(self, spr):
"""Move to the opposite side when leaving 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
# Do not exceed the maximum number of asteroids
if ASTEROID_LIMIT < len(self.asteroids):
return
# Add a new 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"""
Game()
if __name__ == "__main__":
main()
Enter fullscreen mode
Exit fullscreen mode
When you run the game, it looks like this:
Next Time…
Thank you for reading!
In the next chapter, we’ll cover “Let’s Fire Bullets.”
Stay tuned!
