Implement Collision Detection
In this chapter, we will implement collision detection between bullets and asteroids.
The complete code is provided at the end.
1. Implement Collision Detection Logic
First, add a new intersects() method to the BaseSprite class in sprite.py.
This method checks whether the sprite overlaps with another sprite passed as an argument.
It returns True if they are colliding, and False otherwise.
(This implementation uses the AABB concept, but you don’t need to dive deep into it for now.)
# sprite.py (added to BaseSprite)
def intersects(self, other):
""" Rectangle-based collision detection (AABB) """
if other.x + other.w < self.x: return False
if self.x + self.w < other.x: return False
if other.y + other.h < self.y: return False
if self.y + self.h < other.y: return False
return True
2. Bullet and Asteroid Collisions
Next, we check for collisions between bullets and asteroids during the bullet update phase.
As before, since we remove elements from the list while iterating,
we traverse the list in reverse order to avoid index issues.
To keep the logic simple, we immediately return from the update() method
once a collision is detected.
When a collision occurs, the score is incremented by 1.
# main.py (added to the Game.update() method)
# Update bullets (reverse order)
for bullet in self.bullets[::-1]:
bullet.update()
# Remove bullets that leave the screen
if bullet.y < 0:
self.bullets.remove(bullet)
continue
# Collision detection (bullet x asteroid)
for asteroid in self.asteroids[::-1]:
if asteroid.intersects(bullet):
self.score += 1 # Increase score
self.bullets.remove(bullet)
self.asteroids.remove(asteroid)
return # Simplified handling
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 """
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 velocity
self.vy = spd * math.sin(rad) # Y velocity
def flip_x(self):
""" Flip X direction """
self.vx *= -1
def intersects(self, other):
""" Rectangle-based collision detection (AABB) """
if other.x + other.w < self.x: return False
if self.x + self.w < other.x: return False
if other.y + other.h < self.y: return False
if self.y + self.h < other.y: return False
return True
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
)
class BulletSprite(BaseSprite):
def __init__(self, x, y):
""" Constructor """
super().__init__(x, y)
self.x += self.w / 2 - 1 # Center alignment
def draw(self):
""" Draw """
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
# 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 """
# 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)
# Update bullets (reverse order)
for bullet in self.bullets[::-1]:
bullet.update()
# Remove bullets that leave the screen
if bullet.y < 0:
self.bullets.remove(bullet)
continue
# Collision detection (bullet x asteroid)
for asteroid in self.asteroids[::-1]:
if asteroid.intersects(bullet):
self.score += 1
self.bullets.remove(bullet)
self.asteroids.remove(asteroid)
return
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()
# Draw bullets
for bullet in self.bullets:
bullet.draw()
def control_ship(self):
""" Player actions """
if pyxel.btnp(pyxel.KEY_SPACE):
self.ship.flip_x() # Reverse movement
# Fire 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 sprite around 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 asteroid count
if ASTEROID_LIMIT < len(self.asteroids):
return
# Spawn 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()
Result
Running the game produces the following result:
Next Chapter
Thank you for reading!
In the next chapter, we’ll implement game over detection.
Stay tuned!!

Top comments (0)