• 浅谈客户端框架设计|一|二|三


    浅谈客户端框架设计|一|二|三


    博客很久没有更新了,有必要更新一下;一是:近一年在技术上有了很多知识积累,想和各位技术人分享一下;二是:也必须要更一下,不能让各位以为我GameOver了。刚好近一年一直在公司做自研产品的客户端框架设计和搭建,有了诸多的心得和体会,同时在客户端框架的设计上也积累了一些实操的经验;这里大概会分三篇文章来阐述如何设计出可灵活扩展和符合多人协同开发的客户端框架。废话不多说,咱们直切正题。


    一、框架技术栈


    一个好的框架,必然不是从零开始的,这其中也必不可少的会集成一些市面上开源的优秀框架,我们这里设计的框架也不例外。集成了一些免费、开源、流行、强大、敏捷的第三方成熟框架。主要包含以下几个:

    • Zenject
    • UniRx
    • UniTask
    • EasySave3
    • Odin Inspector

     UniRx : 不做过多的介绍,我之前的博客里翻译了几篇UniRx的官方文档教程;其使用方法、优势、入门教程,我之前的文章里都有详细的说明,大家自己到历史消息里去找。

     Zenject :这里着重介绍一下这个真香框架。相信很多小伙伴都接触过MVC设计模式、MVVM模式或者一些其它框架的设计模式,再不济,应该也听说过C#的Asp.Net Core MVC框架。没错,这个框架是一个U3D版的DI框架,什么是DI框架,全称(Dependency Injection),中文名:依赖注入框架。咱们的代码结构设计的灵魂在于对扩展开放、对修改关闭。因此,针对代码设计也涌出了诸多的设计原则,什么单一职责原则啊、里氏替换原则啊等等,诸多需要遵循的规范。那什么是依赖注入呢?为什么叫依赖注入?为什么需要依赖注入?这里我举一个大家在开发过程中很容易忽略的一个因素。在U3D开发中,我们经常会用到单例模式,用单例模式的主要目的就是在两个类之间进行数据交换(比如:方法调用、变量赋值、委托注册)等等,那么这两个类之间就相当于产生了互相依赖。A类的代码中有了B类的单实例的声明,两个类脚本互相产生了依赖。回想一下我们面向对象设计的原则中的依赖倒置原则:

    抽象不应该不依赖于细节,细节应该依赖于抽象。
    换言之,要针对接口编程,而不是针对实现编程

    原则里面已经定性了,两个对象类之间可以有相互依赖,但他们之间的依赖不应该是具体类的实现,而应该依赖的是接口,因为接口是抽象的。正所谓接口是抽象的,那必然会对应有接口的具体实现,如何让具体的实现的接口对应起来?这就是DI框架需要做的事情了。DI框架(依赖注入框架),我们在A类中声明了一个B类的接口,那么A类就对B类产生了依赖,那么A类如何确定A类中的接口要对应哪一个实例呢?这个时候就需要通过框架把A类接口需要的实例注入进来,这就是注入。另外一个问题,框架是如何知道A类接口要获取的具体是B类的哪一个实例呢?这里又引入了另外一个名词,叫做绑定。其实DI框架很简单,无非就是:声明接口->绑定实例->注入使用。其它的细节(如何获取实例、如何实例化)均交给框架去处理。总结一句话就是:一个类中有了另外一个类的依赖,那么这个类应该依赖的是另外一个类的接口,而不是具体实现。同时,类的具体实现(比如构造、New 操作),也不能直接在类中实现,而应该把具体的实现细节交给框架来完成。

     UniTask :这也是一个真香框架。重所周知,U3D是一个逻辑上单线程的游戏引擎,想要在U3D里面通过多线程访问U3D提供的API及组件,那必然是不被允许的,同时,在U3D里面写多线程代码也相对比较麻烦;那UniTask能提供什么呢?那必然是优雅的在U3D中写多线程代码,包括使用await/async语法,包括线程的上下文切换,以及一系列的基于时间、基于帧的操作,大大提升我们在U3D中进行异步多线程开发的效率。真香,强烈推荐!!!

     EasySave3 : 顾名思义,这是一个容易保存的框架;主要用来做数据存储、存档、读挡功能,提供了类似于PlayerPref.SetValue和GetValue的功能。当然,其远不止如此;包括Json文件的序列化、反序列化;一些U3D\C#数据结构的数据(字典、列表、数组、GameObject\Transform\MonoBehaviour等等)的保存、还原;数据加密、解密、本地化、远程加载存储等等,总之,啥数据都能存、都能还原,香的很。

     Odin Inspector :这玩意不用我说吧,大名鼎鼎的编辑器扩展插件,UnityEditor编辑器扩展开发的最佳神器,编辑器内数据结构可视化完美的呈现。


    框架的结构设计

    一个引擎有很多的模块组成,比如:音频模块、动画模块、场景管理、渲染模块、网络模块等等;框架也一样,同样也有很多模块组成,不同的是,框架是在引擎提供的基础模块上根据业务需求和开发习惯,针对引擎提供的二次扩展和封装,确保开发过程更加高效和敏捷。同样,市面上的大多数U3D框架也都提供了譬如:动画管理模块、U3D资源加载模块(AssetBundle)、常规资源加载模块(音频、视频、图片、文本)、相机管理模块、用户管理模块、配置模块、网络请求模块,语言描述可能很复杂,咋们看图说话;以下是我们这次要将的这个客户端框架设计的逻辑结构图:

    在这里插入图片描述

    从设计结构上大概可以分为三层,从下到上依次为:基础框架层、框架模块实现层、以及DI绑定层。


    基础框架

    基础框架上面已经做过了详细的介绍,主要为我们的框架中提供一些便捷的实现。比如:UniRx提供了基于观察和订阅模式的编程思维,包括

    • 微协程 (Micro Corotinue)
    • 基于游戏帧的操作(NextFrame)
    • 基于时间逻辑的操作 (Timer)
    • 基于事件流的操作(EveryUpdate)

    UniTask相当于UniRx的一个异步版本,不仅提供了UniRx式的编程思维,而且完全支持异步方式编程,比如:

    • await/async
    • 基于线程池的多线程编程

    EasySave3: 数据存档、加密等等,针对所有资源的序列化操作。

    本篇是一个前言,中间的框架模块作为我们主要开发和实现的模块,会在接下来的两篇博客中详细介绍,包括实现思路和设计细节,如何抽象,保证模块设计的扩展性。

  • 相关阅读:
    1)SpringBoot简介
    acwing算法提高之图论--无向图的双连通分量
    webpack学习使用
    angular:html2canvas对ion-avatar节点渲染不正确
    ThingsBoard处理设备上报的属性并转换为可读属性
    使用C语言实现队列
    Vue3 toRaw 和 markRaw
    字符串函数详解
    Java 集合面试题小结(1)
    mysql调优
  • 原文地址:https://blog.csdn.net/JianZuoGuang/article/details/126432524