前面我们完成了组件的绑定和判断条件后,我们就可以开始写攀岩的具体功能了。
首先当我们攀岩时,character应该是处于一个缩着的状态,那我们的碰撞范围也要相应的变小。
重载我们的OnMovementModeChanged,从CharacterMovementComponent中调用,通知角色移动模式已经改变。
virtual void OnMovementModeChanged(EMovementMode PreviousMovementMode, uint8 PreviousCustomMode) override;
DECLARE_DELEGATE(FOnEnterClimbState)
DECLARE_DELEGATE(FOnExitClimbState)
FOnEnterClimbState OnEnterClimbStateDelegate;
FOnExitClimbState OnExitClimbStateDelegate;
void UCustomMovementComponent::OnMovementModeChanged(EMovementMode PreviousMovementMode, uint8 PreviousCustomMode)
{
if (IsOrNotClimbingModel())
{
bOrientRotationToMovement = false;//将我们的旋转给取消(character的旋转取决于加速度的方向)
CharacterOwner->GetCapsuleComponent()->SetCapsuleHalfHeight(48.f);//将我们碰撞胶囊体范围变为一半
OnEnterClimbStateDelegate.ExecuteIfBound();
}
if (PreviousMovementMode == MOVE_Custom && PreviousCustomMode == ECustomMovementMode::MOVE_Climb)//当之前的模式为custom时
{
bOrientRotationToMovement = true;
CharacterOwner->GetCapsuleComponent()->SetCapsuleHalfHeight(96.f);
const FRotator DirtyRotation = UpdatedComponent->GetComponentRotation();//重置旋转
const FRotator CleanStandRotation = FRotator(0.f, DirtyRotation.Yaw, 0.f);
UpdatedComponent->SetRelativeRotation(CleanStandRotation);
StopMovementImmediately();//立即停止运动
OnExitClimbStateDelegate.ExecuteIfBound();
}
Super::OnMovementModeChanged(PreviousMovementMode, PreviousCustomMode);
}
重载运动更新
void UCustomMovementComponent::PhysCustom(float deltaTime, int32 Iterations)
{
if (IsOrNotClimbingModel())
{
PhysicsClimb(deltaTime, Iterations);
}
Super::PhysCustom(deltaTime, Iterations);
}
当我们开始攀爬时,我们的物理模式理应也要进入到我们的攀爬状态,所以创建我们的物理状态,将我们的飞行物理状态复制下来进行修改
void PhysicsClimb(float deltaTime, int32 Iterations);
void UCustomMovementComponent::PhysicsClimb(float deltaTime, int32 Iterations)
{
if (deltaTime < MIN_TICK_TIME)
{
return;
}
//Process all the Climbable surface info
TraceClimbableSurface();
ProcessClimbableSurfaceInfo();//获取攀爬信息
bool reachedFloor = CheckHasReachedFloor();
if (CheckShouldStopClimbing()|| CheckHasReachedFloor())//当character当地顶部或者接触到地面时
{
StopClimbing();
}
RestorePreAdditiveRootMotionVelocity();
if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity())
{
CalcVelocity(deltaTime, 0.f, true, MaxBreakClimbDeceleration);
}
ApplyRootMotionToVelocity(deltaTime);
FVector OldLocation = UpdatedComponent->GetComponentLocation();
const FVector Adjusted = Velocity * deltaTime;
FHitResult Hit(1.f);
//Handle climb rotation
SafeMoveUpdatedComponent(Adjusted, GetClimbRotation(deltaTime), true, Hit);
if (Hit.Time < 1.f)
{
//adjust and try again
HandleImpact(Hit, deltaTime, Adjusted);
SlideAlongSurface(Adjusted, (1.f - Hit.Time), Hit.Normal, Hit, true);
}
if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity())
{
Velocity = (UpdatedComponent->GetComponentLocation() - OldLocation) / deltaTime;
}
SnapMovementToClimbableSurfaces(deltaTime);
}
void UCustomMovementComponent::ProcessClimbableSurfaceInfo()
{
CurrentClimbableSurfaceLocation = FVector::ZeroVector;
CurrentClimbableSurfaceNormal = FVector::ZeroVector;
if (ClimbableSurfacesResults.IsEmpty()) return;
for (const FHitResult& TracedHitResult : ClimbableSurfacesResults)
{
CurrentClimbableSurfaceLocation += TracedHitResult.ImpactPoint;//获取碰撞点
CurrentClimbableSurfaceNormal += TracedHitResult.ImpactNormal;//获取碰撞点的法线
}
CurrentClimbableSurfaceLocation /= ClimbableSurfacesResults.Num();//从该数组中获取所以这些可能的位置
CurrentClimbableSurfaceNormal = CurrentClimbableSurfaceNormal.GetSafeNormal();//归一化
}
FQuat UCustomMovementComponent::GetClimbRotation(float DeltaTime)
{
const FQuat CurrentQuat = UpdatedComponent->GetComponentQuat();
if (HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocity())//当我们有root驱动人物运动时
{
return CurrentQuat;
}
const FQuat TargetQuat = FRotationMatrix::MakeFromX(-CurrentClimbableSurfaceNormal).ToQuat();//控制我们的旋转始终对着墙壁
return FMath::QInterpTo(CurrentQuat, TargetQuat, DeltaTime, 5.f);//从“当前”到“目标”的四元数插值。
}
void UCustomMovementComponent::SnapMovementToClimbableSurfaces(float DeltaTime)//控制character始终根据碰撞的方向进行移动
{
const FVector ComponentForward = UpdatedComponent->GetForwardVector();
const FVector ComponentLocation = UpdatedComponent->GetComponentLocation();
const FVector ProjectedCharacterToSurface =
(CurrentClimbableSurfaceLocation - ComponentLocation).ProjectOnTo(ComponentForward);
const FVector SnapVector = -CurrentClimbableSurfaceNormal * ProjectedCharacterToSurface.Length();
UpdatedComponent->MoveComponent(SnapVector * DeltaTime * MaxClimbSpeed,UpdatedComponent->GetComponentQuat(),true);
}
当物体到达顶部和地面时,我们要让character停止攀爬
bool UCustomMovementComponent::CheckShouldStopClimbing()
{
if (ClimbableSurfacesResults.IsEmpty()) return true;
const float DotResult = FVector::DotProduct(CurrentClimbableSurfaceNormal, FVector::UpVector);
const float DegreeDiff = FMath::RadiansToDegrees(FMath::Acos(DotResult));//获取点乘的角度
Debug::Print(FString::SanitizeFloat(DegreeDiff), FColor::Cyan,1);
if (DegreeDiff <= 60.f)
{
return true;
}
return false;
}
bool UCustomMovementComponent::CheckHasReachedFloor()
{
const FVector DownVector = -UpdatedComponent->GetUpVector();
const FVector StartOffset = DownVector * 50.f;
const FVector Start = UpdatedComponent->GetComponentLocation()+StartOffset;
const FVector End = Start + DownVector;
TArray<FHitResult> PossibleFloorHits = GetTraceClimbableSurface(Start, End, true,false);
if (PossibleFloorHits.IsEmpty()) return false;
for (const FHitResult& PossibleFloorHit : PossibleFloorHits)
{
const bool bFloorReached =
FVector::Parallel(PossibleFloorHit.ImpactNormal, FVector::UpVector) &&
GetUnrotatedClimbVelocity().Z < -10.f;//查看两个法向量是否几乎平行,这意味着它们之间的角度接近 0 度。
if (bFloorReached)
{
return true;
}
}
return false;
}
FVector UCustomMovementComponent::GetUnrotatedClimbVelocity() const
{
return UKismetMathLibrary::Quat_UnrotateVector(UpdatedComponent->GetComponentQuat(),Velocity);
}
完成之后,我们设置我们回到我们的character文件里设置上下移动
void HandleGroundMovementInput(const FInputActionValue& Value);
void HandleClimbMovementInput(const FInputActionValue& Value);
void AClimbingSystemCharacter::Move(const FInputActionValue& Value)
{
// input is a Vector2D
if(!CustomMovementComponent) return;
if(CustomMovementComponent->IsOrNotClimbingModel())//判断使用哪种模式
{
HandleClimbMovementInput(Value);
}
else
{
HandleGroundMovementInput(Value);
}
}
void AClimbingSystemCharacter::HandleGroundMovementInput(const FInputActionValue& Value)
{
const FVector2D MovementVector = Value.Get<FVector2D>();
if (Controller != nullptr)
{
// find out which way is forward
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
// get forward vector
const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
// get right vector
const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
// add movement
AddMovementInput(ForwardDirection, MovementVector.Y);
AddMovementInput(RightDirection, MovementVector.X);
}
}
void AClimbingSystemCharacter::HandleClimbMovementInput(const FInputActionValue& Value)
{
const FVector2D MovementVector = Value.Get<FVector2D>();
const FVector ForwardDirection = FVector::CrossProduct(
-CustomMovementComponent->GetClimbableSurfaceNormal(),
GetActorRightVector()
);
const FVector RightDirection = FVector::CrossProduct(
-CustomMovementComponent->GetClimbableSurfaceNormal(),
-GetActorUpVector()
);
// add movement
AddMovementInput(ForwardDirection, MovementVector.Y);
AddMovementInput(RightDirection, MovementVector.X);
}