I wrote code to mimic grabbing mechanic from Fall Guys. To preface, I am not willing to swap the solution with a physics based one. I have found that Velocity calculated by pending added force will be completely be negated by ApplyVelocityBraking
resulting in standstill. This is stange because AddForce
is the recommended method over AddImpulse when choosing to add velocity over time.
Doc: An impulse is an instantaneous force, usually applied once. If you want to continually apply * forces each frame, use AddForce().
Any help with this is appreciated. Thank you
void UMyGameCharacterMovementComponent::InitializeComponent()
{
Super::InitializeComponent();
_Character = Cast<AMyGameCharacter>(GetOwner());
}
void UMyGameCharacterMovementComponent::BeginPlay()
{
Super::BeginPlay();
_Physics = UCourageTypeRegistrySubsystem::GetObjectOfType<UMyGamePhysics>();
}
UMyGameCharacterMovementComponent::UMyGameCharacterMovementComponent(const class FObjectInitializer& Init)
: Super(Init)
{
bWantsInitializeComponent = true;
PrimaryComponentTick.bCanEverTick = true;
}
bool UMyGameCharacterMovementComponent::Grab(AMyGameCharacter* Source)
{
if (Source->Movement)
{
_GrabbedSourceMovement = Source->Movement;
_GrabbedDistance = Source->Movement->GrabDistance;
return true;
}
return false;
}
bool UMyGameCharacterMovementComponent::ReleaseGrab()
{
if (_GrabbedSourceMovement)
{
AddImpulse(-_GrabbedForce * Mass);
_GrabbedForce = { 0, 0, 0 };
_GrabbedSourceMovement = nullptr;
return true;
}
return false;
}
void UMyGameCharacterMovementComponent::ApplyVelocityBraking(float DeltaTime, float Friction, float BrakingDeceleration)
{
// Problem occurs due to ApplyVelocityBraking completely negating velocity caused by pending added force
// Simply overriding ApplyVelocityBraking so it does nothing results in sliding behavior when grabbing.
Super::ApplyVelocityBraking(DeltaTime, Friction, BrakingDeceleration);
}
void UMyGameCharacterMovementComponent::PerformMovement(float DeltaTime)
{
using namespace Courage;
if (_GrabbedSourceMovement)
{
_GrabbedForce = _GrabbedSourceMovement->_CalculateGrabForce(this);
// Factor in back the mass
AddImpulse(_GrabbedForce * Mass);
}
Super::PerformMovement(DeltaTime);
if (_GrabbedSourceMovement)
{
FVector SourcePosition(_GrabbedSourceMovement->GetActorLocation());
FVector TargetPosition(XY_(GetActorLocation(), SourcePosition.Z));
FVector ToSourceDispl = SourcePosition - TargetPosition;
if (ToSourceDispl.Length() > _GrabbedDistance)
{
FVector ToSourceDir = ToSourceDispl.GetSafeNormal();
FVector ToSourceDesiredDispl = _GrabbedDistance * ToSourceDir;
if (auto SourceOwner = _GrabbedSourceMovement->GetOwner())
{
SourceOwner->TeleportTo(TargetPosition + ToSourceDesiredDispl, SourceOwner->GetActorRotation());
}
}
_GrabbedForce = { 0, 0, 0 };
}
}
FVector UMyGameCharacterMovementComponent::_CalculateGrabForce(UMyGameCharacterMovementComponent* TargetMovement)
{
using namespace Courage;
FVector GrabbedForce{ 0, 0, 0 };
FVector TargetPosition(TargetMovement->GetActorFeetLocation());
FVector SourcePosition(GetActorFeetLocation());
FVector Displ = SourcePosition - TargetPosition;
FVector Dir = Displ.GetSafeNormal();
FVector DesiredDirection = Acceleration.GetSafeNormal();
FVector DesiredVelocity = DesiredDirection * GetMaxSpeed();
float OutDot = 0;
if (DotProduct(Dir, DesiredDirection, OutDot))
{
GrabbedForce = OutDot * DesiredVelocity;
}
else if(!IsNearlyZero(OutDot))
{
GrabbedForce = -OutDot * DesiredVelocity;
}
DrawDebugDirectionalArrow(GetWorld(), GetActorLocation(), GetActorLocation() + Acceleration, 10.0f, FColor::Blue, false, -1);
return GrabbedForce;
}