前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Unity 引擎资源管理代码分析 ( 1 )

Unity 引擎资源管理代码分析 ( 1 )

原创
作者头像
李海辰
修改2017-11-17 18:03:39
7.9K0
修改2017-11-17 18:03:39
举报
文章被收录于专栏:李海辰的专栏李海辰的专栏

一、 简介

目前网络上已经有很多介绍Unity资源管理机制、和API使用方法的文章,但少有文章从Unity源码层面对其实现进行深度解析。作为一名喜欢打破砂锅璺到底程序猿,又有幸在鹅厂接触到Unity官方授权的源代码,实在是克制不住对其实现分析一通。学习收获一二,与众分享,希望能在项目中帮到大家!

本文主要基于Unity 4.6.9的引擎源代码,重点介绍了Unity中GameObject、Asset和Prefab等概念在引擎中的实现,并分析了Resources类和AssetBundle的常用资源加/卸载API的工作机理,及其应用优劣。

二、 资源类型

1. Unity C++ 类图

在分析Unity的资源管理机制之前,我们首先要从Unity引擎的代码层面去理解GameObject、Component、Asset、Prefab等不同类型对象的具体实现,以及它们之间的关系。

其实在Unity引擎的C++源代码实现中,所有的对象都是基于UnityEngine.Object类的。而其中Asset和Prefab只是两个抽象的概念,并没有对应的C++ Class实现。具体见下图:

2. GameObject(橙)、Component(紫)、Asset(红)

在类图中我分别用橙、紫、红三种颜色将我们能在Unity编辑器中直接见到的C++ Class分为了三大类。这些类的属性和方法其实都是由C++代码实现的,只不过暴露给了C#脚本。也就是说在创建这些对象时系统会同时在C#的managed heap和C++ native heap中分配内存。

其中橙色的GameObject类就是我们在编辑器中可创建的对象节点,它本身并不实现任何的渲染或游戏逻辑等功能,即便最基本的空间变换功能也是由默认挂接的Transform组件所实现的。但我们可以在GameObject上挂接MeshRenderer、Animator、SpriteRenderer以及继承于MonoBehavior的自定义脚本组件实现各种各样的渲染及逻辑功能。这些用于实现某个特性功能脚本组件在上图中均被标记为紫色。

这里注意,这些功能组件本身并不包含资源,而只负责实现某个功能。以MeshRenderer组件举例,它通过MeshFilter组件间接获取引用的Mesh模型资源,通过Material对象获取渲染用的材质属性、Shader、以及纹理资源。而像Mesh、Material、Shader、Texture、AnimationController等在上图中标为红色的类则用来保存实际的资源数据,这些类的数据通常都可从文件中读取出来,或者可以被保存为文件。这些对象是货真价实的Assets资源。

3. Prefab

那么Prefab又是什么?我们知道可以将多个GameObject对象挂接为父子级,组成一个完整的场景树。而当我们把其中的一部分子树在Unity编辑器中拖拽到资源视图中时就会生成一个对应的.prefab文件。这个.prefab文件中保存的就是这个场景子树中包含的所有GameObject,这些GameObject下挂接的组件、属性、及对资源的引用关系。

当我们通过Resources.Load之类的接口加载.prefab文件时,引擎则会自动创建这些GameObject、Component,加载其所引用的资源,并恢复其组织关系。保存时则反之。但注意,由于组件并不实际保存资源数据,因此.prefab文件也并不直接保存其引用的任何资源数据。取而代之,.prefab文件通过一个guid来索引其引用到的资源。

下图为保存了一个人物模型的prefab文件和这个模型fbx对应的meta文件内容的部分截图。可见prefab文件中mesh和avatar记录的guid都跟meta文件中的guid一致,也就是说其中Animator组件引用的avatar资源和SkinMeshRenderer组件引用的mesh资源都是从这个fbx文件中加载的。

三、 资源管理API分析

1. Resources.Load

为了方便理解Unity引擎的工作机制、避免AssetBundle等资源打包机制造成干扰,我们从最原始、最直接的Resources.Load接口开始分析资源加载流程。但由于版权问题下文不会直接贴出Unity引擎源代码,只会对执行流程作大概的解释,敬请谅解。

在Unity引擎中,Resources.Load接口对应的C++函数为Resources_CUSTOM_Load。该函数做的第一件事是在ResourceManager.GetPathRange函数中根据传入的资源路径字符串在一个std::multimap<UnityStr, PPtr<Object>>类型的map中查找资源对象的指针。在不考虑AssetBundle的情况下,只有Assets/Resources/目录下的资源会被预先索引到这个表中。

这个查找过程有三个地方值得注意:

第一,路径中所有的字符会首先被转换为小写,且所有的目录分隔符都使用“/”,并去除了扩展名。也就是说当资源路径只有大小写或扩展名区分的时候,对Unity来说这两个路径是没有区别的。

第二,这个Unity自己定义的PPtr类其实并没有存储Object指针的成员变量,它实际上只存储了一个int类型的InstanceID,但是它重载了所有对指针进行访问的操作符。当访问对象指针时,它会通过Object::IDToPointer函数在一个全局对象表中查找实际的对象指针,并返回。

第三,这个查找表是一个std::multimap类型的容器,也就是说它是允许使用相同路径的键值存储多个对象。也就是说假设在相同目录下,除了存在我们需要加载的foo.prefab外,还有个foo.shader的资源时,引擎只会加载multimap中同名的第一个资源。如果我们在调用Resources.Load接口指定了第二个对象类型的参数,Unity引擎则会在加载完对象后去判断这个对象的类型与我们指定的类型是否相同(或是否为其子类),如果相同则break跳出循环,不加载其后的对象。因此我强烈建议大家不要让资源的命名重复,或在加载资源时不指定具体的类型。这不但会造成多余的资源加载操作,还有可能造成资源类型转换错误。

对于有兴趣阅读Unity源代码的同学,这里我要多提个醒,Unity的Object对象创建及数据读取代码是隐藏在PPtr<T>::operatorT* () const这个操作符重载函数里的,也就是说你看到第一行尝试对Object指针进行访问的代码即是实际对象加载的位置。其反序列化的内部函数为PersistentManager::ReadObject。我第一次跟代码的时候也一不小心就F10过去了……

例如我们要加载一个foo.prefab这个文件,这个文件中包含三个GameObject:A、B、C,其中GameObject B、C下各挂接了自己的MeshRenderer和MeshFilter组件,并引用了自己的Mesh、Material资源,共享了一个Shader资源。如下图:

当PersistentManager::ReadObject函数加载完这个foo.prefab中的根级GameObject A之后,它会调用这个对象的CheckConsistency函数,这个函数是Object基类的虚函数,负责检查在该对象中包含的所有可永续化的(代码原文Persistent,直白的说就是可通过文件存取。)成员变量的正确性。无论GameObject还是Component,所有继承自Object的子类都必须实现。于是乎,当GameObject - A检查它包含的Component的时候,发现其下的Transform组件又引用了GameObject B和C,则会去获取GameObject B和C的指针。从而无可避免地又通过PersistentManager::ReadObject函数去加载GameObject B、C。加载B、C的时候又引用到了他们的MeshFilter和MeshRenderer组件,从而进一步加载了它们的Mesh和Material资源。当首先加载Material B的时候,由于Shader B&C尚未加载,因此会自动加载它。而当它再次加载Material C的时候,由于发现Shader B&C已在InstanceID to Pointer的全局表中存在了,所以就直接引用它,而不会重复加载。举一反三,所有Prefab的节点树及其组件和资源就是按照这样的方式被加载完成的。

(精彩待续……写文档真是个体力活,后续内容还未整理完毕。大家多多支持,我会再接再厉的!)

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、 简介
  • 二、 资源类型
    • 1. Unity C++ 类图
      • 2. GameObject(橙)、Component(紫)、Asset(红)
        • 3. Prefab
        • 三、 资源管理API分析
          • 1. Resources.Load
          相关产品与服务
          容器服务
          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档