Files
2026-03-03 01:23:02 +08:00

244 lines
8.5 KiB
C++

// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Mover/Zipline/GMS_ZipliningMode.h"
#include "MoverComponent.h"
#include "DefaultMovementSet/Settings/CommonLegacyMovementSettings.h"
#include "Kismet/KismetSystemLibrary.h"
#include "MoveLibrary/MovementUtils.h"
#include "Mover/Zipline/GMS_ZiplineInterface.h"
#include "Mover/Zipline/GMS_ZiplineModeTransition.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_ZipliningMode)
// FGMS_ZipliningState //////////////////////////////
FMoverDataStructBase* FGMS_ZipliningState::Clone() const
{
FGMS_ZipliningState* CopyPtr = new FGMS_ZipliningState(*this);
return CopyPtr;
}
bool FGMS_ZipliningState::NetSerialize(FArchive& Ar, UPackageMap* Map, bool& bOutSuccess)
{
Super::NetSerialize(Ar, Map, bOutSuccess);
Ar << ZiplineActor;
Ar.SerializeBits(&bIsMovingAtoB, 1);
bOutSuccess = true;
return true;
}
void FGMS_ZipliningState::ToString(FAnsiStringBuilderBase& Out) const
{
Super::ToString(Out);
Out.Appendf("ZiplineActor: %s\n", *GetNameSafe(ZiplineActor));
Out.Appendf("IsMovingAtoB: %d\n", bIsMovingAtoB);
}
bool FGMS_ZipliningState::ShouldReconcile(const FMoverDataStructBase& AuthorityState) const
{
const FGMS_ZipliningState* AuthorityZiplineState = static_cast<const FGMS_ZipliningState*>(&AuthorityState);
return (ZiplineActor != AuthorityZiplineState->ZiplineActor) ||
(bIsMovingAtoB != AuthorityZiplineState->bIsMovingAtoB);
}
void FGMS_ZipliningState::Interpolate(const FMoverDataStructBase& From, const FMoverDataStructBase& To, float Pct)
{
const FGMS_ZipliningState* FromState = static_cast<const FGMS_ZipliningState*>(&From);
const FGMS_ZipliningState* ToState = static_cast<const FGMS_ZipliningState*>(&To);
ZiplineActor = ToState->ZiplineActor;
bIsMovingAtoB = ToState->bIsMovingAtoB;
}
// UGMS_ZipliningMode //////////////////////////////
UGMS_ZipliningMode::UGMS_ZipliningMode(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
Transitions.Add(CreateDefaultSubobject<UGMS_ZiplineEndTransition>(TEXT("ZiplineEndTransition")));
}
#if ENGINE_MINOR_VERSION >=6
void UGMS_ZipliningMode::GenerateMove_Implementation(const FMoverTickStartData& StartState, const FMoverTimeStep& TimeStep, FProposedMove& OutProposedMove) const
#else
void UGMS_ZipliningMode::OnGenerateMove(const FMoverTickStartData& StartState, const FMoverTimeStep& TimeStep, FProposedMove& OutProposedMove) const
#endif
{
UMoverComponent* MoverComp = GetMoverComponent();
// Ziplining is just following a path from A to B, so all movement is handled in OnSimulationTick
OutProposedMove = FProposedMove();
}
#if ENGINE_MINOR_VERSION >=6
void UGMS_ZipliningMode::SimulationTick_Implementation(const FSimulationTickParams& Params, FMoverTickEndData& OutputState)
#else
void UGMS_ZipliningMode::OnSimulationTick(const FSimulationTickParams& Params, FMoverTickEndData& OutputState)
#endif
{
// Are we continuing a move or starting fresh?
const FGMS_ZipliningState* StartingZipState = Params.StartState.SyncState.SyncStateCollection.FindDataByType<FGMS_ZipliningState>();
FMoverDefaultSyncState& OutputSyncState = OutputState.SyncState.SyncStateCollection.FindOrAddMutableDataByType<FMoverDefaultSyncState>();
FGMS_ZipliningState& OutZipState = OutputState.SyncState.SyncStateCollection.FindOrAddMutableDataByType<FGMS_ZipliningState>();
USceneComponent* UpdatedComponent = Params.MovingComps.UpdatedComponent.Get();
UMoverComponent* MoverComp = Params.MovingComps.MoverComponent.Get();
AActor* MoverActor = MoverComp->GetOwner();
USceneComponent* StartPoint = nullptr;
USceneComponent* EndPoint = nullptr;
FVector ZipDirection;
FVector FlatFacingDir;
const float DeltaSeconds = Params.TimeStep.StepMs * 0.001f;
FVector ActorOrigin;
FVector BoxExtent;
MoverActor->GetActorBounds(true, OUT ActorOrigin, OUT BoxExtent);
const FVector ActorToZiplineOffset = MoverComp->GetUpDirection() * BoxExtent.Z;
if (!StartingZipState)
{
// There is no existing zipline state... so let's find the target
// A) teleport to the closest starting point, set the zip direction
// B) choose the appropriate facing direction
// C) choose the appropriate initial velocity
TArray<AActor*> OverlappingActors;
MoverComp->GetOwner()->GetOverlappingActors(OUT OverlappingActors);
for (AActor* CandidateActor : OverlappingActors)
{
bool bIsZipline = UKismetSystemLibrary::DoesImplementInterface(CandidateActor, UGMS_ZiplineInterface::StaticClass());
if (bIsZipline)
{
const FVector MoverLoc = UpdatedComponent->GetComponentLocation();
USceneComponent* ZipPointA = IGMS_ZiplineInterface::Execute_GetStartComponent(CandidateActor);
USceneComponent* ZipPointB = IGMS_ZiplineInterface::Execute_GetEndComponent(CandidateActor);
if (FVector::DistSquared(ZipPointA->GetComponentLocation(), MoverLoc) < FVector::DistSquared(ZipPointB->GetComponentLocation(), MoverLoc))
{
OutZipState.bIsMovingAtoB = true;
StartPoint = ZipPointA;
EndPoint = ZipPointB;
}
else
{
OutZipState.bIsMovingAtoB = false;
StartPoint = ZipPointB;
EndPoint = ZipPointA;
}
ZipDirection = (EndPoint->GetComponentLocation() - StartPoint->GetComponentLocation()).GetSafeNormal();
const FVector WarpLocation = StartPoint->GetComponentLocation() - ActorToZiplineOffset;
FlatFacingDir = FVector::VectorPlaneProject(ZipDirection, MoverComp->GetUpDirection()).GetSafeNormal();
OutZipState.ZiplineActor = CandidateActor;
UpdatedComponent->GetOwner()->TeleportTo(WarpLocation, FlatFacingDir.ToOrientationRotator());
break;
}
}
// If we were unable to find a valid target zipline, refund all the time and let the actor fall
if (!StartPoint || !EndPoint)
{
FName DefaultAirMode = DefaultModeNames::Falling;
if (UCommonLegacyMovementSettings* LegacySettings = MoverComp->FindSharedSettings_Mutable<UCommonLegacyMovementSettings>())
{
DefaultAirMode = LegacySettings->AirMovementModeName;
}
OutputState.MovementEndState.NextModeName = DefaultModeNames::Falling;
OutputState.MovementEndState.RemainingMs = Params.TimeStep.StepMs;
return;
}
}
else
{
check(StartingZipState->ZiplineActor);
OutZipState = *StartingZipState;
USceneComponent* ZipPointA = IGMS_ZiplineInterface::Execute_GetStartComponent(StartingZipState->ZiplineActor);
USceneComponent* ZipPointB = IGMS_ZiplineInterface::Execute_GetEndComponent(StartingZipState->ZiplineActor);
if (StartingZipState->bIsMovingAtoB)
{
StartPoint = ZipPointA;
EndPoint = ZipPointB;
}
else
{
StartPoint = ZipPointB;
EndPoint = ZipPointA;
}
ZipDirection = (EndPoint->GetComponentLocation() - StartPoint->GetComponentLocation()).GetSafeNormal();
FlatFacingDir = FVector::VectorPlaneProject(ZipDirection, MoverComp->GetUpDirection()).GetSafeNormal();
}
// Now let's slide along the zipline
const FVector StepStartPos = UpdatedComponent->GetComponentLocation() + ActorToZiplineOffset;
const FVector DesiredEndPos = StepStartPos + (ZipDirection * MaxSpeed * DeltaSeconds); // TODO: Make speed more dynamic
FVector ActualEndPos = FMath::ClosestPointOnSegment(DesiredEndPos,
StartPoint->GetComponentLocation(),
EndPoint->GetComponentLocation());
bool bWillReachEndPosition = (ActualEndPos - EndPoint->GetComponentLocation()).IsNearlyZero();
FVector MoveDelta = ActualEndPos - StepStartPos;
FMovementRecord MoveRecord;
MoveRecord.SetDeltaSeconds(DeltaSeconds);
if (!MoveDelta.IsNearlyZero())
{
FHitResult Hit(1.f);
UMovementUtils::TrySafeMoveUpdatedComponent(Params.MovingComps, MoveDelta, FlatFacingDir.ToOrientationQuat(), true, Hit, ETeleportType::None, MoveRecord);
}
const FVector FinalLocation = UpdatedComponent->GetComponentLocation();
const FVector FinalVelocity = MoveRecord.GetRelevantVelocity();
OutputSyncState.SetTransforms_WorldSpace(FinalLocation,
UpdatedComponent->GetComponentRotation(),
FinalVelocity,
nullptr); // no movement base
UpdatedComponent->ComponentVelocity = FinalVelocity;
if (bWillReachEndPosition)
{
FName DefaultAirMode = DefaultModeNames::Falling;
if (UCommonLegacyMovementSettings* LegacySettings = MoverComp->FindSharedSettings_Mutable<UCommonLegacyMovementSettings>())
{
DefaultAirMode = LegacySettings->AirMovementModeName;
}
OutputState.MovementEndState.NextModeName = DefaultAirMode;
// TODO: If we reach the end position early, we should refund the remaining time
}
}