Intro: Endlosschleife raus, Alt+Enter-Vollbild, kelloggs.ini
- Keine Endlosschleife mehr: nach der Titelbild-Animation bleibt das fertige, voll-farbige Titelbild STEHEN und die Titelmusik loopt weiter, bis Leertaste oder ESC das Programm beendet (neue wait_for_key()). run_once() laeuft nur noch einmal; scene_title haelt am Ende auf Tastendruck (hold_forever), --once haelt stattdessen kurz (fuer headless/Tests). - ALT+ENTER schaltet zwischen Fenster- und Vollbildmodus um (SCALED-Display skaliert das 960x720-Logikbild sauber hoch). Zentrale Event-Behandlung in _process_event(); Zeichenroutinen holen die aktuelle Surface per pygame.display.get_surface(), damit das Neuerzeugen des Display-Modes beim Umschalten keinen veralteten Screen-Bezug hinterlaesst (screen-Parameter aus den Scenes entfernt). - Vollbild-Zustand wird in kelloggs.ini gemerkt (load/save_fullscreen_pref) und beim naechsten Start wiederhergestellt; ohne kelloggs.ini = Fenstermodus. kelloggs.ini ist gitignored (pro-Rechner-Laufzeitzustand). Verifiziert (headless): Config load/save, Alt+Enter-Toggle, Space/ESC, wait_for_key(Space)->quit; python -m game --once exit 0 in ~17s. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
7aea2fac26
commit
95e5b1edb4
@@ -12,3 +12,6 @@ game/tfmx_player/*.o
|
|||||||
# Python-Bytecode-Cache
|
# Python-Bytecode-Cache
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|
||||||
|
# Laufzeit-Einstellungen des Remakes (z.B. Vollbild-Zustand) -- pro Rechner
|
||||||
|
kelloggs.ini
|
||||||
|
|||||||
@@ -51,13 +51,23 @@ sudo apt install python3-pygame python3-pil python3-numpy gcc
|
|||||||
## Starten
|
## Starten
|
||||||
|
|
||||||
```
|
```
|
||||||
python3 -m game # Intro in Endlosschleife (wie Original-Titelbildschirm)
|
python3 -m game # Intro; bleibt am Titelbild stehen (Musik loopt)
|
||||||
python3 -m game --once # ein Durchlauf, dann beenden
|
python3 -m game --once # Titelbild nur kurz halten (für Tests/headless)
|
||||||
```
|
```
|
||||||
|
|
||||||
Steuerung im Intro: **Leertaste** überspringt den aktuellen Screen, **ESC**
|
Ablauf: Rauser → Factor5 → Kellogg's → Titelbild-Animation; danach bleibt das
|
||||||
beendet das Intro. Ohne Soundkarte (z.B. headless) läuft das Spiel stumm weiter
|
fertige Titelbild stehen und die Titelmusik läuft in Schleife, bis eine Taste
|
||||||
statt abzustürzen.
|
das Programm beendet.
|
||||||
|
|
||||||
|
Steuerung:
|
||||||
|
- **Leertaste** — überspringt den aktuellen Intro-Screen (auf dem Titelbild
|
||||||
|
beendet sie das Programm)
|
||||||
|
- **ESC** — beendet das Programm
|
||||||
|
- **Alt+Enter** — zwischen Fenster- und Vollbildmodus umschalten; der Zustand
|
||||||
|
wird in `kelloggs.ini` gemerkt und beim nächsten Start wiederhergestellt
|
||||||
|
(Standard ohne `kelloggs.ini`: Fenstermodus)
|
||||||
|
|
||||||
|
Ohne Soundkarte (z.B. headless) läuft das Spiel stumm weiter statt abzustürzen.
|
||||||
|
|
||||||
Liegt `PCKELL.DAT` woanders, kann der Pfad per Umgebungsvariable gesetzt werden:
|
Liegt `PCKELL.DAT` woanders, kann der Pfad per Umgebungsvariable gesetzt werden:
|
||||||
|
|
||||||
|
|||||||
+168
-59
@@ -105,9 +105,14 @@ Ladebildschirm (render_audio_with_loading() -- Rendering laeuft in einem
|
|||||||
Thread, Hauptthread pumpt weiter Events). run_once() greift danach nur noch
|
Thread, Hauptthread pumpt weiter Events). run_once() greift danach nur noch
|
||||||
auf fertige WAV-Pfade zu, nie mehr Live-Rendering im Hauptloop.
|
auf fertige WAV-Pfade zu, nie mehr Live-Rendering im Hauptloop.
|
||||||
|
|
||||||
Aufruf: python3 -m game [--once] (--once: nach einem Durchlauf beenden,
|
Aufruf: python3 -m game [--once]
|
||||||
sonst loopt die Sequenz weiter wie im Original-Titelbildschirm)
|
Ablauf: Rauser -> Factor5 -> Kellogg's -> Titelbild-Animation; danach bleibt das
|
||||||
Steuerung: LEERTASTE ueberspringt den aktuellen Screen, ESC beendet das Intro.
|
fertige Titelbild STEHEN (Titelmusik loopt) bis Leertaste/ESC -> Programm-Ende.
|
||||||
|
Keine Endlosschleife der ganzen Sequenz. (--once: Titelbild nur kurz halten,
|
||||||
|
fuer Tests/headless.)
|
||||||
|
Steuerung: LEERTASTE ueberspringt den aktuellen Screen (auf dem Titelbild
|
||||||
|
beendet sie das Programm), ESC beendet immer, ALT+ENTER schaltet Fenster/Vollbild
|
||||||
|
um (Zustand wird in kelloggs.ini gemerkt).
|
||||||
|
|
||||||
Stand 24.07.2026 (gegen echte DOSBox-Referenzaufnahmen abgeglichen):
|
Stand 24.07.2026 (gegen echte DOSBox-Referenzaufnahmen abgeglichen):
|
||||||
- Rauser wird direkt in Fensteraufloesung gerendert (ein Nearest-Upscale der
|
- Rauser wird direkt in Fensteraufloesung gerendert (ein Nearest-Upscale der
|
||||||
@@ -121,6 +126,7 @@ import sys
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import threading
|
import threading
|
||||||
|
import configparser
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
|
||||||
from .formats import DATContainer, load_pcc
|
from .formats import DATContainer, load_pcc
|
||||||
@@ -140,6 +146,80 @@ DAT_PATH = os.environ.get('KELLOGG_DAT', os.path.join(PROJECT_ROOT, 'game', 'ori
|
|||||||
WINDOW_W, WINDOW_H = 960, 720
|
WINDOW_W, WINDOW_H = 960, 720
|
||||||
BG_COLOR = (0, 0, 0)
|
BG_COLOR = (0, 0, 0)
|
||||||
|
|
||||||
|
# Vollbild-Zustand wird in kelloggs.ini gemerkt (Alt+Enter schaltet um). Liegt
|
||||||
|
# noch keine kelloggs.ini vor, ist der Default Fenstermodus.
|
||||||
|
CONFIG_PATH = os.environ.get('KELLOGG_CONFIG', os.path.join(PROJECT_ROOT, 'kelloggs.ini'))
|
||||||
|
_fullscreen = False # aktueller Zustand; in main() aus der INI geladen
|
||||||
|
|
||||||
|
|
||||||
|
def load_fullscreen_pref():
|
||||||
|
'''Liest den gewuenschten Vollbild-Zustand aus kelloggs.ini. Fehlt die
|
||||||
|
Datei, ist der Default Fenstermodus (False).'''
|
||||||
|
cfg = configparser.ConfigParser()
|
||||||
|
try:
|
||||||
|
cfg.read(CONFIG_PATH)
|
||||||
|
return cfg.getboolean('display', 'fullscreen', fallback=False)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def save_fullscreen_pref(fullscreen):
|
||||||
|
'''Schreibt den aktuellen Vollbild-Zustand nach kelloggs.ini.'''
|
||||||
|
cfg = configparser.ConfigParser()
|
||||||
|
cfg['display'] = {'fullscreen': 'true' if fullscreen else 'false'}
|
||||||
|
try:
|
||||||
|
with open(CONFIG_PATH, 'w') as f:
|
||||||
|
cfg.write(f)
|
||||||
|
except Exception as exc:
|
||||||
|
print(f'[intro] konnte {CONFIG_PATH} nicht schreiben: {exc}', file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
def _display_flags(fullscreen):
|
||||||
|
# SCALED: das 960x720-Logikbild wird auf Fenster- bzw. Bildschirmgroesse
|
||||||
|
# skaliert -- so ist Vollbild ein sauberes Hochskalieren statt eines
|
||||||
|
# winzigen 960x720-Bildes mit schwarzem Rand.
|
||||||
|
flags = pygame.SCALED
|
||||||
|
if fullscreen:
|
||||||
|
flags |= pygame.FULLSCREEN
|
||||||
|
return flags
|
||||||
|
|
||||||
|
|
||||||
|
def toggle_fullscreen():
|
||||||
|
'''Alt+Enter: zwischen Fenster und Vollbild umschalten und den Zustand in
|
||||||
|
kelloggs.ini merken (fuers naechste Starten). Die Zeichenroutinen holen die
|
||||||
|
aktuelle Surface jeweils per pygame.display.get_surface(), daher ist das
|
||||||
|
Neuerzeugen des Display-Modes hier unkritisch.'''
|
||||||
|
global _fullscreen
|
||||||
|
_fullscreen = not _fullscreen
|
||||||
|
try:
|
||||||
|
pygame.display.set_mode((WINDOW_W, WINDOW_H), _display_flags(_fullscreen))
|
||||||
|
except Exception as exc:
|
||||||
|
print(f'[intro] Vollbild-Umschalten fehlgeschlagen: {exc}', file=sys.stderr)
|
||||||
|
save_fullscreen_pref(_fullscreen)
|
||||||
|
|
||||||
|
|
||||||
|
def _process_event(event):
|
||||||
|
'''Gemeinsame Event-Behandlung. Rueckgabe 'quit'/'skip'/None. Alt+Enter
|
||||||
|
(Vollbild-Toggle) wird hier abgefangen und zaehlt NICHT als skip/quit.'''
|
||||||
|
if event.type == pygame.QUIT:
|
||||||
|
return 'quit'
|
||||||
|
if event.type == pygame.KEYDOWN:
|
||||||
|
if event.key == pygame.K_RETURN and (event.mod & pygame.KMOD_ALT):
|
||||||
|
toggle_fullscreen()
|
||||||
|
return None
|
||||||
|
if event.key == pygame.K_ESCAPE:
|
||||||
|
return 'quit'
|
||||||
|
if event.key == pygame.K_SPACE:
|
||||||
|
return 'skip'
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _blit_centered(surface):
|
||||||
|
'''Zeichnet `surface` zentriert/skaliert auf die AKTUELLE Display-Surface
|
||||||
|
(per get_surface(), damit ein Vollbild-Umschalten mitten in einer Szene
|
||||||
|
keinen veralteten Screen-Bezug hinterlaesst).'''
|
||||||
|
blit_scaled_centered(pygame.display.get_surface(), surface, WINDOW_W, WINDOW_H)
|
||||||
|
|
||||||
# "praesentiert"-Text-Faelle in KELLOGGS.PCC (siehe Docstring)
|
# "praesentiert"-Text-Faelle in KELLOGGS.PCC (siehe Docstring)
|
||||||
PRAESENTIERT_FADE_INDICES = [34, 42, 43, 44, 45]
|
PRAESENTIERT_FADE_INDICES = [34, 42, 43, 44, 45]
|
||||||
PRAESENTIERT_FADE_SECONDS = 2.5
|
PRAESENTIERT_FADE_SECONDS = 2.5
|
||||||
@@ -356,19 +436,30 @@ def wait_or_skip(clock, seconds, screen_update_fn, fps=30):
|
|||||||
pygame.display.flip()
|
pygame.display.flip()
|
||||||
return None
|
return None
|
||||||
for event in pygame.event.get():
|
for event in pygame.event.get():
|
||||||
if event.type == pygame.QUIT:
|
r = _process_event(event)
|
||||||
return 'quit'
|
if r:
|
||||||
if event.type == pygame.KEYDOWN:
|
return r
|
||||||
if event.key == pygame.K_ESCAPE:
|
|
||||||
return 'quit'
|
|
||||||
if event.key == pygame.K_SPACE:
|
|
||||||
return 'skip'
|
|
||||||
screen_update_fn(t)
|
screen_update_fn(t)
|
||||||
pygame.display.flip()
|
pygame.display.flip()
|
||||||
clock.tick(fps)
|
clock.tick(fps)
|
||||||
|
|
||||||
|
|
||||||
def render_audio_with_loading(screen, clock, container, name, song_index, label):
|
def wait_for_key(clock, draw_fn, fps=30):
|
||||||
|
'''Zeigt das Standbild (draw_fn(1.0)) und wartet UNENDLICH, bis der User
|
||||||
|
Leertaste oder ESC drueckt (oder das Fenster schliesst). Alt+Enter schaltet
|
||||||
|
dabei weiter Vollbild um. Die geloopte Titelmusik laeuft die ganze Zeit
|
||||||
|
weiter. Gibt immer 'quit' zurueck -- danach ist das Programm zu Ende.'''
|
||||||
|
while True:
|
||||||
|
for event in pygame.event.get():
|
||||||
|
r = _process_event(event)
|
||||||
|
if r in ('quit', 'skip'): # auf dem letzten Screen beendet beides
|
||||||
|
return 'quit'
|
||||||
|
draw_fn(1.0)
|
||||||
|
pygame.display.flip()
|
||||||
|
clock.tick(fps)
|
||||||
|
|
||||||
|
|
||||||
|
def render_audio_with_loading(clock, container, name, song_index, label):
|
||||||
'''Rendert einen TFMX-Song per tfmx_audio.render_wav in einem Hintergrund-
|
'''Rendert einen TFMX-Song per tfmx_audio.render_wav in einem Hintergrund-
|
||||||
Thread, waehrend der Hauptthread weiter Events pumpt und einen simplen
|
Thread, waehrend der Hauptthread weiter Events pumpt und einen simplen
|
||||||
Ladebildschirm zeichnet.
|
Ladebildschirm zeichnet.
|
||||||
@@ -418,12 +509,16 @@ def render_audio_with_loading(screen, clock, container, name, song_index, label)
|
|||||||
for event in pygame.event.get():
|
for event in pygame.event.get():
|
||||||
if event.type == pygame.QUIT:
|
if event.type == pygame.QUIT:
|
||||||
return None, True
|
return None, True
|
||||||
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
|
if event.type == pygame.KEYDOWN:
|
||||||
return None, True
|
if event.key == pygame.K_ESCAPE:
|
||||||
|
return None, True
|
||||||
|
if event.key == pygame.K_RETURN and (event.mod & pygame.KMOD_ALT):
|
||||||
|
toggle_fullscreen()
|
||||||
now = time.monotonic()
|
now = time.monotonic()
|
||||||
if now - last_dot_tick > 0.4:
|
if now - last_dot_tick > 0.4:
|
||||||
dots = (dots + 1) % 4
|
dots = (dots + 1) % 4
|
||||||
last_dot_tick = now
|
last_dot_tick = now
|
||||||
|
screen = pygame.display.get_surface()
|
||||||
screen.fill(BG_COLOR)
|
screen.fill(BG_COLOR)
|
||||||
if font is not None:
|
if font is not None:
|
||||||
text = font.render(f'Lade {label}{"." * dots}', True, (200, 200, 200))
|
text = font.render(f'Lade {label}{"." * dots}', True, (200, 200, 200))
|
||||||
@@ -433,12 +528,12 @@ def render_audio_with_loading(screen, clock, container, name, song_index, label)
|
|||||||
|
|
||||||
thread.join()
|
thread.join()
|
||||||
if 'error' in result:
|
if 'error' in result:
|
||||||
print(f'[intro_sequence] {label} konnte nicht gerendert werden: {result["error"]}', file=sys.stderr)
|
print(f'[intro] {label} konnte nicht gerendert werden: {result["error"]}', file=sys.stderr)
|
||||||
return None, False
|
return None, False
|
||||||
return result.get('path'), False
|
return result.get('path'), False
|
||||||
|
|
||||||
|
|
||||||
def preload_all_audio(screen, clock, assets):
|
def preload_all_audio(clock, assets):
|
||||||
'''Rendert VOR der eigentlichen Sequenz alle drei benoetigten TFMX-Songs
|
'''Rendert VOR der eigentlichen Sequenz alle drei benoetigten TFMX-Songs
|
||||||
einmalig (siehe render_audio_with_loading-Docstring) -- danach ist jeder
|
einmalig (siehe render_audio_with_loading-Docstring) -- danach ist jeder
|
||||||
run_once()-Durchlauf (auch der allererste!) garantiert freeze-frei, weil
|
run_once()-Durchlauf (auch der allererste!) garantiert freeze-frei, weil
|
||||||
@@ -455,7 +550,7 @@ def preload_all_audio(screen, clock, assets):
|
|||||||
audio_paths = {}
|
audio_paths = {}
|
||||||
for key, label in jobs:
|
for key, label in jobs:
|
||||||
name, song_index = key
|
name, song_index = key
|
||||||
path, quit_requested = render_audio_with_loading(screen, clock, assets.container, name, song_index, label)
|
path, quit_requested = render_audio_with_loading(clock, assets.container, name, song_index, label)
|
||||||
audio_paths[key] = path
|
audio_paths[key] = path
|
||||||
if quit_requested:
|
if quit_requested:
|
||||||
return audio_paths, True
|
return audio_paths, True
|
||||||
@@ -557,15 +652,17 @@ def render_rauser_slide(canvas, parts, slide_t):
|
|||||||
canvas.blit(r3, (r3_final_x, r3_y))
|
canvas.blit(r3, (r3_final_x, r3_y))
|
||||||
|
|
||||||
|
|
||||||
def _draw_rauser(screen, parts, bg_color, slide_t):
|
def _draw_rauser(parts, bg_color, slide_t):
|
||||||
'''Zeichnet einen Rauser-Frame direkt in Fensteraufloesung: Hintergrund
|
'''Zeichnet einen Rauser-Frame direkt in Fensteraufloesung: Hintergrund
|
||||||
fuellen, dann die (transparent-hinterlegten) Logo-Teile an ihrer Slide-
|
fuellen, dann die (transparent-hinterlegten) Logo-Teile an ihrer Slide-
|
||||||
Position druebersetzen.'''
|
Position druebersetzen. Zielt immer auf die aktuelle Display-Surface
|
||||||
|
(get_surface), damit ein Vollbild-Umschalten nichts kaputt macht.'''
|
||||||
|
screen = pygame.display.get_surface()
|
||||||
screen.fill(bg_color)
|
screen.fill(bg_color)
|
||||||
render_rauser_slide(screen, parts, slide_t)
|
render_rauser_slide(screen, parts, slide_t)
|
||||||
|
|
||||||
|
|
||||||
def scene_rauser(screen, clock, assets, audio_paths):
|
def scene_rauser(clock, assets, audio_paths):
|
||||||
'''Rauser-Karte: weisser Grund, Slide-in, Sound-Sting bei Ankunft, Fade zu
|
'''Rauser-Karte: weisser Grund, Slide-in, Sound-Sting bei Ankunft, Fade zu
|
||||||
grauem Grund, kurz halten. Rueckgabe 'quit'/'skip'/None (siehe wait_or_skip).
|
grauem Grund, kurz halten. Rueckgabe 'quit'/'skip'/None (siehe wait_or_skip).
|
||||||
Wird jetzt DIREKT in Fensteraufloesung gerendert (kein 320er-Zwischen-
|
Wird jetzt DIREKT in Fensteraufloesung gerendert (kein 320er-Zwischen-
|
||||||
@@ -587,7 +684,7 @@ def scene_rauser(screen, clock, assets, audio_paths):
|
|||||||
sting_played = [False]
|
sting_played = [False]
|
||||||
|
|
||||||
def show_slide(t):
|
def show_slide(t):
|
||||||
_draw_rauser(screen, parts, RAUSER_WHITE, t)
|
_draw_rauser(parts, RAUSER_WHITE, t)
|
||||||
if t >= 1.0 and not sting_played[0]:
|
if t >= 1.0 and not sting_played[0]:
|
||||||
sting_played[0] = True
|
sting_played[0] = True
|
||||||
if sting is not None:
|
if sting is not None:
|
||||||
@@ -602,19 +699,19 @@ def scene_rauser(screen, clock, assets, audio_paths):
|
|||||||
sting_played[0] = True
|
sting_played[0] = True
|
||||||
|
|
||||||
r = wait_or_skip(clock, RAUSER_HOLD_SECONDS,
|
r = wait_or_skip(clock, RAUSER_HOLD_SECONDS,
|
||||||
lambda _t: _draw_rauser(screen, parts, RAUSER_WHITE, 1.0))
|
lambda _t: _draw_rauser(parts, RAUSER_WHITE, 1.0))
|
||||||
if r:
|
if r:
|
||||||
return r
|
return r
|
||||||
|
|
||||||
def show_fade_grey(t):
|
def show_fade_grey(t):
|
||||||
bg = tuple(int(RAUSER_WHITE[i] + (RAUSER_GREY[i] - RAUSER_WHITE[i]) * t) for i in range(3))
|
bg = tuple(int(RAUSER_WHITE[i] + (RAUSER_GREY[i] - RAUSER_WHITE[i]) * t) for i in range(3))
|
||||||
_draw_rauser(screen, parts, bg, 1.0)
|
_draw_rauser(parts, bg, 1.0)
|
||||||
r = wait_or_skip(clock, RAUSER_FADE_TO_GREY_SECONDS, show_fade_grey)
|
r = wait_or_skip(clock, RAUSER_FADE_TO_GREY_SECONDS, show_fade_grey)
|
||||||
if r:
|
if r:
|
||||||
return r
|
return r
|
||||||
|
|
||||||
return wait_or_skip(clock, RAUSER_GREY_HOLD_SECONDS,
|
return wait_or_skip(clock, RAUSER_GREY_HOLD_SECONDS,
|
||||||
lambda _t: _draw_rauser(screen, parts, RAUSER_GREY, 1.0))
|
lambda _t: _draw_rauser(parts, RAUSER_GREY, 1.0))
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -711,12 +808,12 @@ def render_title_wipe_frame(quads, t):
|
|||||||
return canvas
|
return canvas
|
||||||
|
|
||||||
|
|
||||||
def scene_pause(screen, clock, seconds):
|
def scene_pause(clock, seconds):
|
||||||
'''Kurze schwarze Pause zwischen zwei Screens.'''
|
'''Kurze schwarze Pause zwischen zwei Screens.'''
|
||||||
return wait_or_skip(clock, seconds, lambda _t: screen.fill(BG_COLOR))
|
return wait_or_skip(clock, seconds, lambda _t: pygame.display.get_surface().fill(BG_COLOR))
|
||||||
|
|
||||||
|
|
||||||
def scene_factor5(screen, clock, assets, audio_paths):
|
def scene_factor5(clock, assets, audio_paths):
|
||||||
'''Factor5-Studio-Logo (statisches Bild) + TITEL2-Song 1 ("Brausen").
|
'''Factor5-Studio-Logo (statisches Bild) + TITEL2-Song 1 ("Brausen").
|
||||||
KORREKTUR (Stefan 24.07.2026): der Screen wird EXAKT so lange gezeigt wie
|
KORREKTUR (Stefan 24.07.2026): der Screen wird EXAKT so lange gezeigt wie
|
||||||
der Sound spielt -- ist er zu Ende, geht es zu Kellogg's weiter. Ohne
|
der Sound spielt -- ist er zu Ende, geht es zu Kellogg's weiter. Ohne
|
||||||
@@ -734,13 +831,13 @@ def scene_factor5(screen, clock, assets, audio_paths):
|
|||||||
snd.play()
|
snd.play()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
print(f'[intro] Factor5-Sound konnte nicht geladen werden: {exc}', file=sys.stderr)
|
print(f'[intro] Factor5-Sound konnte nicht geladen werden: {exc}', file=sys.stderr)
|
||||||
return wait_or_skip(clock, hold,
|
return wait_or_skip(clock, hold, lambda _t: _blit_centered(factor5))
|
||||||
lambda _t: blit_scaled_centered(screen, factor5, WINDOW_W, WINDOW_H))
|
|
||||||
|
|
||||||
|
|
||||||
def scene_kelloggs(screen, clock, assets, audio_paths):
|
def scene_kelloggs(clock, assets, audio_paths):
|
||||||
'''Kellogg's-Markenlogo; darunter faedet "praesentiert" per Palettenfade
|
'''Kellogg's-Markenlogo; darunter faedet "praesentiert" per Palettenfade
|
||||||
ein. Hier setzt auch die Titelmusik (TITEL Song 0) geloopt ein.'''
|
ein. Hier setzt auch die Titelmusik (TITEL Song 0) geloopt ein und laeuft
|
||||||
|
dann bis zum Ende des Programms durch (siehe scene_title).'''
|
||||||
titel_path = audio_paths.get(('TITEL', None))
|
titel_path = audio_paths.get(('TITEL', None))
|
||||||
if pygame.mixer.get_init() and titel_path:
|
if pygame.mixer.get_init() and titel_path:
|
||||||
try:
|
try:
|
||||||
@@ -753,7 +850,7 @@ def scene_kelloggs(screen, clock, assets, audio_paths):
|
|||||||
|
|
||||||
def show_kelloggs(t):
|
def show_kelloggs(t):
|
||||||
frame = build_kelloggs_frame(kw, kh, kidx, kpal, t)
|
frame = build_kelloggs_frame(kw, kh, kidx, kpal, t)
|
||||||
blit_scaled_centered(screen, build_letterboxed(frame), WINDOW_W, WINDOW_H)
|
_blit_centered(build_letterboxed(frame))
|
||||||
r = wait_or_skip(clock, PRAESENTIERT_FADE_SECONDS, show_kelloggs)
|
r = wait_or_skip(clock, PRAESENTIERT_FADE_SECONDS, show_kelloggs)
|
||||||
if r:
|
if r:
|
||||||
return r
|
return r
|
||||||
@@ -761,33 +858,40 @@ def scene_kelloggs(screen, clock, assets, audio_paths):
|
|||||||
return wait_or_skip(clock, 1.0, lambda _t: show_kelloggs(1.0))
|
return wait_or_skip(clock, 1.0, lambda _t: show_kelloggs(1.0))
|
||||||
|
|
||||||
|
|
||||||
def scene_title(screen, clock, assets):
|
def scene_title(clock, assets, hold_forever=True):
|
||||||
'''Titelbild -- 2x2-Quadranten-Reveal, Scanlines loesen sich von der Mitte
|
'''Titelbild -- 2x2-Quadranten-Reveal, Scanlines loesen sich von der Mitte
|
||||||
auf (siehe render_title_wipe_frame), danach das fertige Bild halten.'''
|
auf. Danach bleibt das fertige, voll-farbige Titelbild STEHEN (die
|
||||||
|
Titelmusik loopt weiter), bis der User Leertaste/ESC drueckt -- dann endet
|
||||||
|
das Programm. hold_forever=False (fuer --once/Tests) haelt stattdessen nur
|
||||||
|
kurz und kehrt zurueck, statt auf eine Taste zu warten.'''
|
||||||
quads = build_title_quadrants(assets)
|
quads = build_title_quadrants(assets)
|
||||||
r = wait_or_skip(clock, TITLE_WIPE_SECONDS,
|
r = wait_or_skip(clock, TITLE_WIPE_SECONDS,
|
||||||
lambda t: blit_scaled_centered(screen, render_title_wipe_frame(quads, t), WINDOW_W, WINDOW_H))
|
lambda t: _blit_centered(render_title_wipe_frame(quads, t)))
|
||||||
if r:
|
if r == 'quit':
|
||||||
return r
|
return 'quit'
|
||||||
|
# 'skip' waehrend der Wipe-Animation -> direkt zum fertigen Titelbild
|
||||||
title_img = build_title_image(quads)
|
title_img = build_title_image(quads)
|
||||||
return wait_or_skip(clock, 3.0,
|
draw = lambda _t: _blit_centered(title_img)
|
||||||
lambda _t: blit_scaled_centered(screen, title_img, WINDOW_W, WINDOW_H))
|
if hold_forever:
|
||||||
|
return wait_for_key(clock, draw)
|
||||||
|
return wait_or_skip(clock, 3.0, draw)
|
||||||
|
|
||||||
|
|
||||||
def run_once(screen, clock, assets, audio_paths):
|
def run_once(clock, assets, audio_paths, hold_title=True):
|
||||||
'''Spielt die komplette Intro-Sequenz einmal ab. LEERTASTE ueberspringt den
|
'''Spielt die komplette Intro-Sequenz EINMAL ab (keine Endlosschleife mehr).
|
||||||
jeweils aktuellen Screen (die Scene liefert dann 'skip' -> es geht sofort
|
LEERTASTE ueberspringt den jeweils aktuellen Screen (die Scene liefert dann
|
||||||
zur naechsten Scene), ESC/Fenster-X beendet das Intro komplett. Gibt True
|
'skip' -> naechste Scene), ESC/Fenster-X beendet das Intro. Der letzte
|
||||||
zurueck, wenn der User ganz beenden will (dann bricht auch die
|
Screen (Titelbild) bleibt bei hold_title=True stehen und die Titelmusik
|
||||||
Endlosschleife in main() ab).'''
|
loopt, bis Leertaste/ESC das Programm beendet. hold_title=False (--once)
|
||||||
|
haelt das Titelbild nur kurz -- fuer automatisierte/headless Laeufe.'''
|
||||||
scenes = [
|
scenes = [
|
||||||
lambda: scene_rauser(screen, clock, assets, audio_paths),
|
lambda: scene_rauser(clock, assets, audio_paths),
|
||||||
lambda: scene_pause(screen, clock, 0.5),
|
lambda: scene_pause(clock, 0.5),
|
||||||
lambda: scene_factor5(screen, clock, assets, audio_paths),
|
lambda: scene_factor5(clock, assets, audio_paths),
|
||||||
lambda: scene_pause(screen, clock, 0.5),
|
lambda: scene_pause(clock, 0.5),
|
||||||
lambda: scene_kelloggs(screen, clock, assets, audio_paths),
|
lambda: scene_kelloggs(clock, assets, audio_paths),
|
||||||
lambda: scene_pause(screen, clock, 0.5),
|
lambda: scene_pause(clock, 0.5),
|
||||||
lambda: scene_title(screen, clock, assets),
|
lambda: scene_title(clock, assets, hold_forever=hold_title),
|
||||||
]
|
]
|
||||||
for scene in scenes:
|
for scene in scenes:
|
||||||
r = scene()
|
r = scene()
|
||||||
@@ -802,6 +906,7 @@ def run_once(screen, clock, assets, audio_paths):
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
global _fullscreen
|
||||||
once = '--once' in sys.argv
|
once = '--once' in sys.argv
|
||||||
pygame.mixer.pre_init(44100, -16, 2, 512)
|
pygame.mixer.pre_init(44100, -16, 2, 512)
|
||||||
pygame.init()
|
pygame.init()
|
||||||
@@ -810,8 +915,12 @@ def main():
|
|||||||
except pygame.error as exc:
|
except pygame.error as exc:
|
||||||
# Kein Audio-Device vorhanden (z.B. headless VM/Container ohne
|
# Kein Audio-Device vorhanden (z.B. headless VM/Container ohne
|
||||||
# Soundkarte) -- Sequenz laeuft stumm weiter statt abzustuerzen.
|
# Soundkarte) -- Sequenz laeuft stumm weiter statt abzustuerzen.
|
||||||
print(f'[intro_sequence] Audio nicht verfuegbar, laeuft stumm: {exc}', file=sys.stderr)
|
print(f'[intro] Audio nicht verfuegbar, laeuft stumm: {exc}', file=sys.stderr)
|
||||||
screen = pygame.display.set_mode((WINDOW_W, WINDOW_H))
|
|
||||||
|
# Vollbild-Zustand aus kelloggs.ini (Default Fenster). Alt+Enter schaltet zur
|
||||||
|
# Laufzeit um und speichert den neuen Zustand wieder dorthin.
|
||||||
|
_fullscreen = load_fullscreen_pref()
|
||||||
|
pygame.display.set_mode((WINDOW_W, WINDOW_H), _display_flags(_fullscreen))
|
||||||
pygame.display.set_caption("Tony & Friends in Kellogg's Land -- Intro (Remake)")
|
pygame.display.set_caption("Tony & Friends in Kellogg's Land -- Intro (Remake)")
|
||||||
clock = pygame.time.Clock()
|
clock = pygame.time.Clock()
|
||||||
|
|
||||||
@@ -819,14 +928,14 @@ def main():
|
|||||||
|
|
||||||
# Alle TFMX-Songs EINMALIG vor der eigentlichen Sequenz rendern (mit
|
# Alle TFMX-Songs EINMALIG vor der eigentlichen Sequenz rendern (mit
|
||||||
# sichtbarem, responsivem Ladebildschirm) -- siehe render_audio_with_loading
|
# sichtbarem, responsivem Ladebildschirm) -- siehe render_audio_with_loading
|
||||||
# zum Hang-Bug, den das behebt. Danach greifen alle run_once()-Durchlaeufe
|
# zum Hang-Bug, den das behebt. Danach greift die Sequenz nur noch auf
|
||||||
# nur noch auf fertige WAV-Pfade zu, nie mehr live-render im Hauptloop.
|
# fertige WAV-Pfade zu, nie mehr live-render im Hauptloop.
|
||||||
audio_paths, quit_requested = preload_all_audio(screen, clock, assets)
|
audio_paths, quit_requested = preload_all_audio(clock, assets)
|
||||||
|
|
||||||
|
# EIN Durchlauf (keine Endlosschleife): endet am stehenden Titelbild, das
|
||||||
|
# bis Leertaste/ESC gehalten wird (Titelmusik loopt). --once haelt nur kurz.
|
||||||
if not quit_requested:
|
if not quit_requested:
|
||||||
quit_requested = run_once(screen, clock, assets, audio_paths)
|
run_once(clock, assets, audio_paths, hold_title=not once)
|
||||||
while not quit_requested and not once:
|
|
||||||
quit_requested = run_once(screen, clock, assets, audio_paths)
|
|
||||||
|
|
||||||
if pygame.mixer.get_init():
|
if pygame.mixer.get_init():
|
||||||
pygame.mixer.music.stop()
|
pygame.mixer.music.stop()
|
||||||
|
|||||||
Reference in New Issue
Block a user