![Unity MOBA 多人竞技手游制作教程](https://wfqqreader-1252317822.image.myqcloud.com/cover/974/32435974/b_32435974.jpg)
3.3 登录逻辑实现
3.3.1 基础知识
在登录逻辑开发过程中,主要实现两大功能:显示服务区列表;组队匹配。在此过程中,会涉及一些基础知识点。下面通过示例对这些内容进行简单介绍,熟悉后再根据所学的知识将整个逻辑功能实现出来。
◎按钮触发函数
在游戏运行过程中,经常会和界面进行交互。最直接的就是通过单击按钮,触发某个功能,本质上是执行某个或多个函数。在通过示例演示此功能的实现过程,可以分为以下三步。
● 第一步:定义功能函数
首先,创建一个ButtonEvent脚本;然后,在此脚本中定义一个Public类型的功能函数。代码如下所示:
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_50_2.jpg?sign=1739525111-Yvo2WfVUVWZpbxrVhkRKsaigPWCANDxk-0-764eb290aa7c63022774894b94cb6d9b)
● 第二步:添加组件
具体操作步骤如下。
Step 01 创建一个Test场景,在场景中创建一个Sprite对象,用于存放按钮皮肤,在Sprite对象中再创建一个Label对象用于显示文字,如图3-8所示。
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_51_1.jpg?sign=1739525111-SxTQVQzzuxVvTjPyfmiM09f6NOFp9sN2-0-b373d65149374aa49b20ff53d3005ae8)
图3-8
Step 02 单击Sprite对象,将其改名为Button,并添加碰撞器(BoxCollider)与按钮组件(UIButton),可通过如图3-9所示的Add Component按钮添加组件。
Step 03 修改碰撞器的大小,使其与按钮大小一致。此组件的主要作用是触发此按钮。
● 第三步:绑定功能函数
给按钮添加点击事件。具体操作步骤如下。
Step 01 将挂载ButtonEvent脚本的对象拖曳到Notify栏中,如图3-10所示。
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_51_2.jpg?sign=1739525111-jGkQcREqvgi9PU5QJ3p3OpgzLxe2pZCW-0-9cde7b3d0c174286d6f6e7263a686f31)
图3-9
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_51_3.jpg?sign=1739525111-rSYuOaetM8FLbytn5q0gSTtRGe4iUIlg-0-060c032d6313fa6958a1fc38562525de)
图3-10
由于在此场景中,把ButtonEvent脚本挂载到了Button按钮对象上,因此,拖曳的对象便是Button对象。
Step 02 指定要执行的函数,在Method处,选择定义功能函数的类,并选取对应的功能函数,如图3-11所示。
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_52_1.jpg?sign=1739525111-haOMzY1VlPNI9yWrIeN0Q0mfxW2LhHVU-0-cd049ae05405625219d39968a2e4294a)
图3-11
最后,运行游戏,可以在Console控制台中看到“开始游戏事件被调用”,说明此函数被调用。
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_52_2.jpg?sign=1739525111-R4n1jDH3hQlkHjIqmIRuDuEdmU0GXZvl-0-4d6d1142fc2cca9a08ad4602216ab78b)
小提示
(1)UI Button组件是NGUI中的脚本,框架中包含了NGUI插件,所以能够直接添加此组件。
(2)按钮点击事件的修饰符为public。
游戏中包含多个界面,界面与界面的切换可以利用GameObject类中的函数SetActive。此函数可以停止渲染对象,而且场景中看不到停止渲染的对象,使用方式如下所示(参数表示是否显示此对象,True表示激活显示此对象,False表示禁用隐藏此对象)。
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_53_1.jpg?sign=1739525111-YJIymmQNRF3k6KmmlSDlWw0p4kQIo7KN-0-6d19d137005618c34f534ffd676cdc62)
◎字典的使用
当程序中包含多种同类元素,并需要快速定位某个元素时,可以用集合中的Dictionary(字典)。它可以快速地基于键值的方式查找元素。Dictionary包含在System.Collections.Generic命名空间中。因此,使用它时,需要导入命名空间。其结构如下:
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_53_2.jpg?sign=1739525111-AEVPBT1IZEykM5px3MKTXizfiL0Ut5Nk-0-7466e7147fb504751449c7974922da02)
● 特点
□ 它是键值对的映射,每个键都会有对应的值。
□ 每个键都必须是唯一的。
□ 键不能为空引用null,若值为引用类型,则可以为空值。
□ 键和值可以是任何类型(string、int、custom class等)。
● 使用方法
Step 01 创建字典并初始化。代码如下:
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_53_3.jpg?sign=1739525111-RnbqmpvKh4TQIOXyLnCqmVpzwpYcThgW-0-e8aeb1dece8391a9215cd8c61b818cf1)
Step 02 添加元素。代码如下:
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_53_4.jpg?sign=1739525111-JYcrUvKhrzd4iLlHKYtrvapLNJdiq9OQ-0-ae90604396ca7ca5d3ec7d83f7b2a869)
Step 03 通过Key查找元素。代码如下:
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_53_5.jpg?sign=1739525111-xv8d1dpv497DtW9yI7F7VtnXfZ86rOZb-0-0d1a7851455ba88d69638edfb77c6f9d)
将上述代码写入脚本中,完整示例代码如下所示:
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_53_6.jpg?sign=1739525111-NbLB8Zh0IViVy9aNuCCnJnrySpQBBNZr-0-4db0106df270023ec4cfbd3d7eec1475)
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_54_1.jpg?sign=1739525111-t2jfWG4lK7yMrcqSWtOqiu4DIcXvfBe9-0-8721353caa6a57c77227d81dc30056a9)
保存脚本,并将脚本挂载到场景对象上,运行游戏,可以看到如图3-12所示的结果。
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_54_2.jpg?sign=1739525111-M1qsORp6HnGXwIAbj2RE0eqO4HC99opb-0-55ae1fbf3f53b4507db9687a6dcdc24c)
图3-12
3.3.2 完善登录逻辑
◎创建游戏管理器
了解过基础知识点后,现在来完善登录逻辑。打开登录场景。此场景中包含着所有的UI界面。首先激活开始游戏的背景图,如图3-13所示,此界面就是登录界面。先来创建第一个脚本,实现登录过程。
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_54_3.jpg?sign=1739525111-sldqpGKPWUm6Qse2rxAS5PFs0r2VtTyI-0-adf1ed0b666edbff9200957f3dc90dce)
图3-13
如图3-14所示,新建一个脚本,通过单击菜单栏Assets→Create→C#Script命令创建一个脚本,将其命名为GameStart。此脚本可以作为游戏管理器,负责处理界面逻辑与消息,所以将此脚本挂载在场景中对象上。新建一个空物体,同样命名为GameStart,并将脚本直接拖曳到此对象上。为了使资源整洁,先将开发的脚本存储在Study文件夹中,再双击打开。
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_55_1.jpg?sign=1739525111-pXoRCYdwNEspjeQhhhwkFCbC4xNCuDuD-0-7a0ad94a94d9d216a25e060621da3651)
图3-14
打开脚本后,里面存在两个方法。Start与Update,这两个函数在开发中都会被用到。Start函数在游戏开始时会被系统调用,并且只调用一次,一般此类函数是程序执行的入口。Update函数的每一帧都会被系统调用,每秒钟大约60次。登录的基本逻辑就在此脚本中完成。
◎开始登录事件
登录界面中包含“开始”按钮,现在为“开始”按钮添加一个事件函数,函数名称可以命名为OnPlaySubmit,代码如下所示(“开始”按钮的功能是连接服务器并登录,实际上就是通知服务器有一个客户端登录了。所以在此函数中需要连接服务器并输出一句话,这是为了单击按钮时方便观察)。
“开始”按钮的目的就是连接服务器,但是服务器有多种,最终处理功能的服务器是网关服务器GateSever,但当用户数量过于庞大时,需要利用平衡服务器来分配区域,所以开始游戏时首先需要连接的是平衡服务器。
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_55_2.jpg?sign=1739525111-S8KK3bVVs1nBtmbdEOOHhey1wjtX3mTB-0-3ec8889af93eace2a33d10c3947bc822)
◎网络消息接收处理
想要在GameStart中接收服务器返回的消息,需先在Awake初始化函数中注册接收消息的事件,代码如下所示(其中,GameEvent_NotifyNetMessage是消息类型,HandleNetMsg是事件函数,这句话表示为消息接收添加了一个监听器,当客户端接收到此消息时,会直接调用HandleNetMsg来处理消息。此消息是在框架中NetWorkManager中接收到消息后广播的,这部分内容读者不用进行操作,只需要注册此事件即可)。
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_56_1.jpg?sign=1739525111-2yT3x3Sh2dzJrpVbpvPp6EbSrP78KVjj-0-d30c4560a83ae3c635d692ab731e4aad)
在脚本中创建一个名为HandleNetMsg的函数,并为此函数添加两个参数,如下所示(Stream代表消息体,n32ProtocalID代表消息类型。如果想清楚服务器返回的消息类型,可以直接在此函数中输出n32ProtocalID)。
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_56_2.jpg?sign=1739525111-rSFhngnPwPWaB3g8CfkZAi3VHCSqdja5-0-503f78584d7b87606351b46524970af7)
小提示
Stream是系统提供的数据类型,如果想要使用Stream,需要在类的最上方引用IO的命名空间:using System.IO。
◎网络消息处理
服务器连接成功之后,返回连接结果。消息的接收在GameStart中的HandleNetMsg()完成,因为接收服务器端的消息很多,所以并不在此函数中处理,而是转到了MessageHandler进行处理,消息处理流程如图3-15所示。
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_56_3.jpg?sign=1739525111-rW1IcrkEOTFxbrAe0TZg6FquWvZglCRp-0-1cf01878550ac60dd641232707463f99)
图3-15
在GameStart类中接收到消息后,将此消息转到MessageHandler类中进行处理。如果在处理过程中需要用到GameStart中的数据或函数,可以将此消息再广播回来。接下来详细介绍网络消息的处理过程。
● HandleNetMsg
在HandleNetMsg中接收消息。定义时,包含两个参数:消息体与消息类型。代码如下(服务器端传回的所有消息都在此函数中进行处理,所以根据消息类型进行不同的操作。但如果在此函数中处理消息会使得此函数过于庞大。因此,在接收到消息之后,在MessageHandler中定义相应函数进行处理,MessageHandler是新定义的一个类,稍后详解)。
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_57_1.jpg?sign=1739525111-XDKj3QNjdo496gPDPJn1OEKihTMXrBTf-0-bbff83a88f95a6dfc58054d4dc541c7a)
● MessageHandler
MessageHandler是消息处理中心。主要作用是新建的一个类,代码如下所示(此类中专门定义消息处理函数。MessageHandler中的函数定时都是以大写On开始,并结合消息类进行命名的。此类创建完成后继承UnitySingleton,这样可以直接通过MessageHandler.Instance来调用消息处理函数)。
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_57_2.jpg?sign=1739525111-oGJFS7OLEEbu3OaVbpEatzo1W5jNGtME-0-c6671cfa689d2e29e062daaf84c6a4d4)
服务器接收到客户端的连接网关服务器的请求后,就会返回网关服务器的IP地址与端口,消息体中包含了网关服务器的IP、端口等。用于连接网关服务器,但是本游戏的所有服务器的IP与端口是相同的,所以没有使用返回的IP地址与端口。
然而,网关服务器的IP与端口定义在GameStart中,所以返回GameStart中进行连接,那么如何调用GameStart中的函数呢?这里用到了框架中的消息机制。
◎连接网关服务器
到此,onNotifyGateServerInfo函数被调用,此函数目的就是连接网关服务器,代码如下所示。在连接新的服务器之前,要先断开之前的连接,才能重新开始连接服务器。断开连接是通过NetWorkManager中的Close()函数来实现的。然后通过Init()函数重新设置网关服务器的IP与端口,连接的服务器类型为GateServer,最后一个参数依然是true,代表在此类中接收消息。
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_58_1.jpg?sign=1739525111-2z7sPA6TLAHxP8kCpM6TzcnczBYjEZaK-0-ce6839240bdcdb28ce59304834bd2de7)
总结
单击“开始游戏”按钮后,最终的目的是连接网关服务器,最终连接函数在GamaStart中的onNotifyGateServerInfo中,由此连接完成。当连接完成后。网关服务器端依然会返回消息,接下来,所有的逻辑都是由服务器的消息来驱动进行的。
◎换区事件
换区功能就是将服务器端返回的登录服务区列表在客户端显示出来。完成此功能大概需要以下三步:
Step 01 切换界面。
Step 02 获取服务器信息。
Step 03 显示服务器列表。
下面通过这三步来完成显示服务区列表的功能。
● 切换界面
登录界面中还有一个功能就是换区,游戏开始时显示默认的服务区,单击“开始”按钮直接进行游戏。单击“换区”按钮时,可以切换到服务器窗口并且显示所有的服务器列表。为此,在界面中找到换区对象ChangeSever并为此添加BoxCollider与UIButton、设置与此按钮绑定的事件OnPlayServer。在GameStart中,此事件主要负责切换窗口,代码如下所示。
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_58_2.jpg?sign=1739525111-I6K6Yc65MaLFSekrZaErnNKS80ZCmlT5-0-c6e954a50fc35471a58c4634a7013542)
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_59_1.jpg?sign=1739525111-k1Ar9I5pTCVDBHH2adhxNKxl1eYZjAH2-0-068de4a0cf038f94f75af0a9a6a597b4)
mRootLogin与mRootSever是窗口的根节点,在GameStart中定义的公开的变量、所有的窗口的根节点或者预制体等,都是公开的变量并且在外部指定,如图3-16所示。将对象拖到指定的变量中去,脚本中定义的变量就有值了。
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_59_2.jpg?sign=1739525111-CAnXspAi8nEhYHyLtLgvA78nDZXg7zGh-0-939f2a1f507e5312cb443b6050701a40)
图3-16
小知识
定义的变量可以在外部指定,也可以利用GameObject.Find来寻找场景中的对象。
SetActive函数是GameObject类中的函数,所有的对象都是GameObject的子类,所以子类可以直接使用父类中公有的函数。此函数用于激活或禁用对象,激活时,对象在场景中显示;禁用时,对象在场景中隐藏。由此,可以利用此函数对界面进行切换。隐藏开始界面mRootLogin,显示mRootSever服务器界面。ShowSeverItem函数负责显示服务器列表,但是列表信息是由服务器返回的,所以需先获取服务器列表的信息。
● 获取服务器信息
游戏运行后,单击“换区”按钮便可以显示服务器列表,因此在游戏一开始时就需要获取服务器列表的信息。服务器列表信息在连接登录服务器信息时返回,也就是说在游戏一开始时,就要连接登录服务器。
在GameStart中的初始化函数Start中,通过Init函数连接登录服务器,IP地址与端口通过变量已经设置好,与其他服务器的IP是一样的,服务器类型选择为LoginServer。代码如下所示。
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_60_1.jpg?sign=1739525111-m4Gj17zpGYNXPKBNjoDyyOqlXkylMG0m-0-53043a02ebf6608c51d16f1e8fc30912)
连接上登录服务器后,服务器返回所有的服务器列表。消息接收在HandleNetMsg中。接收到服务器列表的消息之后,将消息转到MessageHandler中,定义一个函数OnNotifyServerAddr用来处理此消息。代码如下所示。
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_60_2.jpg?sign=1739525111-XBTk2POTsFJHTEYzcuTKjKhfhjEXpfWH-0-86942e775a24865d350a37d98796d7ec)
pMsg消息体中包含着所有服务器列表的信息。所以利用循环将列表信息获取,并添加到存储服务器信息的集合中。但是在添加之前首先要清空集合中所有的信息,以防信息重复。
小知识
字符串分割可以使用Split函数,此函数将字符串分割成数组。
● 显示服务器列表
切换到服务器界面的目的是显示服务器列表并提供玩家选择。当界面切换到服务器选择界面时,定义的ShowSeverItem被调用,此函数负责生成服务器列表。mAreaItem是服务器列表单元格,定义完成之后在外部指定,此对象指的是Scroll View父节点下Grid下的AreaItem子对象。利用Instantiate函数生成对象,此函数是GameObject类中的函数,负责生成对象。紧接着将生成的对象obj的位置、大小、父物体以及名称重新设置。创建完成所有的服务器单元格后,利用mAreaGrid(网格)重新排列所有的单元格。代码如下所示。
![](https://epubservercos.yuewen.com/104103/17517092107475806/epubprivate/OEBPS/Images/36590_61_1.jpg?sign=1739525111-kB6LaZMYjpjgZIoZpbxmlb4z0tTPIxbP-0-5be3c97e7b4a6ca2afa2241534e6991e)
在生成的所有的服务器单元格中,每一个单元格都包含BoxCollider与UIButton组件,目的就是使每一个单元格可以实现按钮的功能。