Die Engine basiert auf Python und PyGame. PyGame macht es einfach, eine Steuerung per Maus oder Tastatur zu implementieren. Außerdem gibt es Funktionen zum Zeichnen von Formen. Das reicht bereits für eine einfache 2D Engine.
Für die Übersichtlichkeit ist die Implementation der Engine objektorientiert. Die Klassen sind folgendermaßen strukturiert:
Damit man einfach und übersichtlich Berechnungen durchführen kann, gibt es die Klassen Vector
, Matrix
und Ray
mit entsprechenden Methoden. Außerdem haben alle darstellbaren Objekte die Superklasse drawable
. Diese ist abstrakt und dient dazu, dass verschiedenste Objekte einheitlich benutzt werden können, da zum Beispiel alle Klassen, die von drawable
erben eine draw()
Methode haben. Die Klasse drawable
erbt von der Python Klasse list
, da die Methoden von PyGame zum zeichnen Punktlisten annehmen. So nehmen sie auch „drawables“ an.
Als Erstes muss die Engine Objekte darstellen. Der Einfachheit halber wird der Spieler immer in der Mitte des Bildes als Kreis dargestellt. So muss beim Zeichnen einfach von der Position der Objekte die Position des Spielers abgezogen werden.
Da ein nichteuklidischer Raum nicht auf die ebene Bildschirmfläche passt, müssen irgendwie die Unstimmigkeiten kaschiert werden. Die einfachste Methode ist es, hinter Objekten zu verstecken. Damit das in 2D funktioniert braucht es also Schatten.
Nun braucht es noch Portale, die die Engine nichteuklidisch machen (eine genaue Erläuterung findet sich weiter unten↓). Dafür sind insbesondere wieder die Schatten wichtig, denn da, wo eigentlich der Schatten des Portals wäre, soll nun die Portalwelt dargestellt werden. Dazu müssen die zu malenden Objekte außerdem an Linien zerschnitten werden können, um nicht über den Portalbereich hinaus gezeichnet zu werden. Um diese Engine einfach zu halten, wird nicht der Spieler teleportiert, sondern die Welt.
(Hier ist das Portal hervorgehoben; für eine perfekte Illusion ist dies in der Engine nicht der Fall.)
Zu guter Letzt sind Kollisionen mit den Objekten in der Engine essenziell (eine genaue Erläuterung findet sich weiter unten↓). Jede Illusion würde zerbrechen, wenn man einfach durch die Wand gehen kann.
Ein Portal ist eine Linie – ein gerader, längst begrenzter „Schnitt“ im Raum. Dieser Schnitt hat nun 2 Seiten, die man beide mit verschiedenen Orten verbinden („zusammenkleben“) kann. Betrachtet man nun eine Seite des Portals, so soll man an einem anderen Ort herauskommen können. Also braucht es einen Verschiebungsvektor für die Portalwelt.
Für eine gute Illusion verbindet man normalerweise 2 Portale miteinander, sodass man in beide Richtungen gehen kann und im Bestfall gar nichts davon bemerkt. Diese Portale können aber eine unterschiedliche Ausrichtung haben, also muss ein Portal die Welt auch drehen können. Zusätzlich müsste die Welt gestaucht und gestreckt werden können, um Effekte wie den langen/kurzen Tunnel nachzustellen.
Um nicht alle Funktionen einzeln darstellen zu müssen, macht die Engine gebrauch der linearen Algebra. Betrachtet man die Welt als einen Vektorraum, so kann eine Basistransformationsmatrix all diese Transformationen auf einmal erledigen und sogar noch Scherungen.
Somit kann eine Seite eines Portals mit einer Transformationsmatrix und einem Verschiebungsvektor dargestellt werden. Da hierbei alles linear bleibt, d. h. alle Linien gerade bleiben, müssen für Polygone nur die Eckpunkte betrachtet werden. Ist $T$ die Transformationsmatrix und $v$ der Verschiebungsvektor, so lässt sich für einen Eckpunkt am Ort $p$ folgendermaßen seine Position im Portal $p'$ berechnen:
$p' ~=~ T·p ~+~ v$
Möchte man nun 2 Portale kohärent miteinander verbinden, so braucht man für die Gegenseite nur die Formel umstellen:
$p' ~=~ T·p ~+~ v ~~\Leftrightarrow~~ T^{-1}·p' ~=~ p ~+~ T^{-1}·v ~~\Leftrightarrow~~ p ~=~ T^{-1}·p' ~-~ T^{-1}·v$
Also ist die $T^{-1}$ Transformationsmatrix und $T^{-1}·v$ der Verschiebungsvektor der Gegenseite.
(Sollte $T^{-1}$ nicht existieren, so war $T$ keine Basistransformationsmatrix und der Raum wurde auf eine Linie abgebildet.)
Die Kollisionen sollen lediglich den Spieler daran hindern, durch Objekte zu gleiten, aber nicht dahin zu kommen, wo er hin möchte. Bleibt der Spieler einfach stehen, wenn er ein Objekt berührt, wird es schnell frustrierend. Ist eine Kollision ein Physikalisch korrekter, elastischer Stoß, so prallt der Spieler einfach wie ein Ball von dem Objekt ab, meist in die entgegengesetzte Richtung, in die der Spieler eigentlich wollte.
Der verwendete Ansatz betrachtet den Bewegungsvektor des Spielers und entfernt den Anteil, der senkrecht zum Hindernis steht.
Ist $v$ der Bewegungsvektor $w$ die Richtung der Wand, mit der eine Kollision stattfindet. So gibt es einen Vektor $w'$ mit $w·w' ~=~ 0$ (senkrecht zueinander). Dann gilt:
$v ~=~ λ·w ~+~ μ·w'$ für gewisse Werte $λ, ~μ$.
Hier beschreibt $λ·w$ den Anteil des Bewegungsvektors, der längst der Wand geht (nicht kollidiert) und $μ·w'$ der senkrecht zur Wand steht (kollidiert).
Somit ist der neue Bewegungsvektor $v'$ nach der Kollision:
$v' ~=~ v ~-~ μ·w' ~=~ λ·w$
Ist das Hindernis keine Linie, sondern ein Punkt, so wird der Vektor vom Spieler zum Punkt als $w'$ interpretiert.