Title: miniworlds: Mehrere Welten für UI und Inventar

# Mehrere Welten für UI und Inventar

In miniworlds muss nicht alles in derselben Welt liegen. Das eigentliche Spiel
kann in einer `World` laufen, während UI, Punkte oder Inventar in einer zweiten
Welt angezeigt werden.

Das ist praktisch, wenn die Spielfigur von einer Kamera verfolgt wird: Die
Spielwelt darf sich bewegen, aber die Anzeige soll ruhig am Rand stehen bleiben.

### Eine zweite Welt andocken

Für UI-Elemente gibt es `Toolbar`. Eine Toolbar ist eine eigene kleine Welt, die
rechts oder unten an die Hauptwelt angedockt werden kann.

import miniworlds

world = miniworlds.World(500, 300)

spieler = miniworlds.Actor((120, 150))

ui = miniworlds.Toolbar()
ui.add(miniworlds.Label("Inventar"))
ui.add(miniworlds.Label("Muenzen: 0"))
ui.add(miniworlds.Label("Schluessel: 0"))

world.camera.add_right(ui, size=160)

world.run()

Die Hauptwelt bleibt `500 x 300` Pixel groß. Rechts daneben hängt jetzt eine
zweite Welt mit `160` Pixeln Breite. Actors in der Hauptwelt und Widgets in der
Toolbar bleiben dadurch getrennt.

!!! note Zwei Aufgaben, zwei Welten
Die Hauptwelt enthält Spielfiguren, Gegner, Münzen und Level. Die UI-Welt
enthält Anzeigen, Buttons und Inventarplätze. Beide gehören zum selben Programm,
aber sie haben unterschiedliche Aufgaben.

### Gemeinsame Spieldaten

Die UI-Welt soll Daten aus dem Spiel anzeigen. Dafür speichern wir den Zustand
an einer gemeinsamen Stelle, zum Beispiel in einem Dictionary.

import miniworlds

world = miniworlds.World(500, 300)

spieler = miniworlds.Actor((120, 150))

ui = miniworlds.Toolbar()

titel = miniworlds.Label("Inventar")
muenzen_text = miniworlds.Label("Muenzen: 0")
schluessel_text = miniworlds.Label("Schluessel: 0")

ui.add(titel)
ui.add(muenzen_text)
ui.add(schluessel_text)
world.camera.add_right(ui, size=170)

def aktualisiere_ui():
muenzen_text.text = f"Muenzen: {spiel['muenzen']}"
schluessel_text.text = f"Schluessel: {spiel['schluessel']}"

@spieler.register
def on_key_down_right(self):
self.x = self.x + 10

@spieler.register
def on_key_down_left(self):
self.x = self.x - 10

world.run()

Die Funktion `aktualisiere_ui()` gehört nicht zu einem bestimmten Actor. Sie
liest die gemeinsamen Spieldaten und schreibt sie in die Labels der UI-Welt.

### Münzen einsammeln und UI aktualisieren

Jetzt bekommt die Hauptwelt Sammelobjekte. Wenn der Spieler eine Münze berührt,
wird die Münze entfernt, der Wert in `spiel` erhöht und die Anzeige neu gesetzt.

import miniworlds

world = miniworlds.World(500, 300)

spieler = miniworlds.Actor((80, 150))

muenzen = []

muenze = miniworlds.Circle(position, 13)
muenzen.append(muenze)

ui = miniworlds.Toolbar()

ui.add(miniworlds.Label("Inventar"))
muenzen_text = miniworlds.Label("Muenzen: 0")
schluessel_text = miniworlds.Label("Schluessel: 0")
ui.add(muenzen_text)
ui.add(schluessel_text)

world.camera.add_right(ui, size=170)

def aktualisiere_ui():
muenzen_text.text = f"Muenzen: {spiel['muenzen']}"
schluessel_text.text = f"Schluessel: {spiel['schluessel']}"

@spieler.register
def act(self):
for muenze in muenzen:
if self.detect(muenze):
muenze.remove()
muenzen.remove(muenze)
spiel["muenzen"] = spiel["muenzen"] + 1
aktualisiere_ui()
break

@spieler.register
def on_key_down_right(self):
self.x = self.x + 10

@spieler.register
def on_key_down_left(self):
self.x = self.x - 10

@spieler.register
def on_key_down_up(self):
self.y = self.y - 10

@spieler.register
def on_key_down_down(self):
self.y = self.y + 10

world.run()

Die Münzen liegen in der Hauptwelt. Die Texte liegen in der UI-Welt. Verbunden
werden beide nur durch die gemeinsamen Daten und die Funktion
`aktualisiere_ui()`.

### Buttons senden Nachrichten

Eine Toolbar kann nicht nur Texte anzeigen. Buttons senden beim Anklicken eine
Nachricht an die Welt. Die Hauptwelt kann mit `on_message` darauf reagieren.

import miniworlds

world = miniworlds.World(500, 300)

spieler = miniworlds.Actor((120, 150))

ui = miniworlds.Toolbar()

leben_text = miniworlds.Label("Leben: 3")
trank_text = miniworlds.Label("Traenke: 1")

ui.add(miniworlds.Label("Status"))
ui.add(leben_text)
ui.add(trank_text)
ui.add(miniworlds.Button("Trank benutzen"))

world.camera.add_right(ui, size=180)

def aktualisiere_ui():
leben_text.text = f"Leben: {spiel['leben']}"
trank_text.text = f"Traenke: {spiel['trank']}"

@world.register
def on_message(self, message):
if message == "Trank benutzen" and spiel["trank"] > 0:
spiel["trank"] = spiel["trank"] - 1
spiel["leben"] = spiel["leben"] + 1
aktualisiere_ui()

@spieler.register
def on_key_down_right(self):
self.x = self.x + 10

@spieler.register
def on_key_down_left(self):
self.x = self.x - 10

world.run()

Der Button verändert den Spieler nicht direkt. Er sendet nur die Nachricht
`"Trank benutzen"`. Die Hauptwelt entscheidet dann, was diese Nachricht im Spiel
bedeutet.

### Eine Kamera und ein festes Inventar

Besonders nützlich wird die zweite Welt bei großen Spielwelten. Die Kamera folgt
dem Spieler, aber die UI-Welt bleibt am Rand stehen.

import miniworlds

world = miniworlds.World(500, 300)
world.camera.world_size_x = 1200

spieler = miniworlds.Actor((80, 220))

boden = miniworlds.Rectangle.from_topleft((x, 260), 80, 40)

muenzen = []

muenze = miniworlds.Circle((x, 220), 13)
muenzen.append(muenze)

ui = miniworlds.Toolbar()
ui.add(miniworlds.Label("Inventar"))
muenzen_text = miniworlds.Label("Muenzen: 0")
ui.add(muenzen_text)

world.camera.add_right(ui, size=170)

def aktualisiere_ui():
muenzen_text.text = f"Muenzen: {spiel['muenzen']}"

@spieler.register
def act(self):
world.camera.from_actor(self)

for muenze in muenzen:
if self.detect(muenze):
muenze.remove()
muenzen.remove(muenze)
spiel["muenzen"] = spiel["muenzen"] + 1
aktualisiere_ui()
break

@spieler.register
def on_key_down_right(self):
self.x = self.x + 15

@spieler.register
def on_key_down_left(self):
self.x = self.x - 15

world.run()

Die Münzen haben Weltpositionen wie `x = 750` oder `x = 1000`. Das Label
`Muenzen: ...` gehört aber zur angedockten UI-Welt. Darum bleibt es sichtbar,
auch wenn die Kamera weit nach rechts scrollt.

!!! note Merksatz
Lagere alles aus, was nicht Teil des Levels ist: Punkte, Lebensanzeige,
Inventar, Buttons oder Debug-Informationen. Die Spielwelt bleibt dadurch für
das Spiel zuständig, die zweite Welt für die Anzeige.