网页版
https://yekdniwue.blogspot.com/2020/08/CustomMove2.html
简介
在前一篇我已经介绍一个完整的范例,
说明如何制作一个支援网络的功能,
如何做测试,
跟如何验证这个做好的功能,
在网络延迟的环境是否能正常运作。
前一个做法确定是不行的,玩家会感到画面不流畅。
因此本篇要介绍如何正确地修改移动相关的功能,
让Server端能够信任Client发送的指令,
避免Server频繁的矫正Client的位置。
这篇其实会比较偏向程式码实作,叙述会少很多。
简单来说就是看code比较快啦。
当初是从CharacterMovementComponent的Crouch挖出来的。
所以如果想要自己试试看的话,可以去挖出引擎有关Crouch的程式来看。
跟之前一样,里面的程式码都是我经过精简过了,
多余的实作项目我尽可能的都没列在里面,
所以最好还是按照顺序看完,以免出错。
再继续往下看之前,请确认以下的词你都知道是什么意思:
1. Replicated Property
2. ROLE Authority
3. ROLE Simulated Proxy
4. ROLE AutonomousProxy
碍于篇幅的关系,这边不会多做介绍。如果有不熟悉的项目,请先前往
https://docs.unrealengine.com/en-US/Gameplay/Networking/Actors/Roles/index.html
恶补一下。
为角色新增移动模式
要制作这种由玩家的操作改变移动速度的作法,
其实要用到的是MovementMode的切换。
也就是制作一个新的MovementModeStrafe,
然后玩家按键的时候进到这个MovementMode,
放开的时候回到默认的MovementMode。
为了达到这个目的,我们总共需要新增
两个C++ class,以及一个BP class,
所以就是5个档案。
1. CustomCharacter
2. CustomMoveComponent
3. BP_CustomCharacter
而移动速度的改变,核心做法是override GetMaxSpeed(),
如果角色正在Strafe状态,MaxSpeed就回传
Super::GetMaxSpeed()*StrafeSpeedRatio
CustomCharacter实作
大致上要实作的项目
CustomCharacter要能接受玩家的输入,所以跟之前的作法一样,
要开出Strafe以及UnStrafe两个函式给外部使用。
为了得知Strafe/UnStrafe的状态变化时机,所以我也开出了四个函式:
1. OnStartStrafe
2. OnEndStrafe
3. BP_OnStartStrafe
4. BP_OnEndStrafe
分别是C++以及BP的事件,提供给Gameplay需要的时候使用。
Server需要将Movement的状态同步给SimulatedProxy,
所以要新增一个变量bIsStrafed,
用来让SimulatedProxy获得事件通知。
也因为要Replicate变量bIsStrafed,
就要实作GetLifetimeReplicatedProps函式。
因为我们会以CustomMoveComponent取代
原生的CharacterMovementComponent,
我会建立一个指标指向CustomMoveComponent。
然后在Constructor的时候做替换。
替换MoveComponent以及同步变量
在Constructer替换MoveComponent,
然后在GetLifetimeReplicatedProps同步变量bIsStrafed,
并且只同步给Simulated Proxy。
因为Autonumous Proxy在输入的当下就切换状态了。
Strafe与UnStrafe实作
在CustomCharacter内,Strafe跟UnStrafe只是负责
将指令带给CustomMoveComponent,
后续就交给CustomMoveComponent在传递资讯的时候做处理。
变量同步与事件通知
收到变量bIsStrafed改变的通知时,
我们就是呼叫CharMovement对应的函式做处理。
而收到OnStartStrafe与OnEndStrafe时,就是呼叫BP版本的对应函式。
可能会有人有疑惑说为什么一个事件要分C++跟BP两个函式,
而不是直接使用BlueprintNativeEvent直接一个函式定义起来。
主要是因为如果使用NativeEvent,那BP端是可以不呼叫C++实作的。
这个做法可以确保C++的部分一定会被执行到,不会被跳过。
到这边CustomCharacter的实作就结束了。
CustomMoveComponent实作
大致上要实作的项目
CustomCharacter的实作其实还是比较偏Gameplay层,
就是开出函式,建立事件通知。
CustomMoveComponent要实作的项目才是核心的部份,
里面的程式码比较少见(其他系统不会看到这些类别与函式)。
CustomMoveComponent的实作根据不同的ROLE,有不同的实作部份
1. Autonomuous Proxy handling
2. Authority handling
Autonomuous Proxy要把bWantsToStrafe的资讯塞进FCustomSavedMove中,
这样client给Server的每个移动资讯都会带有这个移动是否是Strafe的资讯。
Authority从FCustomSavedMove内抽出bWantsToStrafe的资讯后,
就会以正确的速度计算移动,需要减速移动就会减速移动。
因为移动跟速度变化绑定在同一包,
所以Server不会认为Client的移动有异常,就不会做位置矫正。
一些基本杂项设定
设定PawnOwner:
有两个地方要设定PawnOwner。SetUpdatedComponent以及PostLoad。
Strafe与UnStrafe:
如果是Server(Authority)的话,直接变更bIsStrafed,
这样SimulatedProxy会在OnRep_IsStrafed收到通知并处理。
而到底移动速度的更改怎么实作,就是透过检查现在是否是Strafe,
然后override GetMaxSpeed函式,如果Strafe中就乘上减速比例。
移动资讯传递
剩下的部份就是处理移动资讯的传递,
总共还有以下几个函式在CustomMoveComponent要实作:
1. UpdateCharacterStateBeforeMovement
2. UpdateFromCompressedFlags
3. GetPredictionData_Client
修改Client送给Server的移动结构
要把Strafe的状态告诉Server,就是要将Strafe的状态塞进移动资料。
在FSavedMove的CompressedFlags提供了四个custom的bit可供我们使用,
也就是说我们被允许最多建立出16种移动状态。
在这边我会借用FLAG_Custom_0的0代表UnStrafe,1代表Starfe。
为了修改FSavedMove,我们要实作自己的版本:
class FCustomSavedMove : public FSavedMove_Character
而SavedMove是被装在FNetworkPredictionData_Client_Character里面。
所以我们要实作自己的版本:
class FNetworkPredictionData_Client_Custom :
public FNetworkPredictionData_Client_Character
然后实作函式AllocateNewMove,里面回传我们自定义的FCustomSavedMove。
最后就是在CustomMoveComponent里面实作GetPredictionData_Client。
在GetPredictionData_Client里面我们要回传自定义的class
FNetworkPredictionData_Client_Custom。
这样就能以我们自定义的移动结构取代原来的移动结构了。
将Strafe状态整合进移动资料
可分为输入输出两种情况,
输入:Client把Strafe状态塞进移动资料传给Server。
输出:Server从移动资料获得Client送来的Strafe状态。
输入实作:
Client(Autonumous)传送移动资料给Server的处理流程:
CharacterMovementComponent::TickComponent()
ReplicateMoveToServer
SetMoveFor
CanCombineWith
PerformMovement
UpdateCharacterStateBeforeMovement
CallServerMove
GetCompressedFlags
SetMoveFor:
从CustomMoveComponent复制bWantsToStrafe到FCustomSavedMove。
CanCombineWith:
如果bWantsToStrafe有变动,那两个SavedMove要避免合并,
以免Strafe状态的资料因为合并而遗失。
UpdateCharacterStateBeforeMovement:
在套用移动前再次检查状态是否有变化。
GetCompressedFlags:
将bWantsToStrafe存入CompressedFlags的FLAG_Custom_0。
输出实作:
Server(Authority)收到玩家资料的处理流程:
ServerMove_Implementation
MoveAutonomous
UpdateFromCompressedFlags
PerformMovement
UpdateCharacterStateBeforeMovement
UpdateFromCompressedFlags:
从SavedMove的FLAG_Custom_0取得Strafe状态,存入CustomMoveComponent。
UpdateCharacterStateBeforeMovement:
在套用移动前再次检查状态是否有变化。
Server端Strafe状态的变化事件会在这里触发。
程式码在哪?
说了这么多,你一定想问"到底要不要附程式码"
你可以参考我当初找到的范例
[连结]
或是参考UnrealEngine的原始码有关bWantsToCrouch相关的部份。
或是参考我实作的版本,已做成Plugin,但是BP角色的串接就要自己实作了。
[连结]
最后一哩路
按照以上作法实作,BP的character改为继承CustomCharacter之后,
只要在使用者输入的时候呼叫Strafe/UnStrafe,就完成了。
其他的事情都已经在C++处理完毕。如图所示。
[图]
实作完毕后你可以用前一篇提的方法做测试,
会发现就算lag设为500ms也不会有异常。
可参考影片:
[影片]
结论
本系列所提的方法是为了解决如果玩家操作可以改变移动速度,
如果没有把状态变化跟移动资料一起传给Server,
Server与Client就会计算出不同的位置,
然后Client就会收到Server传来的位置矫正,
造成玩家感受不良的问题。
然而一般常见的玩家对玩家,例如缓速技能,冻结技能,
这种牵扯的对象不是只有玩家对Server,
就没有办法使用这种方式处理。
要解决这种问题的困难度也高很多。
目前没有继续往后研究下去,所以这个系列应该会到此结束。
哪一天真的有实作出来并且经过验证再分享吧(应该不会有机会)。