3.3 登录逻辑实现
3.3.1 基础知识
在登录逻辑开发过程中,主要实现两大功能:显示服务区列表;组队匹配。在此过程中,会涉及一些基础知识点。下面通过示例对这些内容进行简单介绍,熟悉后再根据所学的知识将整个逻辑功能实现出来。
◎按钮触发函数
在游戏运行过程中,经常会和界面进行交互。最直接的就是通过单击按钮,触发某个功能,本质上是执行某个或多个函数。在通过示例演示此功能的实现过程,可以分为以下三步。
● 第一步:定义功能函数
首先,创建一个ButtonEvent脚本;然后,在此脚本中定义一个Public类型的功能函数。代码如下所示:
● 第二步:添加组件
具体操作步骤如下。
Step 01 创建一个Test场景,在场景中创建一个Sprite对象,用于存放按钮皮肤,在Sprite对象中再创建一个Label对象用于显示文字,如图3-8所示。
图3-8
Step 02 单击Sprite对象,将其改名为Button,并添加碰撞器(BoxCollider)与按钮组件(UIButton),可通过如图3-9所示的Add Component按钮添加组件。
Step 03 修改碰撞器的大小,使其与按钮大小一致。此组件的主要作用是触发此按钮。
● 第三步:绑定功能函数
给按钮添加点击事件。具体操作步骤如下。
Step 01 将挂载ButtonEvent脚本的对象拖曳到Notify栏中,如图3-10所示。
图3-9
图3-10
由于在此场景中,把ButtonEvent脚本挂载到了Button按钮对象上,因此,拖曳的对象便是Button对象。
Step 02 指定要执行的函数,在Method处,选择定义功能函数的类,并选取对应的功能函数,如图3-11所示。
图3-11
最后,运行游戏,可以在Console控制台中看到“开始游戏事件被调用”,说明此函数被调用。
小提示
(1)UI Button组件是NGUI中的脚本,框架中包含了NGUI插件,所以能够直接添加此组件。
(2)按钮点击事件的修饰符为public。
游戏中包含多个界面,界面与界面的切换可以利用GameObject类中的函数SetActive。此函数可以停止渲染对象,而且场景中看不到停止渲染的对象,使用方式如下所示(参数表示是否显示此对象,True表示激活显示此对象,False表示禁用隐藏此对象)。
◎字典的使用
当程序中包含多种同类元素,并需要快速定位某个元素时,可以用集合中的Dictionary(字典)。它可以快速地基于键值的方式查找元素。Dictionary包含在System.Collections.Generic命名空间中。因此,使用它时,需要导入命名空间。其结构如下:
● 特点
□ 它是键值对的映射,每个键都会有对应的值。
□ 每个键都必须是唯一的。
□ 键不能为空引用null,若值为引用类型,则可以为空值。
□ 键和值可以是任何类型(string、int、custom class等)。
● 使用方法
Step 01 创建字典并初始化。代码如下:
Step 02 添加元素。代码如下:
Step 03 通过Key查找元素。代码如下:
将上述代码写入脚本中,完整示例代码如下所示:
保存脚本,并将脚本挂载到场景对象上,运行游戏,可以看到如图3-12所示的结果。
图3-12
3.3.2 完善登录逻辑
◎创建游戏管理器
了解过基础知识点后,现在来完善登录逻辑。打开登录场景。此场景中包含着所有的UI界面。首先激活开始游戏的背景图,如图3-13所示,此界面就是登录界面。先来创建第一个脚本,实现登录过程。
图3-13
如图3-14所示,新建一个脚本,通过单击菜单栏Assets→Create→C#Script命令创建一个脚本,将其命名为GameStart。此脚本可以作为游戏管理器,负责处理界面逻辑与消息,所以将此脚本挂载在场景中对象上。新建一个空物体,同样命名为GameStart,并将脚本直接拖曳到此对象上。为了使资源整洁,先将开发的脚本存储在Study文件夹中,再双击打开。
图3-14
打开脚本后,里面存在两个方法。Start与Update,这两个函数在开发中都会被用到。Start函数在游戏开始时会被系统调用,并且只调用一次,一般此类函数是程序执行的入口。Update函数的每一帧都会被系统调用,每秒钟大约60次。登录的基本逻辑就在此脚本中完成。
◎开始登录事件
登录界面中包含“开始”按钮,现在为“开始”按钮添加一个事件函数,函数名称可以命名为OnPlaySubmit,代码如下所示(“开始”按钮的功能是连接服务器并登录,实际上就是通知服务器有一个客户端登录了。所以在此函数中需要连接服务器并输出一句话,这是为了单击按钮时方便观察)。
“开始”按钮的目的就是连接服务器,但是服务器有多种,最终处理功能的服务器是网关服务器GateSever,但当用户数量过于庞大时,需要利用平衡服务器来分配区域,所以开始游戏时首先需要连接的是平衡服务器。
◎网络消息接收处理
想要在GameStart中接收服务器返回的消息,需先在Awake初始化函数中注册接收消息的事件,代码如下所示(其中,GameEvent_NotifyNetMessage是消息类型,HandleNetMsg是事件函数,这句话表示为消息接收添加了一个监听器,当客户端接收到此消息时,会直接调用HandleNetMsg来处理消息。此消息是在框架中NetWorkManager中接收到消息后广播的,这部分内容读者不用进行操作,只需要注册此事件即可)。
在脚本中创建一个名为HandleNetMsg的函数,并为此函数添加两个参数,如下所示(Stream代表消息体,n32ProtocalID代表消息类型。如果想清楚服务器返回的消息类型,可以直接在此函数中输出n32ProtocalID)。
小提示
Stream是系统提供的数据类型,如果想要使用Stream,需要在类的最上方引用IO的命名空间:using System.IO。
◎网络消息处理
服务器连接成功之后,返回连接结果。消息的接收在GameStart中的HandleNetMsg()完成,因为接收服务器端的消息很多,所以并不在此函数中处理,而是转到了MessageHandler进行处理,消息处理流程如图3-15所示。
图3-15
在GameStart类中接收到消息后,将此消息转到MessageHandler类中进行处理。如果在处理过程中需要用到GameStart中的数据或函数,可以将此消息再广播回来。接下来详细介绍网络消息的处理过程。
● HandleNetMsg
在HandleNetMsg中接收消息。定义时,包含两个参数:消息体与消息类型。代码如下(服务器端传回的所有消息都在此函数中进行处理,所以根据消息类型进行不同的操作。但如果在此函数中处理消息会使得此函数过于庞大。因此,在接收到消息之后,在MessageHandler中定义相应函数进行处理,MessageHandler是新定义的一个类,稍后详解)。
● MessageHandler
MessageHandler是消息处理中心。主要作用是新建的一个类,代码如下所示(此类中专门定义消息处理函数。MessageHandler中的函数定时都是以大写On开始,并结合消息类进行命名的。此类创建完成后继承UnitySingleton,这样可以直接通过MessageHandler.Instance来调用消息处理函数)。
服务器接收到客户端的连接网关服务器的请求后,就会返回网关服务器的IP地址与端口,消息体中包含了网关服务器的IP、端口等。用于连接网关服务器,但是本游戏的所有服务器的IP与端口是相同的,所以没有使用返回的IP地址与端口。
然而,网关服务器的IP与端口定义在GameStart中,所以返回GameStart中进行连接,那么如何调用GameStart中的函数呢?这里用到了框架中的消息机制。
◎连接网关服务器
到此,onNotifyGateServerInfo函数被调用,此函数目的就是连接网关服务器,代码如下所示。在连接新的服务器之前,要先断开之前的连接,才能重新开始连接服务器。断开连接是通过NetWorkManager中的Close()函数来实现的。然后通过Init()函数重新设置网关服务器的IP与端口,连接的服务器类型为GateServer,最后一个参数依然是true,代表在此类中接收消息。
总结
单击“开始游戏”按钮后,最终的目的是连接网关服务器,最终连接函数在GamaStart中的onNotifyGateServerInfo中,由此连接完成。当连接完成后。网关服务器端依然会返回消息,接下来,所有的逻辑都是由服务器的消息来驱动进行的。
◎换区事件
换区功能就是将服务器端返回的登录服务区列表在客户端显示出来。完成此功能大概需要以下三步:
Step 01 切换界面。
Step 02 获取服务器信息。
Step 03 显示服务器列表。
下面通过这三步来完成显示服务区列表的功能。
● 切换界面
登录界面中还有一个功能就是换区,游戏开始时显示默认的服务区,单击“开始”按钮直接进行游戏。单击“换区”按钮时,可以切换到服务器窗口并且显示所有的服务器列表。为此,在界面中找到换区对象ChangeSever并为此添加BoxCollider与UIButton、设置与此按钮绑定的事件OnPlayServer。在GameStart中,此事件主要负责切换窗口,代码如下所示。
mRootLogin与mRootSever是窗口的根节点,在GameStart中定义的公开的变量、所有的窗口的根节点或者预制体等,都是公开的变量并且在外部指定,如图3-16所示。将对象拖到指定的变量中去,脚本中定义的变量就有值了。
图3-16
小知识
定义的变量可以在外部指定,也可以利用GameObject.Find来寻找场景中的对象。
SetActive函数是GameObject类中的函数,所有的对象都是GameObject的子类,所以子类可以直接使用父类中公有的函数。此函数用于激活或禁用对象,激活时,对象在场景中显示;禁用时,对象在场景中隐藏。由此,可以利用此函数对界面进行切换。隐藏开始界面mRootLogin,显示mRootSever服务器界面。ShowSeverItem函数负责显示服务器列表,但是列表信息是由服务器返回的,所以需先获取服务器列表的信息。
● 获取服务器信息
游戏运行后,单击“换区”按钮便可以显示服务器列表,因此在游戏一开始时就需要获取服务器列表的信息。服务器列表信息在连接登录服务器信息时返回,也就是说在游戏一开始时,就要连接登录服务器。
在GameStart中的初始化函数Start中,通过Init函数连接登录服务器,IP地址与端口通过变量已经设置好,与其他服务器的IP是一样的,服务器类型选择为LoginServer。代码如下所示。
连接上登录服务器后,服务器返回所有的服务器列表。消息接收在HandleNetMsg中。接收到服务器列表的消息之后,将消息转到MessageHandler中,定义一个函数OnNotifyServerAddr用来处理此消息。代码如下所示。
pMsg消息体中包含着所有服务器列表的信息。所以利用循环将列表信息获取,并添加到存储服务器信息的集合中。但是在添加之前首先要清空集合中所有的信息,以防信息重复。
小知识
字符串分割可以使用Split函数,此函数将字符串分割成数组。
● 显示服务器列表
切换到服务器界面的目的是显示服务器列表并提供玩家选择。当界面切换到服务器选择界面时,定义的ShowSeverItem被调用,此函数负责生成服务器列表。mAreaItem是服务器列表单元格,定义完成之后在外部指定,此对象指的是Scroll View父节点下Grid下的AreaItem子对象。利用Instantiate函数生成对象,此函数是GameObject类中的函数,负责生成对象。紧接着将生成的对象obj的位置、大小、父物体以及名称重新设置。创建完成所有的服务器单元格后,利用mAreaGrid(网格)重新排列所有的单元格。代码如下所示。
在生成的所有的服务器单元格中,每一个单元格都包含BoxCollider与UIButton组件,目的就是使每一个单元格可以实现按钮的功能。