漫谈手游商业游戏前端框架

今天想聊一聊手游商业游戏前端框架. 既然是前端框架,就先从选哪个引擎开始聊起吧.

技术选型

这其中有两个关键字,一是”手游”, 二是”商业” .

说到”手游”, 基本上引擎就定位在了 Unity. 在手游开发行业, 除了个别技术实力非常雄厚的公司,和个别另辟蹊径的公司, 绝大部分用的引擎都是 Unity ; 还有一些公司因为旧项目依旧能盈利,拖着商业包袱,坚守着 Cocos2DX ; UE4现在用的越来越多, 但总的来说还是偏少,我不熟悉,因此在这不多加评论.就绝大部分情况来说,Unity还是绝对的主流.

说到”商业” , 在需求上”热更新” 几乎是必须要做的.不仅要做,而且要做到几乎所有游戏内容都可以热更新.这就限制了开发时的技术策略. 资源能热更新肯定是必须的, 代码能热更新几乎就限制了主要逻辑的开发语言必须是 Lua . 当然也可以用其他脚本语言, 我这里就只聊主流. 目前 Lua 绝对是手游开发里面的霸主, 去招聘 App 上搜一搜工作机会一目了然.

引擎定了, 开发语言定了,就要构思一下代码框架了. 咱们这里以 Unity 引擎 + XLua 举例.

在游戏项目中, C# 和 Lua 谁是主力,谁是辅助,必须得在开发最开始就定清楚.有的游戏是 C# 开发业务逻辑, Lua 做一些辅助. 我早年参与过的项目 “雷神2 暗黑世界” 就是这样. 底层提供了足够多的接口, 因为涉及的逻辑比较少, Lua 很多时候甚至是给策划写的. 程序员写一些 逻辑经常变化的 lua 脚本, 而这部分 Lua 脚本也仅仅是处理一些 C# / C++ 关键的回调. 这种开发模式统称为 C#为主, Lua 为辅.

另一种情况目前更流行. 开局一个 DoString() , 引擎的原生语言 C# 直接创建好 Lua 虚拟机, DoString() 一个 Lua 文件, 然后就大撒把了. 这样之后, 所有的游戏主框架, 都需要在 Lua 里做. 原生语言 C# 只提供少量的 桥接调用,把引擎的 Update() 之类的关键函数 调用到 Lua 里. 其他时候, Lua 只有在需要图形声音的地方 , 调用一下 引擎 C# 导出到 Lua 的 API . 这种模式更加灵活, 可以达到 “整个游戏内容全部热更新” 的效果. 我们称之为 Lua 为主, C# 为辅.

第二种情况在目前的商业游戏里明显更流行,也更加灵活. 因此,商业手游前端最稳健的技术选型就定下来了: 引擎选Unity , 开发语言选 Lua, Lua库选择目前最流行的 XLua, 并且要明确 Lua 为主, C# 为辅.

流程框架

接下来说一下写这样一个商业手游, 客户端代码都要经历哪些流程.

launcher

进游戏, 需要一个最小化的 “launcher” .这个 launcher 一定是很少的 C# 代码和 Lua 代码完成的.并且也只拥有最少量的美术资源. 比如只在游戏开头有一个非常简单的公司 LOGO 之类的图片做占位. 这样做是因为这部分将会是游戏唯一一块无法被热更新的部分. 这部分的 美术资源 要少, 因为你将来没有办法替换这一部分的内容. 这部分即使是 Lua 文件 逻辑也不要依赖于游戏逻辑框架, 因为将来游戏逻辑框架是要被热更新的, 可以理解为游戏流程跑到这里, 其他的功能代码还不存在. 因此这里越简单越好.

对应到 Unity 引擎里, 这部分的 Lua 代码和美术资源可以放在 Resources 目录.

热更新

从 launcher 出来,紧接着就要进入到热更新阶段.可能有些游戏会先登录,再进入热更新. 这样做的劣势很明显: 登录界面是无法被热更新的. 将来更换主题, 添加版号等内容时,这样做都是麻烦. 因此, 从 launcher 出来,第一步一定要进入热更新阶段.

这部分的 Lua 代码就可以放到常规开发路径了,因为这部分代码将来是有可能发生改变的.热更新的流程也有一套模板化规律可循:

  1. 检查版本号. 已经最新则进入下一阶段.否则准备热更新
  2. 请求即将更新到的版本的资源列表. 比较待更新资源和本地资源的 md5 值, 确定一个更新 list
  3. 把需要更新的文件下载到本地. 这个目录在 Unity 里通常可以是 Application.persistentPath.因为这个目录是可读写的目录.
  4. 在更新下来的目录里,写入新的版本号.

至此,完成了一套热更新的流程.

对应到 Unity 引擎里面. Resources 目录存放 launcher 步骤相关的最小化的游戏内容. 这一步之前已经提到过. Application.persistentPath 用于存储所有的游戏资源. 同时,这里还要负责维护当前更新到的资源的版本号,以及各个资源文件的md5值.

可以更新的资源, 通常只包含两类: AssetBundle 和 Lua . 在游戏开发阶段,采用 AssetDatabase 的方法来读取本地资源,在编辑器下跑游戏. 在非编辑器模式下, 需要把游戏资源目录下的所有资源打成 AssetBundle 到 Application.persistentPath, 并把 Lua 文件拷贝到该目录, 用加载 AssetBundle 的形式测试正常的游戏读取资源的流程.

这两种资源读取方式的区分 , 对上层逻辑必须是透明的,并且 保证行为是一致的.否则如果需要上层逻辑根据编辑器 和 实际环境不同需要写不同的代码, 就大乱套了.

游戏登录 & SDK

完成了热更新之后,可以走正常的登录流程和SDK 登录流程了. SDK的接入有一点我想说的是, 尽量避免修改过多的 iOS 和 Android 的原生代码, 流程控制能让 Lua 控制的地方,一定要托管给 Lua . 因为无论是 iOS 的 ObjC ,Swift 还是 Android 的 Java,Kotlin 都是无法热更新的. 而SDK 流程如果能做到全部交给 Lua 处理, 就可以享受到 在热更新之后再进入登录流程这样做的好处了.

进入游戏主页

登录成功之后,就可以准备进入游戏了.

游戏逻辑组成部分

手机游戏通常可以分为以下几个组成部分

  • 面向对象
  • 事件机制
  • MVC 处理 界面业务逻辑
  • 核心战斗 ECS
  • timer 机制
  • 网络通信
  • 教学
  • 支付

这些部分可以统一用一个全局的 game 对象来做管理.下面我逐一来说.

面向对象

之所以把面向对象放到第一个来说, 是因为 Lua 并没有很多语言天然支持面向对象. 必须自己把 metatable 进行简单的封装,才能支持面向对象编程. 而确认大家风格统一, 使用同样的面向对象机制,编写一个正确的面向对象逻辑就显得格外重要.

否则大家使用 Lua 时全局变量乱飞, 各处都是简单粗暴的 table 将会使得项目无法维护.

事件机制 & MVC

当今的手游随随便便就可以数出来若干个子系统: 主线关卡, 抽卡,英雄养成, 装备养成, 活动, 签到, 工会联盟, 聊天, 各式玩法副本,教学, 红点等等.每个功能少说也要涉及 五六个界面, 多了10个20个都很正常. 每个子系统还都要存储数据, 数据结构也五花八门. 如果是 3D 游戏那还涉及很多场景. MVC 这种数据表现分离的代码模式几乎是唯一的解决方案.

事件机制是实现观察者模式的基础.如果一个游戏没有事件机制无法想象逻辑要写的有多么混乱.MVC 更无从谈起.

把这些子系统要想有条不紊的安排好, 只能采用 MVC 的思想: Model 用于存储 服务器数据的拷贝, 每次网络通信都要和服务器完全同步; View 负责处理各个界面; 模块和模块之间采用 观察者模式, 大家互相抛事件的方式来处理业务逻辑 .

拥有一套完善的 MVC 框架 和 事件机制 , 才能保证各种各样的子系统能安稳的在游戏里面运行 . Lua 里没有 C# 现成的 delegate , event 的机制, 因此 事件机制需要自己手动实现 .

核心战斗 ECS

介绍 ECS 的文章非常多, 我强烈推荐大家都去了解 ECS 的思想和写法,并且在实际项目中尝试运用因为 ECS 真的能把逻辑写的非常清晰有条理. 我经手的项目在使用 ECS 之前逻辑一团乱码, 使用之后逻辑清晰明了, 易于扩展和维护,我对 ECS 的优势体会颇深.
ECS 的 “System 只负责逻辑没有状态, Component 只有状态没有逻辑” 是 ECS 的精髓.并且当一件事发生需要处理时,在 Component 里做标记,等待某个 System 集中处理 ,这种延时处理的方式, 也是 ECS 重要的一大特点 .
并且用 ECS 写逻辑还可以附带这样的好处: 可以把游戏逻辑写成一个 单独的 Lua 工程, 这个 Lua 工程靠死循环 来 驱动逻辑帧步步向前, 做逻辑处理. 与表现相关的都记录在 Component 数据里.但是这个 Lua逻辑工程不写表现相关的 System ,相当于表现相关的数据不做任何处理.
在 Unity 游戏环境下 ,依然调用这段逻辑代码, 唯一不同的是 驱动逻辑帧步步向前的 是 Unity的 Update() 函数.再给 游戏搭配上 grafic system , sfx system ,这样就使得 战斗逻辑有了表现和声音.
将来无论是做战斗录像, 还是做服务器战斗演算, 还是模拟战斗, 只需要在 裸 Lua 工程里 做一些改动,就可以实现了.
由于逻辑帧是脱离于表现单独可以跑的, 因此在将来加入联网和同步机制时, 逻辑也是现成的.
ECS 在实现中需要注意的是, 经常要根据各种各样的 component 条件,来做 entity 的筛选. 这样的筛选非常频繁,必须充分考虑到效率问题.

总结

剩余的 timer 机制, 网络通信, 教学, 支付等等话题我今天先不提了.我还设想要再聊一聊版本管理, 打包等话题 , 但是那又将是非常大的两个话题. 这些内容都留给以后吧.

本文地址:https://blog.csdn.net/korekara88730/article/details/109268215

(0)
上一篇 2022年3月22日
下一篇 2022年3月22日

相关推荐