Mein erster Versuch, ein Schiff zu steuern, war direkt: Pfeil hoch heißt Position +1 in Blickrichtung, Pfeil hoch loslassen heißt Stop. Funktioniert, fühlt sich aber wie ein Schreibmaschinen-Wagen an. Das Schiff hat keine Masse, keine Trägheit, keinen Drift. Drei kleine Bausteine reichen, um daraus echte 2D-Bewegung zu machen.
Beschleunigung statt direkter Geschwindigkeit
Statt vx = SOLL_GESCHWINDIGKEIT, addiere ich pro Frame nur einen Bruchteil:
CONST ACCEL = 0.25
IF IsKeyPressed(KEY_UP) THEN
vx = vx + COS(RAD(angle)) * ACCEL
vy = vy + SIN(RAD(angle)) * ACCEL
END IF
vx und vy bauen sich beim Drücken nach und nach auf. Wer eine Sekunde Schub gibt, fliegt schneller als wer kurz tippt. Das ist physikalisch trivial, aber genau der Unterschied zwischen „Cursor-Position" und „Objekt mit Trägheit".
Friction statt harter Stop
Loslassen reicht nicht, wenn das Schiff weiter mit vx und vy fliegen soll. Andersrum will man auch nicht ewig driften. Friction macht beides in einer Zeile pro Frame:
CONST FRICTION = 0.98
vx = vx * FRICTION
vy = vy * FRICTION
Jede Frame schrumpft die Geschwindigkeit um zwei Prozent. Wer nichts drückt, wird langsam langsamer und kommt irgendwann zum Stillstand. Wer Schub gibt, kämpft gegen die Reibung, also stabilisiert sich die Top-Geschwindigkeit von selbst. Der Wert 0.98 ist Geschmackssache; höher heißt rutschiger, niedriger heißt klebriger.
Speed-Clamp per Vektor-Normalisierung
Damit das Schiff nicht beliebig schnell wird, will ich eine Maximalgeschwindigkeit. Naiv wäre der Komponenten-Clip:
' Naiv: Komponenten einzeln klemmen
IF vx > MAX_SPEED THEN vx = MAX_SPEED
IF vx < -MAX_SPEED THEN vx = -MAX_SPEED
IF vy > MAX_SPEED THEN vy = MAX_SPEED
IF vy < -MAX_SPEED THEN vy = -MAX_SPEED
Das fühlt sich auf der Diagonale falsch an, weil die Diagonal-Geschwindigkeit dann SQR(MAX² + MAX²), also etwa 1.41 × MAX, sein kann. Die Lösung ist, den Vektor als Ganzes anzuschauen und nur die Länge zu begrenzen:
CONST MAX_SPEED = 5
speed = SQR(vx * vx + vy * vy)
IF speed > MAX_SPEED THEN
vx = (vx / speed) * MAX_SPEED
vy = (vy / speed) * MAX_SPEED
END IF
Das nennt sich Vektor-Normalisierung mit anschließender Skalierung. Die Richtung bleibt erhalten, nur die Länge wird auf MAX_SPEED gestaucht. Das Schiff ist auf der Diagonale genauso schnell wie horizontal, was sich richtig anfühlt.
Zusammen ergibt das
Acht Zeilen pro Frame, mehr braucht es nicht für ein Schiff mit echter 2D-Bewegung:
IF IsKeyPressed(KEY_UP) THEN
vx = vx + COS(RAD(angle)) * ACCEL
vy = vy + SIN(RAD(angle)) * ACCEL
END IF
speed = SQR(vx * vx + vy * vy)
IF speed > MAX_SPEED THEN
vx = (vx / speed) * MAX_SPEED
vy = (vy / speed) * MAX_SPEED
END IF
vx = vx * FRICTION
vy = vy * FRICTION
x = x + vx
y = y + vy
Das Pattern überträgt sich eins zu eins auf alles, was sich in 2D bewegt: Charaktere in einem Top-Down-Spiel, Mauszeiger mit Smoothing, Partikel in einer Simulation, Kameras, die einem Ziel folgen. Die drei Bausteine bleiben gleich, nur die Konstanten variieren.