Powrót do Bloga

Kompletna poprawka rotacji teleportu w Unreal Engine GAS dla niestandardowych efektów ruchu

Opublikowano 10 kwietnia 2026
Kompletna poprawka rotacji teleportu w Unreal Engine GAS dla niestandardowych efektów ruchu

Udręka desynchronizacji teleportacji we współczesnym Unreal Engine

Każdy deweloper indie zna ten moment, w którym kod sieciowy go zawodzi. Aktywujesz pozornie prostą umiejętność teleportacji, Twoja postać znika, pojawia się we właściwych współrzędnych, ale w niewytłumaczalny sposób stoi twarzą do ściany zamiast do przeciwnika. Serwer uważa, że postać patrzy na północ, klient przewiduje, że patrzy na wschód, a cały system walki rozpada się pod wpływem desynchronizacji. Jeśli używasz Gameplay Ability System (GAS) w połączeniu z nowszymi architekturami ruchu w Unreal Engine 5, ten koszmarny scenariusz jest niezwykle powszechny.

Deweloperzy naturalnie sięgają po QueueInstantMovementEffect lub ScheduleInstantMovementEffect, aby natychmiast przemieścić postać na mapie. Szybko jednak odkrywają rażącą wadę architektoniczną: te domyślne efekty skrupulatnie obsługują translację (pozycję), ale całkowicie ignorują rotację. Kiedy wymuszasz teleportację, standardowy efekt natychmiastowy aktualizuje wektor pozycji, ale pozostawia kwaternion rotacji nienaruszony, co prowadzi do poważnego efektu gumowania (rubber-banding) lub wizualnych przeskoków, gdy system orientacji odzyskuje kontrolę.

Ten przewodnik dostarczy kompleksową, krok po kroku poprawkę rotacji teleportu w Unreal Engine GAS. Zagłębimy się w tworzenie niestandardowych efektów ruchu, manipulowanie synchronizacją stanu symulacji i wdrażanie sprawdzonych w boju praktyk sieciowych dla trybu wieloosobowego, aby zapewnić, że Twoje umiejętności działają idealnie na każdym kliencie.

Zrozumienie przyczyny źródłowej: Dlaczego GAS ignoruje rotację podczas teleportacji?

Aby zrozumieć rozwiązanie, musisz najpierw poznać architekturę eksperymentalnej wtyczki Mover, która działa inaczej niż starszy UCharacterMovementComponent. Wtyczka Mover opiera się na ciągłej pętli symulacji opartej na tickach. Efekty ruchu są zaprojektowane jako przejściowe modyfikacje tej pętli — stosowanie impulsu fizycznego, modyfikowanie tarcia lub dodawanie wektora prędkości.

Kiedy wywołujesz AActor::TeleportTo, wymuszasz aktualizację transformacji komponentu głównego (root) na poziomie silnika. Silnik fizyki respektuje to natychmiast. Jednak komponent Mover działa na ścisłym stanie symulacji reprezentowanym przez FMoverSyncState.

Jeśli natychmiastowy efekt ruchu modyfikuje fizyczną transformację aktora, ale nie zaktualizuje FMoverSyncState o nową orientację, symulacja po prostu nadpisze rotację aktora w następnym ticku starymi danymi, które miała wcześniej w pamięci podręcznej. Pozycja może zostać zachowana, jeśli prędkość zostanie wyzerowana, ale rotacja przeskoczy z powrotem do punktu wyjścia. To jest właśnie powód, dla którego wbudowane natychmiastowe efekty ruchu zawodzą w przypadku złożonych umiejętności teleportacji wymagających określonego kierunku patrzenia.

Krok 1: Architektura niestandardowego efektu poprawionej teleportacji

Aby wdrożyć solidną poprawkę rotacji teleportu w Unreal Engine GAS, nie możemy polegać na domyślnych funkcjach silnika. Musimy zaprojektować niestandardową strukturę efektu ruchu, która dziedziczy po FBaseMovementEffect. Ta struktura będzie jawnie nakazywać stanowi symulacji zaakceptowanie naszego nowego kwaternionu rotacji i odrzucenie zapisanych wartości.

Najpierw zdefiniujmy nagłówek dla naszego nowego efektu. Musimy udostępnić zmienne, które pozwolą projektantom dyktować docelową lokalizację i rotację bezpośrednio z Blueprintu Gameplay Ability.

#pragma once

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

/**
 * Niestandardowy efekt ruchu zaprojektowany do obsługi natychmiastowej translacji i rotacji
 * bez problemów z desynchronizacją stanu Mover.
 */
USTRUCT(BlueprintType)
struct FFixedTeleportEffect : public FBaseMovementEffect
{
    GENERATED_BODY()

public:
    // Dokładna lokalizacja w przestrzeni świata, do której ma zostać teleportowana postać.
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Teleport Settings")
    FVector TargetLocation = FVector::ZeroVector;

    // Pożądana rotacja w przestrzeni świata, w którą postać powinna być skierowana po przybyciu.
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Teleport Settings")
    FRotator TargetRotation = FRotator::ZeroRotator;

    // Jeśli prawda, efekt zignoruje TargetRotation i zachowa obecny kierunek patrzenia aktora.
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Teleport Settings")
    bool bUseActorRotation = false;

    // Główna funkcja, w której odbywa się logika ruchu i synchronizacja stanu.
    virtual bool ApplyMovementEffect(FApplyMovementEffectParams& ApplyEffectParams, FMoverSyncState& OutputState) override;
};

Ta struktura dostarcza niezbędny ładunek danych dla symulacji backendowej. Zmienna logiczna bUseActorRotation jest szczególnie przydatna dla umiejętności typu „Blink” o krótkim zasięgu, gdzie postać powinna po prostu skoczyć do przodu bez zmiany kierunku wzroku.

Krok 2: Nadpisywanie ApplyMovementEffect dla pełnej kontroli stanu

Magia naszej poprawki rotacji teleportu w Unreal Engine GAS dzieje się wewnątrz funkcji ApplyMovementEffect. Funkcja ta jest wywoływana podczas kroku symulacji wtyczki Mover. Otrzymuje ona aktualne parametry symulacji i oczekuje od nas zmodyfikowania OutputState, aby odzwierciedlić nasze fizyczne zmiany.

Napiszmy implementację w C++. Podzielimy to na logiczne fazy, zaczynając od walidacji i fizycznego ruchu.

bool FFixedTeleportEffect::ApplyMovementEffect(FApplyMovementEffectParams& ApplyEffectParams, FMoverSyncState& OutputState)
{
    // 1. Walidacja komponentu i właściciela przed kontynuowaniem
    USceneComponent* UpdatedComponent = ApplyEffectParams.UpdatedComponent;
    if (!IsValid(UpdatedComponent))
    {
        return false;
    }

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

    // 2. Określenie ostatecznej rotacji docelowej na podstawie flagi projektanta
    const FRotator FinalTargetRotation = bUseActorRotation ? UpdatedComponent->GetComponentRotation() : TargetRotation;

    // 3. Wykonanie fizycznej teleportacji na poziomie silnika
    if (OwnerActor->TeleportTo(TargetLocation, FinalTargetRotation))
    {
        // 4. Pobranie zweryfikowanej lokalizacji po teleportacji, aby przekazać ją do symulacji
        const FVector UpdatedLocation = UpdatedComponent->GetComponentLocation();

        // Kontynuacja synchronizacji stanu...

Funkcja TeleportTo jest tutaj kluczowa. Natychmiast przesuwa aktora i zatrzymuje wszelkie oczekujące prędkości fizyczne, co zapobiega dziedziczeniu pędu przez postać po pojawieniu się w nowym miejscu. Jest to jednak tylko warstwa fizyczna; musimy teraz zaktualizować warstwę symulacji.

Krok 3: Wymuszanie uwzględnienia rotacji w wyjściowym stanie synchronizacji

Teraz dochodzimy do najbardziej krytycznej fazy poprawki rotacji teleportu w Unreal Engine GAS. To tutaj wiele implementacji społecznościowych zawodzi.

Często deweloperzy pomyślnie teleportują aktora, ale nie zapisują nowej rotacji w OutputState.SyncStateCollection. Jeśli przyjrzysz się uważnie typowym fragmentom kodu udostępnianym na forach, wielu programistów przypadkowo zeruje rotację podczas aktualizacji stanu synchronizacji, przekazując FRotator::ZeroRotator. Jest to ogromny błąd, który gwarantuje desynchronizację rotacji u klientów.

Musimy wyodrębnić FMoverDefaultSyncState i wstrzyknąć naszą dokładną FinalTargetRotation.

        // Pobranie lub zainicjowanie domyślnego stanu synchronizacji z kolekcji wyjściowej
        FMoverDefaultSyncState& OutputSyncState = OutputState.SyncStateCollection.FindOrAddMutableDataByType<FMoverDefaultSyncState>();
        
        // KRYTYCZNA POPRAWKA: Wstrzyknięcie zaktualizowanej lokalizacji ORAZ FinalTargetRotation.
        // NIE używaj tutaj FRotator::ZeroRotator, bo zepsujesz synchronizację sieciową.
        OutputSyncState.SetTransforms_WorldSpace(
            UpdatedLocation,
            FinalTargetRotation, 
            FVector::ZeroVector, // Reset prędkości liniowej, aby zapobiec ślizganiu po teleporcie
            FVector::ZeroVector, // Reset prędkości kątowej
            nullptr              // Unieważnienie bazy ruchu, ponieważ jesteśmy w nowej lokalizacji
        );

Poprzez jawne zresetowanie wektorów prędkości do zera zapewniamy czyste, statyczne przybycie. Przekazując nullptr jako bazę ruchu, proaktywnie odłączamy postać od jakiejkolwiek ruchomej platformy lub fizycznego aktora, na którym stała wcześniej, zapobiegając dziwnym przesunięciom przestrzennym w następnym ticku.

Krok 4: Unieważnianie pamięci podręcznej Blackboard w Mover

Wtyczka Mover wykorzystuje solidny system blackboard (UMoverBlackboard) do przechowywania wyników kosztownych obliczeń, takich jak wyniki ostatniego śledzenia podłoża (floor line trace). Kiedy teleportujesz postać przez mapę, te zapisane wyniki przestrzenne stają się natychmiast błędne.

Jeśli nie unieważnisz blackboardu, symulacja ruchu może założyć, że postać nadal stoi na ruchomej platformie, która znajduje się teraz 10 000 jednostek dalej. Skutkuje to katastrofalnym uszkodzeniem współrzędnych w następnej klatce, gdy symulacja próbuje ponownie zastosować prędkość odległej platformy do postaci.

        // Dostęp do modyfikowalnego blackboardu symulacji
        if (UMoverBlackboard* SimBlackboard = ApplyEffectParams.MoverComp->GetSimBlackboard_Mutable())
        {
            // Wymuszenie na systemie ruchu ponownego obliczenia grawitacji i sprawdzenia podłoża w następnej klatce
            SimBlackboard->Invalidate(CommonBlackboard::LastFloorResult);
            SimBlackboard->Invalidate(CommonBlackboard::LastFoundDynamicMovementBase);
        }

        // Rozgłoszenie niestandardowego zdarzenia, aby Gameplay Ability wiedziało, że efekt ruchu zakończył się sukcesem
        ApplyEffectParams.OutputEvents.Add(MakeShared<FTeleportSucceededEventData>());
        
        return true;
    }

    // Teleportacja nie powiodła się (np. utknięcie w geometrii)
    return false;
}

Ta kompletna implementacja w C++ gwarantuje, że zarówno warstwa fizyczna aktora, jak i bazowy stan symulacji sieciowej są zgodne co do nowej lokalizacji i, co kluczowe, nowej rotacji.

Ukryte niebezpieczeństwo: Desynchronizacja stanu w trybie wieloosobowym

Nawet przy matematycznie idealnym niestandardowym efekcie ruchu, gry wieloosobowe wprowadzają chaos opóźnień i predykcji po stronie klienta. Kiedy klient aktywuje umiejętność teleportacji, natychmiast przewiduje efekt ruchu lokalnie, aby zapewnić responsywność bez opóźnień.

Jednak autorytatywny serwer musi uruchomić dokładnie ten sam FFixedTeleportEffect i zgodzić się na końcowe transformacje. Jeśli klient przewiduje rotację 90 stopni na osi Z, a serwer obliczy 85 stopni z powodu rozbieżności zmiennoprzecinkowej lub równoległego zdarzenia kolizji, następuje desynchronizacja. Serwer siłowo skoryguje klienta, powodując zauważalny wizualny przeskok.

Debugowanie desynchronizacji stanu wtyczki Mover

Nawet przy bezbłędnej poprawce rotacji teleportu w Unreal Engine GAS, możesz napotkać subtelne anomalie wizualne podczas testów sieciowych z dużymi opóźnieniami. Gdy klient i serwer nie zgadzają się co do transformacji, Unreal Engine stosuje wygładzanie błędów, aby ukryć gwałtowną korektę przed graczem. Chociaż poprawia to wrażenia z gry, sprawia, że debugowanie jest niezwykle trudne.

Aby poprawnie zdiagnozować, czy Twój FFixedTeleportEffect wykonuje się poprawnie po obu stronach, musisz skorzystać z Visual Loggera (VisLog). Dodaj niestandardowe logowanie bezpośrednio wewnątrz funkcji ApplyMovementEffect:

UE_VLOG_LOCATION(OwnerActor, LogMover, Log, UpdatedLocation, 50.f, FColor::Green, TEXT("Teleport Final Location"));
UE_VLOG_ARROW(OwnerActor, LogMover, Log, UpdatedLocation, UpdatedLocation + FinalTargetRotation.Vector() * 100.f, FColor::Red, TEXT("Teleport Final Facing Direction"));

Rejestrując sesję Visual Loggera podczas testu wieloosobowego, możesz przechodzić klatka po klatce i wizualnie potwierdzić, kiedy i gdzie wektor rotacji został naruszony. Jeśli czerwona strzałka wskazuje poprawnie w klatce 10, ale wraca do poprzedniej rotacji w klatce 11, jest to absolutny dowód na to, że FMoverDefaultSyncState został nadpisany przez konkurujący system symulacji.

5 kluczowych najlepszych praktyk dla efektów ruchu GAS

Aby zapewnić wydajność i bezpieczeństwo sieciowe niestandardowych efektów ruchu, ściśle przestrzegaj tych sprawdzonych praktyk:

  1. Zawsze unieważniaj zapisane dane przestrzenne: Jak pokazano w naszym kodzie, za każdym razem, gdy manipulujesz transformacją postaci bezpośrednio, musisz wyczyścić pamięć podręczną podłoża i bazy w Mover Blackboard. Brak tego działania jest główną przyczyną błędów typu „wypadanie pod świat”.
  2. Waliduj cele po stronie serwera przed wykonaniem: Nigdy nie ufaj żądanej przez klienta lokalizacji TargetLocation. Zawsze wykonuj Sweep lub LineTrace po stronie serwera, aby upewnić się, że cel jest dostępny przed zastosowaniem FFixedTeleportEffect.
  3. Oddziel orientację od translacji dla złożonych ruchów: Chociaż nasz efekt teleportacji obsługuje oba te elementy, w przypadku umiejętności ciągłych (jak płynny atak z doskokiem) często lepiej jest użyć oddzielnych efektów.
  4. Zeruj prędkości, aby zapobiec ślizganiu: Podczas teleportacji zawsze wymuszaj zerowe prędkości liniowe i kątowe w wywołaniu SetTransforms_WorldSpace.
  5. Bezpiecznie replikuj kluczowe zmiany stanu: Gdy umiejętności wywołują ogromne zmiany stanu, standardowe RPC mogą czasem zawieść lub dotrzeć w złej kolejności pod dużym obciążeniem sieci.

Alternatywne podejścia: Root Motion vs. Natychmiastowy ruch

Chociaż natychmiastowy efekt ruchu jest matematycznie najczystszym rozwiązaniem dla prawdziwej teleportacji, niektórzy deweloperzy próbują rozwiązać ten problem za pomocą Root Motion poprzez mocno przyspieszony montaż animacji. Użycie montażu Root Motion z ekstremalnie wysokim tempem odtwarzania pozwala danym animacji sterować transformacją, co systemy GAS i Mover naturalnie rozumieją i synchronizują.

Jednak to podejście wprowadza poważne wady. Obliczanie ekstrakcji root motion dla 1-klatkowej teleportacji jest marnotrawstwem zasobów obliczeniowych. Co więcej, root motion implikuje fizyczną podróż przez przestrzeń między punktem początkowym a docelowym. Nawet przy dużych prędkościach bryła kolizyjna postaci może zahaczyć o niewidoczną geometrię, powodując przerwanie teleportacji w trakcie lotu.

Dlatego w przypadku prawdziwej, natychmiastowej podróży z punktu do punktu, należy polegać wyłącznie na architekturze niestandardowego FBaseMovementEffect, którą zbudowaliśmy w tym przewodniku.

Podsumowanie dotyczące wtyczki Mover i niestandardowych efektów

Eksperymentalna wtyczka Mover stanowi ogromny krok naprzód w sposobie, w jaki Unreal Engine obsługuje deterministyczny ruch w trybie wieloosobowym, ale wymaga zmiany paradygmatu w pisaniu logiki umiejętności. Czasy prostego wywoływania SetActorLocation z nadzieją, że stary sterownik sieciowy to ogarnie, dobiegły końca. Przejmując jawną, ręczną kontrolę nad FMoverSyncState, gwarantujesz, że Twój klient, autorytatywny serwer i symulacja fizyki silnika działają na dokładnie tej samej matematycznej rzeczywistości.

Wdrożenie niestandardowej poprawki rotacji teleportu w Unreal Engine GAS to krytyczny rytuał przejścia podczas opanowywania nowoczesnej sieci w Unrealu. Zmusza to do głębokiego zrozumienia potoku silnika — od początkowej aktywacji Gameplay Ability, przez tick symulacji, aż po końcową aktualizację transformacji komponentu.