From 3781a62956a6971e8947d583e17ce526c3f15cb5 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Tue, 25 Aug 2026 10:01:15 +0200 Subject: [PATCH] feat(sim): moving platforms and particle effects --- game/sim/movingPlatform.py | 53 ++++++++++++++++++++++++++ game/sim/particles.py | 77 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 game/sim/movingPlatform.py create mode 100644 game/sim/particles.py diff --git a/game/sim/movingPlatform.py b/game/sim/movingPlatform.py new file mode 100644 index 0000000..16836f1 --- /dev/null +++ b/game/sim/movingPlatform.py @@ -0,0 +1,53 @@ +from .geometry import Aabb + + +class MovingPlatform: + def __init__(self, x, y, w, h, toX, toY, speed, oneWay=False): + self.x = float(x) + self.y = float(y) + self.w = float(w) + self.h = float(h) + self.fromX = float(x) + self.fromY = float(y) + self.toX = float(toX) + self.toY = float(toY) + self.speed = float(speed) + self.oneWay = bool(oneWay) + self.progress = 0.0 + self.direction = 1 + self.deltaX = 0.0 + self.deltaY = 0.0 + + @property + def aabb(self): + return Aabb(self.x, self.y, self.w, self.h) + + def spanLength(self): + dx = self.toX - self.fromX + dy = self.toY - self.fromY + return (dx * dx + dy * dy) ** 0.5 + + def update(self): + span = self.spanLength() + prevX = self.x + prevY = self.y + if span == 0.0: + self.deltaX = 0.0 + self.deltaY = 0.0 + return + step = self.speed / span + self.progress += step * self.direction + if self.progress >= 1.0: + self.progress = 1.0 + self.direction = -1 + elif self.progress <= 0.0: + self.progress = 0.0 + self.direction = 1 + self.x = self.fromX + (self.toX - self.fromX) * self.progress + self.y = self.fromY + (self.toY - self.fromY) * self.progress + self.deltaX = self.x - prevX + self.deltaY = self.y - prevY + + @classmethod + def fromSpawn(cls, spawn): + return cls(spawn.x, spawn.y, spawn.w, spawn.h, spawn.toX, spawn.toY, spawn.speed, spawn.oneWay) diff --git a/game/sim/particles.py b/game/sim/particles.py new file mode 100644 index 0000000..6d04d04 --- /dev/null +++ b/game/sim/particles.py @@ -0,0 +1,77 @@ +import math +import random + +particlePresets = { + "jump": {"count": 4, "color": (230, 230, 230), "life": 16, "spread": 1.2, "gravity": 0.1, "up": -0.5}, + "dash": {"count": 6, "color": (120, 200, 255), "life": 14, "spread": 1.6, "gravity": 0.0, "up": 0.0}, + "stomp": {"count": 6, "color": (200, 120, 60), "life": 18, "spread": 2.0, "gravity": 0.2, "up": -1.0}, + "pickup": {"count": 5, "color": (247, 213, 29), "life": 20, "spread": 1.4, "gravity": -0.05, "up": -1.2}, + "hurt": {"count": 8, "color": (220, 40, 40), "life": 22, "spread": 2.2, "gravity": 0.15, "up": -1.5}, + "spark": {"count": 4, "color": (255, 220, 120), "life": 10, "spread": 2.4, "gravity": 0.0, "up": 0.0}, + "alert": {"count": 3, "color": (255, 80, 80), "life": 16, "spread": 0.6, "gravity": -0.1, "up": -1.4}, + "dust": {"count": 3, "color": (200, 190, 160), "life": 12, "spread": 1.0, "gravity": 0.05, "up": -0.2}, +} + + +class Particle: + __slots__ = ("x", "y", "vx", "vy", "life", "maxLife", "color", "gravity", "size") + + def __init__(self, x, y, vx, vy, life, color, gravity, size): + self.x = x + self.y = y + self.vx = vx + self.vy = vy + self.life = life + self.maxLife = life + self.color = color + self.gravity = gravity + self.size = size + + def update(self): + self.vy += self.gravity + self.x += self.vx + self.y += self.vy + self.life -= 1 + + @property + def alive(self): + return self.life > 0 + + @property + def fade(self): + return max(0.0, self.life / self.maxLife) + + +class ParticleSystem: + def __init__(self, rng=None, cap=512): + self.particles = [] + self.rng = rng if rng is not None else random.Random(1234) + self.cap = cap + + def emit(self, preset, x, y, count=None): + config = particlePresets.get(preset, particlePresets["dust"]) + emitCount = count if count is not None else config["count"] + for _ in range(emitCount): + if len(self.particles) >= self.cap: + break + angle = self.rng.uniform(0, math.tau) + magnitude = self.rng.uniform(0.2, config["spread"]) + vx = math.cos(angle) * magnitude + vy = math.sin(angle) * magnitude + config["up"] + life = int(config["life"] * self.rng.uniform(0.7, 1.1)) + size = self.rng.randint(1, 2) + self.particles.append(Particle(x, y, vx, vy, life, config["color"], config["gravity"], size)) + + def update(self): + survivors = [] + for particle in self.particles: + particle.update() + if particle.alive: + survivors.append(particle) + self.particles = survivors + + def clear(self): + self.particles = [] + + def __len__(self): + return len(self.particles)