Level-Feedback: Laufrichtung, Schraegen-Kollision, HUD, Rennen
Stefans annotierter Live-Test-Screenshot, alle 4 Punkte: - Laufrichtung gefixt: TONY.BOB gerade Frames = rechts (war vertauscht, Figur lief rueckwaerts). - Schraegen-Kollision: ctype 16 = Slope-Tile (per ctype-Farbvisualisierung exakt auf dem Grashang der Referenz identifiziert -- keine Tuer). 45-Grad- Diagonale, Richtung per Nachbar-Heuristik, am Fussmittelpunkt geprueft, Snap nur bei fallender Bewegung (kein Herunterziehen im Sprung). Simulation verifiziert: Hang hoch bis Plateau, kein Durchfallen mehr. - HUD: weisse Kellogg's-Leiste (Schriftzug aus KELLOGGS.PCC gecroppt), Herzen als echte GAMEBAR-Sprites (10=voll/11=leer), Uhr-Box/Layout neu verteilt und Startzeit 9:59 (4-stellige Anzeige passt immer in die Zeile). - RENNEN: ALT halten (Original-Anleitung: Tonys Faehigkeit) bzw. LSHIFT, 185 px/s + schnellere Laufanimation. Verifiziert: Slope-Simulation (min_y=559=Plateau, kein Durchfallen), HUD-Standbild (Leiste/Herzen/Uhr komplett), E2E-Lauf rc=0, --once exit 0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
973e290cf6
commit
0bb66b73a4
@@ -1413,3 +1413,28 @@ deckungsgleich mit DOSBox-Referenz (Haus/Baum/?-Block/Bruecke/Saeulen).
|
||||
**Offen:** Items (ctype 4) einsammeln, Gegner, Duck-Frame-Zuordnung pruefen,
|
||||
Original-HUD-Grafik, Karten-Animationen (KARTE0-9), Levelmusik-Song nach
|
||||
Gehoer, Zeitablauf/Leben-Logik, weitere Level freischalten.
|
||||
|
||||
## 25.07.2026 (2) -- Stefans Level-Feedback eingearbeitet
|
||||
|
||||
Stefans annotierter Screenshot (4 Punkte), alle behoben:
|
||||
1. **Figur lief rueckwaerts** -- TONY.BOB: GERADE Frames = RECHTS, ungerade
|
||||
= links (Zuordnung war exakt vertauscht).
|
||||
2. **Schraegen: fiel durch** -- ctype 16 ist KEINE Tuer, sondern das
|
||||
SCHRAEGEN-Tile (als blaue Diagonale in der ctype-Farbvisualisierung exakt
|
||||
auf dem Grashang gefunden). 45-Grad-Slope-Kollision implementiert
|
||||
(Richtung per Nachbar-Heuristik, Fussmittelpunkt-basiert, Snap-Toleranz
|
||||
10px). Simulation: Tony laeuft den Hang bis Plateau-Hoehe (y=559) hoch,
|
||||
faellt nirgends mehr durch.
|
||||
3. **HUD**: weisse Kellogg's-Leiste ergaenzt (Schriftzug aus KELLOGGS.PCC
|
||||
gecroppt + klein skaliert), Herzen jetzt echte GAMEBAR-Sprites (Frame 10
|
||||
voll / 11 leer; 13-15 sind die rot/blau/gruenen Schluessel -- fuer
|
||||
Stefans "Tueren mit farbigem Knauf brauchen Schluessel" spaeter), Uhr-
|
||||
Layout gefixt + Start bei 9:59 (4-stellig passt immer; Original zeigte
|
||||
~1.5s nach Start ohnehin 9:59).
|
||||
4. **RENNEN** -- Tonys Faehigkeit lt. Original-Anleitung ("MIT ALT
|
||||
AKTIVIERST DU DIE FAEHIGKEITEN... TONY: RENNEN"): ALT halten (LSHIFT als
|
||||
moderne Alternative), 185 statt 110 px/s, schnellere Lauf-Animation.
|
||||
|
||||
Physik-Feel laut Stefan gut. Levelmusik-Song prueft er nach Gehoer.
|
||||
Offen bleiben: Tueren oeffnen (ALT an Tuer, Schluessel-Logik), Items,
|
||||
Gegner, Karten-Animationen.
|
||||
|
||||
+89
-27
@@ -41,23 +41,33 @@ VIEW_W, VIEW_H = 320, 176 # sichtbarer Levelausschnitt
|
||||
HUD_H = 24 # HUD-Balken unten (320x24)
|
||||
TILE = 16
|
||||
|
||||
# Physik (v1 -- nach Spielgefuehl gegen die DOSBox-Referenz grob abgestimmt,
|
||||
# Feinschliff nach Stefans Live-Test)
|
||||
# Physik (v1 -- nach Spielgefuehl gegen die DOSBox-Referenz grob abgestimmt;
|
||||
# Stefans Live-Test 25.07.: "fuehlt sich gut an")
|
||||
WALK_SPEED = 110.0 # px/s
|
||||
RUN_SPEED = 185.0 # px/s -- Tonys Faehigkeit: RENNEN (ALT halten,
|
||||
# wie im Original lt. Anleitung; LSHIFT geht auch)
|
||||
GRAVITY = 900.0 # px/s^2
|
||||
JUMP_VELOCITY = -300.0 # px/s (Sprunghoehe ~50px = gut 3 Tiles)
|
||||
WALK_ANIM_FPS = 12.0
|
||||
|
||||
LEVEL_TIME_SECONDS = 10 * 60 # Uhr im Original zaehlt von 10:00 runter
|
||||
# 9:59 statt 10:00: die Original-Anzeige stand ~1.5s nach Levelstart schon auf
|
||||
# 9:59, und 4-stellig ("9:59") passt sicher in die Uhr-Box (320px-Zeile).
|
||||
LEVEL_TIME_SECONDS = 599
|
||||
|
||||
SOLID_BIT = 32
|
||||
SLOPE_CT = 16 # ctype 16 = Schraegen-Tile (Grashaenge) --
|
||||
# als blaue Diagonale in der ctype-Visualisierung
|
||||
# exakt auf dem Hang der DOSBox-Referenz gefunden
|
||||
# ("faellt durch"-Bug von Stefans Live-Test)
|
||||
|
||||
# TONY.BOB-Frame-Zuordnung (siehe Modul-Docstring)
|
||||
IDLE_L, IDLE_R = 0, 1
|
||||
WALK_L = [0, 2, 4, 6, 8, 10, 12, 14]
|
||||
WALK_R = [1, 3, 5, 7, 9, 11, 13, 15]
|
||||
JUMP_L, JUMP_R = 16, 17
|
||||
DUCK_L, DUCK_R = 24, 25
|
||||
# TONY.BOB-Frame-Zuordnung. KORREKTUR (Stefans Live-Test 25.07.2026: "Figur
|
||||
# laeuft rueckwaerts, Richtung stimmt"): GERADE Indizes sind RECHTS,
|
||||
# UNGERADE LINKS -- genau andersrum als zuerst angenommen.
|
||||
IDLE_R, IDLE_L = 0, 1
|
||||
WALK_R = [0, 2, 4, 6, 8, 10, 12, 14]
|
||||
WALK_L = [1, 3, 5, 7, 9, 11, 13, 15]
|
||||
JUMP_R, JUMP_L = 16, 17
|
||||
DUCK_R, DUCK_L = 24, 25
|
||||
|
||||
|
||||
def _frames_to_surfaces(frames, palette):
|
||||
@@ -120,6 +130,26 @@ class Level:
|
||||
def solid(self, tx, ty):
|
||||
return bool(self.ctype(tx, ty) & SOLID_BIT)
|
||||
|
||||
def slope_surface(self, px, ty):
|
||||
'''Boden-y der 45-Grad-Schraege im Slope-Tile (ctype 16) der Zeile ty
|
||||
an Pixel-x px -- oder None, wenn dort kein Slope-Tile liegt.
|
||||
Steigungsrichtung per Nachbar-Heuristik: schliesst rechts-oberhalb ein
|
||||
weiteres Slope-/Solid-Tile an, steigt die Diagonale nach rechts
|
||||
(Flaeche faellt nach links ab), sonst gespiegelt.'''
|
||||
tx = int(px // TILE)
|
||||
if self.ctype(tx, ty) != SLOPE_CT:
|
||||
return None
|
||||
fx = px - tx * TILE # 0..15 im Tile
|
||||
rising_right = (self.ctype(tx + 1, ty - 1) == SLOPE_CT or
|
||||
self.solid(tx + 1, ty - 1) or self.solid(tx + 1, ty))
|
||||
rising_left = (self.ctype(tx - 1, ty - 1) == SLOPE_CT or
|
||||
self.solid(tx - 1, ty - 1) or self.solid(tx - 1, ty))
|
||||
if rising_right and not rising_left:
|
||||
return (ty + 1) * TILE - 1 - fx # links unten -> rechts oben
|
||||
if rising_left and not rising_right:
|
||||
return ty * TILE + fx # links oben -> rechts unten
|
||||
return (ty + 1) * TILE - 1 - fx # Default: nach rechts steigend
|
||||
|
||||
|
||||
class Tony:
|
||||
'''Spielfigur: Position (px, Fusspunkt-basiert), einfache AABB-Physik
|
||||
@@ -154,10 +184,16 @@ class Tony:
|
||||
move -= 1
|
||||
if keys[pygame.K_RIGHT]:
|
||||
move += 1
|
||||
self.vx = move * WALK_SPEED
|
||||
# Tonys Faehigkeit: RENNEN -- ALT halten (wie im Original laut
|
||||
# Anleitung "MIT ALT AKTIVIERST DU DIE FAEHIGKEITEN"), LSHIFT als
|
||||
# moderne Alternative. (Alt+Enter bleibt Vollbild -- kein Konflikt,
|
||||
# das faengt die Eventschleife ab.)
|
||||
mods = pygame.key.get_mods()
|
||||
running = bool(mods & (pygame.KMOD_ALT | pygame.KMOD_SHIFT))
|
||||
self.vx = move * (RUN_SPEED if running else WALK_SPEED)
|
||||
if move:
|
||||
self.facing = move
|
||||
self.anim_t += dt
|
||||
self.anim_t += dt * (1.5 if running else 1.0)
|
||||
else:
|
||||
self.anim_t = 0.0
|
||||
|
||||
@@ -200,6 +236,22 @@ class Tony:
|
||||
if any(level.solid(tx, ty) for tx in range(left_tx, right_tx + 1)):
|
||||
ny = (ty + 1) * TILE + self.HITBOX_H + 0.01
|
||||
self.vy = 0.0
|
||||
|
||||
# Schraegen (ctype 16): Boden folgt der 45-Grad-Diagonale. Am
|
||||
# FUSSMITTELPUNKT geprueft (nicht an den Hitbox-Kanten -- so "schwebt"
|
||||
# Tony nicht an der Hangkante). Zeile darueber mitpruefen, damit er
|
||||
# beim Bergauflaufen ins naechsthoehere Slope-Tile aufsteigt; Snap nur
|
||||
# bei fallender/stehender Bewegung und kleiner Distanz (kein
|
||||
# Herunterziehen mitten im Sprung).
|
||||
if self.vy >= 0:
|
||||
foot_ty = int(ny // TILE)
|
||||
for ty2 in (foot_ty - 1, foot_ty, foot_ty + 1):
|
||||
surf = level.slope_surface(self.x, ty2)
|
||||
if surf is not None and ny >= surf - 10:
|
||||
ny = surf - 0.01
|
||||
self.vy = 0.0
|
||||
self.on_ground = True
|
||||
break
|
||||
self.y = ny
|
||||
|
||||
def current_frame(self):
|
||||
@@ -238,15 +290,24 @@ class Hud:
|
||||
RED = (150, 14, 14)
|
||||
DARK = (84, 6, 6)
|
||||
|
||||
WHITE = (248, 248, 248)
|
||||
|
||||
def __init__(self, assets):
|
||||
from .menu import BitmapFont, _mapping_from_rows
|
||||
container = assets.container
|
||||
frames = parse_bob(container.entries['GAMEBAR.BOB'])
|
||||
palette = get_bob_palette(container.entries, 'GAMEBAR')
|
||||
self.icons = _frames_to_surfaces(frames, palette)
|
||||
self.heart_full = self.icons[10] # 9x8, pink/rot
|
||||
self.heart_empty = self.icons[11]
|
||||
self.font = BitmapFont(assets.pcc_surface('FON_1B'), 9, 7,
|
||||
_mapping_from_rows(['1234567890;:!"%/()=?,.-\x7f#']),
|
||||
spacing=8)
|
||||
# Kellogg's-Schriftzug fuer die weisse HUD-Leiste links (Stefans
|
||||
# Feedback: "die weisse Kellogg's-Leiste fehlt"): aus KELLOGGS.PCC
|
||||
# gecroppt (Schriftzug ohne "praesentiert") und klein skaliert.
|
||||
logo = assets.pcc_surface('KELLOGGS').subsurface((36, 26, 252, 66))
|
||||
self.kellogg = pygame.transform.smoothscale(logo, (50, 13))
|
||||
|
||||
def _box(self, canvas, x, y, w, h):
|
||||
pygame.draw.rect(canvas, self.GOLD, (x - 1, y - 1, w + 2, h + 2))
|
||||
@@ -258,29 +319,30 @@ class Hud:
|
||||
|
||||
def draw(self, canvas, y0, cookies, score, hearts, lives, time_left):
|
||||
pygame.draw.rect(canvas, self.DARK, (0, y0, 320, HUD_H))
|
||||
pad_y = y0 + 7
|
||||
pad_y = y0 + 8
|
||||
# Kellogg's-Leiste (weiss, abgerundet) ganz links
|
||||
pygame.draw.rect(canvas, self.WHITE, (2, y0 + 3, 58, HUD_H - 6),
|
||||
border_radius=6)
|
||||
canvas.blit(self.kellogg, (6, y0 + 5))
|
||||
# Keks-Zaehler
|
||||
self._box(canvas, 30, pad_y - 3, 34, 15)
|
||||
self._text(canvas, 34, pad_y, f'{cookies:03d}')
|
||||
self._box(canvas, 66, pad_y - 3, 32, 14)
|
||||
self._text(canvas, 70, pad_y, f'{cookies:03d}')
|
||||
# Item-Box (leer)
|
||||
self._box(canvas, 92, pad_y - 3, 40, 15)
|
||||
self._box(canvas, 104, pad_y - 3, 38, 14)
|
||||
# Score
|
||||
self._box(canvas, 148, pad_y - 3, 58, 15)
|
||||
self._box(canvas, 148, pad_y - 3, 56, 14)
|
||||
self._text(canvas, 152, pad_y, f'{score:06d}')
|
||||
# Herzen
|
||||
# Herzen (echte GAMEBAR-Sprites)
|
||||
for i in range(4):
|
||||
col = (230, 40, 60) if i < hearts else (70, 10, 20)
|
||||
hx = 222 + i * 11
|
||||
pygame.draw.circle(canvas, col, (hx + 3, pad_y + 3), 3)
|
||||
pygame.draw.circle(canvas, col, (hx + 8, pad_y + 3), 3)
|
||||
pygame.draw.polygon(canvas, col, [(hx, pad_y + 4), (hx + 5, pad_y + 10), (hx + 11, pad_y + 4)])
|
||||
icon = self.heart_full if i < hearts else self.heart_empty
|
||||
canvas.blit(icon, (210 + i * 11, pad_y - 1))
|
||||
# Leben
|
||||
self._box(canvas, 272, pad_y - 3, 13, 15)
|
||||
self._text(canvas, 275, pad_y, str(lives))
|
||||
# Uhr m:ss
|
||||
self._box(canvas, 259, pad_y - 3, 18, 14)
|
||||
self._text(canvas, 264, pad_y, str(lives))
|
||||
# Uhr m:ss (Format 4-stellig, Startzeit 9:59 -> passt immer)
|
||||
m, s = divmod(max(0, int(time_left)), 60)
|
||||
self._box(canvas, 290, pad_y - 3, 29, 15)
|
||||
self._text(canvas, 292, pad_y, f'{m}:{s:02d}')
|
||||
self._box(canvas, 283, pad_y - 3, 35, 14)
|
||||
self._text(canvas, 286, pad_y, f'{m}:{s:02d}')
|
||||
|
||||
|
||||
def run_level(clock, assets, audio_paths, map_name='W1L1', spawn_tile=(17, 40)):
|
||||
|
||||
Reference in New Issue
Block a user