Unreal Engine 4 C++ PlatformerGame自定义角色控制器源代码分析

来源:互联网 发布:seo中代码优化 编辑:程序博客网 时间:2024/05/16 11:37

Unreal Engine 4 C++ PlatformerGame自定义角色控制器源代码分析

官方商店里有个PlatformerGame整个免费的游戏,是一个卷轴类的跑酷游戏。整个项目的角色控制器很有意思,可以跑、跳、滑行,很酷。这里来分析下它具体是如何实现的。

UCharacterMovementComponent这个类实现了角色控制器的大部分功能,PlatformerGame实现了一个自定义的MovmentComponent,叫UPlatformerPlayerMovementComp,它从标准的UCharacterMovementComponent继承,并扩展了它的行为。

1.构造函数

[cpp] view plain copy
  1. UPlatformerPlayerMovementComp::UPlatformerPlayerMovementComp(const class FPostConstructInitializeProperties& PCIP)   
  2.     : Super(PCIP)  
  3. {  
  4.     MaxAcceleration = 200.0f;  
  5.     BrakingDecelerationWalking = MaxAcceleration;  
  6.     MaxWalkSpeed = 900.0f;  
  7.   
  8.     SlideVelocityReduction = 30.0f;  
  9.     SlideHeight = 60.0f;  
  10.     SlideMeshRelativeLocationOffset = FVector(0.0f, 0.0f, 34.0f);  
  11.     bWantsSlideMeshRelativeLocationOffset = true;  
  12.     MinSlideSpeed = 200.0f;  
  13.     MaxSlideSpeed = MaxWalkSpeed + 200.0f;  
  14.   
  15.     ModSpeedObstacleHit = 0.0f;  
  16.     ModSpeedLedgeGrab = 0.8f;  
  17. }  
MaxAcceleration是继承来的变量,它代表角色控制器的最大的加速度;
BrakingDecelerationWalking,这是角色在行走时,受到摩擦力的影响,产生的阻碍的加速度;
MaxWalkSpeed,角色行走的最大速度;
SlideVelicityReduction,角色在滑行时,单位时间内速度损失的量;
SlideHeight,角色在滑行的时候,角色控制器的高度;跟碰撞有关,滑行了,当人角色会矮一点,这样可以通过一些站立时不能通过的障碍;
SlideMeshRelativeLocationOffset,这是控制角色滑行,模型相对角色控制器的位移;
bWantsSlideMeshRelativeLocationOffset ,是否需要位移角色的模型;
MinSlideSpeed,角色必须初始有一个速度,才能够滑行起来,这是能够滑行的最小速度,低于这个速度是不能滑行;
MaxSlideSpeed,角色滑行所能够达到的最大速度;
ModSpeedObstacleHit,
ModSpeedLedgeGrab

2.重载StartFalling()

[cpp] view plain copy
  1. void UPlatformerPlayerMovementComp::StartFalling(int32 Iterations, float remainingTime, float timeTick, const FVector& Delta, const FVector& subLoc)  
  2. {  
  3.     Super::StartFalling(Iterations, remainingTime, timeTick, Delta, subLoc);  
  4.   
  5.     if (MovementMode == MOVE_Falling && IsSliding())  
  6.     {  
  7.         TryToEndSlide();  
  8.     }  
  9. }  

此方法是继承来的,它在角色从Walking模式切换到Falling模式时被调用。
我们假设,角色正在滑行,碰到前面一个悬崖,它开始下落,这时候,角色必须停止滑行,因为它马上就要开始下落了。以上代码便是实现此功能,在下落的时候,如果发现当前在滑行,就尝试停止滑行。

3.重载ScaleInputAcceleration,实现角色向右跑动

[cpp] view plain copy
  1. FVector UPlatformerPlayerMovementComp::ScaleInputAcceleration(const FVector& InputAcceleration) const  
  2. {  
  3.     FVector NewAccel = InputAcceleration;  
  4.   
  5.     APlatformerGameMode* GI = GetWorld()->GetAuthGameMode<APlatformerGameMode>();  
  6.     if (GI && GI->IsRoundInProgress())  
  7.     {  
  8.         NewAccel.X = 1.0f;  
  9.     }  
  10.   
  11.     return Super::ScaleInputAcceleration(NewAccel);  
  12. }  
游戏一开始,角色就会一直朝右开始跑,玩家只需要控制在合适的时候跳和滑行就行了。那怎么实现不用输入,就一直朝右跑呢?一直给角色一个加速的输入即可。这里判断当前是不是在游戏过程中,是的话,就始终给原始的InputAcceleration参数一个X方向的输入。就好像你一直按着方向键一样。

4.重载PhysWalking()

[cpp] view plain copy
  1. void UPlatformerPlayerMovementComp::PhysWalking(float deltaTime, int32 Iterations)  
  2. {  
  3.     APlatformerCharacter* MyPawn = Cast<APlatformerCharacter>(PawnOwner);  
  4.     if (MyPawn)  
  5.     {  
  6.         const bool bWantsToSlide = MyPawn->WantsToSlide();  
  7.         if (IsSliding())  
  8.         {  
  9.             CalcCurrentSlideVelocityReduction(deltaTime);  
  10.             CalcSlideVelocity(Velocity);  
  11.   
  12.             const float CurrentSpeedSq = Velocity.SizeSquared();  
  13.             if (CurrentSpeedSq <= FMath::Square(MinSlideSpeed))  
  14.             {  
  15.                 // slide has min speed - try to end it  
  16.                 TryToEndSlide();  
  17.             }  
  18.         }  
  19.         else if (bWantsToSlide)  
  20.         {  
  21.             if (!IsFlying() &&  
  22.                 Velocity.Size() > MinSlideSpeed * 2.0f) // make sure pawn has some velocity  
  23.             {  
  24.                 StartSlide();  
  25.             }  
  26.         }  
  27.     }  
  28.   
  29.     Super::PhysWalking(deltaTime, Iterations);  
  30. }  

每当需要更新角色的Walking状态时,PhysWalking都会被调用。这个方法的调用会比较频繁。它会根据角色的输入和当前的状态来更新当前角色的速度、是否滑行等状态。
首先,我们读取角色的输入bWantsToSlide,看是不是要滑行。
如果正在滑行,CalcCurrentSlideVelocityReduction(deltaTime)计算当前角色因为滑行而造成的速度损失。CalcSlideVelocity(),计算出当前滑行的速度。然后对当前的速度和能够滑行的最小速度进行比较,速度损失到无法再进行滑行的话,就停止滑行状态。
用户有输入,说开始滑行吧,那么检查下,看速度是不是达到了可以滑行的条件,再排除是不是在飞行,不是,那么StartSlide(),开始滑行吧。

5.CalcCurrentSlideVelocityReduction()计算滑行的速度损失

[cpp] view plain copy
  1. void UPlatformerPlayerMovementComp::CalcCurrentSlideVelocityReduction(float DeltaTime)  
  2. {  
  3.     float ReductionCoef = 0.0f;  
  4.   
  5.     const float FloorDotVelocity = FVector::DotProduct(CurrentFloor.HitResult.ImpactNormal, Velocity.SafeNormal());  
  6.     const bool bNeedsSlopeAdjustment = (FloorDotVelocity != 0.0f);  
  7.   
  8.     if (bNeedsSlopeAdjustment)  
  9.     {  
  10.         const float Multiplier = 1.0f + FMath::Abs<float>(FloorDotVelocity);  
  11.         if (FloorDotVelocity > 0.0f)  
  12.         {  
  13.             ReductionCoef += SlideVelocityReduction * Multiplier; // increasing speed when sliding down a slope  
  14.         }  
  15.         else  
  16.         {  
  17.             ReductionCoef -= SlideVelocityReduction * Multiplier; // reducing speed when sliding up a slope  
  18.         }+  
  19.     }  
  20.     else  
  21.     {  
  22.         ReductionCoef -= SlideVelocityReduction; // reducing speed on flat ground  
  23.     }  
  24.   
  25.     float TimeDilation = GetWorld()->GetWorldSettings()->GetEffectiveTimeDilation();  
  26.     CurrentSlideVelocityReduction += (ReductionCoef * TimeDilation * DeltaTime);  
  27. }  

先说下CurrentFloor是做什么用的,角色在Walking模式下,始终会站立在一个平面上,这个平面的数据就用CurrentFloor来描述。
[cpp] view plain copy
  1. const float FloorDotVelocity = FVector::DotProduct(CurrentFloor.HitResult.ImpactNormal, Velocity.SafeNormal());  
这一行,其实是在求解当前速度的方向和平面垂直方向的夹角的余弦,向量点乘,应该不难理解。
滑行时速度的损失的程度,和地面与当前速度方向的夹角有关系。你往坡上滑行,速度损失多点,往坡下滑行,相应的损失会少点。如果两个是垂直的,也就说是在一个平坦的面上,那就不用调整了。bNeedsSlopeAdjustment,就是做这个用的。
ReductionCoef是计算出的速度损失系数,最后把它叠加到CurrentSlideVelocityReduction。

6.CalcSlideVelocity()计算滑行速度

[cpp] view plain copy
  1. void UPlatformerPlayerMovementComp::CalcSlideVelocity(FVector& OutVelocity) const  
  2. {  
  3.     const FVector VelocityDir = Velocity.SafeNormal();  
  4.     FVector NewVelocity = Velocity + CurrentSlideVelocityReduction * VelocityDir;  
  5.       
  6.     const float NewSpeedSq = NewVelocity.SizeSquared();  
  7.     if (NewSpeedSq > FMath::Square(MaxSlideSpeed))  
  8.     {  
  9.         NewVelocity = VelocityDir * MaxSlideSpeed;  
  10.     }  
  11.     else if (NewSpeedSq < FMath::Square(MinSlideSpeed))  
  12.     {  
  13.         NewVelocity = VelocityDir * MinSlideSpeed;  
  14.     }  
  15.   
  16.     OutVelocity = NewVelocity;  
  17. }  

上面计算出了滑行的速度损失,保存在了CurrentSlideVelocityReduction。在叠加出新的速度后,这个速度可能大于最大速度,也可能达不到最小速度,需要对其分别操作。大于,新的速度就是最大滑行速度,小于,新的速度就是最小滑行速度。

7.StartSlide()开始滑行

[cpp] view plain copy
  1. void UPlatformerPlayerMovementComp::StartSlide()  
  2. {  
  3.     if (!bInSlide)  
  4.     {  
  5.         bInSlide = true;  
  6.         CurrentSlideVelocityReduction = 0.0f;  
  7.         SetSlideCollisionHeight();  
  8.   
  9.         APlatformerCharacter* MyOwner = Cast<APlatformerCharacter>(PawnOwner);  
  10.         if (MyOwner)  
  11.         {  
  12.             MyOwner->PlaySlideStarted();  
  13.         }  
  14.     }  
  15. }  

初始一些变量,设置角色的碰撞高度,然后通知Character播放开始滑行的动画,等等。

[cpp] view plain copy
  1. void UPlatformerPlayerMovementComp::TryToEndSlide()  
  2. {  
  3.     // end slide if collisions allow  
  4.     if (bInSlide)  
  5.     {  
  6.         if (RestoreCollisionHeightAfterSlide())  
  7.         {  
  8.             bInSlide = false;  
  9.   
  10.             APlatformerCharacter* MyOwner = Cast<APlatformerCharacter>(PawnOwner);  
  11.             if (MyOwner)  
  12.             {  
  13.                 MyOwner->PlaySlideFinished();  
  14.             }  
  15.         }  
  16.     }  
  17. }  

这个方法尝试结束滑行状态,需要把之前的碰撞高度还原,然后通知Character播放滑行结束的动画。

8.SetSlideCollisionHeight()和RestoreCollisionHeightAfterSlide()

[cpp] view plain copy
  1. void UPlatformerPlayerMovementComp::SetSlideCollisionHeight()  
  2. {  
  3.     if (!CharacterOwner || SlideHeight <= 0.0f)  
  4.     {  
  5.         return;  
  6.     }  
  7.   
  8.     // Do not perform if collision is already at desired size.  
  9.     if (CharacterOwner->CapsuleComponent->GetUnscaledCapsuleHalfHeight() == SlideHeight)  
  10.     {  
  11.         return;  
  12.     }  
  13.   
  14.     // Change collision size to new value  
  15.     CharacterOwner->CapsuleComponent->SetCapsuleSize(CharacterOwner->CapsuleComponent->GetUnscaledCapsuleRadius(), SlideHeight);  
  16.   
  17.     // applying correction to PawnOwner mesh relative location  
  18.     if (bWantsSlideMeshRelativeLocationOffset)    
  19.     {  
  20.         ACharacter* DefCharacter = CharacterOwner->GetClass()->GetDefaultObject<ACharacter>();  
  21.         const FVector Correction = DefCharacter->Mesh->RelativeLocation + SlideMeshRelativeLocationOffset;  
  22.         CharacterOwner->Mesh->SetRelativeLocation(Correction);  
  23.     }  
  24. }  

此方法修改碰撞高度,然后根据需要,调整模型的偏移。

[cpp] view plain copy
  1. bool UPlatformerPlayerMovementComp::RestoreCollisionHeightAfterSlide()  
  2. {  
  3.     ACharacter* CharacterOwner = Cast<ACharacter>(PawnOwner);  
  4.     if (!CharacterOwner)  
  5.     {  
  6.         return false;  
  7.     }  
  8.   
  9.     ACharacter* DefCharacter = CharacterOwner->GetClass()->GetDefaultObject<ACharacter>();  
  10.     const float DefHalfHeight = DefCharacter->CapsuleComponent->GetUnscaledCapsuleHalfHeight();  
  11.     const float DefRadius = DefCharacter->CapsuleComponent->GetUnscaledCapsuleRadius();  
  12.   
  13.     // Do not perform if collision is already at desired size.  
  14.     if (CharacterOwner->CapsuleComponent->GetUnscaledCapsuleHalfHeight() == DefHalfHeight)  
  15.     {  
  16.         return true;  
  17.     }     
  18.   
  19.     const float HeightAdjust = DefHalfHeight - CharacterOwner->CapsuleComponent->GetUnscaledCapsuleHalfHeight();  
  20.     const FVector NewLocation = CharacterOwner->GetActorLocation() + FVector(0.0f, 0.0f, HeightAdjust);  
  21.   
  22.     // check if there is enough space for default capsule size  
  23.     FCollisionQueryParams TraceParams(TEXT("FinishSlide"), false, CharacterOwner);  
  24.     FCollisionResponseParams ResponseParam;  
  25.     InitCollisionParams(TraceParams, ResponseParam);  
  26.     const bool bBlocked = GetWorld()->OverlapTest(NewLocation, FQuat::Identity, UpdatedComponent->GetCollisionObjectType(), FCollisionShape::MakeCapsule(DefRadius, DefHalfHeight), TraceParams);  
  27.     if (bBlocked)  
  28.     {  
  29.         return false;  
  30.     }  
  31.   
  32.     // restore capsule size and move up to adjusted location  
  33.     CharacterOwner->TeleportTo(NewLocation, CharacterOwner->GetActorRotation(), falsetrue);  
  34.     CharacterOwner->CapsuleComponent->SetCapsuleSize(DefRadius, DefHalfHeight);  
  35.   
  36.     // restoring original PawnOwner mesh relative location  
  37.     if (bWantsSlideMeshRelativeLocationOffset)  
  38.     {  
  39.         CharacterOwner->Mesh->SetRelativeLocation(DefCharacter->Mesh->RelativeLocation);  
  40.     }  
  41.   
  42.     return true;  
  43. }  

在还原碰撞高度的时候,需要测试当前是不是能够恢复,比如头顶上有个障碍,是不能够恢复的。
[cpp] view plain copy
  1. const bool bBlocked = GetWorld()->OverlapTest(NewLocation, FQuat::Identity, UpdatedComponent->GetCollisionObjectType(), FCollisionShape::MakeCapsule(DefRadius, DefHalfHeight), TraceParams);  
这一行,对要恢复碰撞的那个位置进行测试,看有没有东西覆盖,没有就把角色传送到此位置。
最后恢复角色模型的偏移。

9.碰到障碍和碰到抓取边缘的处理

[cpp] view plain copy
  1. void UPlatformerPlayerMovementComp::PauseMovementForObstacleHit()  
  2. {  
  3.     SavedSpeed = Velocity.Size() * ModSpeedObstacleHit;  
  4.   
  5.     StopMovementImmediately();  
  6.     DisableMovement();  
  7.     TryToEndSlide();   
  8. }  
  9.   
  10. void UPlatformerPlayerMovementComp::PauseMovementForLedgeGrab()  
  11. {  
  12.     SavedSpeed = Velocity.Size() * ModSpeedLedgeGrab;  
  13.   
  14.     StopMovementImmediately();  
  15.     DisableMovement();  
  16.     TryToEndSlide();  
  17. }  
PauseMovementForObstacleHit()处理当遇到障碍时的情况。
PauseMovementForLedgeGrab()处理当抓取障碍边缘时的情况。
都是保存当前的速度,然后停止移动,停止滑行。

10.RestoreMovement()恢复移动

[cpp] view plain copy
  1. void UPlatformerPlayerMovementComp::RestoreMovement()  
  2. {  
  3.     SetMovementMode(MOVE_Walking);  
  4.   
  5.     if (SavedSpeed > 0)  
  6.     {  
  7.         Velocity = PawnOwner->GetActorRotation().Vector() * SavedSpeed;  
  8.     }  
  9. }  

在当前角色面对的方向上赋予保存的速度值。

阅读全文
0 0
原创粉丝点击