I tried to make a video with what you need, please tell me if you need more input in it since I am not very familiar with this.
I play the animation at the end of the video and I actually never try to play montage outside of climbing so it looks like the root motion work as attended and the issue comme from my code instead ? If this is the case i feel very dumb to screeming at the wrong tree for so long.
I am using C++ which basically shoot a line trace in front of the character and if it hit a surface which can be climb, it pass my movement mode to a custom climbing mode. When climbing it check if the character is at the top or bottom of the wall. If it’s at the top it will stop the climbing move mode and play the anim montage of climbing the ledge.
My custom physic:
void UHoloCharacterMovementComponent::PhysClimbing(float deltaTime, int32 Iterations)
{
if (deltaTime < MIN_TICK_TIME)
{
return;
}
//Calculate surfaces normals
ComputeSurfaceInfo();
if (ShouldStopClimbing() || ClimbDownToFloor())
{
// Stop climbing
wantsToClimb = false;
SetMovementMode(EMovementMode::MOVE_Falling);
StartNewPhysics(deltaTime, Iterations);
return;
}
//Calculate velocity
RestorePreAdditiveRootMotionVelocity();
if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity())
{
constexpr float friction = 0.0f;
constexpr bool bFluid = false;
CalcVelocity(deltaTime, friction, false, brakingDecelerationClimbing);
}
ApplyRootMotionToVelocity(deltaTime);
const FVector oldLocation = UpdatedComponent->GetComponentLocation();
MoveAlongClimbingSurface(deltaTime);
TryClimbUpLedge();
if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity())
{
Velocity = (UpdatedComponent->GetComponentLocation() - oldLocation) / deltaTime;
}
else
{
FTransform rootMotionTransform = RootMotionParams.GetRootMotionTransform();
FVector newLocation = UpdatedComponent->GetComponentLocation() + rootMotionTransform.GetTranslation();
MoveUpdatedComponent(newLocation, UpdatedComponent->GetComponentRotation(), true);
}
SnapToClimbingSurface(deltaTime);
}
The code which check if the character can play the climb up ledge montage:
bool UHoloCharacterMovementComponent::TryClimbUpLedge() const
{
if (ownerHoloCharacter->IsPlayingClimbUpLedgeMontage())
{
return false;
}
const float upSpeed = FVector::DotProduct(Velocity, UpdatedComponent->GetUpVector());
const bool isMovingUp = upSpeed >= maxClimbingSpeed / 3;
if (isMovingUp && HasReachedEdge() && CanMoveToLedgeClimbLocation())
{
const FRotator standRotation = FRotator(0, UpdatedComponent->GetComponentRotation().Yaw, 0);
UpdatedComponent->SetRelativeRotation(standRotation);
ownerHoloCharacter->PlayClimbUpLedgeMontage();
return true;
}
return false;
}
bool UHoloCharacterMovementComponent::HasReachedEdge() const
{
const UCapsuleComponent* capsule = CharacterOwner->GetCapsuleComponent();
const float traceDistance = capsule->GetUnscaledCapsuleRadius() * 2.5f;
return !EyeHeightTrace(traceDistance);
}
bool UHoloCharacterMovementComponent::IsLocationWalkable(const FVector& checkLocation) const
{
const FVector checkEnd = checkLocation + (FVector::DownVector * 250.f);
DrawDebugLine(GetWorld(), checkLocation, checkEnd, FColor::Green, false, 30000, 0, 1);
FHitResult ledgeHit;
const bool bHitLedgeGround = GetWorld()->LineTraceSingleByChannel(ledgeHit, checkLocation, checkEnd,
ECC_WorldStatic, climbColQueryParams);
return bHitLedgeGround && ledgeHit.Normal.Z >= GetWalkableFloorZ();
}
bool UHoloCharacterMovementComponent::CanMoveToLedgeClimbLocation() const
{
const FVector verticalOffset = FVector::UpVector * 160.f;
const FVector horizontalOffset = UpdatedComponent->GetForwardVector() * 120.f;
const FVector checkLocation = UpdatedComponent->GetComponentLocation() + horizontalOffset + verticalOffset;
if (!IsLocationWalkable(checkLocation))
{
return false;
}
FHitResult capsuleHit;
const FVector capsuleStartCheck = checkLocation - horizontalOffset;
const UCapsuleComponent* capsule = CharacterOwner->GetCapsuleComponent();
const bool blocked = GetWorld()->SweepSingleByChannel(capsuleHit, capsuleStartCheck, checkLocation,
FQuat::Identity, ECC_WorldStatic, capsule->GetCollisionShape(), climbColQueryParams);
return !blocked;
}