Unity MOBA 多人竞技手游制作教程
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

3.5 英雄选择逻辑实现

3.5.1 基础知识

英雄选择模块的实现包含的内容相对于前边课程较多,涉及的知识点也有很多。为此,在实现英雄选择模块前,首先介绍相关知识点,主要包含5点。

◎生成对象(Instantiate函数)

Instantiate函数是Unity3D中实例化的函数,也是对一个对象进行复制操作的函数。利用此函数可以将模板对象的所有子物体和子组件完全复制,成为一个新的对象。实例化后的新对象拥有与模板对象完全一样的属性,包括坐标值等。模板对象可以是场景中的对象,也可以是一个预制体(Prefab)。下面来看如何利用此函数在场景中生成一个对象。

Step 01 新建一个Test场景,在场景中创建一个对象Cube。

Step 02 创建一个脚本InstantiateTest,挂载到场景中的Cube对象上。

Step 03 打开脚本,定义一个公有变量cube,类型为GameObject并在场景中赋值。

Step 04 利用GameObject.Instantiate重新生成一个对象。代码如下所示。

Step 05 保存脚本,并单击Unity运行按钮,在Hierarchy面板上,出现了一个Cube(Clone)对象,如图3-18所示。

图3-18

由于创建的对象(Cube(Clone))与原对象(Cube)的所有属性均一致,因此在Game视图中直观上只能看到一个Cube对象,为了在观察时更为便捷,引入一种新的创建对象方式,将Cube作为资源加载到场景中,下面就来介绍资源加载。

◎资源加载(Resources.load)

Resources.load是将物体加载到内存中去,并非直接在场景中显示出来,因此,在利用此资源生成对象时经常会搭配Instantiate。使用这种方式加载资源时,需要先在Assets目录下创建一个名为Resources的文件夹(名称不能改),然后把资源文件放进去,当然也可以在Resources中再创建子文件夹,动态加载时需要添加相应的资源路径。下面来看如何利用此函数加载资源并生成对象。

Step 01 在Test场景中将Cube拖曳到Project视图中制成预制体,并拖曳到Resources文件夹中。

Step 02 创建一个脚本ResourceTest,挂载到场景中Camera对象上。

Step 03 打开脚本,利用Resources.load去加载资源文件夹中的对象。加载时,直接根据资源的名称加载。

Step 04 获取资源后,可以利用Instantiate函数创建资源对象。代码如下所示。

Step 05 保存脚本,并单击“运行”按钮。在Hierarchy面板上,可以看到多了一个Cube(Clone)对象,并且在Game视图中出现了相应的对象,如图3-19所示。

图3-19

◎事件监听器(UI EventListener)

玩家可以与界面进行交互,因为添加了UI Button等组件。但是如果动态生成的对象想要实现点击事件功能,动态绑定执行函数就比较麻烦了,因此利用NGUI中的UIEventListener为UI对象绑定执行函数。具体操作过程如下。

Step 01 在Test场景中新建一个UI Sprite,并添加一个BoxCollider组件,BoxCollider的大小与UI Sprite大小一致。

Step 02 重新定义一个变量sprite,类型为UI Sprite,并且在函数外部定义。

Step 03 利用NGUI中的UIEventListener为对象添加一个点击事件。代码如下所示。

Step 04 鼠标点击场景中的图片,在控制台中输出“点击了此对象”。如图3-20所示。

图3-20

◎精灵图片(UI Sprite)

游戏中的界面是通过NGUI来搭建的。搭建界面时,图片是通过设置组件中的Sprite(名称)来设置的,如图3-21所示。因此更改Sprite图片时,只需要重新指定图片的名称即可,但是指定的图片必须是当前图集中的图片。具体操作过程如下。

图3-21

Step 01 利用NGUI创建一个Sprite,并为此设置一个精灵图片。

Step 02 在脚本中新建一个变量sprite,类型为UI Sprite,并在场景中赋值。

Step 03 通过重新设置精灵图片的名称可以更新图片。指定图集后,单击Sprite便可以查看所有图片的名称。代码如下所示。

Step 04 场景中的图片更新完成,如图3-22所示。

图3-22

◎场景加载(SceneManager)

游戏中经常会包含不同的场景,会涉及场景的跳转。随着Unity的不断更新,之前的场景加载Application.LoadLevel已经被弃用,而新的场景加载的用法会使用到SceneManager中的函数。下面来看如何利用SceneManager切换场景。具体操作过程如下。

Step 01 创建两个场景:Scene1和Scene2。在第二个场景中添加Label来进行标识,便于两个场景的区分。

Step 02 新建脚本,挂载到Scene1中的Camera对象上,双击打开。利用SceneManager.LoadScene函数去加载场景。参数为要加载的场景的名称。代码如下所示。

Step 03 单击File→Build Settings命令,将新创建的场景添加到Scenes In Build中,如图3-23所示。

图3-23

Step 04 运行后,场景由Scene1切换到Scene2中,如图3-24所示。

图3-24

3.5.2 完善英雄选择

游戏匹配结束后,服务器端进入英雄选择模块,模块中简化了英雄选择的过程。

主要包含下面5个部分:

□ 切换选择界面。

□ 英雄列表。

□ 选择英雄。

□ 确定英雄。

□ 场景切换。

英雄选择的进行是由消息驱动的。在此模块中,服务器端返回了可选英雄的列表、敌方选择信息、我方信息等。客户端根据不同的信息进行处理。消息的接收同样在HandleNetMsg中,消息处理在MessageHandler中,如果有必要可以将消息广播回来,广播回来时定义相关函数进行处理。

◎切换选择界面

客户端处理的第一个消息是eMsgToGCFromGS_NotifyBattleSeatPosInfo,接收到此消息时,定义一个函数来显示选择界面。在函数中将其他界面隐藏,仅将选择英雄的界面显示出来,代码如下所示。

服务器端返回的消息中包含每个队员的位置信息,本游戏只有一对一模式,所以没有涉及所有队员的位置信息。后期课程优化时会利用此消息对所有对象进行排列。

◎英雄列表

第二个要处理的消息是英雄选择列表,进入选择界面后首先要显示可选英雄的列表,英雄列表信息包含在服务器返回的消息体中,在接收到eMsgToGCFromGS_NotifyHeroList的消息时,定义一个函数onNotifyHeroList处理英雄列表。代码如下所示。

返回的消息体中包含着每个玩家的ID,因为不同玩家所用有的英雄是不同的,所以根据玩家的ID去加载可选英雄的配置文件。heroInfo就是根据玩家ID加载的可选英雄的信息,如果加载成功,则显示所有的可选英雄的列表,并加载所有的英雄模型。重新定义两个函数:LoadSelectHero与LoadModel。LoadSelectHero负责显示所有的英雄列表,LoadModel负责加载列表中英雄对应的模型,但是并不在此时显示,而是在点击某个英雄时显示,如图3-25所示。

图3-25

创建英雄图标

创建英雄图标的整个流程如图3-26所示。

图3-26

LoadSelectHero负责将所有可选英雄图标显示出来,并为所有的英雄图标添加点击事件。代码如下所示。

mMidShow是选择英雄界面SelectHeroBG下的HeroList,此变量在GameStart中定义完成后,在外部指定。获取UIGrid组件时就是在此对象下获取Grid对象再获取组件的。

生成英雄图标时利用mHeroItem,此对象是一个预制体,预制体目录为Resources/Prefab/Hero1,根据此模板生成英雄图标后重新设置它的大小比例以及头像图片。这里需要注意的是,在设置头像时只要将UISprite组件的图片名称更新便可以重置头像,头像的名称从参数中获取,所以在调用此函数时,第一个参数指的就是头像图片的名称。创建完成之后,利用Grid组件将生成的位置自动重置。

此函数中最重要的内容就是为生成的英雄图标添加点击事件,当玩家点击了某一个英雄时,要向服务器发送消息,通知服务器客户端的选择。所以此事件就是向服务器端发送选择英雄的消息。

加载模型

LoadModel函数将模型ID与路径传过来,利用这两个参数去加载模型并生成,但是创建完成后所有的模型都隐藏了,并没有显示出来。最后将生成的模型添加到heroModelTable字典中。代码如下所示。

heroModelTable是一个字典,定义方式如下所示。其中,第一个参数是模型的ID,第二个参数就是模型本身。

◎选择英雄

英雄列表创建完成后,在点击某一个英雄时,客户端向服务器端发送选择英雄的消息。服务器端接收到此消息后返回eMsgToGCFromGS_NotifyTryToChooseHero的消息,接收到此消息时,经过消息的接收与反序列化,在MessageHandler中广播回来。定义一个函数用来处理此消息,代码如下所示。

此函数的功能是更新选择英雄的头像并显示选择的模型。在传回的消息体中,包含了玩家的位置pos。pos等于1代表更新我方的英雄,所以当pos等于1时更新右边的头像,并在中间显示模型,模型在之前已经加载出来并被存储在heroModelTable中,可以直接通过消息体中的模型ID获取并显示。

◎确定英雄

玩家选择某一个英雄后,可以通过单击“确定”按钮来通知服务器,定义一个函数并绑定到“确定”按钮上。消息体的类型为SelectHero,利用NetWorkManager中的SendMsg将此消息发送,并在单击“确定”按钮后显示加载界面,隐藏其他的界面。mLoadingUI是加载界面,定义完成后在外指定,指定的是UI Root下的LoadingBG。代码如下所示。

小提示

“确定”按钮上必须添加BoxCollider组件与UIBtton组件。

◎场景切换

确定英雄时,客户端发送消息通知服务器端已确定的英雄。服务器端接收到消息处理后返回,此时界面处于加载界面,如图3-27所示。

图3-27

此界面主要为更新加载场景的敌我双方选择英雄的精灵图片,返回的消息体中包含敌我双方的位置信息。所以需要定义一个函数onNotifyEnsureHero来更新敌我双方选择的英雄图标。代码如下所示。

此函数内容直接分析if-else语句。在这两段代码中,目标就是更新图标。因此,主要步骤就是获取UISprite,并重新设置精灵图片的名称。需要注意的是,名称的获取是通过配置文件读取的,而配置文件读取是根据选择的英雄ID来进行的。英雄头像的ID包含在eMsgToGCFromGS_NotifyTryToChooseHero的消息体中,所以在接收此消息体时,将此消息体保存下来。为什么在接收确定英雄消息中不包含选择英雄的ID呢?原本的游戏在选择英雄时是同时显示敌我双方的选择情况,类似于英雄联盟。而本游戏中在游戏选择时仅显示我方的选择情况,敌方的选择情况是在加载界面中显示的,所以敌方选择英雄的ID包含在上一个消息体中。也因此,在上一个消息体中将选择英雄的消息体获取并保存。

加载完成敌我双方选择英雄的功能后,服务器通过状态判断客户端的进程。服务器端游戏一开局即进行状态改变。比如匹配状态、英雄选择状态、加载状态等,每种状态的转换都会通过发送消息来驱动,发送的消息类型为eMsgToGCFromGS_NotifyBattleStateChange。定义一个函数用来处理此消息。消息体中包含着游戏的状态。当服务器端的状态转换为战斗场景时,客户端便要加载场景了。因为现在本游戏中并没有涉及状态机的问题,所以在处理场景加载时只关心服务器端的状态是否为转换战斗场景,版本优化时会逐渐完善客户端的问题。

消息体中的state值为2时,代表场景加载。所以在此时去加载场景。但是加载场景要考虑一些问题,场景加载后,原场景的对象全部销毁,处理消息接收的GameStart也会销毁。那么在转换场景前先要停止消息的接收并移除消息接收的监听器。代码如下所示。

定义一个新的变量mHandleMsg。只有在它打开时才会接收消息。在场景切换时关闭。在Update函数中,将连接服务器端的语句加上判断条件,只有mHandleMsg为true时才会进行连接,代码如下所示(一开始游戏就会连接服务器端,所以在Awake函数中将此值设置为true)。

加载场景时利用SceneManager中的异步加载函数LoadSceneAsync,加载完成后返回加载结果。pvp_001是场景的名称,此场景存在于Scenes文件夹中。场景加载完成后一定要通知服务器端加载完成。所以最后要发送LoadComplete消息,服务器端才能返回战斗场景的信息。

小提示

使用SceneManager时,一定要引用命名空间UnityEngine.SceneManagement。