feat(sim): world aggregation and update loop
This commit is contained in:
1 parent
d86ed92816
commit
dccc3a8b4e
1 file changed
+190
@@ -0,0 +1,190 @@
|
|||||||
|
import random
|
||||||
|
|
||||||
|
from .. import constants
|
||||||
|
from ..constants import tileSize
|
||||||
|
from .level import loadLevel
|
||||||
|
from .player import Player
|
||||||
|
from .enemies import spawnEnemy
|
||||||
|
from .items import Collectible
|
||||||
|
from .movingPlatform import MovingPlatform
|
||||||
|
from .particles import ParticleSystem
|
||||||
|
from .audio import AudioBus
|
||||||
|
from .camera import Camera
|
||||||
|
from . import physics
|
||||||
|
|
||||||
|
|
||||||
|
class World:
|
||||||
|
def __init__(self, levelSource, rng=None):
|
||||||
|
self.level = loadLevel(levelSource)
|
||||||
|
self.rng = rng if rng is not None else random.Random(9001)
|
||||||
|
self.player = Player(*self.level.playerSpawn)
|
||||||
|
self.enemies = [spawnEnemy(spawn) for spawn in self.level.enemies]
|
||||||
|
self.items = [Collectible.fromSpawn(spawn) for spawn in self.level.items]
|
||||||
|
self.platforms = [MovingPlatform.fromSpawn(spawn) for spawn in self.level.platforms]
|
||||||
|
self.projectiles = []
|
||||||
|
self.particles = ParticleSystem(rng=self.rng)
|
||||||
|
self.audio = AudioBus()
|
||||||
|
self.camera = Camera(
|
||||||
|
constants.viewWidth,
|
||||||
|
constants.viewHeight,
|
||||||
|
self.level.pixelWidth(),
|
||||||
|
self.level.pixelHeight(),
|
||||||
|
)
|
||||||
|
self.camera.focusOn(self.player.centerX, self.player.centerY)
|
||||||
|
self.score = 0
|
||||||
|
self.coins = 0
|
||||||
|
self.lives = constants.startingLives
|
||||||
|
self.frame = 0
|
||||||
|
self.gameOver = False
|
||||||
|
self.completed = False
|
||||||
|
self.active = True
|
||||||
|
self.activatedCheckpoints = set()
|
||||||
|
|
||||||
|
def playSound(self, name, x=None):
|
||||||
|
self.audio.play(name, x)
|
||||||
|
|
||||||
|
def spawnBurst(self, preset, x, y, count=None):
|
||||||
|
self.particles.emit(preset, x, y, count)
|
||||||
|
|
||||||
|
def spawnProjectile(self, projectile):
|
||||||
|
self.projectiles.append(projectile)
|
||||||
|
|
||||||
|
def step(self, command):
|
||||||
|
if not self.active:
|
||||||
|
return
|
||||||
|
self.frame += 1
|
||||||
|
self.audio.tick()
|
||||||
|
|
||||||
|
for platform in self.platforms:
|
||||||
|
platform.update()
|
||||||
|
self.carryRider(self.player)
|
||||||
|
|
||||||
|
self.player.update(self, command)
|
||||||
|
|
||||||
|
for enemy in self.enemies:
|
||||||
|
if enemy.alive:
|
||||||
|
enemy.update(self)
|
||||||
|
|
||||||
|
for projectile in self.projectiles:
|
||||||
|
if projectile.alive:
|
||||||
|
projectile.update(self)
|
||||||
|
|
||||||
|
self.resolveEnemyCollisions()
|
||||||
|
self.resolveProjectileCollisions()
|
||||||
|
self.resolveItemCollisions()
|
||||||
|
self.resolveHazards()
|
||||||
|
self.resolveCheckpoints()
|
||||||
|
self.resolveExit()
|
||||||
|
|
||||||
|
self.enemies = [enemy for enemy in self.enemies if enemy.alive]
|
||||||
|
self.projectiles = [proj for proj in self.projectiles if proj.alive]
|
||||||
|
|
||||||
|
for item in self.items:
|
||||||
|
item.update()
|
||||||
|
self.particles.update()
|
||||||
|
|
||||||
|
self.handlePlayerDeath()
|
||||||
|
self.camera.update(self.player.centerX, self.player.centerY)
|
||||||
|
|
||||||
|
def carryRider(self, rider):
|
||||||
|
platform = rider.groundPlatform
|
||||||
|
if platform is not None and platform in self.platforms:
|
||||||
|
rider.x += platform.deltaX
|
||||||
|
rider.y += platform.deltaY
|
||||||
|
|
||||||
|
def resolveEnemyCollisions(self):
|
||||||
|
if not self.player.alive:
|
||||||
|
return
|
||||||
|
playerBox = self.player.hurtBox()
|
||||||
|
for enemy in self.enemies:
|
||||||
|
if not enemy.alive:
|
||||||
|
continue
|
||||||
|
if not playerBox.intersects(enemy.hurtBox()):
|
||||||
|
continue
|
||||||
|
fallingOnto = (
|
||||||
|
enemy.stompable
|
||||||
|
and self.player.vy > 0.0
|
||||||
|
and (self.player.bottom - enemy.top) < enemy.h * 0.7
|
||||||
|
and self.player.centerY < enemy.centerY
|
||||||
|
)
|
||||||
|
if fallingOnto:
|
||||||
|
enemy.onStomped(self)
|
||||||
|
self.player.vy = constants.stompBounce
|
||||||
|
self.player.airJumps = constants.maxAirJumps
|
||||||
|
self.player.score += 100
|
||||||
|
self.score += 100
|
||||||
|
self.playSound("stomp", enemy.centerX)
|
||||||
|
self.camera.addShake(6, 1.5)
|
||||||
|
elif not self.player.isInvulnerable():
|
||||||
|
self.player.takeDamage(enemy.contactDamage, enemy.centerX, self)
|
||||||
|
self.camera.addShake(10, 2.5)
|
||||||
|
|
||||||
|
def resolveProjectileCollisions(self):
|
||||||
|
if not self.player.alive:
|
||||||
|
return
|
||||||
|
playerBox = self.player.hurtBox()
|
||||||
|
for projectile in self.projectiles:
|
||||||
|
if not projectile.alive or projectile.owner != "enemy":
|
||||||
|
continue
|
||||||
|
if playerBox.intersects(projectile.aabb):
|
||||||
|
projectile.alive = False
|
||||||
|
if not self.player.isInvulnerable():
|
||||||
|
self.player.takeDamage(projectile.damage, projectile.centerX, self)
|
||||||
|
self.camera.addShake(8, 2.0)
|
||||||
|
|
||||||
|
def resolveItemCollisions(self):
|
||||||
|
if not self.player.alive:
|
||||||
|
return
|
||||||
|
playerBox = self.player.aabb
|
||||||
|
for item in self.items:
|
||||||
|
if item.collected:
|
||||||
|
continue
|
||||||
|
if playerBox.intersects(item.aabb):
|
||||||
|
item.collect(self.player, self)
|
||||||
|
|
||||||
|
def resolveHazards(self):
|
||||||
|
if not self.player.alive:
|
||||||
|
return
|
||||||
|
damage = physics.hazardOverlap(self.player, self.level)
|
||||||
|
if damage > 0 and not self.player.isInvulnerable():
|
||||||
|
self.player.takeDamage(damage, self.player.centerX, self)
|
||||||
|
self.camera.addShake(10, 2.5)
|
||||||
|
|
||||||
|
def resolveCheckpoints(self):
|
||||||
|
for index, point in enumerate(self.level.checkpoints):
|
||||||
|
if index in self.activatedCheckpoints:
|
||||||
|
continue
|
||||||
|
checkpointBox = self.level.rectFor(int(point[0] // tileSize), int(point[1] // tileSize))
|
||||||
|
if self.player.aabb.intersects(checkpointBox):
|
||||||
|
self.activatedCheckpoints.add(index)
|
||||||
|
self.player.setCheckpoint(point[0], point[1])
|
||||||
|
self.playSound("checkpoint", point[0])
|
||||||
|
self.spawnBurst("pickup", point[0] + tileSize * 0.5, point[1], 6)
|
||||||
|
|
||||||
|
def resolveExit(self):
|
||||||
|
if self.level.exit is None or self.completed:
|
||||||
|
return
|
||||||
|
exitCol = int(self.level.exit[0] // tileSize)
|
||||||
|
exitRow = int(self.level.exit[1] // tileSize)
|
||||||
|
exitBox = self.level.rectFor(exitCol, exitRow)
|
||||||
|
if self.player.alive and self.player.aabb.intersects(exitBox):
|
||||||
|
self.completed = True
|
||||||
|
self.playSound("levelComplete", self.level.exit[0])
|
||||||
|
|
||||||
|
def handlePlayerDeath(self):
|
||||||
|
fellOut = self.player.top > self.level.pixelHeight() + tileSize * 2
|
||||||
|
if fellOut and self.player.alive:
|
||||||
|
self.player.alive = False
|
||||||
|
self.playSound("die", self.player.centerX)
|
||||||
|
if self.player.alive:
|
||||||
|
return
|
||||||
|
self.lives -= 1
|
||||||
|
if self.lives <= 0:
|
||||||
|
self.lives = 0
|
||||||
|
self.gameOver = True
|
||||||
|
self.active = False
|
||||||
|
return
|
||||||
|
self.player.hp = self.player.maxHp
|
||||||
|
self.player.respawn()
|
||||||
|
self.projectiles = []
|
||||||
|
self.camera.focusOn(self.player.centerX, self.player.centerY)
|
||||||
Reference in new issue
Block a user