一.联机游戏通用概念

1.多人游戏类型

回合制游戏(棋盘)

“不做要求”

协商(限制较少)

有限(需自实现)

基于会话的实时游戏(cs)

需要

协商(< 1 hour)

高(内置的)

大型多人在线游戏

持续世界游戏(魔兽始界mmo)

需要

“永久”存在

有限(需自实现)

2.基于会话的实时游戏

  • 查找
  • 连接
  • 同步

3.客户端-服务器模型(Client-Server模型,C/S模型)

unity 实现 pico vr 多人联机_Pawn

 

二.理解Gameplay框架 

Gameplay框架 游戏规则、玩家输出与控制、相机和用户界面等核心系统。 ——虚幻引擎4文档

1.框图:

unity 实现 pico vr 多人联机_客户端_02

GamePlay架构的后半部分就自底向上的逐一分析了各个层次的逻辑载体,按照MVC的思想,我们可以把整个游戏的GamePlay分为三大部分:表现(View)、逻辑(Controller)、数据(Model)。一图胜千言:

2.神话故事

创世纪

UE创世,万物皆UObject,接着有Actor。
UObject:
相传在很久很久以前,UE在一片混沌虚无,有感于天地间C++原始之气,便撷取凝实一团C++之气,降下无边魔力,洒下秩序之光,便为这个世界生成了坚实的土壤UObject,并用UClass一一为此命名。
藉着UObject提供的元数据、反射生成、GC垃圾回收、序列化、编辑器可见,Class Default Object等,UE可以构建一个Object运行的世界。
Actor:
世界有了土壤之后,但还少了一些生动色彩,如同女娲造人一般,UE取一些UObject的泥巴,派生出了Actor。

在UE眼中,整个世界从此了有了一个个生动的“演员”,众多的“演员”们,一起齐心协力为观众上演一场精彩的游戏。
脱胎自Object的Actor也多了一些本事:Replication(网络复制),Spawn(生生死死),Tick(有了心跳)。
Actor无疑是UE中最重要的角色之一,组织庞大,最常见的有StaticMeshActor, CameraActor和 PlayerStartActor等。Actor之间还可以互相“嵌套”,拥有相对的“父子”关系。

Actor的概念在UE里其实不是某种具象化的3D世界里的对象,而是世界里的种种元素,用更泛化抽象的概念来看,小到一个个地上的石头,大到整个世界的运行规则,都是Actor.
Component:
世界纷繁复杂,光有一种Actor可不够,自然就需要有各种不同技能的Actor各司其职。在早期的远古时代,每个Actor拥有的技能都是与生俱有,只能父传子一代代的传下去。随着游戏世界的越来越绚丽,需要的技能变得越来越多和频繁改变,这样一组合,唯出身论的Actor数量们就开始爆炸了,而且一个个也越来越胖,最后连UE这样的神也管理不了了。终于,到了第4个纪元,UE窥得一丝隔壁平行宇宙Unity的天机。下定决心,让Actor们轻装上阵,只提供一些通用的基本生存能力,而把众多的“技能”抽象成了一个个“Component”并提供组装的接口,让Actor随用随组装,把自己武装成一个个专业能手。

Pawn:

在众多的Actor中有一类可以被控制的木偶(傀儡),即Pawn,它是由Actor派生出来用来表示肉体的;在Pawn中还有一类更为特殊级即Charater, 它不仅继承了Pawn的属性,还拥有人形"肉体"行走的动画等.

Controller:

光有肉体的Pawn一无是处,需要灵魂即Controller的驱使下来能发挥用处;Controller可以分为PlayerController和AiController,由PlayerController驱使的Pwan就是玩家角色,由AIController驱使的Pwan就是机器人;

PlayerState:

由于Controller(灵魂)可以附身到其他的Pawn上,所以需要有个东西去记录,这个Pawn(肉体)或者这个Controller(灵魂)的信息,我们一般称呼它为PlayerState(记忆).有这样一个受持续控制的由Pawn,Controller以及PlayerState所组成的集合,就可以"为所欲为"了.

GameMode:

但是这个世界中很多东西是不可以改变的就比如说GameMode(世界的规则),尤其是在网络游戏中,GameMode只存在服务器上,客户端无权修改.

Level
在UE的世界中,我们之前已经有了空气(C++),土壤(UObject),物件(Actor)。而现在UE又施展神力创建了一片片大陆(Level),在这片大陆上(.map文件),Actor们秩序井然,各种地形拔地而起,植被繁茂,天空雾云缭绕,圣光普照,这也是玩家们降生开始精彩冒险的地方。

可以从ULevel的前缀U看出来Level(大陆)也确实是继承于UObject(土壤)的。那既然同属于Object下面的各Actor们都拥有了一定的智能能力(支持蓝图脚本),Level自然也得体现出大地的意志,所以默认带了一个土地公(ALevelScriptActor),允许我们在关卡里编写脚本,可以对本关卡里的所有Actor通过名字呼之则来,关卡蓝图实际上就代表着该片大陆上的运行规则。
在Level已经有了管理者之后,一开始大家都挺满意,但渐渐的就发现,好像各个Level需要的功能好像都差不多,都是修改一下光照,物理等一些属性。所以为了方便起见,UE便给每一个Level也都默认配了一个书记官(Info),他一一记录着本Level的各种规则属性,在UE需要的时候便负责相告。更重要的是,在Level需要有其他管理人员一起协助的时候,他也记录着“GameMode”的名字来让UE可以指派

前面我们说过,有一些Actor是不“显示”的(没有SceneComponent),是不能“摆放”到Level里的,但是它依然可以在关卡里出力。其中一个家族系列就是AInfo和其之类。今天我们只简单介绍一下跟Level直接相关的一位书记官:AWorldSettings。

其实虽然名字叫做WorldSettings,但其实只是跟Level相关,我猜可能是在上古时代,当时整个世界只有一块大陆,人们就以为当前的大陆就是整个世界,所以给这块大陆的设置就起名为WorldSettings,后来等技术进步了,发现必须有其他大陆了,这个名字已经用得太多反而不好改了,就只好遗留下来了。当然也有可能是因为当Level被添加进World后,这个Level的Settings如果是主PersistentLevel,那它就会被当作整个World的WorldSettings。
注意,Actors里也保存着AWorldSettings和ALevelScriptActor的指针,所以Actors实际上确实是保存了所有Actor。

World
终于,到了把大陆们(Level)拼装起来的时候了。可以用SubLevel的方式:

3.和网络相关的部分

palyerstate记录buff以及血量等

unity 实现 pico vr 多人联机_客户端_03

4.整体类图

unity 实现 pico vr 多人联机_服务器_04

 

三.Unreal联网游戏基础

 

Unreal Engine 4使用标准的C/S体系结构

结论一:服务器是权威的永远不要相信客户端

客户端将动作或数据发送至服务器,服务器经验证后再做出反应

举几个例子 当在多人对战中作为客户端移动角色时,实际上并没有自己移动角色,而是告诉服务器您要移动它。

然后,服务器会为其他所有人更新角色的位置。

为防止本地客户端出现“滞后”的感觉,通常还允许玩家直接在本地控制其角色,但服务器仍“监控”并会在必要时覆盖本地玩家的位置。

同样的,聊天也是先将消息发送到服务器,再由服务器更新给所有客户端。

1.简述原理

Actor的Transform实际上是根组件的Transform,因此对Actor的同步实际上就是对根组件的同步。

在客户端和服务器的连接后,会在连接的Socket上面建立一个个Channel,服务器上面的每一个对象都对应着一个ActorChannel,

通过这个Channel,客户端和服务器的Actor建立通信通道,然后会进行数据同步和RPC调用,以及发起属性通知。

在Actor中,有一个标记bReplicateMovement被用来标记Actor是否同步,这个属性在蓝图的属性面板上面也有,如果标记为True,那么会进行相关的同步操作。

Actor的Transform属性是通过一个特殊的结构体ReplicatedMovement来进行传递的,里面包含了相关的需要同步的属性,在ReplicatedMovement中的属性值发生改变的时候,会调用OnRep_ReplicatedMovement进行事件通知。

ReplicatedMovement 仅仅是一个用来同步的中间值,并不是Actor的原始数据,对Actor的Transform操作并不会直接作用于 ReplicatedMovement,

那么Actor的真实数据是怎么同步到 ReplicatedMovement然后再同步到客户端的呢?

在服务器对Actor进行同步的时候,会调用PreReplication事件,在这个事件中会使用GatherCurrentMovement函数从当前的Actor信息填充ReplicatedMovement结构体。

ReplicatedMovement同步到客户端之后,会调用OnRep_ReplicatedMovement事件通知,在这个事件中,通过PostNetReceiveVelocity和PostNetReceiveLocationAndRotation来设置位置、旋转和速度。

2.Gameplay框架在多人联机下的划分

Server Only:这些对象仅存在于服务器上

Server & Clients:这些对象存在于服务器与所有客户端上

Server & Owning Client:这些对象仅存在于服务器与拥有其的客户端上

Owning Client Only:这些对象仅存在于拥有其的客户端上

unity 实现 pico vr 多人联机_c++_05

3.UE4的广播和同步

ue4 的Actor和ActorCompoennt和的同步与广播都建立在上面勾选 bRelicated的前提下

unity 实现 pico vr 多人联机_Pawn_06

复制是服务器将信息/数据传递给客户端的行为。

参与复制的最基础的类是AActor 类继承于AActor后,使它们能够根据需要复制属性。

但并不是所有继承于AActor的类都必须复制

标记为复制的Actor在服务器上生成时,也将在所有客户端上生成并复制;但其在客户端上生成时则仅存在于此客户端。

(1).RepNotify /Replication – 变量同步

1】None:表示变量不具备同步功能

【2】Replicated: 表示当进入网络裁剪范围内的时候服务器会同步变量到客户端(如果之前就在网络裁剪范围内,则会与连接的客户端相应变量值比较,如果值不一样,同步下去)。

【3】RepNotify: 表示当进入客户端网络裁剪范围内的时候,服务器会同步变量到客户端,并且触发相应的“Notify”函数(如果之前就在网络裁剪范围内, 会与该客户端相应变量值比较,如果值不一样,则触发“Notify”函数), 如下面所示:

unity 实现 pico vr 多人联机_Pawn_07

 

不带通知事件的变量同步:

 

UPROPERTY(Replicated)
	float a;
 
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

void AMyProject2Character::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	DOREPLIFETIME(AMyProject2Character, a);
}

带通知事件的变量同步

UPROPERTY(ReplicatedUsing = OnRep_NotifyXXX)
	float a;
 
UFUNCTION()
	void OnRep_NotifyXXX();
 
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

void AMyProject2Character::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	DOREPLIFETIME(AMyProject2Character, a);
}

(2).RPC– 函数同步

4.Role和RemoteRole

在这里我先说明一点,UE4的客户端代码和服务器代码是同一套代码(有点牛),服务器可能缺少了渲染等少数功能。

[1]ROLE_None时,根本不存在任意角色

[2]ROLE_SimulatedProxy 网络游戏中别的玩家在本地客户端的一个角色代理

[3]ROLE_AutonomousProxy 网络游戏中自己的玩家在本地客户端的一个角色代理

[4]ROLE_Authority: 网络游戏在服务器上的角色

unity 实现 pico vr 多人联机_客户端_03

 

 

 

验证一:自治和模拟以及权威:

服务器:小灰人在服务器上都是权威的所以设置成蓝色:

客户端:小灰人会把自治的变成绿色,模拟的小灰人变成红色

c++代码:

void AMyProjectCharacter::BeginPlay()
{
	Super::BeginPlay();
 
	UWidgetComponent* nameWidgetComponent = this->FindComponentByClass<UWidgetComponent>();
 
	if (nullptr == nameWidgetComponent)
		return;
 
	UUserWidget* nameUserWidget = nameWidgetComponent->GetUserWidgetObject();
	if (nullptr == nameUserWidget)
		return;
 
	UTextBlock* textBlock = Cast<UTextBlock>(nameUserWidget->GetWidgetFromName(FName("TextBlock_46")));
	if (nullptr == textBlock)
		return;
 
	FString name = "name_none";
 
	if (GetLocalRole() == ENetRole::ROLE_AutonomousProxy)
	{
		name = "ROLE_AutonomousProxy";
	}
	else if (GetLocalRole() == ENetRole::ROLE_SimulatedProxy)
	{
		name = "ROLE_SimulatedProxy";
	}
	else
	{
 
	}
	textBlock->SetText(FText::FromString(name));
 
}

unity 实现 pico vr 多人联机_服务器_09

unity 实现 pico vr 多人联机_Pawn_10

 

结论:服务器都是权威的,客户端都是不可信的

验证二:GameMode在服务器不在客户端

unity 实现 pico vr 多人联机_c++_11

结论:按N,服务器显示trrue客户端显示false;

 

 

验证三:验证actor开启复制通道实现的同步

不开的话客户端过不去,客户端会抖动,防止滞后

unity 实现 pico vr 多人联机_Pawn_12

结论:不开启的会导致不一致的情况发生