Ledge Up

Root Motion Montage


Calling Montage from C++
Upwards Movment check
Reached Ledge check

Ledge Traversability check



Last updated







Last updated
// ZCCharacterMovementComponent.h
private:
// ...
bool TryClimbUpLedge();// ZCCharacterMovementComponent.cpp
void UZCCharacterMovementComponent::PhysClimbing(float DeltaTime, int32 Iterations)
{
// ...
MoveAlongClimbingSurface(DeltaTime);
TryClimbUpLedge();
if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity())
Velocity = (UpdatedComponent->GetComponentLocation() - OldLocation) / DeltaTime;
SnapToClimbingSurface(DeltaTime);
}// ZCCharacterMovementComponent.h
private:
// ...
bool HasReachedLedge() const;
bool IsLedgeWalkable(const FVector& LocationToCheck) const;
bool CanMoveToLedgeClimbLocation() const;
UPROPERTY(Category = "Character Movement: Climbing", EditDefaultsOnly)
UAnimMontage* LedgeClimbMontage;
UPROPERTY()
UAnimInstance* AnimInstance;// ZCCharacterMovementComponent.cpp
void UZCCharacterMovementComponent::BeginPlay()
{
Super::BeginPlay();
AnimInstance = GetCharacterOwner()->GetMesh()->GetAnimInstance();
// ...
}// ZCCharacterMovementComponent.cpp
bool UZCCharacterMovementComponent::TryClimbUpLedge()
{
if (!AnimInstance || !LedgeClimbMontage)
{
return false;
}
if (AnimInstance->Montage_IsPlaying(LedgeClimbMontage))
{
return false;
}
const float UpSpeed = FVector::DotProduct(Velocity.GetSafeNormal(), UpdatedComponent->GetUpVector());
const bool bIsMovingUp = UpSpeed > 0;
if (bIsMovingUp /*&& HasReachedLedge() && CanMoveToLedgeClimbLocation()*/)
{
const FRotator StandRotation = FRotator(0, UpdatedComponent->GetComponentRotation().Yaw, 0);
UpdatedComponent->SetRelativeRotation(StandRotation);
AnimInstance->Montage_Play(LedgeClimbMontage);
bIsInLedgeClimb = true;
return true;
}
return false;
}// ZCCharacterMovementComponent.cpp
bool UZCCharacterMovementComponent::EyeHeightTrace(const float TraceDistance) const
{
FHitResult UpperEdgeHit;
// UpdatedComponent is at the location of the MovementComponent (middle of the character)
const FVector ComponentOrigin = UpdatedComponent->GetComponentLocation();
// ...
// old
//const FVector EyeHeight = ComponentOrigin + (UpdatedComponent->GetUpVector() * GetCharacterOwner()->BaseEyeHeight);
// new, keeping the eye height equal to the un-shrunk capsul
const ACharacter* Owner = GetCharacterOwner();
// Feel free to add/remove height from BaseEyeHeight to fit your root motion animation best.
const float BaseEyeHeight = Owner ? Owner->BaseEyeHeight : 1;
const float EyeHeightOffset = IsClimbing() ? BaseEyeHeight + CollisionCapsulClimbingShinkAmount : BaseEyeHeight;
// ...
// extend it out
const FVector End = EyeHeight + (UpdatedComponent->GetForwardVector() * TraceDistance);
return GetWorld()->LineTraceSingleByChannel(UpperEdgeHit, EyeHeight, End, ECC_WorldStatic, ClimbQueryParams);
}// ZCCharacterMovementComponent.cpp
bool UZCCharacterMovementComponent::HasReachedLedge() const
{
const float ShapeSweepEdge = CollisionCapsulRadius + CollisionCapsulForwardOffset;
return !EyeHeightTrace(ShapeSweepEdge);
}
bool UZCCharacterMovementComponent::TryClimbUpLedge()
{
// ...
if (bIsMovingUp && HasReachedLedge() /*&& CanMoveToLedgeClimbLocation()*/)
{
// ...
}
// ...
}// Some code
bool UZCCharacterMovementComponent::CanMoveToLedgeClimbLocation() const
{
// Change scalars into values that fit your root motion animation's distance best.
// These work for mine so that the animation ends at the ledge edge
const FVector VerticalOffset = FVector::UpVector * 200.f;
const FVector HorizontalOffset = UpdatedComponent->GetForwardVector() * 120.f;
const FVector LocationToCheck = UpdatedComponent->GetComponentLocation() + HorizontalOffset + VerticalOffset;
if (!IsLedgeWalkable(LocationToCheck))
{
return false;
}
FHitResult CapsulHit;
const FVector CapsulStartCheck = LocationToCheck - HorizontalOffset;
const UCapsuleComponent* Capsule = CharacterOwner->GetCapsuleComponent();
const bool bBlocked = GetWorld()->SweepSingleByChannel(CapsulHit, CapsulStartCheck, LocationToCheck, FQuat::Identity, ECC_WorldStatic, Capsule->GetCollisionShape(), ClimbQueryParams);
return !bBlocked;
}
bool UZCCharacterMovementComponent::IsLedgeWalkable(const FVector& LocationToCheck) const
{
// magic number just makes it so we raycast reasonable far enough to hit something
const FVector CheckEnd = LocationToCheck + (FVector::DownVector * 250.f);
FHitResult LedgeHit;
const bool bHitLedgeGround = GetWorld()->LineTraceSingleByChannel(LedgeHit, LocationToCheck, CheckEnd, ECC_WorldStatic, ClimbQueryParams);
return bHitLedgeGround && LedgeHit.Normal.Z >= GetWalkableFloorZ();
}// ZCCharacterMovementComponent.cpp
FQuat UZCCharacterMovementComponent::GetSmoothClimbingRotation(float DeltaTime) const
{
const FQuat Current = UpdatedComponent->GetComponentQuat();
// new, bail early if we're in root motion
if (HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocity())
{
return Current;
}
const FQuat Target = FRotationMatrix::MakeFromX(-CurrentClimbingNormal).ToQuat();
return FMath::QInterpTo(Current, Target, DeltaTime, ClimbingRotationSpeed);
}