效果描述

一个服务器,两个客户端,让他们连接后分别生成不同的Pawn,并且在不同的位置生成。

意义

这是个项目需求,但是我发现如果能够彻底理解并制作出这个功能,会对虚幻4内置的网络功能以及一些重要的Gameplay 的类有更深入的了解。

目前已有解决方案

在Google上也搜了好久,但是相关信息并不多,比较靠谱的最终都指向同一个wiki页面:Spawn Different Pawns For Players in Multiplayer,但是他的解决方案有个缺陷:开发过程中不好测试——原因是他的方案需要一个外部存储的数据或者SaveGame,而开发时你不可能把一个工程复制两份。除非发布以后,把发布好的文件复制两份去测试,但这样也很麻烦。

我的解决方案概述

我采用了蓝图实现。区分不同的客户端主要是依靠OnPostLogin的连接顺序。

在GameMode中设置一个int变量作为用户索引,每次OnPostLogin后递增1。因为服务器(Listen Server)的PostLogin肯定是第一个(没哪个客户端的连接速度会快过本机),所以服务端连接时索引为0。而其他两个客户端在项目中并不需要立即区分其角色,只需要先分别给一个不同的角色,后面如果需要修改,在服务端提供界面进行手动修改即可。

预备知识

  • 首先要熟悉UE4中的网络/复制/RPC相关概念以及引擎GameMode,PlayerController等类在网络环境中的表现,这些信息需要仔细阅读和理解官方的文档:

NetWorking and Multiplayer 其中的ActorReplication章节非常重要需要仔细阅读和理解,MultiPlayer in Blueprints章节是个引导概况性质的章节,也需要仔细读

How to replicate Actors

Replicating Functions

Replicating Variables

  • 其次要阅读GameModeBase源码,了解从PostLogin到真正生成Pawn之间都发生了什么,这里我已经总结成了一幅图:

具体实施步骤

准备工作

先使用ThirdPerson模板创建工程,然后创建ThirdPersonCharacter的三个子类,给不同的颜色作为标记,分别是红,绿,蓝,其中红色准备作为服务器的Pawn,其他两个作为客户端的。

以GameModeBase为基类创建一个GameMode蓝图,这里命名为MyGameMode

以PlayerController为基类窗机一个Controller蓝图,这里明明为MyPlayerController

创建一个Enum,命名为ClientType,包含三个值:Server, ClientA, ClientB

在ThirdPersionExampleMap中,删掉场景中的Character,然后把PlayerStart复制2个出来,摆放好位置,分别给三个PlayerStart的Player Start Tag属性设置为Server,ClientA,ClientB

关键步骤

MyPlayerController

在MyPlayerController中创建一个变量:

MyClientType:ClientType枚举类型,设置为Replicated,用于标记该PlayerController的类型

MyGameMode

在MyGameMode中,创建6个变量:

ClientIndex : int型,默认设置,用于标记不同的客户端连接,每次OnPostLogin后会递增1

PlayerStarts:PlayerStart引用类型,数组,其他默认,用于存放所有的PlayerStart

ServerPawnClass: Pawn类类型,默认设置,表示服务器端Pawn的类

ClientAPawnClass: Pawn类类型,默认设置,表示客户端APawn的类

ClientBPawnClass: Pawn类类型,默认设置,表示客户端BPawn的类

CurrentPlayer: MyPlayerController引用类型,默认设置,临时存放传入的PlayerController

创建两个关于获取PlayerStart的函数:

GetAllPlayerStarts

GetPlayerStartByTag:

这两个函数的含义如其名称,功能也比较简单。不过需要注意调用时机。有人可能会想,直接在BeginPlay里调用GetAllPlayerStarts就可以了,实际上这样不行,因为OnPostLogin事件会在BeginPlay之前发生。

右键搜索OnPostLogin, 创建Event OnPostLogin事件,连接如下图:

步骤释义:

  1. 当有玩家连接进来后(包括服务器自身连接自身),把PlayerController存入一个临时变量Current Player。
  2. 根据Client Index设置Current Player的MyClientType,依次设置为Server,ClientA,ClientB。
  3. 然后把Client Index自增1。
  4. 判断Controller是否已经拥有了Pawn,如果有则销毁。
  5. 调用Restart Player重新生成该Controller的Pawn(注意看上文中的流程图,Restart Player之后进行了什么操作)

到这步之后,Restart之后并没有改变要使用的Pawn的类。

根据上文中的流程图,Pawn的类是在GetDefaultPawnClassForController函数中获取的,在三处都使用了该函数来返回Pawn的类型,因此我们需要覆盖这个函数,点"Functions"中的Override按钮,覆盖该函数。

函数截图如下:

步骤释义:

获取PlayerController,转换为MyPlayerController,根据刚才存入的MyClientType来返回不同的Pawn类型。使用MyGameMode里的三个Pawn Class 变量。

到这里,Pawn类别已经可以正常区分了,但是起始点还不行,都是在同一个位置生成。下面要解决的就是区分PlayerStart。

看上文中的流程图,可以看到,在Restart Player函数中是通过调用Find Player Start函数来决定使用哪个PlayerStart。因此要覆盖FindPlayerStart函数。

在MyGameMode里的Functions里点"Override按钮,覆盖FindPlayerStart函数,覆盖后的截图如下:

因为之前已经给不同的PlayerController分配了不同的角色,所以这步比较简单,也是区别My Client Type,返回不同的PlayerStart即可。

到此为止就完成了

效果截图

UE4联网游戏中让不同的客户端生成不同的Pawn类型的更多相关文章

  1. Java游戏服务器成长之路——弱联网游戏篇(源码分析)

    前言 前段时间由于公司的一款弱联网游戏急着上线,没能及时分享,现在基本做的差不多,剩下的就是测试阶段了(本来说元旦来分享一下服务器技术的).公司的这款游戏已经上线一年多了,在我来之前一直都是单机版本, ...

  2. UE4蓝图与C++交互——射击游戏中多武器系统的实现

    回顾   学习UE4已有近2周的时间,跟着数天学院"UE4游戏开发"课程的学习,已经完成了UE4蓝图方面比较基础性的学习.通过UE4蓝图的开发,我实现了类似CS的单人版射击游戏,效 ...

  3. 体育游戏中的Player类

    最近在做一个棒球的游戏,开始感觉还是挺酷炫的,但是其实做法挺朴实的,想象中的球员是多么智能,这样那样的,其实只是表象. 关于球员的类是游戏里非常重要的部分,这个玩意怎么写呢,可以这样写...... 棒 ...

  4. Aery的UE4 C++游戏开发之旅(3)蓝图

    目录 蓝图 蓝图命名规范 蓝图优化 暴露C++至蓝图 暴露C++类 暴露C++属性 暴露C++函数 暴露C++结构体/枚举 暴露C++接口 蓝图和C++的结合方案 使用继承重写蓝图 使用组合重写蓝图 ...

  5. MMORPG大型游戏设计与开发(客户端架构 part16 of vegine)

    由于近来比较忙碌和有些困倦的原因,所以关于这部分的文章没有及时更新,一句话:让朋友们久等了!今天所讲的是客户端vengine(微引擎)中最后一个部分,就像上节所说,这一部分的内容比较多.可能有些朋友看 ...

  6. MMORPG大型游戏设计与开发(客户端架构 part12 of vegine)

    在游戏中的交互过程中输入是一个必不可少的过程,比如登陆的时候需要用户输入用户名与密码,就算是单机游戏很多时候也要求用户输入一个用户名作为存档的依据.网络游戏中没有了输入,只用鼠标来交互是不切实际的,因 ...

  7. MMORPG大型游戏设计与开发(客户端架构 part9 of vegine)

    时间在人们的生活中是多么重要的东西,如果打乱了时间,不知道这个时间会成什么样子.在客户端中,自然也有时间模块,因为不同的时间可能会处理不同的事情,特别是在追求高度自由化的同时,时间也成为了一个很重要的 ...

  8. MMORPG大型游戏设计与开发(客户端架构 part3 of vegine)

    无论在何处在什么地方,我们都或多或少的接触到数学知识.特别是在客户端中,从打开界面的那一刻起就有太多与数学扯上的关联,如打开窗口的大小,窗口的位置,窗口里面的元件对象,以及UI的坐标等等.而在进入游戏 ...

  9. AS3游戏中可视对象上限及位图相关的内存消耗实测

    前些天连续做了一些测试,以加深对AS3的掌握和在项目中对 游戏 性能.效率优化方面的一些处理,有很多测试实际意义不大,都不过是证明一些猜想是正确的,除此没有什么. 但前天进行的一系列测试中,有一些对游 ...

随机推荐

  1. Java中的静态变量、静态方法、静态代码块

    转载自http://www.cnblogs.com/panjun-Donet/archive/2010/08/10/1796209.html (一)静态方法(1)在Java里,可以定义一个不需要创建对 ...

  2. jQuery中this与$(this)的区别总结

    这里就谈谈this与$(this)的区别. 1.jQuery中this与$(this)的区别 $("#textbox").hover( function() { this.titl ...

  3. 关于golang的label

    1 label所在的代码段在没有跳转的时候按照所在的位置按顺序执行 2 break label和continue label可以一次性从多重循环中跳出 3 goto label的用法和c/c++中的一 ...

  4. 【系统】win10锁屏后,护眼绿自动恢复解决

    针对自己电脑(其他人的不晓得),win10锁屏后,重新登录,护眼绿会自动恢复成白色,查询资料需要修改注册表两个地方: 1.计算机\HKEY_CURRENT_USER\Control Panel\Col ...

  5. JS的for循环包裹异步函数的问题

    有个循环,循环一个异步回调,为啥回调引用的循环值都是最后一步循环的循环值?然后,又有些时候无论什么循环值都得不到? var arr = [1,3,5,7,9]; var arrLength = arr ...

  6. vue梳理(2)

    -app.vue作为根组件被挂载到index.html文件里,其他的所有组件都是在app.vue组件里做文章. 展示给用户的就是app.vue里的内容,你觉得删的没有内容了但实际还有很多是因为什么呢? ...

  7. Spring基础16——使用FactoryBean来创建

    1.配置bean的方式 配置bean有三种方式:通过全类名(class反射).通过工厂方法(静态工厂&实例工厂).通过FactoryBean.前面我们已经一起学习过全类名方式和工厂方法方式,下 ...

  8. LOAD - 装载或重载一个共享库文件

    SYNOPSIS LOAD 'filename' DESCRIPTION 描述 这个命令装载一个共享库文件到PostgreSQL服务器的地址空间. 一旦一个文件被装载,如果该文件前面曾经装载过,那么服 ...

  9. Codeforces 948 数论推导 融雪前缀和二分check 01字典树带删除

    A. 全部空的放狗 B. 先O(NLOGNLOGN)处理出一个合数质因数中最大的质数是多少 因为p1 x1 x2的关系是 x2是p在x1之上的最小倍数 所以x1的范围是[x2-p+1,x2-1]要使最 ...

  10. FAT12

    FAT12 is one of FAT file system families,mostly used on 1.44MB floppy disk. FAT 's full name is File ...