项目为大二上学期的综合程序设计(这里先给出实验报告的内容,之后更新详细的代码思路)~
(文中的代码截图会逐一替换成代码)
主要使用Unity中的UGUI模块、Dotween动画插件、Java Socket编程、SpringBoot
项目已经制作完成以下是GitHub的源码地址
https://github.com/ws100/Unity-YLGY
制作使用的软件
Unity 2021.3.4f1c1
Visual Studio 2017
JetBrains Rider 2022.2.4
IntelliJ IDEA 2021.2.4(JDK 1.8)
PyCharm 2021.2.3(Python3.9)
MySql 5.7.39
实验报告
数据的获取与解析
数据的获取
使用Proxifier将微信小程序的流量代理到Burp监听的端口
在Burp中分析《羊了个羊》小程序发送的网络请求,从而获取数据的地图数据。
图 1 Burp获取数据包
图 2 数据请求的流程
在Python中模拟发送网络请求,先获取地图的md5值,再根据地图的md5值得到地图的Json数据
图 3 在Python中发送网络请求获取数据
数据的解析
将获取的Json放在服务器中,在Unity中使用UnityWebRequest模块发送Get请求,获取在服务器上的Json地图数据。
图 4 Unity中发送网络请求的关键代码
在获取到的Json数据中levelData中存储每一层对应的各个卡牌的基本信息[HTML_REMOVED],blockTypeData中为每个类型卡牌在地图上出现的数量。
图 5解析之后的Json数据
基本玩法还原模块:
生成卡牌
在数据解析过程中随机生成不同类别的卡牌,根据卡牌的坐标信息使用Instantiate函数生成预制的GameObject,并在卡牌的button中添加监听事件,该事件在被点击后执行。
图 6生成卡牌的关键代码
图 7卡牌对象的预制体
卡牌点击逻辑和动画
在卡牌被点击后改变卡牌当前的Status,并更新版面数组的信息,使用DoTween动画插件的DoMove函数,完成卡牌移动的动画,由于动画完成后需要执行三消逻辑和更新其他的数据,这里使用开启协程的方式,等待动画播放完成之后,执行对应的代码逻辑。
图 8卡牌点击之后的动画
图 9卡牌点击动画的关键代码
卡牌遮挡逻辑
对每次卡牌点击之后,根据点击卡牌所在的层数,更新其以下层数的卡牌状态,遍历每个卡牌计算其上层卡牌与该卡牌的X、Y坐标之差,满足条件则被置为被遮挡。
图 10卡牌遮挡
图 11卡牌遮挡的关键代码
单例设计模式
在Unity中常常使用单例设计模式,即一个类只有一个实例化的对象在其它类中,可以使用类名+该对象名进行操作。
项目中,GameManager类为单例类,其中存储游戏的状态,用户信息等数据,在Awake函数中,对单例对象进行初始化,之后其它类即可访问。
图 12GameManager单例类
使用对象池的弹窗消息
项目中使用这样一个消息框,作为用户提示,在消息框管理中,使用对象池算法进行优化,同时设置为单例,在其它类中用ShowTipMessage.Tip.ShowTip()即可生成消息,并弹出。
图 13对象池流程图
图 14消息弹窗
DFS搜索的运用
DFS搜索寻找
图 15 DFS搜索算法示意图
使用DFS深度优先遍历尝试得到正确的游戏步骤,在DFS递归函数中判断当前状态是否满格,并根据当前状态生成下一步的可行路径,循环递归调用DFS函数。由于问题的规模较大,朴素的DFS算法受时间复杂度限制,搜索层数较低,因此我们根据实际情况做出如下的优化。
算法的改进
如果场上出现三对可以消除的卡牌,则可以优先消除这些卡牌。
图 16 搜索中消除的关键代码
图 17 消除示意图
对于每个DFS函数根据当前状态生成的节点进行评分,分数高的节点优先被搜索。分数根据卡牌被点击之后的地图状态和版面状态排序。
图 18 状态评分的关键代码
图 19 改进DFS算法的流程图
图 20 排序搜索的关键代码
在循环中搜索所有的可能路径,评分高的(最有可能是成功的路径)优先被搜索,减少了DFS中的回溯次数,在有限的时间中更有可能搜索到可行解。
DFS函数中的传递数据
如图为DFS中数据传递的关键代码,用全局变量存储DFS中用到的地图卡牌信息和DFS深度信息,用DFS函数中的参数存储版面上的卡牌信息。
图 21 DFS中信息存储与传递示意图
DFS函数搜索结果
运行结果如图所示,由于问题规模较大,在搜索时间在10秒时,改进之后的DFS算法也只能搜索到200步左右,当前地图的目标步数为267步。
图 22 DFS算法Python程序的运行结果
为了更加直观的展示搜索结果,我们将之前用Python编写的DFS算法使用C#重新编写,并在Unity中完成了动画演示。
图 23 DFS算法的C#语言实现
图 24 Unity动画演示
多人玩法拓展和网络通讯
用户注册与登录
支持PC/Android的Unity客户端向服务器发送get/post请求,服务器收到请求后,由SpringBoot框架搭建的服务器向MySQL数据库使用MybatisPlus操作,以获取数据,再将响应到的数据返回到客户端。
图 25 网络请求的流程
图 26 Maven项目的pom文件
图 27 注册和登录的控制器
Socket服务器主要逻辑:
服务器建立监听
客户端初始化 Socket 动态库后创建套接字,然后指定客户端 Socket 的地址,循环绑定 Socket 直至成功,然后开始建立监听,此时客户端处于等待状态,实时监控网络状态;
客户端提出请求
客户端的 Socket 向服务器端提出连接请求,此时客户端描述出它所要连接的 Socket,指出要连接的 Socket 的相关属性,然后向服务器端 Socket 提出请求;
连接确认并建立
当服务器端套接字监听到来自客户端的连接请求之后,立即响应请求并建立一个新进程,然后将服务器端的套接字的描述反馈给客户端,由客户端确认之后连接就建立成功,然后客户端和服务器两端之间可以相互通信,传输数据,此时服务器端的套接字继续等待监听来自其他客户端的请求。
图 28 Socket服务器的基本逻辑
客户端实现
在我们实现双人模式的过程中,为了实现客户端,选择使用Java编写Socket相关的服务端程序,使其能在Linux云服务器中运行。
图 29 Java Socket服务端的关键代码
图 30 Linux云服务器下的运行日志
线程辅助类
在Unity客户端中服务器的消息监听在子线程中进行,子线程接收到消息之后会执行相关的操作会涉及Unity中API函数的调用,但Unity中不允许在子线程中调用API函数,因此我们设计了一个线程辅助类,当子线程需要调用相关函数时,将函数事件发送到线程辅助类,由线程辅助类的Update函数进行调用。
图 31 线程辅助类示意图
图 32 收到服务器消息之后使用线程辅助类
#
看得我一脸懵逼