Hey there!
I have recently started working with LayeredMoves and have noticed a behaviour I’m unsure how to overcome without some hacky solutions, so I might as well ask how to approach this before I do that.
We use an ActionSystem that handles priorities and actions across different scopes (e.g. movement). Put simply, if we receive an action with a higher priority, we drop the current action (and properly clean up) and install the higher one (e.g. a “HitReaction” action).
In our system, I’m trying to implement a “DashAction”, that is basically just a wrapper around `FLayeredMove_Dash` (it derives from `FLayeredMove_LinearVelocity` with `Clone()`, `GetScriptStruct()`, `ToSimpleString()` and `AddReferencedObjects(..)` overriden).
What I want to achieve - when `DashAction` installs itself, we `QueueLayeredMove(…)` and when a higher priority action interrupts us - I want to instantly cancel an ongoing LayeredMove that still has some remaining time.
At first, I was planning on queueing a TSharedPtr<>, but it turned out that the Mover creates a copy of the LayeredMove, and the shared pointer I’m holding onto is basically useless. It seems like the Mover expects to receive a LayeredMove, but won’t allow you to edit/cancel it from the outside once queued/started.
What I tried at first:
(`EnterImpl(…)`, `ExitImpl()`, `UpdateImpl(…)` are our own ActionSystem virtual functions)
bool FDashAction::EnterImpl(const ActionSystem::FActionEnterContext& Context)
{
// "TSharedPtr<FLayeredMove_Dash> LayeredMove" is a member variable
LayeredMove = MakeShared<FLayeredMove_Dash>();
LayeredMove->Velocity = MoverComponent.GetOwner()->GetActorForwardVector() * 5000.0f;
LayeredMove->MixMode = EMoveMixMode::OverrideVelocity;
LayeredMove->DurationMs = 250.0f;
MoverComponent.QueueLayeredMove(LayeredMove);
return true;
}
And once we get interrupted:
void FDashAction::ExitImpl(const ActionSystem::FActionExitContext& Context)
{
if (Context.ExitType == ActionSystem::EActionExitType::Interrupt)
{
// A higher priority action interrupted us, let's cancel the ongoing LayeredMove
}
Super::ExitImpl(Context);
}
Ending my wrapper action when the LayeredMove naturally finishes:
ActionSystem::EActionUpdateResult FDashAction::UpdateImpl(float Dt)
{
auto* ActiveLayeredMove = MoverComponent.FindActiveLayeredMoveByType<FLayeredMove_Dash>();
return ActiveLayeredMove ? ActionSystem::EActionUpdateResult::Continue : ActionSystem::EActionUpdateResult::Done;
}
Therefore, the wrapper DashAction can end for 2 reasons: LayeredMove finished naturally (checking this inside the `UpdateImpl(…)`), or a higher priority arrived (we want to cancel the LayeredMove from within the `ExitImpl(..)` <- unsure how to properly do).
I’ve also tried finding it manually by its type and trying to edit it, but the function is const:
MoverComponent.FindActiveLayeredMoveByType<FLayeredMove_Dash>();
// definition
template <typename MoveT = FLayeredMoveBase UE_REQUIRES(std::is_base_of_v<FLayeredMoveBase, MoveT>)>
const MoveT* FindActiveLayeredMoveByType() const { return static_cast<const MoveT*>(FindActiveLayeredMoveByType(MoveT::StaticStruct())); }
Even when trying to `const_cast` it looks like the Mover SyncState has 2 copies of this struct and uses a double buffer to swap them each frame.
Therefore, I was thinking about some possible hacky solutions, like my derived DashLayeredMove receiving a pointer on creation to the wrapper DashAction and asking `DashAction->ShouldTerminate(..)` inside the `GenerateMove(…)` step, and by that, modifying `DurationMs=-1.0f` from within the LayeredMove, but that seemed pretty disgusting :D.
Thanks for your help!