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(Keys.A)) // Key.A in der .NET4-Version
                strafe -= 1;
            if (ks.IsKeyDown(Keys.D)) // Key.D in der .NET4-Version
                strafe += 1;
            if (ks.IsKeyDown(Keys.W)) // Key.W in der .NET4-Version
                forward += 1;
            if (ks.IsKeyDown(Keys.S)) // Key.S in der .NET4-Version
                forward -= 1;

            // Nutze die Informationen im MouseState-Objekt 'ms',
            // um die Kamera dementsprechend bewegen zu lassen:
            MoveFirstPersonCamera(); // MoveFPSCamera(ms) für die .NET4-Version

            // 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);
        }
    }
}

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.

Damit das First-Person-Waffenmodell anschließend nicht anfängt, in einer Wand zu verschwinden, wenn sich die Spielfigur zu nahe an einer Wand befindet, wird das Waffenmodell nicht als eigenes GameObject geführt. Es wird stattdessen einfach zusätzlich auf den Bildschirm projeziert und ist kein Objekt des Spielgeschehens. Es reagiert somit auf kein anderes Objekt der Szene.

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

public override void Prepare()
{
    KWEngine.LoadModelFromFile("Gun", "@.\modellverzeichnis\gun.fbx");

    SetFirstPersonObjectItemModel(
        "Gun", // Name des darzustellenden 3D-Modells
        true,  // soll das Modell auf Lichtquellen reagieren?
        true); // soll das Modell Schatten empfangen?

    // Verschiebt das Modell in Richtung der drei Hauptachsen
    // (falls eine mittige Ausrichtung nicht gewünscht ist):
    SetFirstPersonObjectItemModelViewOffset(0.5f, -1, 0);

    // Optionale Rotation des Objekts um die drei Weltachsen (x,y,z):
    SetFirstPersonObjectItemModelRotation(0f, 0f, 0f);

    // Optionale Vergrößerung/Verkleinerung des Modells (Standard: 1.0f):
    SetFirstPersonObjectItemModelScale(1.0f);
}

Wichtig:
Das Waffenmodell wird nur angezeigt, wenn der First-Person-Modus tatsächlich aktiv ist!


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.MouseCursorHide();

        // In der .NET4-Version muss dafür statt MouseCursorHide()
        // folgendes verwendet werden:
        // CurrentWindow.CursorVisible = false;
        // CurrentWindow.CursorGrabbed = true;
    }
}

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 = CurrentWindow.GetMouseCursorDelta();
        // In der .NET4-Version muss stattdessen der folgende Befehl 
        // verwendet werden:
        // 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(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 = 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. Erforderliche Felder sind mit * markiert