一、AActor::bReplicates = false,在APawn中初始化为true。另外在AActor中定义了:
/** * If true, replicate movement/location related properties. * Actor must also be set to replicate. * @see SetReplicates() * @see https://docs.unrealengine.com/latest/INT/Gameplay/Networking/Replication/ */ UPROPERTY(ReplicatedUsing=OnRep_ReplicateMovement, Category=Replication, EditDefaultsOnly) uint8 bReplicateMovement:1;
该变量没有初始化,但在APawn中初始化为true.
所以从APawn(包括ACharacter)继承的类,bReplicats已经为真,无需再初始化。
二、使用UPROPERTY()宏指明那个成员变量需要replicate,例如:
UPROPERTY(Replicated) float CharacterHealth;
这一步,注册这个变量用于replicate,并且只要owning对象打开replicate功能,这个变量将通过网络,将其值更新到所有连接的客户端。这也是为什么在一中,提及AActor::bReplicates变量的原因。任何一个变量如果没有指定UPROPERTY(Replicated)则不能复制(replicate)。另外要注意一点就是:当UBT/UHT在头文件中遇见使用replicated标记的变量,它将在代码生成阶段自动添加如下函数声明:
void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
所以,我们切记要在源文件中添加该函数定义。以上述变量声明为例:
{ Super::GetLifetimeReplicatedProps(OutLifetimeProps); DOREPLIFETIME(AYourActor, CurrentHealth); }
代码中依惯例先调用父类对应函数,然后使用DOREPLIFETIME宏,DOREPLIFETIME宏表示:Do Rep Lifetime。第一个参数是变量所在的类名,第二个是要复制的变量名,当然还有许多宏,可以参考文档:
https://docs.unrealengine.com/latest/INT/Gameplay/Networking/Actors/Properties/Conditions/index.html
三、网络角色(role):对于每一个可以复制的actor,可能存在三种角色:Role_Authority, Role_AutonomousProxy, Role_SimulatedProxy。区别:
- Role_Authority:可以复制的角色在服务器中的版本。这个角色很重要(存在于服务器),因为它对可以复制的actor的变量和状态有操作的权限。
- Role_AutonomousProxy:存在于owning客户端的版本。这意味着客户端可以让角色来回移动,直接向服务员发送/接收RPC调用,并且玩家的输入将影响到服务器版本的对应角色的位置插值。
- Role_SimulatedProxy:模拟版本,存在于别的的客户端(不包括上述两种),纯粹用于模拟,并且不能改变actor的状态。
参考CS(客户-服务器)模型,计算机A请求服务器产生一个它将要控制的character A(角色A,为了和role区分,使用英文),character A在拥有者的客户端计算机A上扮演的role就是Role_AutonomousProxy。拥有者客户端就是创建或请求被控制的character第一次发生的地方。character A在服务器上创建的版本扮演的Role就是Role_Authority,在别的客户端计算机上创建的character A扮演的role就是Role_simulatedProxy,这些客户端不能控制character A。
Role主要用于描述网络所有权。对于任意的可复制的actor,总是存在一个服务器拥有的actor(Role_Authority),可能存在也可能不存在Role_AutonomousProxy,并且也可能存在也可能不存在Role_SimulatedProxy。
四、远程调用(RPC)
RPC指的是我们通过网络向一个对象的客户端或者服务器端发送指令。它们(RPCs)在本地调用,并且在远程在另一台计算机上的实例上执行。例如,如果我们接收到输入要执行OnFire()命令,RPC过程大致如下:
- OnFire()函数先在本机执行,播放本地动画和各种效果,通知服务器已经开火。
- 从客户端到服务器进行RPC调用:FireMyWeapon
- 服务器接收到FireMyWeapon后,进行ray-trace来查找是否击中某个物体,然后通知客户端播放PlayerFiredVisuals()
- 从服务器到所有连接的客户端执行RPC调用:PlayerFiredVisual()
- 所有连接的客户端接收到PlayerFiredVisual()指令后:产生和播放粒子发射器,声音等效果。
在上述例子中,character在owning客户机上的实例,接收到开火输入后,调用OnFire()函数,播放本地(local)动画和声音,但是这些效果(动画和声音)别的玩家(在别的客户端上)看不到也听不到,因此owning 客户端将通过RPC调用通知在服务器上本身(开火的character)对应的character的实例执行开火动作。特别注意,因为这个客户端是character的拥有者,因此它可以向服务器中对应的实例发送这个RPC调用。
这个RPC将指示在服务器中该角色(character)对应的实例进行光线跟踪计算。由于所有在服务器上的角色(characters,复数形式)都是authority实例,我们可以安全的假定这些角色(characters)的位置和其它信息都是正确的。直到接收到FireMyWeapon()这个RPC调用后,RPC调用服务器的实例并且通知所有连接的客户端播放第三人称可视化效果,这一步很重要,由于别的相连的客户端必须看到这个character(射击的角色)在自己计算机上(此时开火的character扮演的role是Role_simulatedProxy)的开火效果。由于我们希望在所有客户端上的实例上都播放这些效果,我们必须注意,不能使用直接客户端RPC(UFUNCTION(Client)属性表示就是一个直接客户端),否则只会在owning客户端上播放,我们应该使用网络多播(NetMultiCast).
五、RPC的类型:
//在服务器上调用,在owing客户端(Role_AutonomousProxy)上执行。
//如果在客户端上调用,函数只会在本地执行(没有RPC效果)
UFUNCTION( Client ) void ClientRPCFunction();
//在Owning 客户端上调用,在服务器上执行。
// 如果在非owning 客户端(此时Role为:Role_SimulatedProxy)调用,函数将会被擦除,不会执行。 UFUNCTION( Server ) void ServerRPCFunction();
// 如果在服务员上调用,它将在服务器和所有连接的客户端上执行,包括Role_SimulatedProxy。
// 如果在客户端(包括owning, 非owning),只有本地效果。 UFUNCTION( NetMulticast ) void MulticastRPCFunction();
默认时,RPC是不可靠的,如果需要可靠,应在UFUNCTION中指明。如果在UFUNCTION中指明WithValidation,也将进行诊断。
六、RPC定义
如果UBT/UHT碰到一个函数在UFUNCTION中指定了RPC,它将自动使用生成的函数声明替换原来函数声明(这一步依赖如果声明RPC),以上述ClientRPCFunction为例,它将使用如下声明:
void ClientRPCFunction_Implementation();
如果指定了WithValidation,同时又声明了另一个函数:
bool ClientRPCFunction_Validate();
因此,当我们定义RPC时,我们必须在脑中想到应该按上述的方法去做,否则就是链接错误。
七、网络所有权,玩家控制器和游戏模式(……累了,休息,改日再战)