Debug view improvements, warp animation
* Add a visible frame one tile beyond the normal screen in debug mode. * Fix the display of motion paths for mounted traps in debug mode. * Add an animation for the warp trap.
This commit is contained in:
@@ -213,10 +213,17 @@ class Trap:
|
||||
def sensor_rect(self):
|
||||
return self.current_rect()
|
||||
|
||||
# Translate a level-space rect into render space. Everything a trap draws
|
||||
# goes through this so the debug view's overscan margin shifts it correctly;
|
||||
# it's a no-op (offset (0, 0)) in normal play.
|
||||
def _rt(self, rect):
|
||||
ox, oy = self.level.render_offset
|
||||
return rect.move(ox, oy)
|
||||
|
||||
# Debug helper: tint a rect so a normally-hidden trap is visible when the
|
||||
# level's `debug` flag is on.
|
||||
def _debug_tint(self, surface, rgb, rect=None, alpha=80):
|
||||
r = rect if rect is not None else self.base_rect
|
||||
r = self._rt(rect if rect is not None else self.base_rect)
|
||||
overlay = pygame.Surface(r.size, pygame.SRCALPHA)
|
||||
overlay.fill((*rgb, alpha))
|
||||
surface.blit(overlay, r)
|
||||
@@ -224,7 +231,7 @@ class Trap:
|
||||
# Debug helper: a faded sprite + outline showing where something absent
|
||||
# (e.g. a crumbled-away block) belongs.
|
||||
def _debug_ghost(self, surface, assets, sprite_name, rect=None):
|
||||
r = rect if rect is not None else self.base_rect
|
||||
r = self._rt(rect if rect is not None else self.base_rect)
|
||||
img = assets.get(sprite_name, r.w, r.h).copy()
|
||||
img.fill((255, 255, 255, 70), special_flags=pygame.BLEND_RGBA_MULT)
|
||||
surface.blit(img, r)
|
||||
@@ -233,7 +240,8 @@ class Trap:
|
||||
# Debug helper: outline a path through a list of tile cells (top-left px),
|
||||
# connecting their centres. `closed` joins the last cell back to the first.
|
||||
def _debug_path(self, surface, cells, closed=False, color=(214, 200, 96)):
|
||||
rects = [pygame.Rect(x, y, self.tile, self.tile) for (x, y) in cells]
|
||||
ox, oy = self.level.render_offset
|
||||
rects = [pygame.Rect(x + ox, y + oy, self.tile, self.tile) for (x, y) in cells]
|
||||
if len(rects) >= 2:
|
||||
pygame.draw.lines(surface, color, closed, [r.center for r in rects], 1)
|
||||
for r in rects:
|
||||
@@ -376,7 +384,7 @@ class Spike(Trap):
|
||||
if self.active:
|
||||
hr = self._hazard_rect()
|
||||
angle = self._ANGLE.get(self.direction, 0)
|
||||
surface.blit(assets.get("spike", hr.w, hr.h, angle), hr)
|
||||
surface.blit(assets.get("spike", hr.w, hr.h, angle), self._rt(hr))
|
||||
elif self.level.debug:
|
||||
# A dormant spike — show where it will strike.
|
||||
self._debug_tint(surface, (230, 80, 80), self._hazard_rect(), 60)
|
||||
@@ -605,7 +613,12 @@ class Block(Trap):
|
||||
|
||||
def draw(self, surface, assets):
|
||||
if self.level.debug and len(self.points) > 1:
|
||||
self._debug_path(surface, self.points, closed=(self.mode == "loop"))
|
||||
# `points` live in our own frame — local (relative to the parent) for
|
||||
# a mount — so shift them by the parent origin to draw where the block
|
||||
# actually travels.
|
||||
ox, oy = self._origin
|
||||
cells = [(px + ox, py + oy) for (px, py) in self.points]
|
||||
self._debug_path(surface, cells, closed=(self.mode == "loop"))
|
||||
# crumbled away: hidden (ghost in debug), unless re-forming into the player
|
||||
if self.crumble and self.cstate == "gone" and not self.emerge_kill:
|
||||
if self.level.debug:
|
||||
@@ -614,7 +627,7 @@ class Block(Trap):
|
||||
rect = self._rect()
|
||||
if self.crumble and self.cstate == "crumbling":
|
||||
rect = rect.move(int(self.shake), 0)
|
||||
surface.blit(assets.get(self.sprite, self.tile, self.tile), rect)
|
||||
surface.blit(assets.get(self.sprite, self.tile, self.tile), self._rt(rect))
|
||||
if self.fake and self.level.debug:
|
||||
self._debug_tint(surface, (255, 40, 40), self._rect(), 90)
|
||||
|
||||
@@ -685,16 +698,29 @@ class ArrowShooter(Trap):
|
||||
return [a.rect for a in self.arrows]
|
||||
|
||||
def draw(self, surface, assets):
|
||||
surface.blit(assets.get("arrow_shooter", self.tile, self.tile), self.base_rect)
|
||||
surface.blit(assets.get("arrow_shooter", self.tile, self.tile),
|
||||
self._rt(self.base_rect))
|
||||
for a in self.arrows:
|
||||
surface.blit(assets.get("arrow", a.rect.w, a.rect.h), a.rect)
|
||||
surface.blit(assets.get("arrow", a.rect.w, a.rect.h), self._rt(a.rect))
|
||||
|
||||
|
||||
# --- warp: invisible teleporter ---------------------------------------------
|
||||
class Warp(Trap):
|
||||
"""An invisible tile that teleports the player to ``to: [col, row]`` on
|
||||
contact. The level's ``debug`` flag tints it (and draws a line to its
|
||||
contact. On activation a short aura flashes at both the source and the
|
||||
destination and fades away, so the (otherwise invisible) teleport reads on
|
||||
screen. The level's ``debug`` flag tints it (and draws a line to its
|
||||
destination) while designing."""
|
||||
_PULSE_TIME = 0.35 # seconds the activation aura takes to fade out
|
||||
|
||||
# Concentric rings of the aura: (radius x tile, alpha x, colour). Drawn
|
||||
# largest-first so the bright core sits on top.
|
||||
_AURA = (
|
||||
(1.15, 0.30, (188, 116, 246)),
|
||||
(0.80, 0.55, (222, 158, 252)),
|
||||
(0.45, 0.95, (245, 224, 255)),
|
||||
)
|
||||
|
||||
def __init__(self, spec, level):
|
||||
super().__init__(spec, level)
|
||||
self.invisible = True
|
||||
@@ -704,8 +730,11 @@ class Warp(Trap):
|
||||
|
||||
def reset(self):
|
||||
self._armed = True # re-arms once the player has left the tile
|
||||
self._pulse = 0.0 # 1.0 at activation, fades to 0 over _PULSE_TIME
|
||||
|
||||
def update(self, dt, game):
|
||||
if self._pulse > 0.0:
|
||||
self._pulse = max(0.0, self._pulse - dt / self._PULSE_TIME)
|
||||
inside = game.player.rect.colliderect(self.base_rect)
|
||||
if inside and self._armed:
|
||||
p = game.player
|
||||
@@ -714,16 +743,47 @@ class Warp(Trap):
|
||||
p.vx = p.vy = 0.0
|
||||
p._sync_rect()
|
||||
self._armed = False
|
||||
self._pulse = 1.0 # flash at both ends this frame, then fade
|
||||
elif not inside:
|
||||
self._armed = True
|
||||
|
||||
def render(self, surface, assets):
|
||||
# Warps are invisible, so the base render() would skip draw() in normal
|
||||
# play — but the activation aura should show then too. Draw whenever a
|
||||
# pulse is live (or in debug, for the design tint/line).
|
||||
if self.level.debug or self._pulse > 0.0:
|
||||
self.draw(surface, assets)
|
||||
for c in self.mounts:
|
||||
c.render(surface, assets)
|
||||
|
||||
def _dest_rect(self):
|
||||
return pygame.Rect(self.dest[0] * self.tile, self.dest[1] * self.tile,
|
||||
self.tile, self.tile)
|
||||
|
||||
def _draw_aura(self, surface, rect):
|
||||
# An expanding, fading glow centred on the cell. As the pulse decays the
|
||||
# rings grow a little and thin out, so it reads as a quick flash-and-fade.
|
||||
p = self._pulse
|
||||
grow = 0.55 + (1.0 - p) * 0.85
|
||||
size = self.tile * 3
|
||||
aura = pygame.Surface((size, size), pygame.SRCALPHA)
|
||||
c = (size // 2, size // 2)
|
||||
for rmul, amul, col in self._AURA:
|
||||
radius = int(self.tile * rmul * grow)
|
||||
alpha = int(255 * amul * p)
|
||||
if radius >= 1 and alpha > 0:
|
||||
pygame.draw.circle(aura, (*col, alpha), c, radius)
|
||||
surface.blit(aura, aura.get_rect(center=self._rt(rect).center))
|
||||
|
||||
def draw(self, surface, assets):
|
||||
if self._pulse > 0.0:
|
||||
self._draw_aura(surface, self.base_rect)
|
||||
self._draw_aura(surface, self._dest_rect())
|
||||
if self.level.debug:
|
||||
self._debug_tint(surface, (210, 80, 235), alpha=90)
|
||||
dest = pygame.Rect(self.dest[0] * self.tile, self.dest[1] * self.tile,
|
||||
self.tile, self.tile)
|
||||
dest = self._dest_rect()
|
||||
pygame.draw.line(surface, (210, 80, 235),
|
||||
self.base_rect.center, dest.center, 1)
|
||||
self._rt(self.base_rect).center, self._rt(dest).center, 1)
|
||||
self._debug_tint(surface, (210, 80, 235), dest, 45)
|
||||
|
||||
|
||||
@@ -827,7 +887,7 @@ class PhaseBlock(Trap):
|
||||
img = assets.get("phase_block", self.tile, self.tile).copy()
|
||||
img.fill((255, 255, 255, int(255 * self.alpha)),
|
||||
special_flags=pygame.BLEND_RGBA_MULT)
|
||||
surface.blit(img, self.base_rect)
|
||||
surface.blit(img, self._rt(self.base_rect))
|
||||
|
||||
|
||||
# --- registry + factory ------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user