Dojo Defender: Sessie 5
Vandaag geef je Dojo Defender een diepte-effect met een parallax sterrenachtergrond en explosie-deeltjes. Asteroïden worden ook vernietigbaar: grote asteroïden splitsen in kleinere.
Wat je vandaag leert
- Parallax rekenen: meerdere lagen sterren met eigen snelheid
- Deeltjeslijsten: een lijst van dictionaries voor particles
- Positie-gebaseerde effecten: particles spawnen op de juiste plek
- Recursief splitsen: grote asteroïde → 2 medium → 2 klein → weg
Stap 0: Download de starter
De starter is de oplossing van sessie 4 — Dojo Defender met menu, drie vijandtypes en waves.
- Download de starter voor vandaag, pak uit en open
main.py. - Was je er niet bij? Download de oplossing van sessie 4.
- Klik op Run. Het spel werkt zoals voorheen: schip, asteroïden, kogels, drones, zigzags, chargers.
Stap 1: 🌟 Parallax sterrenachtergrond
Een parallax achtergrond heeft meerdere lagen die met verschillende snelheden bewegen. Ver weg = langzaam, dichtbij = snel. Dat geeft diepte.
Voeg een lijst stars = [] toe bovenaan het bestand, bij de andere globale variabelen:
stars = []Maak een init_stars() functie die drie lagen sterren aanmaakt:
def init_stars():
global stars
stars = []
layers = [
(40, 0.3, 2, 120), # ver weg: 40 sterren, speed 0.3, grootte 2, donkerder
(40, 0.7, 3, 180), # middel: 40 sterren, speed 0.7, grootte 3
(30, 1.5, 4, 255), # dichtbij: 30 sterren, speed 1.5, grootte 4, fel
]
for count, speed, size, brightness in layers:
for _ in range(count):
stars.append({
'x': random.randint(0, WIDTH),
'y': random.randint(0, HEIGHT),
'speed': speed,
'size': size,
'brightness': brightness,
})Wiskunde erachter: elke ster krijgt een y-snelheid. Verre sterren (0.3 px/frame) bewegen bijna niet, nabije sterren (1.5 px/frame) bewegen sneller. Je brein interpreteert dat als diepte, precies zoals in een auto: bomen naast de weg vliegen voorbij, bergen ver weg blijven stil.
Voeg ook de update-functie toe. Die laat sterren zakken en zet ze terug naar boven als ze onder het scherm zijn:
def update_stars():
for star in stars:
star['y'] += star['speed']
if star['y'] > HEIGHT + star['size']:
star['y'] = -star['size']
star['x'] = random.randint(0, WIDTH)En een teken-functie. Kleine sterren teken je met pygame.draw.rect (2×2), grotere met pygame.draw.circle:
def draw_stars(surface):
for star in stars:
c = (star['brightness'], star['brightness'], star['brightness'])
if star['size'] <= 2:
pygame.draw.rect(surface, c, (star['x'], star['y'], star['size'], star['size']))
else:
pygame.draw.circle(surface, c, (int(star['x']), int(star['y'])), star['size'] // 2)Aanroepen: Roep init_stars() één keer aan (bij het opstarten, ná alle functies), update_stars() elke frame (zelfs in het menu!), en draw_stars(screen) na screen.fill() in ALLE states.
Check
Zie je drie lagen sterren naar beneden scrollen, ook in het menu? De verre sterren zijn kleiner en bewegen langzamer.
Stap 2: ✅ Basic — Particle systeem
Een particle is een klein deeltje dat beweegt, vervaagt en verdwijnt. We gebruiken een lijst van dictionaries:
particles = [] # {x, y, vx, vy, life, max_life, color, size}Maak een functie om particles te spawnen:
def spawn_particles(x, y, count, colors, speed_range, size_range, life_range):
for _ in range(count):
angle = random.uniform(0, 2 * math.pi)
speed = random.uniform(speed_range[0], speed_range[1])
particles.append({
'x': x,
'y': y,
'vx': math.cos(angle) * speed,
'vy': math.sin(angle) * speed,
'life': random.randint(life_range[0], life_range[1]),
'max_life': life_range[1],
'color': random.choice(colors),
'size': random.uniform(size_range[0], size_range[1]),
})Wat gebeurt er? Elke particle krijgt:
- Een positie (
x,y) - Een snelheid in x en y (
vx,vy), berekend uit een hoek en snelheid - Een levensduur (
lifewordt elke frame met 1 verlaagd) - Een kleur en grootte
Update-functie (elke frame elke particle 1 stap verplaatsen en de levensduur verkorten):
def update_particles():
for p in particles[:]:
p['x'] += p['vx']
p['y'] += p['vy']
p['life'] -= 1
if p['life'] <= 0:
particles.remove(p)Waarom [:]? We passen de lijst aan tijdens het loopen (we verwijderen particles). Zonder [:] krijg je een runtime error. De slice [:] maakt een kopie van de lijst om veilig over te loopen.
Teken-functie met vervaging (alpha-fade): hoe minder levensduur, hoe kleiner en donkerder:
def draw_particles(surface):
for p in particles:
ratio = p['life'] / p['max_life']
color = tuple(int(c * ratio) for c in p['color'])
pygame.draw.circle(surface, color, (int(p['x']), int(p['y'])), int(p['size'] * ratio))De ratio is een getal tussen 1 (net geboren) en 0 (bijna dood). We vermenigvuldigen de kleur en grootte ermee, zodat de particle langzaam verdwijnt.
Particle-lijst wissen in reset
Voeg particles.clear() toe aan de reset_game() functie, anders blijven dode particles na een restart zweven.
Roep update_particles() aan in de PLAYING state en draw_particles(screen) na draw_stars(screen) in alle states.
Check
Start het spel en druk op SPACE. Er gebeurt nog niets zichtbaars — maar het systeem staat klaar voor de volgende stappen.
Stap 3: ⭐ Stretch — Positie-gebaseerde effecten
Nu gaan we particles spawnen op de juiste momenten en posities.
Uitlaatgassen
Als het schip beweegt, spawn kleine rode/oranje particles onder het schip. Voeg dit toe in de if not game_over sectie, waar je de pijltjestoetsen checked:
keys = pygame.key.get_pressed()
moving = False
if keys[pygame.K_LEFT] and ship.rect.centerx > 30:
ship.rect.centerx -= 5
moving = True
if keys[pygame.K_RIGHT] and ship.rect.centerx < WIDTH - 30:
ship.rect.centerx += 5
moving = True
if moving and ship.visible:
spawn_particles(
ship.rect.centerx, ship.rect.bottom,
1, # of random.randint(1, 2)
[(255, 100, 0), (255, 60, 0), (200, 50, 0)],
(0.5, 1.5), (2, 4), (10, 20),
)De particles spawnen onder het schip (ship.rect.bottom) en krijgen een kleine snelheid omhoog (negatieve vy door de hoek).
Explosie puin
Als een vijand wordt geraakt door een kogel, spawn 8-12 deeltjes in alle richtingen. Voeg dit toe waar je een enemy uit de lijst haalt na een bullet-collision:
spawn_particles(
enemy.rect.centerx, enemy.rect.centery,
random.randint(8, 12),
[(255, 140, 0), (255, 0, 0), (255, 255, 0), (255, 255, 255)],
(1, 4), (2, 5), (20, 40),
)Vijand-sterfsparkels
Als een vijand de onderkant van het scherm bereikt, spawn 3-5 witte vonkjes:
spawn_particles(
enemy.rect.centerx, HEIGHT - 30,
random.randint(3, 5),
[(255, 255, 255), (200, 200, 255)],
(1, 3), (1, 3), (15, 30),
)Check
Beweeg het schip — zie je oranje uitlaatgassen? Schiet een vijand — zie je 8-12 rondvliegende deeltjes? Laat een vijand ontsnappen — zie je witte vonkjes?
Stap 4: 🔥 Expert — Vernietigbare asteroïden
Asteroïden zijn nu recursief splitsbaar:
asteroid_big.png→ wordt geraakt → 2×asteroid_med.pngasteroid_med.png→ wordt geraakt → 2×asteroid_small.pngasteroid_small.png→ wordt geraakt → weg + particles
Pas spawn_asteroid() aan zodat elke asteroïde een asteroid_size krijgt:
def spawn_asteroid(size='big'):
rock = GameObject(f'asteroid_{size}.png', random.randint(30, WIDTH - 30), -30)
rock.speed = random.uniform(3, 5)
rock.asteroid_size = size
asteroids.append(rock)
return rockMaak een split_asteroid() functie die een asteroïde in twee kleinere splitst:
def split_asteroid(rock):
# Eerst: exploderende particles
spawn_particles(
rock.rect.centerx, rock.rect.centery,
random.randint(8, 12),
[(255, 140, 0), (255, 0, 0), (255, 255, 0), (255, 255, 255)],
(1, 4), (2, 5), (20, 40),
)
if rock.asteroid_size == 'big':
for _ in range(2):
r = spawn_asteroid('med')
r.rect.center = rock.rect.center
r.rect.x += random.randint(-20, 20)
r.speed = random.uniform(2, 4)
elif rock.asteroid_size == 'med':
for _ in range(2):
r = spawn_asteroid('small')
r.rect.center = rock.rect.center
r.rect.x += random.randint(-15, 15)
r.speed = random.uniform(1, 3)
asteroids.remove(rock)Wat gebeurt hier?
- We spawnen explosie-particles op de positie van de asteroïde
- We checken de grootte: ‘big’ → 2 ‘med’, ‘med’ → 2 ‘small’, ‘small’ → geen verdere splitsing
- Nieuwe asteroïden spawnen vlakbij de oude positie met een lichte offset
- De kleinere asteroïden zijn langzamer dan de grotere
Pas de collision-logic aan: waar je astersoids.remove(rock) deed bij een kogelraak, roep nu split_asteroid(rock) aan:
for rock in asteroids[:]:
if bullet.rect.colliderect(rock.rect):
bullets.remove(bullet)
explode_sound.play()
split_asteroid(rock)
score += 5
breakBelangrijk: Het geluid (explode_sound.play()) speel je af vóór split_asteroid() zodat het niet per ongeluk twee keer afspeelt.
Check
Schiet een grote asteroïde. Zie je hem splitsen in twee medium asteroïden? Schiet die — ze splitsen in twee kleine. Schiet de kleine — die verdwijnen met een mooie particle-explosie.
Showcase
Laat aan een coach zien dat:
- Parallax sterrenachtergrond met drie snelheden
- Particle systeem met vervaging (alpha-fade)
- Uitlaatgassen onder het bewegende schip
- Explosie-puin bij vernietigde vijanden en asteroïden
- Asteroïden splitsen: big → med → small → weg
Tot de volgende keer!
“Volgende keer: POWER-UPS. Schilden, speed-boosts, spread-shots. En een échte eindbaas.”
Neem mee naar huis
Probeer thuis één van deze uitbreidingen:
- Makkelijk: verander de kleur van de sterren per laag (bijv. verre sterren blauwachtig, nabije geel).
- Middel: voeg een vierde laag sterren toe, nog sneller (speed 3.0), met grote heldere sterren.
- Lastig: laat particle-kleur veranderen tijdens de levensduur (van geel naar rood naar grijs).
- Erg lastig: voeg een “comet” particle type toe dat een klein staartje heeft.