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();
        p1.SetModel("KWCube");
        AddGameObject(p1)

        // Verbinde die Kamera mit dem Objekt:
        SetFirstPersonObject(p1); 
        
        // Optional: versetzt die Kamera um nach oben
        p1.FPSEyeOffset = 2;

        // Mausempfindlichkeit (negativ für invertierte Y-Achse):
        KWEngine.MouseSensitivity = 0.001f;
    }
}

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

public class Player : GameObject
{
    public override void Act(KeyboardState ks, MouseState ms)
    {
        // Prüfe, ob dieses Objekt das "First Person Object" ist:
        if (CurrentWorld.IsFirstPersonMode == true && CurrentWorld.GetFirstPersonObject().Equals(this))
        {
            // 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'
            float forward = 0;
            float strafe = 0;

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

            // Nutze die Informationen im MouseState-Objekt 'ms',
            // um die Kamera dementsprechend bewegen zu lassen:
            MoveFPSCamera(ms);

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

            // Alternative, falls das Objekt fliegen kann:
            // MoveAndStrafeFirstPersonXYZ(forward, strafe, 0.2f);
        }
    }
}

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.SetModel("KWCube");
        AddGameObject(p1);

        // Deaktiviert den Mauszeiger und sorgt dafür, dass sich
        // der Cursor nicht außerhalb des Programmfensters bewegen kann:
        CurrentWindow.CursorVisible = false;
        CurrentWindow.CursorGrabbed = true; // WICHTIG!
    }
}

Player-Klasse:

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

    public override void Act(KeyboardState ks, MouseState ms)
    {
        // Erfrage die aktuellen deltaX- und deltaY-Werte für den Mauszeiger
        // (wie weit hat er sich seit dem letzten Frame bewegt):
        Vector2 msMovement = GetMouseCursorMovement(ms);
        
        // Optional: 
        // Wenn sich die Spielfigur mitdrehen soll, muss hier
        // die Mausbewegung auch zur Player-Rotation addiert werden!
        this.AddRotationY(msMovement.X * _rotationScaleFactor);

        // Diese Methode berechnet die neue Kameraposition:
        UpdateCameraPosition(msMovement);

        // Hier folgen anschließend die Tastaturabfragen für WSAD 
        // oder andere Richtungstasten...
    }

    private void UpdateCameraPosition(Vector3 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 = GetLookAtVector();
        Vector3 playerPosition = Position;

        // 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(Vector3 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 = GetLookAtVector();
        Vector3 playerPosition = Position;

        // 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.Y) + lookAtVector * 5 * lav_factor;
        Vector3 offsetCamTarget = HelperRotation.RotateVector(lookAtVector, -90, Plane.Y) + 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);
    }
}

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.