Hauptmenue, Optionen, Anleitung, Highscores und Weltkarte implementiert
Per DOSBox-Referenzaufnahmen (Xvfb + XTEST-Tastensimulation) den kompletten
Original-Menuefluss erfasst und nachgebaut:
- ESC am stehenden Titelbild oeffnet das Hauptmenue (Original-Verhalten --
Space/Return tun dort nichts, per Einzeltasten-Sessions verifiziert; das
Original zeigt auch nach 115s passiv keinen Attract-Loop).
- Hauptmenue: HIGHSCORES/ANLEITUNG/OPTIONEN/START/QUIT in roter FON_2-
Schrift, Default-Auswahl START, der selektierte Eintrag BLINKT (so zeigt
das Original den Cursor). PARTY1/PARTY2-Charaktergruppen links/rechts,
Credits-Ticker in gelber FON_1-Schrift unter der Box.
- OPTIONEN: MUSIK EIN/AUS (wirkt auf pygame.mixer.music), EFFEKTE EIN/AUS
(Flag fuer spaeteres Gameplay), EXIT.
- ANLEITUNG: gelbe Texttafel (Original-Joystick-Text auf Tastatur uebertragen).
- HIGHSCORES: Tabelle NAME/SCORE/ITEMS/LEV mit den Original-Defaults.
- START: Weltkarte (KARTE.PCC, kompletter Screen) -- v1 statisch, Taste
zurueck ins Menue; Levelauswahl/Animationen folgen mit dem Gameplay.
- QUIT: Programm-Ende.
Technik (game/menu.py):
- BitmapFont-Parser fuer die Original-Fonts: FON_1/FON_1B (9x7-Zellen,
8px-Vorschub -- nur so passen die 32-Zeichen-Zeilen der Anleitung in die
Box) und FON_2 (16x15-Zellen, 3 Reihen). Outline-Rendering fuer die gelbe
Schrift (Original zeichnet sie ueberall mit schwarzem Rand).
- PARTY-Sprites via make_border_bg_transparent freigestellt (papierweisser
BG wie bei den Rauser-Assets -- ein Schwarz-Replace loescht faelschlich
die Konturen, per Vergleichsrender nachgewiesen und verworfen).
- intro.py: Zustandsautomat Titelbild <-> Menue <-> Weltkarte; wait_for_key
unterscheidet jetzt Fenster-X ('quit') von Taste ('key').
Verifiziert: End-to-End-Lauf des Remakes unter Xvfb mit echten XTEST-Tasten
-- Intro -> ESC -> Menue -> START -> Karte -> zurueck -> Optionen -> Anleitung
-> Highscores -> QUIT beendet sauber (rc=0); alle Screens als Screenshots
gegen die DOSBox-Referenz geprueft. python -m game --once weiterhin exit 0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
949a7eeda2
commit
c8f7d4d990
+427
@@ -0,0 +1,427 @@
|
||||
"""Hauptmenue, Optionsmenue, Anleitung, Highscore-Tafel und Weltkarte.
|
||||
|
||||
Alles gegen echte DOSBox-Referenzaufnahmen (24.07.2026, Xvfb + XTEST-Tasten)
|
||||
nachgebaut -- Ablauf im Original:
|
||||
Titelbild --ESC--> Hauptmenue (faedet ein) mit Eintraegen
|
||||
HIGHSCORES / ANLEITUNG / OPTIONEN / START / QUIT (rote FON_2-Schrift,
|
||||
Default-Auswahl START). Der SELEKTIERTE Eintrag BLINKT (das ist der
|
||||
Cursor -- kein Pfeil o.ae.). Links/rechts der Box winken die Charakter-
|
||||
Gruppen PARTY1/PARTY2. Unter der Box laeuft ein Credits-Ticker in
|
||||
kleiner gelber Schrift (FON_1/FON_1B) mit wechselnden Texten
|
||||
("FREEGAMES #3", "REALISATION RAUSER ADVERTAINMENT GMBH REUTLINGEN",
|
||||
"PRODUZIERT VON FACTOR 5", "ANIMATIONEN UND ILLUSTRATIONEN HARTWIG
|
||||
NIEDERGASSEL").
|
||||
OPTIONEN -> eigene Tafel: MUSIK EIN/AUS, EFFEKTE EIN/AUS, EXIT.
|
||||
ANLEITUNG -> gelbe Text-Tafel (Joystick-Steuerung + Faehigkeiten).
|
||||
HIGHSCORES -> Tabelle NAME/SCORE/ITEMS/LEV mit den Original-Defaults
|
||||
(MATTHIAS 100000 ... BORIS 2500).
|
||||
START -> Weltkarte (KARTE.PCC -- kompletter Screen inkl. Rahmen und
|
||||
Charakteren; Animationen KARTE0-9 spaeter).
|
||||
|
||||
Screens sind 320x200 (wie die Original-Videomodi) und werden wie die
|
||||
Intro-Screens letterboxed auf 320x240 -> Fenster skaliert.
|
||||
|
||||
Fonts (Zellen-Grids per Spaltenanalyse der PCC-Assets bestimmt):
|
||||
FON_1 (320x7): 9x7-Zellen, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + AE OE UE ss
|
||||
FON_1B (320x7): 9x7-Zellen, '1234567890;:!"%/()=?,.-' + Block + '#'
|
||||
FON_2 (320x45): 16x15-Zellen, 3 Zeilen:
|
||||
'ABCDEFGHIJKLMNOPQRST' / 'UVWXYZ.,:!?-' / '1234567890'
|
||||
"""
|
||||
import sys
|
||||
import time
|
||||
|
||||
import pygame
|
||||
|
||||
from . import intro
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Bitmap-Fonts aus den FON_*.PCC-Assets
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class BitmapFont:
|
||||
'''Fixed-Cell-Bitmapfont aus einer PCC-Surface. Schwarz (0,0,0) ist
|
||||
Hintergrund und wird transparent. mapping: dict Zeichen -> (col, row).'''
|
||||
|
||||
def __init__(self, surface, cell_w, cell_h, mapping, spacing=None):
|
||||
surface = surface.convert_alpha()
|
||||
# Schwarz -> transparent (PCC-Hintergrund)
|
||||
arr = pygame.PixelArray(surface)
|
||||
arr.replace((0, 0, 0, 255), (0, 0, 0, 0))
|
||||
del arr
|
||||
self.cell_w, self.cell_h = cell_w, cell_h
|
||||
self.spacing = spacing if spacing is not None else cell_w
|
||||
self.glyphs = {}
|
||||
for ch, (col, row) in mapping.items():
|
||||
rect = pygame.Rect(col * cell_w, row * cell_h, cell_w, cell_h)
|
||||
if rect.right <= surface.get_width() and rect.bottom <= surface.get_height():
|
||||
self.glyphs[ch] = surface.subsurface(rect)
|
||||
|
||||
def measure(self, text):
|
||||
return len(text) * self.spacing, self.cell_h
|
||||
|
||||
def render(self, text, outline=None):
|
||||
'''Rendert `text` auf eine neue Surface (Alpha). outline=(r,g,b):
|
||||
1px-Umrandung in dieser Farbe (wie die gelbe Original-Schrift, die
|
||||
ueberall mit schwarzem Rand gezeichnet wird).'''
|
||||
w, h = self.measure(text)
|
||||
pad = 1 if outline else 0
|
||||
surf = pygame.Surface((w + 2 * pad, h + 2 * pad), pygame.SRCALPHA)
|
||||
if outline:
|
||||
# Umriss: Text 4x versetzt in outline-Farbe, dann normal drueber
|
||||
mask = pygame.Surface((w, h), pygame.SRCALPHA)
|
||||
x = 0
|
||||
for ch in text.upper():
|
||||
g = self.glyphs.get(ch)
|
||||
if g:
|
||||
mask.blit(g, (x, 0))
|
||||
x += self.spacing
|
||||
tinted = mask.copy()
|
||||
tint = pygame.Surface(mask.get_size(), pygame.SRCALPHA)
|
||||
tint.fill(outline + (255,))
|
||||
tinted.blit(tint, (0, 0), special_flags=pygame.BLEND_RGBA_MULT)
|
||||
# RGB durch outline-Farbe ersetzen, Alpha der Glyphen behalten
|
||||
for dx, dy in ((0, 1), (2, 1), (1, 0), (1, 2), (0, 0), (2, 2), (0, 2), (2, 0)):
|
||||
surf.blit(tinted, (dx, dy))
|
||||
x = pad
|
||||
for ch in text.upper():
|
||||
g = self.glyphs.get(ch)
|
||||
if g:
|
||||
surf.blit(g, (x, pad))
|
||||
x += self.spacing
|
||||
return surf
|
||||
|
||||
|
||||
def _mapping_from_rows(rows):
|
||||
m = {}
|
||||
for row, chars in enumerate(rows):
|
||||
for col, ch in enumerate(chars):
|
||||
m[ch] = (col, row)
|
||||
return m
|
||||
|
||||
|
||||
class MenuAssets:
|
||||
'''Laedt und cached alle Menue-relevanten Surfaces + Fonts.'''
|
||||
|
||||
def __init__(self, assets):
|
||||
self.assets = assets
|
||||
self.menu_bg = assets.pcc_surface('MENU') # 320x200 Komplett-Screen
|
||||
self.karte = assets.pcc_surface('KARTE') # 320x200 Weltkarte
|
||||
# PARTY-Sprites haben (wie die Rauser-Assets) einen PAPIERWEISSEN
|
||||
# Hintergrund und schwarze Konturen -- NICHT schwarz freistellen
|
||||
# (das loescht die Outlines!), sondern die vom Rand aus
|
||||
# zusammenhaengende weisse Flaeche per Flood-Fill transparent machen.
|
||||
self.party1 = intro.make_border_bg_transparent(assets.pcc_surface('PARTY1'))
|
||||
self.party2 = intro.make_border_bg_transparent(assets.pcc_surface('PARTY2'))
|
||||
|
||||
# kleine gelbe Schrift: FON_1 (Buchstaben) + FON_1B (Ziffern/Zeichen)
|
||||
# -- ein gemeinsamer Font, beide Surfaces uebereinandergelegt waere
|
||||
# falsch (gleiche Zeilenlage), daher zwei Fonts mit gleichem Grid und
|
||||
# ein kombinierter render-Wrapper.
|
||||
# Zellenraster ist 9px, die Engine rendert aber mit 8px Vorschub --
|
||||
# nur so passen die 32-Zeichen-Zeilen der Anleitung in die Box
|
||||
# (32 x 8 = 256px bei ~264px Box-Innenbreite, vgl. Referenz).
|
||||
f1 = BitmapFont(assets.pcc_surface('FON_1'), 9, 7,
|
||||
_mapping_from_rows(['ABCDEFGHIJKLMNOPQRSTUVWXYZ\xc4\xd6\xdc\xdf']),
|
||||
spacing=8)
|
||||
f1b = BitmapFont(assets.pcc_surface('FON_1B'), 9, 7,
|
||||
_mapping_from_rows(['1234567890;:!"%/()=?,.-\x7f#']),
|
||||
spacing=8)
|
||||
# Glyphen zusammenfuehren (disjunkte Zeichensaetze, gleiches Grid)
|
||||
f1.glyphs.update(f1b.glyphs)
|
||||
self.font_small = f1
|
||||
|
||||
self.font_big = BitmapFont(assets.pcc_surface('FON_2'), 16, 15,
|
||||
_mapping_from_rows(['ABCDEFGHIJKLMNOPQRST',
|
||||
'UVWXYZ.,:!?-',
|
||||
'1234567890']))
|
||||
|
||||
@staticmethod
|
||||
def _transparent(surface):
|
||||
s = surface.convert_alpha()
|
||||
arr = pygame.PixelArray(s)
|
||||
arr.replace((0, 0, 0, 255), (0, 0, 0, 0))
|
||||
del arr
|
||||
return s
|
||||
|
||||
|
||||
YELLOW_OUTLINE = (0, 0, 0)
|
||||
TICKER_TEXTS = [
|
||||
'FREEGAMES #3',
|
||||
'REALISATION RAUSER ADVERTAINMENT GMBH REUTLINGEN',
|
||||
'PRODUZIERT VON FACTOR 5',
|
||||
'ANIMATIONEN UND ILLUSTRATIONEN HARTWIG NIEDERGASSEL',
|
||||
]
|
||||
TICKER_SECONDS = 4.0 # Anzeigedauer pro Credits-Text
|
||||
BLINK_SECONDS = 0.35 # Blinkrhythmus des selektierten Eintrags
|
||||
|
||||
MENU_ITEMS = ['HIGHSCORES', 'ANLEITUNG', 'OPTIONEN', 'START', 'QUIT']
|
||||
MENU_START_INDEX = 3 # Default-Auswahl im Original: START
|
||||
|
||||
# Layout (320x200-Koordinaten, aus den DOSBox-Referenzframes vermessen)
|
||||
MENU_ITEM_Y0 = 44 # y des ersten Eintrags (HIGHSCORES)
|
||||
MENU_ITEM_DY = 15 # Zeilenabstand
|
||||
MENU_QUIT_GAP = 7 # QUIT ist etwas abgesetzt
|
||||
PARTY1_POS = (34, 80)
|
||||
PARTY2_POS = (218, 84)
|
||||
TICKER_Y = 172 # Ticker-Zeile unterhalb der Box
|
||||
|
||||
|
||||
def _draw_ticker(canvas, m, t):
|
||||
idx = int(t / TICKER_SECONDS) % len(TICKER_TEXTS)
|
||||
text = TICKER_TEXTS[idx]
|
||||
# lange Texte auf 2 Zeilen brechen (wie im Original: "REALISATION RAUSER /
|
||||
# ADVERTAINMENT GMBH REUTLINGEN" stand zweizeilig)
|
||||
max_chars = 34
|
||||
lines = []
|
||||
if len(text) > max_chars:
|
||||
words, cur = text.split(), ''
|
||||
for wrd in words:
|
||||
if len(cur) + len(wrd) + 1 > max_chars and cur:
|
||||
lines.append(cur)
|
||||
cur = wrd
|
||||
else:
|
||||
cur = (cur + ' ' + wrd).strip()
|
||||
if cur:
|
||||
lines.append(cur)
|
||||
else:
|
||||
lines = [text]
|
||||
y = TICKER_Y
|
||||
for line in lines:
|
||||
surf = m.font_small.render(line, outline=YELLOW_OUTLINE)
|
||||
canvas.blit(surf, ((320 - surf.get_width()) // 2, y))
|
||||
y += 10
|
||||
|
||||
|
||||
def _menu_canvas(m, items, selected, blink_on, t, hidden_indices=()):
|
||||
'''Baut den kompletten Menue-Screen (320x200): Box, Parties, Eintraege
|
||||
(selektierter blinkt), Ticker.'''
|
||||
canvas = m.menu_bg.copy()
|
||||
canvas.blit(m.party1, PARTY1_POS)
|
||||
canvas.blit(m.party2, PARTY2_POS)
|
||||
y = MENU_ITEM_Y0
|
||||
for i, item in enumerate(items):
|
||||
if i == len(items) - 1:
|
||||
y += MENU_QUIT_GAP # letzter Eintrag (QUIT/EXIT) abgesetzt
|
||||
visible = (i != selected) or blink_on
|
||||
if i in hidden_indices:
|
||||
visible = False
|
||||
if visible:
|
||||
surf = m.font_big.render(item)
|
||||
canvas.blit(surf, ((320 - surf.get_width()) // 2, y))
|
||||
y += MENU_ITEM_DY
|
||||
_draw_ticker(canvas, m, t)
|
||||
return canvas
|
||||
|
||||
|
||||
def _text_panel(m, lines, y0=58, line_h=10):
|
||||
'''Tafel-Screen: Menue-Box mit gelbem Text gefuellt (Anleitung/Copyright-
|
||||
Stil). lines: Liste von (text, x) -- x=None zentriert.'''
|
||||
canvas = m.menu_bg.copy()
|
||||
y = y0
|
||||
for entry in lines:
|
||||
text, x = entry if isinstance(entry, tuple) else (entry, None)
|
||||
if text:
|
||||
surf = m.font_small.render(text, outline=YELLOW_OUTLINE)
|
||||
if x is None:
|
||||
x = (320 - surf.get_width()) // 2
|
||||
canvas.blit(surf, (x, y))
|
||||
y += line_h
|
||||
return canvas
|
||||
|
||||
|
||||
def _present(canvas):
|
||||
intro._blit_centered(intro.build_letterboxed(canvas))
|
||||
|
||||
|
||||
def _menu_loop(clock, draw_fn, on_key):
|
||||
'''Generische Menue-Event-Schleife. draw_fn(t) zeichnet, on_key(event)
|
||||
liefert None (weiter) oder ein Ergebnis (Schleife endet). Alt+Enter und
|
||||
Fenster-X werden hier zentral behandelt.'''
|
||||
start = time.monotonic()
|
||||
while True:
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
return 'quit'
|
||||
if event.type == pygame.KEYDOWN:
|
||||
if event.key == pygame.K_RETURN and (event.mod & pygame.KMOD_ALT):
|
||||
intro.toggle_fullscreen()
|
||||
continue
|
||||
r = on_key(event)
|
||||
if r is not None:
|
||||
return r
|
||||
draw_fn(time.monotonic() - start)
|
||||
pygame.display.flip()
|
||||
clock.tick(30)
|
||||
|
||||
|
||||
def run_menu(clock, m):
|
||||
'''Hauptmenue. Rueckgabe: 'start' | 'quit' | 'title' (ESC -> zurueck zum
|
||||
Titelbild).'''
|
||||
state = {'sel': MENU_START_INDEX}
|
||||
|
||||
def draw(t):
|
||||
blink_on = int(t / BLINK_SECONDS) % 2 == 0
|
||||
_present(_menu_canvas(m, MENU_ITEMS, state['sel'], blink_on, t))
|
||||
|
||||
def key(event):
|
||||
if event.key == pygame.K_ESCAPE:
|
||||
return 'title'
|
||||
if event.key in (pygame.K_UP, pygame.K_LEFT):
|
||||
state['sel'] = (state['sel'] - 1) % len(MENU_ITEMS)
|
||||
elif event.key in (pygame.K_DOWN, pygame.K_RIGHT):
|
||||
state['sel'] = (state['sel'] + 1) % len(MENU_ITEMS)
|
||||
elif event.key in (pygame.K_RETURN, pygame.K_SPACE):
|
||||
choice = MENU_ITEMS[state['sel']]
|
||||
if choice == 'START':
|
||||
return 'start'
|
||||
if choice == 'QUIT':
|
||||
return 'quit'
|
||||
if choice == 'OPTIONEN':
|
||||
r = run_options(clock, m)
|
||||
if r == 'quit':
|
||||
return 'quit'
|
||||
elif choice == 'ANLEITUNG':
|
||||
r = run_anleitung(clock, m)
|
||||
if r == 'quit':
|
||||
return 'quit'
|
||||
elif choice == 'HIGHSCORES':
|
||||
r = run_highscores(clock, m)
|
||||
if r == 'quit':
|
||||
return 'quit'
|
||||
return None
|
||||
|
||||
return _menu_loop(clock, draw, key)
|
||||
|
||||
|
||||
def run_options(clock, m):
|
||||
'''Optionsmenue: MUSIK EIN/AUS, EFFEKTE EIN/AUS, EXIT. Rueckgabe 'back'
|
||||
oder 'quit'. Musik-Toggle wirkt sofort auf pygame.mixer.music.'''
|
||||
state = {'sel': 2, 'musik': True, 'effekte': True}
|
||||
|
||||
def items():
|
||||
return [f'MUSIK {"EIN" if state["musik"] else "AUS"}',
|
||||
f'EFFEKTE {"EIN" if state["effekte"] else "AUS"}',
|
||||
'EXIT']
|
||||
|
||||
def draw(t):
|
||||
blink_on = int(t / BLINK_SECONDS) % 2 == 0
|
||||
canvas = m.menu_bg.copy()
|
||||
canvas.blit(m.party1, PARTY1_POS)
|
||||
canvas.blit(m.party2, PARTY2_POS)
|
||||
its = items()
|
||||
y = 52
|
||||
for i, item in enumerate(its):
|
||||
if i == len(its) - 1:
|
||||
y += 18 # EXIT abgesetzt (wie Original-Referenz)
|
||||
if (i != state['sel']) or blink_on:
|
||||
surf = m.font_big.render(item)
|
||||
canvas.blit(surf, ((320 - surf.get_width()) // 2, y))
|
||||
y += MENU_ITEM_DY
|
||||
_draw_ticker(canvas, m, t)
|
||||
_present(canvas)
|
||||
|
||||
def key(event):
|
||||
if event.key == pygame.K_ESCAPE:
|
||||
return 'back'
|
||||
if event.key in (pygame.K_UP, pygame.K_LEFT):
|
||||
state['sel'] = (state['sel'] - 1) % 3
|
||||
elif event.key in (pygame.K_DOWN, pygame.K_RIGHT):
|
||||
state['sel'] = (state['sel'] + 1) % 3
|
||||
elif event.key in (pygame.K_RETURN, pygame.K_SPACE):
|
||||
if state['sel'] == 0:
|
||||
state['musik'] = not state['musik']
|
||||
if pygame.mixer.get_init():
|
||||
if state['musik']:
|
||||
pygame.mixer.music.unpause()
|
||||
else:
|
||||
pygame.mixer.music.pause()
|
||||
elif state['sel'] == 1:
|
||||
state['effekte'] = not state['effekte']
|
||||
# Effekte-Flag greift, sobald es im Gameplay Soundeffekte gibt
|
||||
else:
|
||||
return 'back'
|
||||
return None
|
||||
|
||||
return _menu_loop(clock, draw, key)
|
||||
|
||||
|
||||
ANLEITUNG_LINES = [
|
||||
('STEUERUNG:', 30),
|
||||
('MIT LINKS/RECHTS STEUERST DU DIE', 30),
|
||||
('FIGUR. MIT LEERTASTE ODER PFEIL', 30),
|
||||
('HOCH KANN DIE FIGUR SPRINGEN,', 30),
|
||||
('MIT PFEIL UNTEN KANN SICH DIE', 30),
|
||||
('FIGUR DUCKEN.', 30),
|
||||
('MIT ALT AKTIVIERST DU DIE', 30),
|
||||
('F\xc4HIGKEITEN DER FIGUREN...', 30),
|
||||
'',
|
||||
('TONY: RENNEN', 30),
|
||||
('SMACKS: WASSERBLASEN', 30),
|
||||
('COCO: HANGELN UND KLETTERN', 30),
|
||||
('TOUCAN: FLIEGEN', 30),
|
||||
]
|
||||
|
||||
# Default-Highscores aus der Original-Referenzaufnahme
|
||||
DEFAULT_HIGHSCORES = [
|
||||
('MATTHIAS', 100000, 5000, 20),
|
||||
('JAN', 80000, 4000, 17),
|
||||
('MARC', 60000, 3000, 14),
|
||||
('MICHAEL', 40000, 2000, 12),
|
||||
('URTE', 20000, 1000, 10),
|
||||
('PETER', 10000, 700, 8),
|
||||
('ANTONY', 5000, 400, 6),
|
||||
('BORIS', 2500, 200, 4),
|
||||
]
|
||||
|
||||
|
||||
def run_anleitung(clock, m):
|
||||
'''Anleitungstafel (gelber Text). Beliebige Taste/ESC -> zurueck.
|
||||
Text an die Original-Joystick-Seite angelehnt, auf Tastatur uebertragen
|
||||
(das Remake hat keinen Joystick-Support).'''
|
||||
canvas = _text_panel(m, ANLEITUNG_LINES, y0=36, line_h=8)
|
||||
|
||||
def draw(_t):
|
||||
_present(canvas)
|
||||
|
||||
def key(event):
|
||||
return 'back' # jede Taste schliesst die Tafel
|
||||
|
||||
return _menu_loop(clock, draw, key)
|
||||
|
||||
|
||||
def run_highscores(clock, m):
|
||||
'''Highscore-Tafel (Tabelle NAME/SCORE/ITEMS/LEV). Beliebige Taste ->
|
||||
zurueck. Ticker laeuft weiter (wie im Original).'''
|
||||
def draw(t):
|
||||
lines = [('NAME SCORE ITEMS LEV', 55)]
|
||||
for i, (name, score, items_, lev) in enumerate(DEFAULT_HIGHSCORES, 1):
|
||||
lines.append((f'{i}. {name:<9}{score:>7}{items_:>7}{lev:>5}', 30))
|
||||
canvas = m.menu_bg.copy()
|
||||
y = 44 # eng gepackt, damit alle 9 Zeilen in der Box (bis ~y=145) bleiben
|
||||
for text, x in lines:
|
||||
surf = m.font_small.render(text, outline=YELLOW_OUTLINE)
|
||||
canvas.blit(surf, (x, y))
|
||||
y += 10
|
||||
_draw_ticker(canvas, m, t)
|
||||
_present(canvas)
|
||||
|
||||
def key(event):
|
||||
return 'back'
|
||||
|
||||
return _menu_loop(clock, draw, key)
|
||||
|
||||
|
||||
def run_worldmap(clock, m):
|
||||
'''Weltkarte (KARTE.PCC -- kompletter 320x200-Screen inkl. Rahmen und
|
||||
Charakteren). v1: statisch; die kleinen Animationsframes KARTE0-9 und die
|
||||
Levelauswahl folgen mit dem Gameplay. ESC/Taste -> zurueck ins Menue.'''
|
||||
canvas = m.karte
|
||||
|
||||
def draw(_t):
|
||||
_present(canvas)
|
||||
|
||||
def key(event):
|
||||
return 'menu'
|
||||
|
||||
return _menu_loop(clock, draw, key)
|
||||
Reference in New Issue
Block a user