Zurück zum Blog

Der ultimative Unreal Engine GAS Teleport Rotation Fix für benutzerdefinierte Bewegungseffekte

Veröffentlicht am 10. April 2026
Der ultimative Unreal Engine GAS Teleport Rotation Fix für benutzerdefinierte Bewegungseffekte

Die Qual der Teleportations-Desynchronisation in der modernen Unreal Engine

Jeder Indie-Entwickler kennt den Moment, in dem der Netcode versagt. Man löst eine scheinbar einfache Teleport-Fähigkeit aus, der Charakter verschwindet, erscheint an den richtigen Koordinaten, starrt aber unerklärlicherweise gegen eine Wand statt auf den Gegner. Der Server glaubt, der Charakter blicke nach Norden, der Client prognostiziert Osten, und das gesamte Kampfsystem bricht unter der Desynchronisation zusammen. Wenn Sie das Gameplay Ability System (GAS) in Verbindung mit den neueren Bewegungsarchitekturen der Unreal Engine 5 verwenden, ist dieses Albtraumszenario unglaublich verbreitet.

Entwickler greifen natürlicherweise zu QueueInstantMovementEffect oder ScheduleInstantMovementEffect, um einen Charakter sofort über die Karte zu bewegen. Sie entdecken jedoch schnell einen eklatanten Architekturfehler: Diese Standardeffekte handhaben die Translation (Position) akribisch, ignorieren aber die Rotation komplett. Bei einem erzwungenen Teleport aktualisiert der Standardeffekt den Positionsvektor, lässt das Rotations-Quaternion jedoch unberührt, was zu schwerem Rubber-Banding oder visuellem Springen führt, sobald das Orientierungssystem die Kontrolle wieder übernimmt.

Dieser Leitfaden bietet einen umfassenden Schritt-für-Schritt-Fix für die GAS-Teleport-Rotation in der Unreal Engine. Wir tauchen tief in die Erstellung benutzerdefinierter Bewegungseffekte ein, manipulieren die Synchronisation des Simulationszustands und implementieren praxiserprobte Multiplayer-Networking-Methoden, um sicherzustellen, dass Ihre Fähigkeiten auf jedem Client perfekt funktionieren.

Die Ursache verstehen: Warum ignoriert GAS die Rotation beim Teleportieren?

Um den Fix zu verstehen, müssen Sie zunächst die Architektur des experimentellen Mover-Plugins verstehen, das anders arbeitet als die veraltete UCharacterMovementComponent. Das Mover-Plugin basiert auf einer kontinuierlichen, Tick-basierten Simulationsschleife. Bewegungseffekte sind als flüchtige Modifikationen dieser Schleife konzipiert – etwa das Anwenden eines physischen Impulses, das Ändern der Reibung oder das Hinzufügen eines Geschwindigkeitsvektors.

Wenn Sie AActor::TeleportTo aufrufen, aktualisieren Sie die Transformation der Root-Komponente gewaltsam auf Engine-Ebene. Die Physik-Engine respektiert dies sofort. Die Mover-Komponente arbeitet jedoch mit einem strikten Simulationszustand, der durch FMoverSyncState repräsentiert wird.

Wenn ein sofortiger Bewegungseffekt die physische Transformation des Actors ändert, es aber versäumt, den FMoverSyncState mit der exakten neuen Ausrichtung zu aktualisieren, überschreibt die Simulation die Rotation des Actors beim nächsten Tick einfach mit den veralteten Daten, die sie zuvor zwischengespeichert hatte. Die Position bleibt möglicherweise bestehen, wenn die Geschwindigkeit auf Null gesetzt wurde, aber die Rotation springt in den Ursprungszustand zurück. Genau deshalb scheitern die integrierten Sofort-Bewegungseffekte bei komplexen Teleportationsfähigkeiten, die eine spezifische Blickrichtung erfordern.

Schritt 1: Architektur eines benutzerdefinierten Fixed-Teleport-Effekts

Um einen robusten Fix für die GAS-Teleport-Rotation zu implementieren, können wir uns nicht auf die Standard-Engine-Funktionen verlassen. Wir müssen ein benutzerdefiniertes Bewegungseffekt-Struct entwerfen, das von FBaseMovementEffect erbt. Dieses Struct wird dem Simulationszustand explizit befehlen, unser neues Rotations-Quaternion zu akzeptieren und die zwischengespeicherten Werte zu verwerfen.

Zuerst definieren wir den Header für unseren neuen Effekt. Wir benötigen Variablen, die es Designern ermöglichen, die Zielposition und -rotation direkt aus einem Gameplay Ability Blueprint vorzugeben.

#pragma once

#include "CoreMinimal.h"
#include "MovementEffect.h"
#include "FixedTeleportEffect.generated.h"

/**
 * Ein benutzerdefinierter Bewegungseffekt, der sofortige Translation und Rotation handhabt,
 * ohne unter Mover-Zustands-Desynchronisation zu leiden.
 */
USTRUCT(BlueprintType)
struct FFixedTeleportEffect : public FBaseMovementEffect
{
    GENERATED_BODY()

public:
    // Die exakte Weltposition, zu der der Charakter teleportiert werden soll.
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Teleport Settings")
    FVector TargetLocation = FVector::ZeroVector;

    // Die gewünschte Weltrotation, in die der Charakter bei der Ankunft blicken soll.
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Teleport Settings")
    FRotator TargetRotation = FRotator::ZeroRotator;

    // Wenn wahr, ignoriert der Effekt TargetRotation und behält die aktuelle Blickrichtung des Actors bei.
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Teleport Settings")
    bool bUseActorRotation = false;

    // Die Kernfunktion, in der die Bewegungslogik und die Zustandssynchronisation stattfinden.
    virtual bool ApplyMovementEffect(FApplyMovementEffectParams& ApplyEffectParams, FMoverSyncState& OutputState) override;
};

Dieses Struct liefert die notwendigen Daten für die Backend-Simulation. Der Boolean bUseActorRotation ist besonders nützlich für Kurzstrecken-"Blink"-Fähigkeiten, bei denen der Charakter einfach nach vorne preschen soll, ohne seinen Blickwinkel zu ändern.

Schritt 2: Überschreiben von ApplyMovementEffect für vollständige Zustandskontrolle

Die Magie unseres Fixes passiert innerhalb der Funktion ApplyMovementEffect. Diese Funktion wird während des Simulationsschritts des Mover-Plugins aufgerufen. Sie erhält die aktuellen Simulationsparameter und erwartet von uns, dass wir den OutputState mutieren, um unsere physischen Änderungen widerzuspiegeln.

Implementieren wir dies in C++. Wir unterteilen dies in logische Phasen, beginnend mit der Validierung und der physischen Bewegung.

bool FFixedTeleportEffect::ApplyMovementEffect(FApplyMovementEffectParams& ApplyEffectParams, FMoverSyncState& OutputState)
{
    // 1. Validierung der Komponente und des Owners
    USceneComponent* UpdatedComponent = ApplyEffectParams.UpdatedComponent;
    if (!IsValid(UpdatedComponent))
    { 
        return false;
    }

    AActor* OwnerActor = UpdatedComponent->GetOwner();
    if (!IsValid(OwnerActor))
    { 
        return false;
    }

    // 2. Bestimmung der endgültigen Zielrotation basierend auf dem Designer-Flag
    const FRotator FinalTargetRotation = bUseActorRotation ? UpdatedComponent->GetComponentRotation() : TargetRotation;

    // 3. Ausführung des physischen Teleports auf Engine-Ebene
    if (OwnerActor->TeleportTo(TargetLocation, FinalTargetRotation))
    { 
        // 4. Extrahieren der verifizierten Position nach dem Teleport für die Simulation
        const FVector UpdatedLocation = UpdatedComponent->GetComponentLocation();

        // Weiter zur Zustandssynchronisation...

Die Funktion TeleportTo ist hier entscheidend. Sie bewegt den Actor sofort und stoppt alle anstehenden physikalischen Geschwindigkeiten, was verhindert, dass der Charakter das Momentum übernimmt, nachdem er am neuen Ort erscheint. Dies ist jedoch nur die physische Ebene; wir müssen nun die Simulationsebene aktualisieren.

Schritt 3: Erzwingen der Rotations-Anerkennung im Output Sync State

Nun erreichen wir die kritischste Phase des Fixes. Hier scheitern viele Implementierungen aus der Community.

Oft teleportieren Entwickler den Actor erfolgreich, versäumen es aber, die neue Rotation in die OutputState.SyncStateCollection zu schreiben. Viele Entwickler setzen die Rotation während des Sync-State-Updates versehentlich auf Null, indem sie FRotator::ZeroRotator übergeben. Dies ist ein massiver Fehler, der Rotations-Desyncs zwischen Clients garantiert.

Wir müssen den FMoverDefaultSyncState extrahieren und unsere exakte FinalTargetRotation injizieren.

        // Abrufen oder Initialisieren des Standard-Sync-States aus der Output-Collection
        FMoverDefaultSyncState& OutputSyncState = OutputState.SyncStateCollection.FindOrAddMutableDataByType<FMoverDefaultSyncState>();
        
        // KRITISCHER FIX: Injizieren der aktualisierten Position UND der FinalTargetRotation.
        // Verwenden Sie hier NICHT FRotator::ZeroRotator.
        OutputSyncState.SetTransforms_WorldSpace(
            UpdatedLocation,
            FinalTargetRotation, 
            FVector::ZeroVector, // Lineare Geschwindigkeit zurücksetzen
            FVector::ZeroVector, // Winkelgeschwindigkeit zurücksetzen
            nullptr              // Bewegungsbasis nullen, da wir an einem neuen Ort sind
        );

Durch das explizite Zurücksetzen der Geschwindigkeitsvektoren auf Null gewährleisten wir eine saubere, statische Ankunft. Durch die Übergabe von nullptr an die Bewegungsbasis lösen wir den Charakter proaktiv von jeder beweglichen Plattform, auf der er zuvor stand, was bizarre räumliche Verschiebungen beim nächsten Tick verhindert.

Schritt 4: Invalidierung der Mover Blackboard Caches

Das Mover-Plugin verwendet ein robustes Blackboard-System (UMoverBlackboard), um aufwendige Berechnungen zwischenzuspeichern, wie etwa die Ergebnisse des letzten Floor-Line-Traces. Wenn Sie einen Charakter über die Karte teleportieren, werden diese zwischengespeicherten räumlichen Ergebnisse sofort ungültig.

Wenn Sie das Blackboard nicht invalidieren, könnte die Bewegungssimulation davon ausgehen, dass der Charakter immer noch auf einer beweglichen Plattform steht, die nun 10.000 Einheiten entfernt ist. Dies führt zu katastrophalen Koordinatenfehlern im nächsten Frame.

        // Zugriff auf das veränderbare Simulations-Blackboard
        if (UMoverBlackboard* SimBlackboard = ApplyEffectParams.MoverComp->GetSimBlackboard_Mutable())
        {
            // Erzwingen der Neuberechnung von Gravitation und Bodenprüfung im nächsten Frame
            SimBlackboard->Invalidate(CommonBlackboard::LastFloorResult);
            SimBlackboard->Invalidate(CommonBlackboard::LastFoundDynamicMovementBase);
        }

        // Senden eines benutzerdefinierten Events für die Gameplay Ability
        ApplyEffectParams.OutputEvents.Add(MakeShared<FTeleportSucceededEventData>());
        
        return true;
    }

    // Teleport fehlgeschlagen (z. B. in Geometrie steckengeblieben)
    return false;
}

Diese vollständige C++-Implementierung garantiert, dass sowohl die physische Actor-Ebene als auch der zugrunde liegende Netzwerk-Simulationszustand in Bezug auf die neue Position und – was entscheidend ist – die neue Rotation übereinstimmen.

Die versteckte Gefahr: Multiplayer-Zustands-Desynchronisation

Selbst mit einem mathematisch perfekten Bewegungseffekt bringen Multiplayer-Spiele das Chaos von Latenz und clientseitiger Vorhersage (Prediction) mit sich. Wenn ein Client eine Teleport-Fähigkeit aktiviert, sagt er den Bewegungseffekt lokal sofort voraus, um ein reaktionsschnelles Gefühl zu gewährleisten.

Der autoritative Server muss jedoch genau denselben FFixedTeleportEffect ausführen. Wenn der Client eine Rotation von 90 Grad vorhersagt, der Server aber aufgrund von Gleitkommadifferenzen oder Kollisionen 85 Grad berechnet, tritt ein Desync auf. Der Server wird den Client gewaltsam korrigieren, was ein auffälliges visuelles Ruckeln verursacht.

Sichern Ihrer Teleportationslogik mit einem robusten Backend

Die Handhabung von lokalem Networking und Physik-Vorhersage ist nur die halbe Miete. Wenn ein Spieler eine Fähigkeit nutzt, um in eine völlig neue Region zu teleportieren, muss diese Positionsänderung oft validiert und persistent gespeichert werden. Wenn der Server unmittelbar nach einem Teleport abstürzt, wo loggt sich der Spieler wieder ein?

Der Aufbau einer Infrastruktur für Echtzeit-Speicherungen und sichere Datenbanktransaktionen erfordert normalerweise Wochen dedizierter Entwicklungsarbeit. Mit horizOn sind diese Dienste vorkonfiguriert. Sie erhalten eine Backend-Infrastruktur der Enterprise-Klasse direkt einsatzbereit, sodass Sie Ihren Serverzustand nahtlos in Echtzeit mit einer sicheren Datenbank synchronisieren können.

Debugging von Mover-Plugin-Zustands-Desyncs

Nutzen Sie den Visual Logger (VisLog), um zu diagnostizieren, ob Ihr Effekt auf beiden Seiten korrekt ausgeführt wird. Fügen Sie direkt in Ihre ApplyMovementEffect-Funktion Logging-Befehle ein, um die Zielposition und Blickrichtung visuell zu überprüfen. Wenn der Pfeil im Logger in einem Frame korrekt zeigt und im nächsten zurückspringt, ist dies der Beweis, dass der FMoverDefaultSyncState von einem konkurrierenden System überschrieben wurde.

5 essenzielle Best Practices für GAS-Bewegungseffekte

  1. Zwischengespeicherte räumliche Daten immer invalidieren: Wie gezeigt, müssen Floor- und Base-Caches gelöscht werden.
  2. Ziele serverseitig vor der Ausführung validieren: Vertrauen Sie niemals der vom Client angeforderten TargetLocation.
  3. Entkoppeln von Ausrichtung und Translation bei komplexen Bewegungen: Für kontinuierliche Fähigkeiten (wie Dashes) ist es oft besser, separate Effekte zu nutzen.
  4. Geschwindigkeiten nullen, um Rutschen zu verhindern: Erzwingen Sie beim Teleportieren immer eine Geschwindigkeit von Null.
  5. Wichtige Zustandsänderungen sicher replizieren: Achten Sie darauf, dass RPCs unter Netzlast korrekt ankommen.

Fazit zum Mover-Plugin und benutzerdefinierten Effekten

Das experimentelle Mover-Plugin ist ein gewaltiger Fortschritt für deterministische Multiplayer-Bewegung in der Unreal Engine, erfordert aber ein Umdenken. Die Zeiten, in denen man einfach SetActorLocation aufrief und hoffte, dass der Netzwerk-Treiber es richtet, sind vorbei. Durch die explizite Kontrolle über den FMoverSyncState garantieren Sie, dass Client, Server und Physik-Simulation auf derselben mathematischen Realität operieren.

Die Implementierung eines benutzerdefinierten Fixes für die GAS-Teleport-Rotation ist ein entscheidender Schritt bei der Beherrschung des modernen Unreal-Networkings. Es zwingt Sie dazu, die Pipeline der Engine tiefgreifend zu verstehen – von der Aktivierung der Fähigkeit bis zum finalen Komponenten-Update.