Tuer-Oeffnungs-Sequenz, Slope-Landungs-Fix, Kamera-Logik, Sprite-Feinschliff
Stefans vierte Feedback-Runde:
- Sprites sinken 4px in den Boden ein (Fuesse standen sichtbar UEBER den
Blaettern; Original laesst sie einsinken).
- Echte Steh-Frames: Tony 16/17, Smacks-Walkzyklus ohne die Steh-Frames
12-15 ("letzter Lauf-Frame blieb beim Anhalten stehen").
- Tuer: DOORS[0] ist die OFFENE Tuer -> Ruhe = Tile-Tuer + Kopf-Schild
(DOORS[5]); ALT oeffnet die Tuer sichtbar und nach 1s folgt die Aktion
(Charakterwechsel-Menue) -- Muster fuer alle Tueren inkl. der
Schluessel-Tueren (DOORS 1-4).
- Slope-Landung: Swept-Kollision ueber alle ueberstrichenen Zeilen +
Herkunftsbedingung -- kein Durchfallen mehr bei schnellen Landungen
(Stress-Test: 50 Drops ueber die Hangbreite, 0 Durchfaller).
- Kamera: Figur steht bei ~75% Bildhoehe; DUCKEN gehalten (>=1.2s) faehrt
die Kamera sanft ~40px nach unten (Wasser unter Abgruenden sichtbar).
Verifiziert: Tunneling-Stresstest 0/50, Idle-Frames korrekt, Standbild
(Fuesse in Blaettern, Schild auf zu-Tuer, Kamerahoehe), --once exit 0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1567,3 +1567,25 @@ HUD-Render deckungsgleich mit Stefans Original-Screenshot. --once/E2E ok.
|
||||
mit Stefans Original-Screenshot (Kopf-Schild + Punkte).
|
||||
- Nebenfund: W1ANI/W2ANI/W3ANI.ICO = Tile-ANIMATIONSPHASEN (broeckelnde
|
||||
Bloecke etc.) -- fuer spaeter (animierte Tiles).
|
||||
|
||||
## 25.07.2026 (8) -- vierte Feedback-Runde: Tuer-Sequenz, Kamera, Feinschliff
|
||||
|
||||
1. **Fuesse schwebten**: Sprites sinken beim Zeichnen 4px ein (SPRITE_SINK)
|
||||
-- im Original stehen die Figuren IN den Blaettern, nicht drauf.
|
||||
2. **Steh-Frames**: Tony idle=16/17 (0/1 sind Schrittphasen -- "letzter
|
||||
Lauf-Frame blieb stehen"); Smacks-Walk auf [0..11] gekuerzt (12-15 sind
|
||||
Steh-/Uebergangsframes, Idle 12/13 jetzt nicht mehr im Zyklus).
|
||||
3. **Tuer-Sequenz**: DOORS[0] ist die OFFENE Tuer (Stefans Korrektur).
|
||||
Ruhezustand = geschlossene Tile-Tuer + Kopf-Schild (DOORS[5]); ALT ->
|
||||
Tuer zeigt Frame 0 (offen), nach 1s folgt die Tuer-Aktion (Charakter-
|
||||
Menue). Gilt kuenftig fuer alle Tueren (Schluessel-Tueren DOORS 1-4).
|
||||
4. **Slope-Tunneling** beim Landen gefixt: SWEPT-Check ueber alle zwischen
|
||||
alter und neuer Position ueberstrichenen Zeilen (bei 8px/Frame Fall
|
||||
wurde die duenne Graskante sonst uebersprungen) + Herkunftsbedingung
|
||||
(alte Position muss ueber der Kante gewesen sein). Stress-Test: 50
|
||||
Drops ueber die ganze Hangbreite, 0 durchgefallen.
|
||||
5. **Kamera**: Standard so, dass die Figur bei ~75% Hoehe steht (mehr Blick
|
||||
nach unten); DUCKEN >=1.2s gehalten faehrt die Kamera sanft ~40px runter
|
||||
(Wasser unter Abgruenden sichtbar), Loslassen faehrt zurueck.
|
||||
|
||||
Tony-auf-Wasser+Frosties-Idle von Stefan als "sehr geil" abgenommen (kein Fix).
|
||||
|
||||
+63
-23
@@ -47,6 +47,8 @@ IDLE_ANIM_AFTER = 5.0 # Sekunden Stillstand bis zur Idle-Animation
|
||||
IDLE_ANIM_FPS = 2.5
|
||||
WATER_DAMAGE_EVERY = 5.0 # im Wasser: alle 5s ein Herz (Stefans Angabe)
|
||||
SPAWN_ANIM_SECONDS = 1.1 # Start-Sprite beim Levelstart/Charakterwechsel
|
||||
SPRITE_SINK = 4 # Fuesse sinken optisch in die Blaetter/Gras ein
|
||||
# (Stefans "Figuren schweben ein paar Pixel")
|
||||
WATER_WADE_DEPTH = 14 # Nicht-Schwimmer sinken nur bis hierhin ein
|
||||
# (Original: Tony "springt auf der Wasser-
|
||||
# oberflaeche", taucht NICHT unter)
|
||||
@@ -61,7 +63,9 @@ CHAR_DEFS = {
|
||||
bob='TONY', can_run=True, can_swim=False,
|
||||
walk_r=[0, 2, 4, 6, 8, 10, 12, 14], walk_l=[1, 3, 5, 7, 9, 11, 13, 15],
|
||||
run_r=[26, 28, 30, 32], run_l=[27, 29, 31, 33],
|
||||
idle_r=0, idle_l=1, jump_r=18, jump_l=19, duck_r=24, duck_l=25,
|
||||
# 16/17 = echte Steh-Frames (0/1 sind Schrittphasen -- "letzter
|
||||
# Lauf-Frame bleibt stehen"-Bug)
|
||||
idle_r=16, idle_l=17, jump_r=18, jump_l=19, duck_r=24, duck_l=25,
|
||||
swim_r=None, swim_l=None,
|
||||
idle_anim_r=[44, 46, 48], idle_anim_l=[45, 47, 49],
|
||||
spawn_frame=22, # Arm nach oben gestreckt
|
||||
@@ -69,7 +73,8 @@ CHAR_DEFS = {
|
||||
),
|
||||
'SMACKS': dict(
|
||||
bob='SMACKS', can_run=False, can_swim=True,
|
||||
walk_r=[0, 2, 4, 6, 8, 10, 12, 14], walk_l=[1, 3, 5, 7, 9, 11, 13, 15],
|
||||
# Zyklus OHNE 12-15 (das sind Steh-/Uebergangsframes)
|
||||
walk_r=[0, 2, 4, 6, 8, 10], walk_l=[1, 3, 5, 7, 9, 11],
|
||||
run_r=None, run_l=None,
|
||||
# Idle 12/13: Smacks' echte Steh-Frames (0/1 sind Schrittphasen --
|
||||
# Stefans Bug "beim Anhalten wird noch das Laufen-Sprite angezeigt")
|
||||
@@ -353,12 +358,18 @@ class Player:
|
||||
# Boden ist (bergauf laufen), darf mit Toleranz nach OBEN aufsteigen.
|
||||
if self.vy >= 0:
|
||||
was_grounded = self.on_ground or self._grounded_before
|
||||
old_ty = int(self.y // TILE)
|
||||
foot_ty = int(ny // TILE)
|
||||
tys = (foot_ty - 1, foot_ty, foot_ty + 1) if was_grounded else (foot_ty, foot_ty + 1)
|
||||
# SWEPT: alle zwischen alter und neuer Position ueberstrichenen
|
||||
# Zeilen pruefen -- bei schnellem Fall (bis 8px/Frame) konnte die
|
||||
# duenne Graskante sonst uebersprungen werden (Stefans "falle
|
||||
# durch den Slope-Boden wenn ich ungluecklich lande").
|
||||
start = min(old_ty, foot_ty) - (1 if was_grounded else 0)
|
||||
tol = 8 if was_grounded else 0
|
||||
for ty2 in tys:
|
||||
for ty2 in range(start, foot_ty + 2):
|
||||
surf = level.slope_surface(self.x, ty2)
|
||||
if surf is not None and ny >= surf - tol:
|
||||
if (surf is not None and ny >= surf - tol
|
||||
and self.y <= surf + tol + 6):
|
||||
ny = surf - 0.01
|
||||
self.vy = 0.0
|
||||
self.on_ground = True
|
||||
@@ -411,7 +422,7 @@ class Player:
|
||||
i = self.current_frame_index()
|
||||
surf = self.surfs[i] if i < len(self.surfs) else self.surfs[0]
|
||||
screen.blit(surf, (int(self.x - surf.get_width() / 2 - cam_x),
|
||||
int(self.y - surf.get_height() - cam_y)))
|
||||
int(self.y - surf.get_height() - cam_y) + SPRITE_SINK))
|
||||
|
||||
|
||||
class Hud:
|
||||
@@ -545,12 +556,16 @@ def run_level(clock, assets, audio_paths, map_name='W1L1'):
|
||||
player = Player(assets, info['chars'][0], spawn_px)
|
||||
hud = Hud(assets)
|
||||
door = pygame.Rect(info['door_px']) if info.get('door_px') else None
|
||||
# Echte Tuergrafik: DOORS.BOB Frame 0 = Tuer MIT Kopf-Schild (32x48,
|
||||
# exakt Tuergroesse; Frames 1-4 sind die Schluessel-Tueren mit farbigen
|
||||
# Knaeufen -- fuer spaeter).
|
||||
door_frames = parse_bob(assets.container.entries['DOORS.BOB'])
|
||||
door_pal = get_bob_palette(assets.container.entries, 'DOORS')
|
||||
door_sprite = _frames_to_surfaces(door_frames, door_pal)[0]
|
||||
# Tuergrafiken (DOORS.BOB): Frame 0 = OFFENE Tuer (Stefans Korrektur),
|
||||
# Frame 5 = Kopf-Schild einzeln. Ruhezustand = geschlossene Tile-Tuer der
|
||||
# Map + Schild; bei ALT oeffnet sich die Tuer (Frame 0) und nach ~1s
|
||||
# passiert die Tuer-Aktion (hier: Charakterwechsel-Menue). Frames 1-4
|
||||
# sind die Schluessel-Tueren mit farbigen Knaeufen (spaeter).
|
||||
_door_surfs = _frames_to_surfaces(parse_bob(assets.container.entries['DOORS.BOB']),
|
||||
get_bob_palette(assets.container.entries, 'DOORS'))
|
||||
door_open_sprite = _door_surfs[0]
|
||||
door_sign_sprite = _door_surfs[5]
|
||||
door_open_t = None # None = zu; sonst Sekunden seit ALT-Druck
|
||||
|
||||
if pygame.mixer.get_init():
|
||||
path = audio_paths.get(('ONGAME2', None))
|
||||
@@ -567,6 +582,8 @@ def run_level(clock, assets, audio_paths, map_name='W1L1'):
|
||||
map_h_px = level.height * TILE
|
||||
hearts, lives = 6, 3 # 6 Herzen (3x2) -- Original-Screenshot
|
||||
water_t = 0.0
|
||||
duck_hold = 0.0
|
||||
look_down = 0.0 # Kamera-Offset beim gehaltenen Ducken
|
||||
|
||||
while True:
|
||||
dt = min(clock.tick(60) / 1000.0, 1 / 20)
|
||||
@@ -580,20 +597,29 @@ def run_level(clock, assets, audio_paths, map_name='W1L1'):
|
||||
if event.key == pygame.K_ESCAPE:
|
||||
return 'map'
|
||||
# Tuer benutzen: ALT im STEHEN vor der Tuer (nicht beim
|
||||
# Rennen -- Stefans Regel) -> Charakterwechsel-Menue
|
||||
# Rennen -- Stefans Regel) -> Tuer oeffnet sich (Frame 0),
|
||||
# ~1s spaeter kommt die Tuer-Aktion (Charakterwechsel-Menue)
|
||||
if event.key in (pygame.K_LALT, pygame.K_RALT):
|
||||
if (door and door.collidepoint(player.x, player.y - 1)
|
||||
and player.on_ground and abs(player.vx) < 1):
|
||||
choice = run_character_select(clock, assets,
|
||||
info['chars'], player.char)
|
||||
if choice == 'quit':
|
||||
return 'quit'
|
||||
if choice and choice != player.char:
|
||||
player.set_character(choice)
|
||||
and player.on_ground and abs(player.vx) < 1
|
||||
and door_open_t is None):
|
||||
door_open_t = 0.0
|
||||
|
||||
keys = pygame.key.get_pressed()
|
||||
player.update(level, dt, keys)
|
||||
|
||||
# Tuer-Sequenz: offen zeigen, nach 1s die Aktion ausfuehren
|
||||
if door_open_t is not None:
|
||||
door_open_t += dt
|
||||
if door_open_t >= 1.0:
|
||||
door_open_t = None
|
||||
choice = run_character_select(clock, assets,
|
||||
info['chars'], player.char)
|
||||
if choice == 'quit':
|
||||
return 'quit'
|
||||
if choice and choice != player.char:
|
||||
player.set_character(choice)
|
||||
|
||||
# Wasser-Schaden (alle 5s ein Herz), Schwimmer sind sicher
|
||||
if level.in_water(player.y) and not player.swimming:
|
||||
water_t += dt
|
||||
@@ -610,13 +636,27 @@ def run_level(clock, assets, audio_paths, map_name='W1L1'):
|
||||
else:
|
||||
water_t = 0.0
|
||||
|
||||
# Kamera: Standard so, dass die Figur bei ~75%% der Hoehe steht
|
||||
# (Stefans Referenz-Screenshot); haelt man DUCKEN gedrueckt, faehrt
|
||||
# die Kamera nach kurzer Zeit sanft ~40px nach unten (Wasser unterm
|
||||
# Abgrund sichtbar -- Original-Verhalten).
|
||||
if player.ducking and player.on_ground:
|
||||
duck_hold += dt
|
||||
else:
|
||||
duck_hold = 0.0
|
||||
target_down = 40.0 if duck_hold >= 1.2 else 0.0
|
||||
look_down += (target_down - look_down) * min(1.0, dt * 3.0)
|
||||
cam_x = max(0, min(int(player.x) - VIEW_W // 2, map_w_px - VIEW_W))
|
||||
cam_y = max(0, min(int(player.y) - VIEW_H * 2 // 3, map_h_px - VIEW_H))
|
||||
cam_y = max(0, min(int(player.y) - int(VIEW_H * 0.75) + int(look_down),
|
||||
map_h_px - VIEW_H))
|
||||
|
||||
canvas.blit(level.surface, (0, 0), (cam_x, cam_y, VIEW_W, VIEW_H))
|
||||
# Tuer mit Kopf-Schild (DOORS.BOB Frame 0) ueber der Tile-Tuer
|
||||
# Tuer: zu = Tile-Tuer + Kopf-Schild; offen (nach ALT) = Frame 0
|
||||
if door:
|
||||
canvas.blit(door_sprite, (door.x - cam_x, door.y - cam_y))
|
||||
if door_open_t is not None:
|
||||
canvas.blit(door_open_sprite, (door.x - cam_x, door.y - cam_y))
|
||||
else:
|
||||
canvas.blit(door_sign_sprite, (door.x - cam_x, door.y - cam_y))
|
||||
player.draw(canvas, cam_x, cam_y)
|
||||
# Wasser: dunkles Overlay unterhalb der Wasserlinie (im Original ein
|
||||
# VGA-Palette-Effekt, nur bei machine=vgaonly sichtbar -- Stefans Fund)
|
||||
|
||||
Reference in New Issue
Block a user