UE4网络的简单原理

UE4内部封装实现了大量和网络相关的代码,使得我们不需要再自行编写很多底层的网络同步代码了(连接主机,套接字传输等等)。我们只需要实现相关的函数接口,并进行一定的设置,就可以实现网络游戏的功能了。 
下面先讲讲UE4NetWork的几个要点: 
*1.UE的网络同步和Replicate密切相关,从字面上理解,就是Server到Client的信息同步。 
2.大部分的自动化Replicate都是Server到Client的,Client向Server发信息同步需要额外的设置。 
3.GameMode只有一个实例,位于Server上。 
4.默认情况下每当一个Client连接到Server,会在Server和Client两边都生成一个相应的Controller和玩家Actor。 
5.Server拥有的Actor是源实例,别的Client上看到的是副本,是simulating copy。 
6.相应地,如果一个Client连接到Server,会在Server和所有连接的Client上给他生成一个玩家Actor,但是注意了,该Client本地的这个Actor不是simulating copy,而其他Client中生成的这个副本是simulating copy。比如如果有3个玩家,一个Server两个Client,则第二个Client玩家的ActorC2在第一个Client玩家本地生成的ActorC2是simulating copy。但是第二个Client自己的ActorC2不是simulating copy,因为这个是源实例。别人的才是simulating copy。 
7.Server在默认情况下(你不修改Actor内部实现)仅仅对远端(Remote)是simulating copy的Actor进行位置同步。比如,Server上连接了两个Client,Client1的玩家实例是ActorC1,Client2的是ActorC2。那么在Server和两个Client上都会同时存在3个Actor:ActorS,ActorC1,ActorC2。同步Replicate发生时,ActorC1不会把自己的位置同步给Client1,因为Server上的ActorC1查看了自己在远端Client1上的副本,发现不是simulating copy,所以不同步位置。而ActorC1在查看自己在远端Client2上的副本时,发现是simulating copy,所以会把自己的位置同步给Client2。*

那么我们现在实现一个最简单的网络同步试试:

1.创建一个项目,创建新的Pawn类。 
2.在Pawn类的构造函数里添加一句:

//这个语句就是启动UE4内建的同步功能,同步功能是UE4内建在Actor类内部的,可以同步很多Actor的细节信息
//比如Actor的位置,角度,Attach顺序,是否模拟物理等等,这就是为什么Controller,GameMode等等都继承自Actor且被放进场景的原因,因为这样他们就可以享受Actor内部同步逻辑带来的好处
this->SetReplicates(true);

 
 
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

3.编写其他控制代码,使得Pawn可以相应操作,可以移动。 
4.更改Play的Options,把玩家数改为2,窗口模式改为弹出额外窗口。 
5.把DefaultPawnClass改为我们编写的可以网络同步的Pawn。(这一步是因为两个玩家都是UE自动Spawn的,他必须知道类型) 
6.点击Play,可以看到弹出两个窗口,一个是Client一个是Server。 
7.移动Server端游戏里的PawnS,可以发现Client上,那个PawnS也是移动的。 
7.但是如果移动的是Client的PawnC,Server上的PawnC是不会动的,这就是我们提到的Replicate方向问题。自动的Replicate都是Server向Client进行同步的,Client不会实时向Server同步数据。 
这一点很重要,因为Replicate都是Server向Client进行同步,所以我们大部分的运算和判定都应发生在Server上,然后Server把运算好的游戏世界同步到Client,而Client怎么通知Server输入或者一些重要的事件呢?

我们再编写下一个小程序: 
1.直接修改刚才的源代码,对Client的输入监听函数进行一些修改: 
原来的代码:

//控制这个Pawn向前移动,幅度为in
void moveforward(float in);
//控制水平移动的函数
void moveleft(float in);

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

修改为:

//这个依然是监听输入的函数,但是实现有变
    void moveforward(float in);
    //这个函数就比较重要了,他有Server修饰,表示这个函数即使被Client调用,也会在Server上处理,同理也有Client修饰符号
    //WithValidation表示这个函数在调用远端相应函数时会进行有效性检查,有一种防止作弊的味道
    //而reliable表示这个函数在本地调用之后,无论网络传输是否丢包,一定会在远端得到调用,是一个可靠传输
    UFUNCTION(Server, WithValidation, reliable)
    void Servermoveforward(float in);
    //这个函数是Servermoveforward真正的实现代码,Servermoveforward本身没有实现!
    void Servermoveforward_Implementation(float in);
    //这个函数会在Server上调用,是判断Client发来的参数是否正确可信,就是上边WithValidation对应的检查函数
    bool Servermoveforward_Validate(float in);

    void moveleft(float in);
    UFUNCTION(Server, WithValidation, reliable)
    void Servermoveleft(float in);
    void Servermoveleft_Implementation(float in);
    bool Servermoveleft_Validate(float in);

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

cpp实现为:

void ANetWorkPawn::moveforward(float in)
{
    if (in == 0)
        return;

    if (this->Role < ENetRole::ROLE_Authority)//看看是不是在Server上
    {
        this->Servermoveforward(in);//不是的话,让Server处理这个移动
    }
    else
        this->Servermoveforward_Implementation(in);//是的话,自己处理这个移动
}

void ANetWorkPawn::Servermoveforward_Implementation(float in)
{
    auto locnow = this->GetActorLocation();
    auto dir = this->GetActorForwardVector()*in * 10;
    this->SetActorLocation(locnow + dir);
}

bool ANetWorkPawn::Servermoveforward_Validate(float in)
{
    //暂时先不考虑防作弊,直接返回true,如果返回false,Server会强制断开请求这个函数的Client
    return true;
}

void ANetWorkPawn::moveleft(float in)
{
    if (in == 0)
        return;

    if (this->Role < ENetRole::ROLE_Authority)
    {
        this->Servermoveleft(in);
    }
    else
        this->Servermoveleft_Implementation(in);
}

void ANetWorkPawn::Servermoveleft_Implementation(float in)
{
    auto locnow = this->GetActorLocation();
    auto dir = this->GetActorRightVector()*in * 10;
    this->SetActorLocation(locnow + dir);
}

bool ANetWorkPawn::Servermoveleft_Validate(float in)
{
    return true;
}


 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

可以看到,我们如果在Client进行前进输入,依然会调用到moveforward函数,但是moveforward已经不再是之前那样本地修改Actor的位置了,而是进行了Role的判断,Role是这个Actor所在位置的说明,如果是ROLE_Authority,表明这个Actor是Server上的Actor,如果不是,表明这个Actor是Client上的副本。所以我们判断当前调用输入的Actor到底是不是Server上的,如果是,我们直接进行移动逻辑(就是调用Servermoveforward_Implementation,因为移动逻辑都在这里),如果不是,我们调用Servermoveforward,这个函数看似是调用了本地的函数,但是因为他有UFUNCTION(Server)的修饰,UE将发送一些必要信息,使得这个函数在Server端被调用,于是Server端的Servermoveforward_Implementation就被调用了。而在Servermoveforward_Implementation中我们实现了移动逻辑。所以,Client在进行输入,他的输入请求被发送到了Server上相应的Actor内部,在那里进行了处理,之后Server又再次把Server端更新过的位置同步到Client(想想文章开头的simulating copy问题,这一句真的正确吗?),所以Client上的PawnC最后也移动了。

那么。。。如果真的照这个方法写的话,会发现一个问题:Client端移动PawnC,Server端的PawnC移动了(说明我们的远程调用是成功的),但是Client窗口中,他自己(PawnC)却纹丝未动。这就是文章开头提到的simulating copy身份问题,因为你拥有这个PawnC,所以在Server端看这个Actor的远端身份,不是simulating copy,所以不会把信息同步给你,我们看看UE4的Actor同步相关的源代码:

//Pawn的同步函数调用了其父类Actor的同步函数,而位置的同步也发生在底层--Actor中,所以我们找到这个函数
void AActor::GetLifetimeReplicatedProps( TArray< FLifetimeProperty > & OutLifetimeProps ) const
{
    UBlueprintGeneratedClass* BPClass = Cast<UBlueprintGeneratedClass>(GetClass());
    if (BPClass != NULL)
    {
        BPClass->GetLifetimeBlueprintReplicationList(OutLifetimeProps);
    }

    DOREPLIFETIME( AActor, Role );
    DOREPLIFETIME( AActor, RemoteRole );
    DOREPLIFETIME( AActor, Owner );
    DOREPLIFETIME( AActor, bHidden );

    DOREPLIFETIME( AActor, bTearOff );
    DOREPLIFETIME( AActor, bCanBeDamaged );
    DOREPLIFETIME( AActor, AttachmentReplication );

    DOREPLIFETIME( AActor, Instigator );

    //可以看到,同步是有条件的,只有当该Actor的远端(Remote)是simulated或者使用了物理引擎时,才进行位置同步
    //而我们作为Client,拥有这个Actor,所以远端身份不是simulating copy,自然不会得到同步,PawnC在Client窗口也就不会移动了
    DOREPLIFETIME_CONDITION( AActor, ReplicatedMovement, COND_SimulatedOrPhysics );
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

UE为什幺这样写?因为本地拥有的Actor应该自己本地实现数据更新,之后在更新Server的数据,不能指望Server全部算好给你同步,这样可以节省不少的带宽。

那么我们的第二个程序怎样才能这正确呢?最简单的方法就是给Actor加上物理引擎模拟了

this->mesh->SetSimulatePhysics(true);

 
 
  • 1
  • 2
  • 1
  • 2

这样会强制使得Server端同步你的位置信息给你。因为那里的同步条件就是simulated或者使用了物理引擎。 
最好的方法当然不是这样,我们应该在自己的Client端先进行移动逻辑,之后把自己的位置变动通知给Server,让Server更新自己那边的信息,把我们的位置移动转发到所有玩家哪里去(因为对于别的玩家,我们是simulating copy),代码更改为:

void ANetWorkPawn::moveforward(float in)
{
    if (in == 0)
        return;

    //无论是Server还是Client,我们都先进行移动逻辑,如果是Server,他的移动也会被同步到Client,所以没有问题,如果是Client,他先自己移动一下,之后通知Server,让Server上和自己同步,达到所有人都同步的效果
    auto locnow = this->GetActorLocation();
    auto dir = this->GetActorForwardVector()*in * 10;
    this->SetActorLocation(locnow + dir);
    if (this->Role < ENetRole::ROLE_Authority)
    {
        this->Servermoveforward(in);//如果是Client,我们要通知Server我们移动了
    }
}

void ANetWorkPawn::Servermoveforward_Implementation(float in)
{
    auto locnow = this->GetActorLocation();
    auto dir = this->GetActorForwardVector()*in * 10;
    this->SetActorLocation(locnow + dir);
}

bool ANetWorkPawn::Servermoveforward_Validate(float in)
{
    return true;
}

void ANetWorkPawn::moveleft(float in)
{
    if (in == 0)
        return;

    auto locnow = this->GetActorLocation();
    auto dir = this->GetActorRightVector()*in * 10;
    this->SetActorLocation(locnow + dir);
    if (this->Role < ENetRole::ROLE_Authority)
    {
        this->Servermoveleft(in);
    }
}

void ANetWorkPawn::Servermoveleft_Implementation(float in)
{
    auto locnow = this->GetActorLocation();
    auto dir = this->GetActorRightVector()*in * 10;
    this->SetActorLocation(locnow + dir);
}

bool ANetWorkPawn::Servermoveleft_Validate(float in)
{
    return true;
}


 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

一个小小的移动同步就要写这么多吗?是也不是,UE4的Character类中的几个移动函数是内建网络同步了的,不需要在进行多于处理。因为这里我们从Pawn级别开始写,所以就比较麻烦。

这里的代码和宏基本能实现简单的网络同步逻辑,但是我们可以看到,服务器等待,客户端接入,游戏开始等等功能的代码我们都没写,都是UE的editor帮我们直接搭建好的(在我们按下play的时候)但是一个网络游戏肯定不是一打开就连接好切地图环境都搭建好的,所以想制作一个完整的网络游戏我们还需UE4中很重要的一个部分,就是Session,wiki上有相关的介绍https://wiki.unrealengine.com/How_To_Use_Sessions_In_C%2B%2B,非常详细,Session就可以帮助我们实现从寻找房间,等待接入,玩家接入,发送地图,初始化地图环境等等功能了。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值