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.