Creating a Scrolling Star Background
In this article, we will create a scrolling star background to make the game screen more lively.
Here, we focus on introducing sample code only.
The complete source code is provided at the end.
1. Creating Classes to Draw Stars
In sprite.py, we define a Star class for drawing individual stars, and a Background class that updates and draws all stars in the background.
# sprite.py (excerpt)
class Star:
def __init__(self, x, y, w, h):
""" Constructor """
self.x = x
self.y = y
self.w = w
self.h = h
self.c = random.randint(0, 15) # Color
self.spd = random.randint(1, 3) # Falling speed
def update(self):
""" Update process """
self.y += self.spd
if self.h < self.y:
self.y = 0
def draw(self):
""" Draw process """
pyxel.pset(self.x, self.y, self.c)
class Background:
def __init__(self, w, h):
""" Constructor """
self.w = w # Screen width
self.h = h # Screen height
self.stars = [] # List to manage stars
for _ in range(30):
x = random.randint(0, w)
y = random.randint(0, h)
star = Star(x, y, self.w, self.h)
self.stars.append(star)
def update(self):
""" Update process """
for star in self.stars:
star.update()
def draw(self):
""" Draw process """
for star in self.stars:
star.draw()
2. Using the Background Class
First, create a Background instance in the constructor of the Game class in main.py.
# main.py (inside Game.__init__)
# Background
self.background = sprite.Background(W, H)
Next, call the update process inside update().
# main.py (inside Game.update)
# Background
self.background.update()
Finally, call the draw process inside draw().
# main.py (inside Game.draw)
# Background
self.background.draw()
By grouping related processes into classes like this, you can keep your code clean and easy to read.
Complete Code
Below is the complete code implementing all the features introduced in this article.
# 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 process """
self.x += self.vx
self.y += self.vy
def draw(self):
""" Draw process (implemented in subclasses) """
pass
def move(self, spd, deg):
""" Move sprite """
rad = deg * math.pi / 180
self.vx = spd * math.cos(rad)
self.vy = spd * math.sin(rad)
def flip_x(self):
""" Flip movement in x direction """
self.vx *= -1
def intersects(self, other):
""" AABB collision detection """
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 process """
pyxel.blt(self.x, self.y, 0, 0, 0, self.w, self.h, 0)
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 process """
pyxel.blt(
self.x,
self.y,
0,
self.w * self.index,
0,
self.w,
self.h,
0
)
class BulletSprite(BaseSprite):
def __init__(self, x, y):
""" Constructor """
super().__init__(x, y)
self.x += self.w / 2 - 1 # Center adjustment
def draw(self):
""" Draw process """
pyxel.rect(self.x, self.y, 2, 2, 12)
class Star:
def __init__(self, x, y, w, h):
""" Constructor """
self.x = x
self.y = y
self.w = w
self.h = h
self.c = random.randint(0, 15)
self.spd = random.randint(1, 3)
def update(self):
""" Update process """
self.y += self.spd
if self.h < self.y:
self.y = 0
def draw(self):
""" Draw process """
pyxel.pset(self.x, self.y, self.c)
class Background:
def __init__(self, w, h):
""" Constructor """
self.w = w # Screen width
self.h = h # Screen height
self.stars = [] # List of stars
for _ in range(30):
x = random.randint(0, w)
y = random.randint(0, h)
star = Star(x, y, self.w, self.h)
self.stars.append(star)
def update(self):
""" Update process """
for star in self.stars:
star.update()
def draw(self):
""" Draw process """
for star in self.stars:
star.draw()
# 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
class Game:
def __init__(self):
""" Constructor """
# Game over flag
self.game_over_flg = False
# Initialize score
self.score = 0
# Background
self.background = sprite.Background(W, H)
# 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 = []
# Initialize Pyxel
pyxel.init(W, H, title="Hello, Pyxel!!")
pyxel.load("shooter.pyxres")
# Sound (shot)
pyxel.sound(0).set(
"c4g3",
tones="p",
volumes="76",
effects="f",
speed=20
)
# Sound (hit)
pyxel.sound(1).set(
"e4d4c4",
tones="p",
volumes="76",
effects="n",
speed=20
)
pyxel.run(self.update, self.draw)
def update(self):
""" Update process """
if self.game_over_flg:
return
# Background
self.background.update()
# Player
self.ship.update()
self.control_ship()
self.overlap_spr(self.ship)
self.check_interval()
# Asteroids
for asteroid in self.asteroids:
asteroid.update()
self.overlap_spr(asteroid)
if asteroid.intersects(self.ship):
self.game_over_flg = True
# Bullets (reverse order)
for bullet in self.bullets[::-1]:
bullet.update()
if bullet.y < 0:
self.bullets.remove(bullet)
continue
for asteroid in self.asteroids[::-1]:
if asteroid.intersects(bullet):
self.score += 1
self.bullets.remove(bullet)
self.asteroids.remove(asteroid)
pyxel.play(1, 1)
return
def draw(self):
""" Draw process """
pyxel.cls(0)
# Background
self.background.draw()
if self.game_over_flg:
msg = "GAME OVER"
pyxel.text(W / 2 - len(msg) * 2, H / 2, msg, 13)
pyxel.text(10, 10, f"SCORE:{self.score:04}", 12)
self.ship.draw()
for asteroid in self.asteroids:
asteroid.draw()
for bullet in self.bullets:
bullet.draw()
def control_ship(self):
""" Player action """
if pyxel.btnp(pyxel.KEY_SPACE):
self.ship.flip_x()
bullet = sprite.BulletSprite(self.ship.x, self.ship.y)
bullet.move(BULLET_SPD, 270)
self.bullets.append(bullet)
pyxel.play(0, 0)
def overlap_spr(self, spr):
""" Wrap 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):
self.asteroid_time += 1
if self.asteroid_time < ASTEROID_INTERVAL:
return
self.asteroid_time = 0
if ASTEROID_LIMIT < len(self.asteroids):
return
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 process """
Game()
if __name__ == "__main__":
main()
Result
The game will look like this when running.
Final Words
Thank you for reading until the end.
I hope this series becomes a trigger for starting game development.
If you liked it, a 👍 would be greatly appreciated!

Top comments (0)