Game Engine Math

KWEngine, Teil 7: First und Third Person View

First Person View

In ihrer aktuellen Welt muss das Objekt explizit als First-Person-Objekt gekennzeichnet werden:

public class GameWorld : World
{
    public override void Prepare()
    {
        Player p1 = new Player();
        AddGameObject(p1)

        // Verbinde die Kamera mit dem Objekt und 
        // verschiebe sie um 2 Einheiten nach oben: 
        SetCameraToFirstPersonGameObject(p1, 2f); 
        
        // Mausempfindlichkeit einstellen (negativ für invertierte Y-Achse):
        KWEngine.MouseSensitivity = 0.05f;

        // Deaktiviert den Mauszeiger und sorgt dafür, dass sich
        // der Cursor nicht außerhalb des Programmfensters bewegen kann:
        MouseCursorGrab();
    }
}

Anschließend muss noch in der Player-Klasse auf die Tastatur- und Mauseingaben reagiert werden:

public class Player : GameObject
{
    public override void Act()
    {
            // Initialisiere zwei Variablen:
            // forward == +1 -> Indikator für 'vorwärts'
            // forward == -1 -> Indikator für 'rückwärts'
            // strafe  == +1 -> Indikator für 'strafe rechts'
            // strafe  == -1 -> Indikator für 'strafe links'
            int forward = 0;
            int strafe = 0;

            if (Keyboard.IsKeyDown(Keys.A))
                strafe -= 1;
            if (Keyboard.IsKeyDown(Keys.D))
                strafe += 1;
            if (Keyboard.IsKeyDown(Keys.W))
                forward += 1;
            if (Keyboard.IsKeyDown(Keys.S))
                forward -= 1;

            // Nutze die Informationen im Mouse-Objekt,
            // um die Kamera dementsprechend rotieren zu lassen:
            CurrentWorld.AddCameraRotationFromMouseDelta();

            // Bewege das Objekt entlang der aktuellen Blickrichtung
            // mit Hilfe der Variablenwerte in 'forward' und 'strafe'.
            // Der dritte Parameter ist die Geschwindigkeit.
            MoveAndStrafeAlongCameraXZ(forward, strafe, 0.01f);

            // Aktualisiere die Kameraposition:
            // (Erneut wird die Kamera um 2 Einheiten weiter oben platziert)
            CurrentWorld.UpdateCameraPositionForFirstPersonView(Center, 2f);

            // Optional: Drehe das (unsichtbare) Player-Objekt mit der Kamera:
            TurnTowardsXZ(CurrentWorld.CameraPosition + CurrentWorld.CameraLookAtVector);
        }
    }
}

Waffenmodell anzeigen

Um im First-Person-Modus zusätzlich ein Waffenmodell anzuzeigen, das von der Spielfigur gehalten wird, importiert man zunächst das dafür nötige 3D-Modell auf gewohntem Weg.

Das First-Person-Waffenmodell wird immer über allen anderen Gegenständen gezeichnet, damit es nicht in einer Wand verschwindet, wenn die Spielfigur zu nahe davor steht. Die dafür verwendete Klasse lautet ViewSpaceGameObject, und Sie müssen eine eigene Unterklasse dieses Typs erstellen. Die Bezeichnung „view space“ kommt daher, weil sich dieses immer an der Kamera orientiert und in dessen Blickfeld („view space“) liegt.

public class MyFirstPersonWeapon : ViewSpaceGameObject
{
    public override void Act()
    {
        // Passe das Objekt der ggf. geänderten 
        // Kameraposition an:
        UpdatePosition();
    }
}

In der Prepare()-Methode der Welt-Klasse führt folgender Code zur Einblendung eines Waffenmodells:

public override void Prepare()
{
    KWEngine.LoadModel("Gun", "./modelfolder/gun.fbx");
    
    MyFirstPersonWeapon fpw = new MyFirstPersonWeapon();
    fpw.SetModel("Gun");
    fpw.SetOffset(0.0f, -0.1f, 0.1f); // Verschiebung relativ zur Kamera
    fpw.SetScale(1.0f); // Skaliere das Objekt entsprechend

    SetViewSpaceGameObject(fpw);
}

Wichtig:
Dieses Objekt ist nur dann sinnvoll, wenn die Kamera wie im First-Person-Modus gesteuert wird.


Third Person View

Um die Kamera stets in der Nähe des Spielerobjekts zu behalten, ist kein besonderer Modus einzustellen. Stattdessen sind ein paar einzelne Methodenaufrufe und Berechnungen nötig:

GameWorld-Klasse:

public class GameWorld : World
{
    public override void Prepare()
    {
        Player p1 = new Player();
        p1.SetRotation(0, 180, 0); // Spielfigur soll sich um 180° drehen, um der Kamera den Rücken zuzudrehen
        AddGameObject(p1);

        // Deaktiviert den Mauszeiger und sorgt dafür, dass sich
        // der Cursor nicht außerhalb des Programmfensters bewegen kann:
        MouseCursorGrab();
    }
}

Player-Klasse:

public class Player : GameObject
{
    private Vector2 _currentCameraRotation = new Vector2(0, 0);
    private float _limitYUp = 5;
    private float _limitYDown = -75;

    public override void Act()
    {
        // Optional: 
        // Wenn sich die Spielfigur mitdrehen soll, muss hier
        // die Mausbewegung auch zur Player-Rotation addiert werden!
        AddRotationY(-MouseMovement.X * KWEngine.MouseSensitivity);

        // Diese Methode berechnet die neue Kameraposition:
        UpdateCameraPosition(MouseMovement * KWEngine.MouseSensitivity);

        // Hier folgen anschließend die Tastaturabfragen für WSAD 
        // oder andere Richtungstasten. Für Bewegungen auf der XZ-Achse 
        // (Standard) könnte es wie folgt gelöst werden:
        // Initialisiere zwei Variablen:
            // forward == +1 -> Indikator für 'vorwärts'
            // forward == -1 -> Indikator für 'rückwärts'
            // strafe  == +1 -> Indikator für 'strafe rechts'
            // strafe  == -1 -> Indikator für 'strafe links'
            int forward = 0;
            int strafe = 0;

            if (Keyboard.IsKeyDown(Keys.A))
                strafe -= 1;
            if (Keyboard.IsKeyDown(Keys.D))
                strafe += 1;
            if (Keyboard.IsKeyDown(Keys.W))
                forward += 1;
            if (Keyboard.IsKeyDown(Keys.S))
                forward -= 1;

            // Bewege das Objekt entlang der aktuellen Blickrichtung
            // mit Hilfe der Variablenwerte in 'forward' und 'strafe'.
            // Der dritte Parameter ist die Bewegungsgeschwindigkeit.
            MoveAndStrafeAlongCameraXZ(forward, strafe, 0.01f);
    }

    private void UpdateCameraPosition(Vector2 msMovement)
    {
        // Berechne anhand der Mausbewegung, um wieviel Grad die Kamera
        // sich drehen müsste:
        _currentCameraRotation.X += msMovement.X;
        _currentCameraRotation.Y += msMovement.Y;
        // Damit die Kamera nicht "über Kopf" geht, wird die Rotation nach
        // oben und unten begrenzt:
        if (_currentCameraRotation.Y < _limitYDown)
        {
            _currentCameraRotation.Y = _limitYDown;
        }
        if (_currentCameraRotation.Y > _limitYUp)
        {
            _currentCameraRotation.Y = _limitYUp;
        }

        // Erfrage aktuelle Position der Spielfigur:
        Vector3 playerPosition = Center;

        // Berechne die neue Kameraposition anhand der gesammelten Infos:
        Vector3 newCamPos = HelperRotation.CalculateRotationForArcBallCamera(
                playerPosition,                  // Drehpunkt
                10f,                             // Distanz zum Drehpunkt
                _currentCameraRotation.X,        // Drehung links/rechts
                _currentCameraRotation.Y,        // Drehung oben/unten
                false,                           // invertiere links/rechts?
                false                            // invertiere oben/unten?
        );

        // Setze die neue Kameraposition und das Kameraziel:
        CurrentWorld.SetCameraPosition(newCamPos);
        CurrentWorld.SetCameraTarget(playerPosition);
    }
}

Falls die Kamera nicht direkt hinter der Spielfigur positioniert werden soll, benötigen Kameraposition und -ziel eine leichte Verschiebung (ein sogenannter Offset). Dann gilt folgende Alternative für die UpdateCameraPosition()-Methode:

public class Player : GameObject
{
    // ...

    private void UpdateCameraPosition(Vector2 msMovement)
    {
        // Berechne anhand der Mausbewegung, um wieviel Grad die Kamera
        // sich drehen müsste:
        _currentCameraRotation.X += msMovement.X * _rotationScaleFactor;
        _currentCameraRotation.Y += msMovement.Y * _rotationScaleFactor;
        // Damit die Kamera nicht "über Kopf" geht, wird die Rotation nach
        // oben und unten begrenzt:
        if (_currentCameraRotation.Y < _limitYDown)
        {
            _currentCameraRotation.Y = _limitYDown;
        }
        if (_currentCameraRotation.Y > _limitYUp)
        {
            _currentCameraRotation.Y = _limitYUp;
        }

        // Erfrage aktuelle Blickrichtung und Position der Spielfigur:
        Vector3 lookAtVector = LookAtVector;
        Vector3 playerPosition = Center;

        // Berechne für Kameraposition und -ziel einen individuellen Offset-Wert:
        float lav_factor = (0.00012f * (_currentCameraRotation.Y * _currentCameraRotation.Y) + 0.02099f * _currentCameraRotation.Y + 0.89190f);
        float lav_factor2 = _currentCameraRotation.Y >= -15 ? (_currentCameraRotation.Y + 15) / 20f : 0f;
        Vector3 offsetCamPos = HelperRotation.RotateVector(lookAtVector, -90, Plane.XZ) + lookAtVector * 5 * lav_factor;
        Vector3 offsetCamTarget = HelperRotation.RotateVector(lookAtVector, -90, Plane.XZ) + lookAtVector * 2 + Vector3.UnitY * 2 * lav_factor2;
        
        // Berechne die neue Kameraposition anhand der gesammelten Infos:
        Vector3 newCamPos = HelperRotation.CalculateRotationForArcBallCamera(
                playerPosition,                  // Drehpunkt
                10f,                             // Distanz zum Drehpunkt
                _currentCameraRotation.X,        // Drehung links/rechts
                _currentCameraRotation.Y,        // Drehung oben/unten
                false,                           // invertiere links/rechts?
                false                            // invertiere oben/unten?
        );

        // Setze die neue Kameraposition und das Kameraziel:
        CurrentWorld.SetCameraPosition(newCamPos + offsetCamPos);
        CurrentWorld.SetCameraTarget(playerPosition + offsetCamTarget);
    }
}

Beitrag veröffentlicht

in

von

Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.