244 lines
8.5 KiB
C++
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
|
|
}
|
|
}
|