4:虚幻引擎网络架构:技术要点总结篇

Posted on 2015-10-10 20:00 neocsl 阅读(...) 评论(...) 编辑 收藏

Replication要点

 
 
1.simulated function 
在网络环境中只有exec,client , simulated 函数才在客户端进行调用。如果一个函数没有任何前缀,它只会在Server中进行调用。
另外,对于一个simulated function,他要么是被另外一个simulated function 调用,要么就是在native函数中被调用才能在客户端执行。
 
应用举例
simulated function PostBeginPlay()
{
`log("PostBeginPlay");
NumberOne();
}
 
simulated function NumberOne()
{
`log("Number One");
NumberTwo();
}
 
function NumberTwo()
{
    `log("Number Two");
    NumberThree();
}
 
simulatedf function NumberThree()
{
     `log("Number Three");
}
 
 
Server端的运行结果:
PostBeginPlay
NumberOne
NumberTwo
NumberThree
 
Client端的运行结果:
PostBeginPlay
NumberOne
 
结论:
由于NumberTwo()没有simulated前缀,我们在前边总结到,任何没有前缀修饰的函数只会在Server端进行调用。
 
由于节省带宽的需要,我们尽可能的让客户端和服务器端保持一样的运行代码,所以可以使用simulated来进行这样的工作。
 
但是我们又不能把全部的函数都写成simulated,因为在客户端不能访问到所有的Actor。(例如一些生成器Spawner Actor,即使在其Simulated PostBeginPlay中打Log都不会有任何的输出)
 
 
2.哪些在服务器端运行,哪些在客户端运行
我们知道非simulated function 是一定不再客户端上运行的,但是当我们在simulated function中我们也想有一些权限,设置一些我们不愿意在客户端中运行的代码段。
还有的是,我们怎么知道我们当前的游戏是完全运行在网络环境的。
 
Role和RemoteRole
在Actor中有一个枚举类型ENetRole ,定义了服务器和客户端怎么来对待这一个Actor
Role_None
Role_SimulatedProxy
Role_AutonomousProxy
Role_Authority
 
var ENetRole Role,RemoteRole; 
 
默认状况下,在服务器端Role=Role_Authority。这是因为,在服务器端,服务器需要维护整个世界中的所有Actor,因此他有所有Actor的控制权。
 
 
RemoteRole设置我们在客户端如何对待这个Actor。例如在以下RemoteRole的几个属性设置中:
 
Role_None: 这个Actor永远不会被复制到客户端。例如GameInfo永远是在Server中运行的,其RemoteRole=Role_None,游戏中的ActorFactor或是Spawner都可以这样设置,因为你不需要客户端有这些内容。
 
Role_SimulatedProxy:基本上所有需要复制的Actor都是这个属性,例如Projectile,Vehicle,所有客户端需要预测其物理和其他行为的Actor。
 
Role_AutonomousProxy:只用在两个地方,一个是client自己的Pawn,也就是玩家Pawn,另一个是DemoRecSpectators extends UTPlayerController。该属性和Role_SimulatedProxy比较接近,除过他们不限于simulated
 
Role_Authority:RemoteRole不能使用,该属性表示这个actor要么在服务器端,要么是单人游戏。
 
 
运行下面的代码
simulated function PostBeginPlay()
{
`log("PostBeginPlay");
`log("Role="@Role);
`log("RemoteRole"@RemoteRole);
}
 
在服务器端的结果:
PostBeginPlay
Role=Role_Authority;
RemoteRole=Role_SimulatedProxy;
 
在客户端的结果:
PostBeginPlay
Role=Role_SimulatedProxy
RemoteRole=Role_Authority
 
完全是相反的结果,这是为什么呢?
服务器端如我们预料的,但是对于客户端完全相反,我们重新做一个实验。
 
 
simulated function PostBeignPlay()
{
`log("PostBeginPlay");
if(Role==Role_Authority)
`log("I'm on Server");
else
`log("I'm on Client");
}
 
在Server端运行的话
I'm on Server
 
在客户端的话
I'm on Server
 
因为在服务器端运行的话,Actor的Role为Role_Authority
在客户端Actor的Role为Role_SimulatedProxy
 
根据这一个特性,我们可以划定有一些功能在特定的端口环境运行,例如一个溅血例子不需要在服务器端运行,所以只需要判断当前的Role<Role_Authoriy,如果不是的话就说明其在客户端中运行。
if(Role<Role_Authority)
Spawn(Particle);      //溅血
 
有时有一些游戏性的代码又只需要在服务器端,我们可以反着来即可。
 
 
 
3.是否运行在网络环境NetMode
在WorldInfo中有一个枚举ENetMode
NM_Standalone,             //单机运行: 没有服务器
NM_DedicatedServer,    //侦测服务器: 运行在一个服务器上,其他客户端连接过去。MMO
NM_ListenServer,           //监听服务器:一个本地客户端主持作为服务器,Host Game,其他玩家连接过来,代替了远程机器。
NM_Client                      //客户端: 作为一个客户端连接到服务器。
 
 
由于是在WorldInfo中,所以我们可以划定和查询该关卡的NetMode
simulated function PostBeginPlay()
{
`log("NetMode:"@WorldInfo.NetMode);
}
 
对于服务器端
NetMode: NM_DedicatedServer
 
对于客户端
NetMode:NM_Client
 
既然有了Role为什么还需要NetMode这种枚举区分?
因为NetMode在服务器端有两种:NM_DedicatedServer或NM_ListenServer,而对于Role在服务器端永远是Role_Authority
 
那么什么时候使用NetMode呢?
前面说过不让血粒子在服务器端进行播放,但是对于NM_ListenServer服务器,像CS中的玩家主机(其实就是客户端在做HostGame主机),当然也要播放血粒子,因此可以用NM_ListenServer做出区别。
 
 
 
 
 
4.Replication变量复制
变量复制任何状态下都是Reliable。
 
数组变量复制除外。static数组可以复制,动态数组不能。除非你将其每一个单独值传递给中间变量,但最好不要。
 
做一个实例:
var int TestNum;
 
replication
{
 if(bNetDirty)
 TestNum;
}
 
 
simulated function PostBeginPlay()
{
    if(Role=Authority)    //在服务器端执行
    SetTimer(1,true,'ServerTest');          
    else                            //在客户端执行
    SetTimer(1,true,'ClientTest');
}
 
function ServerTest()
{
   TestNum++;
  `log("Server:"@TestNum);
}
 
 
function ClientTest()
{
   `log("Client:"@TestNum);
}
 
 
结果分析:
在Server端游
Server: 1
Server: 2
Server:3
...
 
 
在Client端有
Client: 1
Client: 2
Client: 3
...
 
因为Server端不断地变化,bNetDirty==true,将会一直将值传递给Client
 
 
变量复制的一些关键词:
NetPriority: 优先级,注意提高这个变量的优先级,复制可以比别的变量优先,但是不能使其复制变得更快,因为还存在别的Actor变量优先级。
 
bNetDirty:变量值发生了变化。
 
bNetInitial: true,如果所有变量初始化复制完成。
 
bNetOwner: 如果本地PlayerController拥有这个变量则为true
 
bAlwaysRelevant: 为true的话,这个变量将会针对所有client进行更新。注意一些情况,例如别的玩家根本看不见这个变量相关引起的内容就应该不要使其为true,以节省带宽。
 
bReplicateInstigator: 如果这个变量有instigator,将其复制给Clients,例如给这个actor造成伤害的pawn。
 
bReplicateMovement:复制位置和运动变量,例如velocity。
 
bSkipActorPropertyReplication:不要复制这个Actor的任何属性
 
NetUpdateFrequency:更新复制的频率,数值越低优先级就越低
 
if ( (!bSkipActorPropertyReplication || bNetInitial) &&
(Role==ROLE_Authority)
&& bNetDirty && bReplicateInstigator )
Instigator;
Reading it, this tells us that if we're not skipping property replication or we're still initializing,
and we're the server, and a replicated property has changed, and we want to replicate the
instigator, then replicate the Instigator variable. These replication statements can seem
confusing at first, but examining what each variable does and taking a look at other examples
in the source code will give you an understanding of when they should be used.
 
 
 
5.ReplicateEvent
在一些变量前加修饰符 Repnotify,当这个变量被Replicate的时候会引起RelicateEvent被调用。
 
实例:
结果是在服务器端:
ServerTest:1
 
在客户端:
This Replicated Event is Working
 
 
 
6.对Actor的一些设置
一般网络Actor的出现
如果Actor只在Server显示而没有在Client上存在,设置其Actor属性
RemoteRole=Role_SimulatedProxy                     //告知客户端如何对待Actor,大部分的Projectile和Vehicle,可以被击碎的箱子都可以这样设置
bAlwaysRelevant=true                                         //这个也可以随着距离设置以便节省带宽
 
RemoteRole=Role_SimulatedProxy- 告知游戏的客户端怎么处理这个Actor,意思是:这个Client有一个复制于Server的本地copy表示这个Actor。
bAlwaysRelevant=true 针对于玩家这个是相关的
 
 
获取本地PlayerController
GetALocalPlayerController()和Foreach LocalPlayerControllers是永远失效的,因此必须使用别的遍历方式例如DynamicActors等
 
 
 
7.GameReplicationInfo
GameInfo不存在于Client端,因此我们创建了GameReplicationInfo来给Client端传递GameInfo中的信息
 
在GameInfo的DefaultProperties中有
 
defaultproperties
{
   GameReplicationInfoClass=class'ArtGameReplicationInfo'
}
 
下面举一个示例:
如果GameInfo中有一些变量需要玩家知道,例如当前场景中还有多少敌人
var int EnemyNumLeft;
 
在ArtGame中有
ArtGameReplicationInfo(ArtGameReplicationInfo).EnemyNumLeft=EnemyNumLeft;
 
然后在class ArtGameReplication中
var int EnemyNumLeft;
 
replication
{
    if(bNetDirty)
    EnemyNumLeft;
}
 
同样的道理,大部分在PlayerController中和Pawn中的数据我们可以搬到ArtPlayerReplicationInfo中去处理HUD显示,例如玩家当前的经验和等级
 
 
 
8.关于function的修饰符
Reliabale VS Unreliable
 
Reliable总是可靠地传输,基于TCP传输模式即使在网络环境糟糕的情况下也会最终以重新发送的方式到达目的地。
Unreliable基于UDP方式,虽然效率高,但是有可能会传输丢失,一些不重要的执行可以用这种方式,例如溅血等。
 
 
 
client function:
当服务器想在客户端上调用一个函数,这个函数永远不会在服务器端而在只在客户端执行。例如在PlayerController中有一个GivePawn函数,该函数负责给客户端设置一个Pawn,而服务器端不关心,仅仅是对客户端说:“嘿,这是你的责任,赶快去处理吧!”。
reliable client function GivePawn(Pawn NewPawn)
 
做一个示例:
unreliable client function PlayTeleportEffect()
{
    //在客户端播放一个传送效果的粒子,
}
 
当然client function只在被这个客户端拥有的Actor上执行,什么Actor通常被这个Client拥有呢?
一般情况下,像PlayerController,Pawn,Weapon,Inventory,PlayerReplicationInfo等被这个client拥有
大部分在场景中放置的Actor都不被Client拥有,Spawner往往也不被client拥有。
 
做一个实例:
在场景中放置一个Actor,然后PostBeginPlay中调用
 
 
//只在客户端执行的函数
reliable client function ClientCall()
{
  `log("Client Function Called by the Server");
}
 
发现在服务器端和客户端都不会出现该Log.
 
再在ArtPawn和ArtPlayerController的客户端中会出现.
 
 
 
Server function:
Server funcionn是从客户端发送给扶我端的调用,例如客户端会表达:“我要执行一个动作,你也得保证你在服务器端对这个事件做出对应的响应。”
例如一个exec函数只在本地客户端执行,而服务器端不会发生相应的动作,这个时候就得通知。
 
进行一个实例:
exec function use()
{
  `log("I'm using the trigger!") ;       
}
 
在客户端运行将会执行log,但是服务器端不会进行动作。这个时候只需要
 
exec function use()
{
   `log("I'm using the trigger!") ; 
   ServerUse();     
}
 
reliable server function ServerUse()
{
  `log("Server using the trigger");
}
 
这时候服务器端将会执行对应的动作而客户端不会
服务器端输出:
Server using the trigger
 
 
当然我们也可以在客户端传递参数给服务器端:
reliable server function ServerUse(int Num)
{
  `log("Server using the trigger:"@num);
}
 
在use()中
exec function use()
{
   `log("I'm using the trigger!") ; 
   ServerUse(3);  
}
 
最后服务器端会输出
Server using the trigger 3