Title: miniworlds: Actors zueinander

# Actors zueinander

Actors können miteinander interagieren: einer verfolgt einen anderen, zwei
Actors stoßen zusammen, ein Feind greift an, wenn er nahe genug ist. Dafür
braucht man Abstandsberechnungen und Kollisionserkennung. Dieses Tutorial
zeigt beide Wege: zuerst mit eigenen Berechnungen, dann mit der API.

## Abstand messen

### Ohne API: Pythagoras

Den Abstand zwischen zwei Punkten berechnet man mit dem Satz des Pythagoras.
Die Positionen der Actors sind ihre Mittelpunkte.

import miniworlds
import math

world = miniworlds.World(460, 300)

spieler = miniworlds.Circle((80, 150), 20)

ziel = miniworlds.Circle((360, 150), 20)

anzeige = miniworlds.Text((10, 10), "")
anzeige.font_size = 18

@world.register
def act(self):
dx = ziel.x - spieler.x
dy = ziel.y - spieler.y
abstand = math.sqrt(dx * dx + dy * dy)
anzeige.text = f"Abstand: {abstand:.0f}"

world.run()

`dx` und `dy` sind die Differenzen in x- und y-Richtung. Pythagoras ergibt
daraus die gerade Linie zwischen den Mittelpunkten.

### Mit API: get_distance_to()

`get_distance_to()` liefert denselben Wert ohne eigene Rechnung.

import miniworlds

world = miniworlds.World(460, 300)

spieler = miniworlds.Circle((80, 150), 20)

ziel = miniworlds.Circle((360, 150), 20)

anzeige = miniworlds.Text((10, 10), "")
anzeige.font_size = 18

@world.register
def act(self):
abstand = spieler.get_distance_to(ziel)
anzeige.text = f"Abstand: {abstand:.0f}"

world.run()

`get_distance_to()` akzeptiert auch eine feste Position als Tupel:
`spieler.get_distance_to((200, 150))`.

## Berührung erkennen

### Ohne API: Abstand mit Radien vergleichen

Zwei Kreise berühren sich, wenn ihr Abstand kleiner ist als die Summe ihrer
Radien.

import miniworlds import math world = miniworlds.World(460, 300) world.add_background((230, 238, 248)) ball = miniworlds.Circle((80, 150), 20) ball.color = (60, 120, 220) ball.vx = 3 ball.vy = 0 ziel = miniworlds.Circle((360, 150), 25) ziel.color = (230, 170, 60) @ball.register def act(self): self.x = self.x + self.vx dx = ziel.x - self.x dy = ziel.y - self.y abstand = math.sqrt(dx * dx + dy * dy) if abstand < 20 + 25: # Summe der Radien ziel.color = (220, 80, 80) world.run()

Für Actors, die keine Kreise sind, funktioniert die Radienprüfung nur
näherungsweise. Dort müsste man stattdessen die Rechtecke vergleichen.

### Mit API: detect()

`detect()` prüft, ob ein Actor einen anderen berührt. Es arbeitet mit den
tatsächlichen Umrissen des Actors und ist für alle Formen korrekt.

import miniworlds

world = miniworlds.World(460, 300)

ball = miniworlds.Circle((80, 150), 20)
ball.direction = 90
ball.speed = 3

ziel = miniworlds.Circle((360, 150), 25)

@ball.register
def act(self):
self.move()
if self.detect(ziel):

world.run()

### Ereignis-Ansatz: on_detecting_actor

Wenn du auf jede Berührung reagieren willst, ohne jeden Frame aktiv zu prüfen,
registriere `on_detecting_actor`. miniworlds ruft es auf, sobald sich zwei
Actors überschneiden.

import miniworlds

world = miniworlds.World(460, 300)

ball = miniworlds.Circle((80, 150), 20)
ball.direction = 90
ball.speed = 3

ziel = miniworlds.Circle((360, 150), 25)

@ball.register
def act(self):
self.move()

@ball.register
def on_detecting_actor(self, actor):
if actor == ziel:
self.direction = -self.direction # umkehren

world.run()

Der Parameter `actor` enthält den Actor, der gerade berührt wird. Du kannst
mit `if actor == ziel:` prüfen, welcher Actor es konkret ist.

## Auf einen Actor zuhalten

### Ohne API: Richtung berechnen

Um einen Actor in Richtung eines anderen zu bewegen, berechnet man `dx`/`dy`
und normiert den Vektor auf die gewünschte Schrittweite.

import miniworlds
import math

world = miniworlds.World(460, 300)

jaeger = miniworlds.Circle((80, 150), 18)

beute = miniworlds.Circle((360, 150), 18)

@world.register
def on_key_down_right(self):
beute.x = beute.x + 20

@world.register
def on_key_down_left(self):
beute.x = beute.x - 20

@world.register
def on_key_down_up(self):
beute.y = beute.y - 20

@world.register
def on_key_down_down(self):
beute.y = beute.y + 20

@jaeger.register
def act(self):
dx = beute.x - self.x
dy = beute.y - self.y
laenge = math.sqrt(dx * dx + dy * dy)
if laenge > 0:
self.x = self.x + dx / laenge * 2
self.y = self.y + dy / laenge * 2

world.run()

Das Normieren (`/ laenge`) sorgt dafür, dass der Jäger immer gleich schnell
ist – unabhängig davon, wie weit entfernt die Beute ist.

### Mit API: move_towards()

`move_towards()` übernimmt die Vektorrechnung. Der zweite Parameter legt die
Schrittweite pro Frame fest.

import miniworlds

world = miniworlds.World(460, 300)

jaeger = miniworlds.Circle((80, 150), 18)

beute = miniworlds.Circle((360, 150), 18)

@world.register
def on_key_down_right(self):
beute.x = beute.x + 20

@world.register
def on_key_down_left(self):
beute.x = beute.x - 20

@world.register
def on_key_down_up(self):
beute.y = beute.y - 20

@world.register
def on_key_down_down(self):
beute.y = beute.y + 20

@jaeger.register
def act(self):
self.move_towards(beute, 2)

world.run()

Alternativ: `point_towards_actor()` richtet den Actor aus, danach bewegt
`move()` ihn in diese Richtung.

@jaeger.register
def act(self):
self.point_towards_actor(beute)
self.move(2)

## Actors abprallen lassen

Wenn zwei Actors aufeinandertreffen und beide mit `direction`/`move()` bewegt
werden, kann `bounce_from_actor()` die Richtungen automatisch korrigieren.

import miniworlds

world = miniworlds.World(460, 300)

a.direction = 90
a.speed = 3

b.direction = 270
b.speed = 3

@a.register
def act(self):
self.move()
if self.detect(b):
self.bounce_from_actor(b)

@b.register
def act(self):
self.move()
if self.detect(a):
self.bounce_from_actor(a)

world.run()

Beide Actors erkennen die Kollision und passen ihre Richtung an. Das Ergebnis
ist eine einfache physikalische Reflexion.

## Zusammenfassung

# Abstand messen
abstand = a.get_distance_to(b) # in Pixeln, von Mittelpunkt zu Mittelpunkt

# Berührung prüfen (aktiv)
if a.detect(b):
pass # a und b überschneiden sich

# Berührung als Ereignis
@a.register
def on_detecting_actor(self, actor):
pass # wird aufgerufen wenn a und actor sich berühren

# Auf Actor zuhalten
a.move_towards(b, 2) # 2 Pixel pro Frame
a.point_towards_actor(b) # nur ausrichten, nicht bewegen

# Abprallen
a.bounce_from_actor(b) # Richtung nach Kollision korrigieren

| Aufgabe | Ohne API | Mit API | |---|---|---| | Abstand | `math.sqrt(dx² + dy²)` | `get_distance_to(b)` | | Berührung | Abstand < Summe der Radien | `detect(b)` | | Hinbewegen | Vektor normieren, x/y addieren | `move_towards(b, 2)` | | Abprallen | vx/vy manuell tauschen | `bounce_from_actor(b)` |