Spieler Physik

Autor: Jonas Knerr

Bewegung

Da wir nicht nur einen Ghost haben wollten, der über die Welt fliegen kann, mussten wir zusätzlich eine Spieler Physik Entwickeln. Grundlegende Dinge, wie Bewegung durch W,A,S,D und die Mausbewegungen waren durch den Ghost schon gegeben. Diese haben wir dann entsprechen angepasst um eine ordentliche Spieler Physik zu erhalten. Der Kern der Spieler Physik ist die Update Funktion, diese wird in jeder Iteration aufgerufen. Dort wird dann auf die entsprechenden Tasten, die gedrückt werden reagiert.

Da der Spieler im Gegensatz zum Ghost eine gewisse Beschleunigung braucht um auf seine Höchstgeschwindigkeit zu bekommen, haben wir eine Exponentialfunktion implementiert. Durch diese erhalten wir eine realitätsnahe Beschleunigung und zusätzlich eine Maximalgeschwindigkeit, die nicht überschritten werden kann.

        if self.acceleration.x != 0.0 {
            self.velocity.x = self.acceleration.x * delta * delta * self.shift_speed *
                              (1.0 - (-((self.timer_velx * delta) / (1.0))).exp());

}

Im Eventhandler wird falls die "W" Taste gedrückt wird die Beschleunigung in X-Richtung auf 100 gesetzt, mit dieser wird dann pro Iteration die entsprechende Geschwindigkeit des Spieler in X-Richtung erhöht. Am Ende jeder Iteration wird dann noch die getestet ob eine Kollision entsteht, falls dies nicht der Fall ist wird die Kamera des Spielers um die Entsprechende Strecke in X- Richtung nach vorne bewegt.

            if self.velocity.x > 0.0001 || self.velocity.x < -0.0001 {
                self.velocity.x /= 1.4;
            } else {
                self.velocity.x = 0.0;
}

Falls der Spieler die "W" Taste los lässt wird die Beschleunigung auf 0 gesetzt. Dann wird durch eine angenäherte Reibung die Geschwindigkeit des Spielers langsam verringert, damit kein abruptes abstoppen entsteht. Da wir immer durch 1.4 teilen, wird der Wert nie 0 ergeben, deswegen wird ab einem Wert kleiner als 0.0001 der Wert auf null gesetzt.

Bei der Rückwärtsbewegung wird die Beschleunigung in X-Richtung, auf einen negativen Wert initialisiert. Somit kann man dieselbe Methode wie oben angegeben verwenden um die Rückwärtsbewegung zu berechnen. Zusätzlich ist der Wert bei einer Rückwärtsbewegung nicht ganz so groß, da man sich rückwärts nicht im gleichen Tempo wie vorwärts bewegen kann. Die Rechts/-Linksbewegung wird nach dem gleichen Prinzip realisiert. Statt sich aber in X-Richtung zu bewegen, bewegt sich die Kamera nun in Y-Richtung. Auch hier wird die Beschleunigung des Spieler durch Reibung langsam wieder auf 0 gesetzt. Da X und - Y-Richtung separat behandelt werden ist es auch mögliche sich schräg zu Bewegen.

Höhe

Nun hat der Spieler die Möglichkeit sich in alle Richtungen zu bewegen, aber leider nur auf einer Ebene. Um den Spieler auf den HexPillars laufen zu lassen, müssen wir uns also die Höhe der HexPillars holen. Dies geschieht in der get_ground_height_at Methode. Diese berechnet die Höhe des HexPillars an der Position des Spielers. Um an die Höhe des HexPillars zu kommen müssen wir uns einen AxialPoint berechnen, durch den AxialPointbekommt man den entsprechenden HexPillar auf dem der Spieler steht.

Dadurch können wir die Höhe des Spielers auf die entsprechende Höhe der Welt setzen, da die Welt aber nur in den wenigsten stellen eben ist, muss die Höhe des Spielers immer wieder abgeglichen werden. Zusätzlich wird zur Höhe des Spielers noch ein Wert von 1.75 addiert, damit der Spieler nicht direkt in der Welt steht, sondern "auf" der Welt.

Dadurch ist es aber immer noch nicht möglich sich in bzw. durch Höhlen zu bewegen. Um dies auch noch realisieren zu können muss der Spieler nicht nur auf die höchste Höhe eines HexPillars gesetzt werden, sondern es muss berechnet werden welche Höhe die richtige ist. Wenn man z.B. abwärts geht und vor einem eine Höhle auftaucht, sollte man weiter in die Höhle gehen und nicht auf der Höhle landen. Deswegen werden die HexPillar noch auf die unterschiedlichen Höhen untersucht.

Jeder HexPillar ist in verschieden Sections aufgeteilt. In den Sections wird aber nur abgespeichert, wo Material vorhanden ist. Da man sich bei der Spieler-Physik aber die Teile der Sections wichtig sind, in denen kein Material vorhanden ist, müssen wir alle Sections durchgehen und diese mit unserer Höhe vergleichen um zu wissen auf welche Höhe wir den Spieler setzen müssen.

                        if new_pillar_vec[i].top.to_real() < self.cam.position.z &&
                           self.cam.position.z < new_pillar_vec[i + 1].bottom.to_real() {
                            height = new_pillar_vec[i].top.to_real();
                            above = new_pillar_vec[i + 1].bottom.to_real();
                            break;
}

Dazu überprüfen wir, ob die Höhe des Spielers zwischen height und above liegt. Falls dies der Fall ist, liegt ein Loch vor dem Spieler und er kann sich in dieses hinein Bewegen, falls nicht liegt kein Loch vor und der Spieler wird auf die höchste Höhe des HexPillars gesetzt . Zu der entsprechenden Höhe geben wir die Höhe des anfangs der nächsten Section zurück(above), damit der Spieler sich nicht in beliebig kleine Löcher bewegen.

Sprung

       if self.velocity.z != 0.0 {

            self.velocity.z += (-delta * GRAVITY) * 0.2;

            if self.cam.position.z + self.velocity.z > above {
                self.velocity.z = 0.0;
            } else {
                self.cam.move_up(self.velocity.z);
}

Außerdem hat der Spieler ein Sprung implementiert. Um zu Springen muss man die Leertaste betätigen. Dann wird die Beschleunigung des Spielers in Z-Richtung hoch gesetzt. Damit der Spieler nicht in einen HexPillar rein springt, vergleichen wir die Höhe des Spielers beim Sprung mit dem above des HexPillars. Die Beschleunigung des Spielers wird dann durch die Gravitation automatisch verringert. Dies hat auch zur Folge, dass der Spieler auch über längere Distanzen sehr geschmeidig auf den Boden fällt. Dies lässt den Spieler auch angenehm von einem HexPillar zum anderen Fallen.

Kollision

Autor: Roman Schott

Da nun alle möglichen Bewegungen für den Spieler implementiert sind, fehlt nur noch die Kollision des Spielers an den Wänden. Bevor die Implementierung erfolgen kann, sind gewisse Informationen nötig wie die Position aller umliegenden HexPillar, dazu auch die Höhe der HexPillar und auch der Boden des darüber liegenden HexPillar, falls sich darüber ein HexPillar befindet. Diese Informationen lassen sich durch die Funktion get_ground_height_at ermitteln indem nicht die aktuelle Position des Spielers übergeben wird, sondern alle nächstmöglichen Positionen.

Um die nächstmögliche Position zu bekommen, wird der aktuelle Richtungsvektor der Kamera benötigt. Wie auch bei dem Ghost geschieht dies durch die beiden Variablen theta und phi.

let vec_0 = Vector2 {
            x: self.cam.phi.cos(),
            y: self.cam.phi.sin(),
        };

Wobei anzumerken ist, dass das theta nicht benötigt wird, da die Kollision nur in der horizontalen Richtung erfolgen soll. Der erhaltene Richtungsvektor wird in einer Variable von Typ Vector2 gespeichert. Um auch nun alle anderen Richtungsvektoren zu erhalten, sollte dafür folgende Grafik betrachtet werden:

Wie man an der Grafik deutlich sehen kann befinden sich alle benötigten HexPillar in einem Winkel von 60° zu dem vorhin ermittelten Richtungsvektor. Dabei ist das egal wo genau der Spieler zu dem HexPillar schaut oder wo der Spieler sich auf dem HexPillar befindet. Es sind immer alle umliegenden HexPillar durch die Vektoren sichtbar. Um auch die zu ermitteln wird folgende Rotationsmatrix verwendet:

let vec_60 = Vector2 {
            x: vec_0.x * angle.cos() + vec_0.y * angle.sin(),
            y: vec_0.x * (-(angle.sin())) + vec_0.y * angle.cos(),
        };

Hier ist ein Beispiel, wie die einzelnen Richtungsvektoren berechnet werden. Dabei wird der Vektor von vorhin verwendet und anhand der Matrix multipliziert. Der Verständlichkeit halber werden sämtliche errechneten Vektoren mit deren Winkel im Variablennamen gespeichert (vec_0, vec_60, vec_120, vec_180, vec_240, vec_300). Da nun alle Vektoren vorhanden sind, müssen jetzt zu den einzelnen Vektoren die benötigten Daten der HexPillar bestimmt werden. Darunter die Höhe, die der Spieler erhalten würde wenn er sich in die Richtung bewegen würde sowie, ob sich ein HexPillar über dem Spieler befinden würde. Diese Informationen erhält man aus den sections die beim Aufruf der Funktion get_ground_height_at ermittelt und genutzt werden. Um den Aufruf der Funktion im späteren Verlauf der Kollisionsprüfung zu vermeiden, werden sämtliche, erhaltene Daten in einem Tupel gespeichert. Diese sind ebenfalls wie die Vektoren mit dem Wert des Winkels abgespeichert.

let pillar_0 = (vec_0, self.get_ground_height_at(vec_0).0.unwrap_or(0.0),  self.get_ground_height_at(vec_0).2.unwrap_or(0.0));

Da nun alle Informationen beisammen sind folgt letztendlich die Kollisionsabfrage. Dabei wird überprüft in welche Richtung der Spieler sich bewegt und anhand der Informationen ermittelt, ob sich an der Position ein nicht erreichbarer HexPillar befindet sei es eine Lücke oder ein zu hoher HexPillar.

       //Movement Forward
        if (self.velocity.x > 0.001 &&
            (self.get_ground_height_at(Vector2 {
                x: vec_0.x - 0.2,
                y: vec_0.y - 0.1,
            })
            .0
            .unwrap_or(0.0) > height + self.step_size ||
             self.get_ground_height_at(Vector2 {
                x: vec_0.x + 0.2,
                y: vec_0.y + 0.1,
            })
            .0
            .unwrap_or(0.0) > height + self.step_size ||
             pillar_0.1 > height + self.step_size)) || (pillar_0.2 < 3.0) {
            if (self.velocity.y > 0.001 && pillar_60.1 > height + self.step_size) ||
               pillar_60.2 < 3.0 {
                self.velocity.x = 0.0;
                self.velocity.y = 0.0;
            }
            if (self.velocity.y < -0.001 && pillar_300.1 > height + self.step_size) ||
               pillar_300.2 < 3.0 {
                self.velocity.x = 0.0;
                self.velocity.y = 0.0;
            } else {
                self.velocity.x = 0.0;
            }
        }

Der Quellcode oben ist ein Beispiel für die Bewegung nach vorne und zusätzliche, seitliche Bewegungen. Zunächst einmal wird untersucht in welche Richtung der Spieler sich bewegt. Danach erfolgt der Test, ob sich in der entsprechenden Richtung ein HexPillar befindet, dessen Höhe größer ist als die Position des Spielers plus der möglichen Bewegung in die Höhe, dass durch die Variable step_size gesetzt wird. Wie man aber oben erkennen kann ist dies nicht der einzige Test. Zusätzlich werden noch zwei kleine, leicht dem Richtungsvektor abweichende Vektoren überprüft, um einen speziellen Fall auszuschließen, der in dieser Grafik erläutert wird.

Falls der Spieler sich nicht direkt auf einen HexPillar zubewegt, sondern an einem entlang läuft (siehe blauer Pfeil), kann es sein, dass der Vektor den falschen HexPillar auswählt. Dadurch ist es möglich für den Spieler in den an ihm nebenliegenden HexPillar zu laufen und er würde sich somit in einem HexPillar befinden, was bei einer Kollision nicht geschehen darf. Daher werden sowohl links als auch rechts von dem Richtungsvektor zusätzlich Vektoren überprüft um diesen Fall auszuschließen. Zu guter Letzt werden auch die Höhenunterschiede überprüft, damit es dem Spieler nicht möglich ist zwischen einen HexPillar zu laufen, dessen Höhenunterschied kleiner als die Höhe des Spielers ist. Nachdem auch das abgeschlossen ist, kann es durchaus sein, dass der Spieler zusätzlich nach rechts oder nach links läuft. Daher eine zusätzliche Abfrage ob dies der Fall ist durch die in Bewegungsrichtung, anliegenden HexPillar. Auch hier wird der Höhenunterschied abgefragt. Sobald ein HexPillar erkannt wird, der die Bewegung des Spielers blockiert, wird die Geschwindigkeit vom Spieler auf 0.0 gesetzt. Dieses Verfahren wird für sämtliche Bewegungsrichtungen des Spielers verwendet.

Erst nachdem der Kollisionstest erfolgt ist, wird am Ende der update-Funktion die horizontale Bewegung des Spielers durchgeführt durch den Aufruf der Funktionen:

self.cam.move_forward(self.velocity.x);
self.cam.move_right(self.velocity.y);

results matching ""

    No results matching ""