背景介绍
随着之家3D虚拟化需求的增加,各产品线使用Unity引擎的项目也越来越多,新老项目共存,代码维护成本也随之增加。代码质量参差加之代码规范仍没有完全统一产生高昂学习成本进一步加重了项目维护负担。
为应对这些问题,我们决定借助主机厂数科产品线销冠神器VR版本大升级为契机,开发一套移动端通用Unity代码框架,旨在统一Unity项目开发流程和规范,使不同项目开发人员能够快速上手业务开发,实现不同项目之间代码组件化复用,降低学习成本,提高项目的健壮性和复用性。
1.Unity 架构调研
Unity通用架构核心想帮助Unity开发人员加速项目开发效率。该架构的设计基于大量的经验和最佳实践,旨在使项目开发更加高效和规范化。通过使用通用架构,开发人员可以轻松地构建高质量、健壮和可扩展的项目,同时降低学习成本和维护成本。该架构的模块化设计也允许不同的项目之间实现组件复用,从而进一步提高开发效率。无论是初学者还是经验丰富的开发人员,使用Unity通用架构都可以获得更好的开发体验。
1.1Unity与其他技术栈差异
与其他平台相比,Unity的技术生态相对较为有限,缺少许多开源项目的支持。此外,Unity项目的类型繁多,从重度MMRPG项目到轻度虚拟仿真,这本身就是整理出一套通用的基础架构十分困难的原因之一。与此同时,大多数基础功能都需要收费,而大型公司也很少开源他们的源代码。因此,与其他平台相比,Unity想要整理出一套通用前端技术框架确实面临着很多挑战。
1.2业界常用开源Unity框架
下面分析一下市面上常见的Unity架构,并列举不适合我们的原因。
►UnityGameFramework
UnityGameFramework使用一套UnityFramework和一套Gameframework对Unity进行了一次封装。
在封装的基础上做了一些方便开发者使用的扩展,如ECS/UI等功能。
►QFramework
QFramework 是提供一套简单、强大、易上手、符合 SOLID 原则、支持领域驱动设计(DDD)、事件驱动、数据驱动、分层、MVC 、CQRS、模块化、易扩展的架构。
1.3场景适用性思考
以上两个架构已足够优秀,但是要集成到我们的技术栈中还是有很多挑战:
►UnityGameFramework
这套架构太“重”了,使用A模块必须依赖架构内的B模块甚至C/D/E模块,只想使用简单的UI功能可能要把整套架构都迁移进来。
太“重”也引起了修改一个模块会牵连很多其他模块的问题。
适合需求明确的中重度游戏,商业化项目。
►QFramework
学习成本较高,需要理解很多设计原则才可以上手使用。
它与UnityGameFramework相比又太“轻”了,源码少,可魔改的余地少。适合小而精的项目。
►其他
其他架构都缺少线上足够项目实际验证与配套的开源生态,健壮性与扩展性无法确保。
1.4架构关注点思考
好的架构应该注重以下几个方面:
►生命周期
良好的生命周期设计,可以提供简单而高效的生命周期管理机制使开发人员在合适的时机创建、修改和销毁对象。
生命周期感兴趣的同学可以深入了解一下Unity的MonoBehavior设计。
►分层设计
分层设计可以使代码解耦,将参考后端多种架构设计理念,MVC、DDD、洋葱架构等,免代码耦合,为提高架构防腐度,降低续的化和重构提的频率。
►学习成本
考虑到员工技能水平的参差不齐,学习成本是设计架构时最需要考虑的因素之一。,果架构过于“重”,那么就需要了解很多底层/中间层逻辑才能使用,且出现问题后也不易于修改。
►上线验证
如果一套架构已经通过多个项目的上线验证,那么就不太需要担心架构中还有未解决的问题。开源架构都会列出自己的产品案例。
2.之家Unity架构设计
综合上述总结,在设计Unity通用架构时重点考虑了分层设计,好的分层边界能降低学习成本提高复用性。
2.1分层设计
汽车之家Unity通用架构采用四层设计:逻辑层、中间层、基础层和数据层。
►逻辑层
逻辑层处理不同项目的交互逻辑,调用中间层和基础层功能。在逻辑层中,可以按照项目需求进行设计,而不需要考虑复用性。
具体模型和数据功能通过调用底基础层和数据层接口现。
►中间层
中间层对逻辑层和基础层进行封装,使得逻辑层调用更加清晰,基础层有更好的抽象环境。
中间层又分为业务层和适配器层。业务层主要针对逻辑层进行封装和特殊处理,而适配器层则对基础层进行二次封装,组合多个基础层能力以应对复杂功能。
►基础层
基础层对基础功能进行抽象,使用统一的接口设计,支持所有Unity项目。这一层可以使用市面上普及的解决方案如TMP/DoTween等。
基础层应对功能抽象,不关心具体需求,具有良好的健壮性和可扩展性。
►数据层
数据层用于后台存储、数据和模型信息等。只要使用同样的后台服务和美术规范,新的Unity项目就不需要对数据层再做兼容。
如有特殊需求,也可以在中间层对数据层进行处理,参考洋葱架构设计。
2.2架构图
以下是汽车之家Unity通用架构的架构设计图与销冠神器使用通用架构后的架构图:
►Unity通用架构图
►销冠神器架构图
2.3代码示例
以原生端通信功能为例,说明分层和复用性设计在架构中的体现。
这里ativeMessage是一个可以与原生端进行通信的模块。该模块负责向iOS和Android平台发送和接收消息,用于处理一些原生交互的逻辑。
基础层的NativeMessage在Plugin文件夹中,只有核心的发送消息和接受消息的能力
中间层的XGNativeMessage在Scripts/Manager文件夹中,继承基础层并添加业务相关的设置。
逻辑层在Module或Controller中对中间层的XGNativeMessage进行调用
大致流程如下:
►优势总结:
这种设计可以确保基础层有100%的复用性,使其可以方便地将其迁移至其他项目中使用。此外,中间层的封装可以集中处理发送消息的逻辑,从而避免在逻辑层中编写大量代码。
采用分层设计的具有很方便的升级和扩展性。如果需要对其中某一层进行升级,可以直接修改对应层级的代码,而不会影响其他层的功能。这使得系统更加灵活和可维护。
分层设计和复用性是非常重要的架构设计原则。在NativeMessage模块的设计中,成功应用了这些原则,使得该模块具有高度的可复用性和可维护性。
3.架构收益
通用架构1.0上线后,我们量化了架构收益:
►代码质量 升20%
底层和中间层按照功能解耦,可以提高代码质量,也降低单个迭代SP的bug率20%以上。
►开发效率 升30%
按照相同的框架发可规范,高开单人力研发需求交付效率30%以上同时,不同项目组之间可以共享同一套底层功能,从而互相帮助和提高生产力。
代码规范和模块拆分的方式符合Unity行业的通用解决方案,这可以帮助Unity开发人员更快地理解和掌握项目的架构设计和开发规范。
►各项性能指标提升20%
通过架构升级,不仅解耦了代码,还带来了其他收益。例如,将GLB升级为AssetBundle,可以显著降低内存占用量,并减少CPU负载30%以上。
功能模块化设计使得我们可以更好地统计启动时各个阶段所占用的时间,并针对下载/加载等阶段进行优化,从而使启动时间降低了50%。
这些优化措施可以进一步提高应用程序的性能和用户体验,提高产品的竞争力。
►跨项目代码复用度提升50%
通用架构需要支持之家的所有Unity项目,所以需要考虑不同项目中的代码复用。代码复用性可以根据分层由低到高来考虑,最底层的代码复用性越高。
逻辑层复用率0%,因为每个项目的交互逻辑不同,过多考虑复用会引起很多问题。这一层不需要考虑复用性的设计。
中间层复用率60%,中间层对逻辑层和基础层进行抽象和二次封装,应该在开发过程中尽量考虑复用性。至少适配器层要能快速地复用到其他项目中。
基础层复用率100%,基础层抽象基础功能,只考虑功能而不关心业务。
数据层复用率100%,数据层由后端提供,使用相同的服务和美术规范。
4.总结
分层设计降低了上手成本,只要逻辑层足够清晰简单,那么初级程序员就可以很容易的去写一些业务相关的功能,有能力的程序员可以持续为架构输出健壮的中间层和底层能力。逻辑层只采用最简单的状态机设计,如果之后业务需求复杂也可以扩展成分层状态机来实现复杂的业务需求。
5.经验分享
架构设计应该注重分层设计与上手成本,当这两点设计较好时,像易用性,复用性,解耦等优点就会自然出现。
分层设计可以让业务代码不会侵入功能代码,而学上手本低也会带来易维护,提效等好处。
6.引用
分享一下本文所引用的架构链接:
UnityGameFramework:
https://github.com/EllanJiang/UnityGameFramework
QFramework:
https://github.com/liangxiegame/QFramework
本文使用ChatGPT帮忙检查语法和拼写错误,并提供优化建议以提高文章的流畅性和可读性。
作者简介
胡春源
■ 主机厂事业部-创新项目团队
■ 2022年2月入职汽车之家,先后负责VI销冠神器和全息仓的架构,优化,功能开发等工作。现在主要负责Unity项目的架构和功能的相关工作。