Unity3D架构小结

时间:2024-04-01 08:34:04

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tyuiof/article/details/52814511

刚开始学习Unity编程时,感觉非常的不习惯。一直以来,我都是使用基于类的面向对象编程,当转到Unity开发后,发现所有的代码都是拖到一个个组件上面去的,早已习惯了的mvc,mvp等架构思想在Unity上却不太适用了。

所以在我前期开发Unity时,完全是没有架构可言的,每次都是某个组件需要什么功能,我就把相应的功能代码托上去,可是写到后面,我发现整个项目的逻辑越来越混乱,各种组件在代码中互相引用,修改某一个组件上的代码就会导致其他组件的修改。后来当我慢慢了熟悉Unity的开发,熟悉了这种基于组件的编程思想后,开始慢慢的组织自己的代码逻辑和项目架构。所以在这里,我将我这段时间对于U3D项目架构的总结记录下来。

一 文件目录架构

Unity官方文档介绍的文件目录架构如下

  • Assets:主文件夹,包含所有工程需要用到的资源。

    • Editor:所有在Editor和它的子文件夹的脚本,都不会作为运行期脚本被编译,而是作为动态添加Unity编译器功能的脚本来编译。在该文件夹和其子文件夹的脚本不能被添加到GameObject上。

    • Editor Default Resources:必须作为Assets的子文件夹,作为Editor脚本的资源使用,通过函数EditorGUIUtility.Load来使用。

    • Gizmos:场景面板中的图标图片资源,使用Gizmos.DrawIcon 函数在场景面板新建图标,图标的图片资源必须位于这个文件夹内。(比如Unity的各种插件)

    • Plugins:所有的插件资源,包括用C/C++编写的Dll库。

    • Resources:一般用来存放需要通过代码初始化使用的资源,可以放在工程中的任意文件夹中,在Resources文件夹中的文件可以通过Resources.Load 来读取。方式如下。

      rend.material.mainTexture = Resources.Load(“glass”) as Texture;
      GameObject instance = Instantiate(Resources.Load(“enemy”,typeof(GameObject))) as GameObject;

    • Standard Assets:导入标准材质包时新建的,拥有自己的编译顺序。

    • StreamingAssets:通过Application.streamingAssetsPath 获取目录路径,该目录中的文件将会原封不动的被拷贝到不同的平台上,每个平台对应的目录不一致,但是都可以通过上述方法获得。一般用来存放视频等资源文件。

    • Hidden Assets:一般用来存放README以及一些操作系统生成的临时文件,这些文件/文件夹一般以. ~ 或者名为cvs 或者扩展名是tmp 。

二 代码组织架构

①小型项目
开发小型项目时,不需要太复杂的架构方式,直接将逻辑脚本拖到响应的控件即可,但是这个时候有一个点需要考虑的就是脚本内的代码会随着逻辑的增加而越来越多,所以,这个时候最好的做法是将脚本拆分成几个脚本,每个脚本处理相应的逻辑业务,然后全部添加到组件上去。

我们以做一个飞机大战的小游戏举例,飞机有前后左右飞行的逻辑,有发射子弹的逻辑,有被击中的逻辑……我们可以把这些逻辑分别写在不同的脚本上,然后挂载到飞机上,而不是将所有的逻辑放在一个脚本内。这样我们对飞机的各个逻辑业务会更加的清晰,也更好控制。

②中小型项目
当项目越来越大时,通过拆分代码逻辑的方法已经不够用了,所以这个时候需要用统一的Manager来管理业务逻辑。在一个中型项目中,一般需要需要Level Manager,Pool Manager和Save Manager这三个管理类。下面我来说说这三个Manager的作用

  • Level Manager
    当游戏的关卡比较多时,利用Application.LoadLevel()方法加载关卡已经比较繁琐了,这个时候需要一个Level Manager来专门管理需要加载的关卡,并可以在里面配置加载策略,方便的更改加载关卡的顺序,方式等。
      
  • Pool Manager
    游戏中经常出现来频繁创建,销毁的物体,比如怪物,子弹等,如果频繁GameObject.Instantiate()来创建,用Deatory()方法来销毁会占用大量内存,引起GC,这个时候就需要一个Pool Manager来统一生成,缓存,或者销毁这些对象。
    我们来看看PoolManager的用法
  Private List<GameObject > dormantObjects =new List<GameObject>()
  public GameObject Spawn(GameObject go)  
  {
  //如果dormantObjects中存在这个object则直接取出来,否则创建该GameObject
  }
  // 将用完的GameObject缓存dormantObjects中
  public void Despawn(GameObject go)  
  {
       go.transform.parent = PoolManager.transform;
       go.SetActive(false);
       dormantObject.Add(go);
       Trim();
  }
  //最久不使用的先销毁
  public void Trim()  
  {
       while (dormantObjects.Count > Capacity)
       {
            GameObject dob = dormantObjects[0];
            dormantObjects.RemoveAt(0);
            Destroy(dob);
       }
  }

我们可以看到,PoolManager的作用就是将我们需要频繁创建和销毁的预制体放在一个list中,当容器满了之后,删除最底层使用频度最少的预制体。dormantObjects可以有很多个,用来分别存放不同的物体。

Unity3D架构小结

  • Save Manager
    Save Manager可以专门用来存储和加载游戏,通过将数据序列化(Serialize)以二进制的形式保存下来,会让游戏加载的更快。

③大型项目
在大型的项目中,上面三个Manager已经不够用,这个时候我们需要更多的Manager来进行统一管理。

Unity3D架构小结


可以看到,一个项目中有一个MainManager来统一管理不同的子Manager。子Manager中又有EventManager对事件进行管理,AduioManager对音效进行管理等等。

 

在比较大的项目中,虽然设置了很多Manager来进行统一管理,但是项目的结构还是会越来越混乱。所以这个时候我们可以采用MVCS的模式。这种设计思想我是从StrangeIOC这款Unity插件中学来的。MVCS就是在MVC的基础上加了一层Service。我们来看看这种设计思想。

Unity3D架构小结

 

这种的设计模式的业务流程是这样的

  1. 玩家点击了一下UI
  2. VIEW中的mediator得到通知(回调),因为它绑定了UI事件
  3. mediator触发了一次CONTROLLER中command执行指令,因为mediator和command进行了绑定
  4. command执行自己的execute方法
  5. 请求Service从一个文件中读取数据

当然,MVCS的设计模式只是我们设计架构时参考的一种思路。大型Unity项目开发的经验我也不是很足,等以后有了更多的经验之后,我会继续补充Unity的架构知识。