在UE4中实现降落伞(Parachute)
降落伞原理

跳伞过程中速度、加速度变化(Free fall, v=0, gravity=1, acceleration=10)
acceleration-- 速度越高,空气阻力越大,因此人的加速度不是恒定的10,而是不断减小,最终会达到一个恒定速度

在恒定速度下降一段时间后,可以打开降落伞,此时空气阻力>重力,因此速度开始减小,随着速度减小,空气阻力也在变小,最终再次达到一个恒定速度(此时降落伞+自然空气阻力=重力),这是一个较低的降落速度。随后玩家一直降落到地面
UE4中,LinearDamping默认为0.01,而阻力f=阻尼c*速度v,也就是设置阻尼的话阻力也会随速度增大(但是它的实际影响效果非常小)
实现方式
为了更好的实现降落伞我们需要定制CustomMovementMode
网上的方案大多是修改AirControl和GravityScale实现的"低速漂浮",并非真实降落伞效果,所以不可参考
我们用一个ParachuteDamping实现降落时的阻力控制,该数值会与速度相乘,得到与重力反向的加速度,在NewFallVelocity中,将其应用给速度
游戏中玩家可以通过移动输入控制前后左右,实现慢速移动,不输入则以下落时的惯性速度移动
旋转即边输入速度边旋转角度,这样即可实现盘旋。可接受InputAxis的输入然后在GetPlayerInputVector里针对MovementMode进行处理
基础逻辑
MovementMode=Falling:检查条件是否可以按开伞
PhysParachuting:开伞时触发,显示降落伞Actor,以及模拟降落伞阻力,移动输入
CancelParachuting:隐藏降落伞Actor,清除降落伞阻力等
EventOnLand:Reset,根据落地进入不同状态
重写NewFallVelocity函数
UCharacterMovementComponent::NewFallVelocity(const FVector& InitialVelocity, const FVector& Gravity, float DeltaTime) const
// Apply gravity.Result += Gravity * DeltaTime;// Don't exceed terminal velocity.const float TerminalLimit = FMath::Abs(GetPhysicsVolume()->TerminalVelocity);if (Result.SizeSquared() > FMath::Square(TerminalLimit)){const FVector GravityDir = Gravity.GetSafeNormal();if ((Result | GravityDir) > TerminalLimit){Result = FVector::PointPlaneProject(Result, FVector::ZeroVector, GravityDir) + GravityDir * TerminalLimit;}}
实现PhysParachuting函数
参考自Falling,因为CharacterMovement是引擎里的类,在项目的VRCharMove类中有些会缺少引用
SCOPE_CYCLE_COUNTER:监听函数性能消耗。这个是程序性能优化调试相关的
类似的还有DECLARE_STATS_GROUP: 自定义Stat群组,DECLARE_CYCLE_STAT :自定义埋点(UE4支持通过预设宏快速的定义Stat)
其他无法在子类访问的变量,替换数值即可
在Character蓝图中添加Parachuting MovementMode的处理函数
EventUpdateMovement

双端同步处理
项目中需要AddCustomEvent,然后将该Event设为RunOnServer(Reliable),再在该结点里驱动逻辑。然后传送用Teleport

EventOnLand这种默认逻辑则不需要,本身就处理了同步
Movement原理
旋转实现及其原理
我们项目里的移动一般不用InputAxisMoveXX->AddMovementInput,而是用InputAxisMotionControllerThumbXX->SetMovementInputX/Y。这个东西本质上还是AddMovementInput,只是优化了手柄DeadZone,因为它最后给到GetPlayerInputVector
网络上的实现一般是输入按键的时候,朝该方向旋转角色的X轴,每帧转1度,Clamp到max角度,实现倾斜
倾斜后如果没有输入,要用Timeline让其在0.5秒内从1变回0(X轴旋转)
然后再把这个X向的Direction作为AddMovementInput,实现Actor的旋转(或者让Actor的Z轴也-1度旋转或+1度旋转)
项目里Smooth的旋转实现:

MotionControllerThumbRight_X对应MouseX,调用SetRotationInputXMotionControllerThumbRight_Y对应MouseY,只会旋转相机,不能控制角色旋转(项目里用来做TryCrouch的输入)
左摇杆移动,右摇杆旋转(除非设置IsRightHandLocomotion)
SetRotationInputX就是设置RotationInput_X,随后用于Yaw的旋转
在Character.UpdateMovement(),调用Controller.AddYawInput(RotationInput_X * PlayerRotationSpeed * DeltaSeconds)
PlayerController中,在UpdateRotation()中计算出RotationInput附加上去后的FRotator——DeltaRot
PlayerCameraManager->ProcessViewRotation(DeltaTime, ViewRotation, DeltaRot);
第一人称游戏中,PlayerCamera就放在接近人眼的位置,而实际上人眼能看的角度是有限制的,所以就在ProcessViewRotation中,做了LimitViewPitch等的限制
随后将计算好的ViewRotation应用到相机 XRCamera->ApplyHMDRotation(this, ViewRotation);
这样就从鼠标/控制器的输入变到了Camera的旋转
随后SetControlRotation,这个是SetController这个Actor本身的Rotation
最后一步是GetPawnOrSpectator()->FaceRotation(ViewRotation, DeltaTime),也是最重要的一步,这个函数里将ViewRotation选择的要同步的旋转Set到ActorRotation
在TPP游戏里确实是这么旋转的,但是项目里不是的,Actor不会旋转(Rotation一直维持初始值),旋转的是这个东西:

手是一直随视角旋转的,而身体的Mesh是逐渐Lerp旋转过去的,不会一开始就旋转(这个在VRCharacterBase的EventTick中实现)

这样做是为了方便PC测试,将键鼠旋转和摇杆旋转区分开来(因为摇杆旋转时身体不会跟着转,和键鼠操作习惯不同)
这个类里有一个UpdateGroundRotation,拿AimingRotation处理后赋给TargetMeshActualRot(我们看到Mesh初始就是和RootComp相差-90度的Yaw的,所以每次都要处理加上这个-90度的旋转)这里如果非SmoothTurn且有旋转输入就直接走TargetMeshActualRot设置,否则就走SmoothCharacterRotation函数来处理

SmoothCharacterRotation会根据这些函数和参数分别处理TargetRotation和TargetMeshActualRot


我试过把UpdateRotation和VRCamera里的TickComponent->SetWorldLocationAndRotation全注掉,但没有任何影响。只有InputAxisTurn和LookUp,能够控制相机旋转,在ProjectSetting也可以看到,它俩绑定的MouseX/Y,控制TestCameraRoot的旋转

这是非VR模式测试加入的辅助根组件(将VRRoot旋转与Mesh旋转分开),实际VR还是走ThumbRight->SetRotationInput那些。这在我们游戏中是分开的,键鼠操作就是纯模拟,手柄操作就会走上面说的UpdateRotation那套,这两套是分开的
那么我们要做降落伞,实际游戏肯定还得走SetRotationInputX,键鼠测试可以在这修改。
GetPlayerInputVector
这个函数里为CustomMovementMode如Climb计算了MotionControllerModifiedTransformLocationOffset值附加到ClimbVectorCombined。最后把这个值*0.05最后作为返回值。该值最后用于AddMovementInput
左摇杆输入SetMovementInputX / Y(MovementInput_X/Y),右摇杆输入SetRotationInputX(RotationInput_X → AddYawInput)
在GetPlayerInputVector_Implementation()中,我们可以看到返回值为 MovementInput_X/Y 的结合计算结果(即左摇杆的综合输入),而RotationInput_X没有参与进来,那个在UpdateMovement(要测试这个,需要把非VR模式关掉,不然是鼠标驱动的角色旋转)
动态添加降落伞Actor
AttachActorToComponent
计划
- 实现滑翔的移动模式(空中坠落,旋转、倾斜、减缓、落地)2.9
- 实现降落伞交互(降落伞显示,交互,输入)2.14
- 条件检查、速度配置和快速坠落(左摇杆)2.17
- 调优和美术资源、动作配置 2.22