nodes/effects.pyΒΆ

Part of Tanks of Freedom.

  1"""Short-lived visual effects: capture flash, attack flash, explosions.
  2
  3Each effect is a Node2D that lives for a fixed lifetime, then destroys itself.
  4Owners spawn and forget; the world tree garbage-collects.
  5"""
  6
  7from __future__ import annotations
  8
  9from simvx.core import Node2D
 10from simvx.core.animation.sprite import Sprite2D
 11
 12from .data import PLAYER_COLOUR, TILE_H, TILE_W
 13from .textures import make_explosion, make_range_overlay
 14
 15
 16class FlashOverlay(Node2D):
 17    """Brief tinted rectangle pinned to a cell: used for capture / damage feedback."""
 18
 19    def __init__(
 20        self,
 21        *,
 22        cell,
 23        tile_map,
 24        colour=(1.0, 1.0, 0.4, 0.8),
 25        duration: float = 0.5,
 26        w: int = TILE_W,
 27        h: int = TILE_H,
 28        **kwargs,
 29    ):
 30        super().__init__(**kwargs)
 31        wx, wy = tile_map.map_to_world(cell)
 32        self.position = (wx, wy)
 33        self._duration = duration
 34        self._t = 0.0
 35
 36        # A diamond overlay tinted to the requested colour.
 37        rgba = tuple(int(c * 255) for c in (colour[0], colour[1], colour[2]))
 38        rgba = (*rgba, int(colour[3] * 255))
 39        self._sprite = Sprite2D(
 40            texture=make_range_overlay(rgba),
 41            width=w,
 42            height=h,
 43            filter="nearest",
 44        )
 45        self.add_child(self._sprite)
 46
 47    def on_update(self, dt: float) -> None:
 48        self._t += dt
 49        u = min(1.0, self._t / self._duration)
 50        # Fade alpha out
 51        col = self._sprite.colour
 52        self._sprite.colour = (col[0], col[1], col[2], max(0.0, 1.0 - u))
 53        if u >= 1.0:
 54            self.destroy()
 55
 56
 57class ExplosionEffect(Node2D):
 58    """Animated explosion at a cell. ~0.4s long."""
 59
 60    DURATION = 0.4
 61
 62    def __init__(self, *, cell, tile_map, **kwargs):
 63        super().__init__(**kwargs)
 64        wx, wy = tile_map.map_to_world(cell)
 65        self.position = (wx, wy - TILE_H * 0.4)
 66        self._t = 0.0
 67        self._sprite = Sprite2D(
 68            texture=make_explosion(0.0),
 69            width=44,
 70            height=44,
 71            filter="nearest",
 72        )
 73        self.add_child(self._sprite)
 74
 75    def on_update(self, dt: float) -> None:
 76        self._t += dt
 77        u = min(1.0, self._t / self.DURATION)
 78        # Refresh texture each frame for the procedural explosion look.
 79        self._sprite.texture = make_explosion(u)
 80        if u >= 1.0:
 81            self.destroy()
 82
 83
 84def capture_flash(world, cell, owner: int) -> None:
 85    """Spawn a player-tinted capture flash at ``cell`` (briefly highlights the building)."""
 86    col = PLAYER_COLOUR[owner]
 87    flash = FlashOverlay(
 88        cell=cell,
 89        tile_map=world.tile_map,
 90        colour=(col[0], col[1], col[2], 0.85),
 91        duration=0.6,
 92    )
 93    world.tile_map.add_child(flash)
 94
 95
 96def damage_flash(world, cell) -> None:
 97    flash = FlashOverlay(
 98        cell=cell,
 99        tile_map=world.tile_map,
100        colour=(1.0, 0.25, 0.2, 0.85),
101        duration=0.35,
102    )
103    world.tile_map.add_child(flash)
104
105
106def explosion(world, cell) -> None:
107    fx = ExplosionEffect(cell=cell, tile_map=world.tile_map)
108    world.tile_map.add_child(fx)