第三章:目录规划与打包策略

转载自: 知乎 - 伽蓝之洞

第三章:目录规划与打包策略


第三章:目录规划与打包策略

1. 基于管线角色的目录规划

在商业项目中,目录结构不仅仅是文件的分类方式,更是构建管线(Build Pipeline)的映射

传统的 “按资源类型分类”(如 Assets/Textures, Assets/Models)在自动化构建中是低效的,因为它割裂了功能模块的内聚性。

推荐采用基于管线角色的几大区域划分:

1
2
3
4
5
6
7
8
9
10
11
12
13
Assets/

├── 3rd/ # \[隔离区] 第三方插件与SDK

├── Editor/ # \[工具层] 项目级构建脚本、管线工具

├── GameResources/ # \[源产区] 美术原始素材 

├── GameAssets/ # \[构建区] 运行时资产,构建管线白名单

├── Scenes/ # \[场景区] 启动场景,美术用场景等

└── Scripts/ # \[代码层] C# 源码

1.1 核心区域详解

A. GameResources (源产区)
  • 内容 :美术提交的 .fbx, .psd, .wav 等原始文件。

  • 规则

    • 被引用 :它们只能作为 GameAssets 中 Prefab 的依赖项存在。

    • 自动化入口 :第二章提到的AssetPostprocessor 主要监控此目录,强制执行压缩标准。

B. GameAssets (构建区)
  • 内容 :Prefab, ScriptableObject,UnityAsset (Timeline/Animator),Texture 等。

  • 规则

    • 构建白名单 :打包脚本扫描 此目录。

    • 模块化结构 :此目录下的子文件夹直接映射为AssetBundle 的颗粒度。

    • 示例结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
GameAssets/

├── Common/ # \[公共包] 被多处引用的基础资源 (Fonts, Shaders, CommonUI)

  ├── Shaders/ # \[着色器包] 着色器,shadervariants等

  └── Fonts/ # 游戏中用到的字体

├── Scenes/ # \[场景包] .unity 文件

├── Configs/ # \[数据包] 二进制配置, 文本

├── Textures/ # \[整图相关] 单独加载显示的大图,背景图等

├── Prefabs/ # \[动态加载]

  ├── Hero/ # 游戏要加载的角色

  └── Monster/ # 游戏要加载的怪物

└── UIModules/ # \[UI功能模块]

  ├── UILogin/ # UILoginPanel.prefab

  └── UIMain/ # UIMainPanel.prefab

└── 省略…… # 根据需求添加有特效,图集等需要用到的资源
C. 3rd (隔离区)
  • 内容 :使用的第三方插件。

  • 规则

    • .asmdef 隔离 :为每个大插件创建 Assembly Definition,防止重编译波及业务代码。

    • 构建剥离 :构建脚本应强制忽略此目录下的非代码资源(防止插件自带的 Demo 资源混入包体)。

2. 打包策略:粒度分析与比较

AssetBundle 的划分粒度直接决定了运行时的I/O 效率内存占用热更大小 。这是一个需要权衡的工程决策。

2.1 三种策略对比
1
2
3
4
5
6
7
策略,描述,优点,缺点

细粒度 (按文件),每个 Prefab/Texture 一个包,极致的热更最小化;加载无冗余。,I/O 瓶颈:大量文件句柄消耗;内存浪费:每个包头(Header)占用数KB,海量小包导致内存积压。

粗粒度 (按类型/大类),整个文件夹打成一个包,I/O 次数最少;压缩率高。,热更灾难:改动 1KB 需重新下载几百 MB。

逻辑粒度 (按目录),\[推荐] 按功能模块/生命周期分组,平衡方案。同生命周期的资源一起加载/卸载。,需要严格的目录规范配合。
2.2 推荐方案:基于生命周期的逻辑划分

基于 GameAssets 目录结构,实施以下打包规则:

  1. UI / 功能模块按面板文件打包
  • 规则:GameAssets/UIModules/UILogin 目录下的每个面板独立打包 uiloginpanel.bundle。

  • 理由:UI 的加载卸载友好。

  1. 公共资源按类型打包
  • 规则:GameAssets/Common/Fonts/-> fonts.bundle。

  • 理由:公共资源通常常驻内存,独立打包便于引用计数管理,防止交叉引用导致的卸载错误。

  1. 场景独立打包
  • 规则:Level_01.unity -> scene_level_01.bundle。

  • 理由:SceneManager.LoadScene 是天然的资源卸载 / 加载边界。

3. 依赖管理:冗余解决方案

隐式依赖 是导致包体膨胀的头号杀手。

3.1 问题
  • 描述 :UIModules/UILogin/UILoginPanel.prefab 和 UIModules/UIShop/UIShopPanel.prefab 里的 RawImage 都引用了 GameResources/UI/BackBg.png。

  • 错误配置 :BackBg.png 位于 GameResources,未被显式标记 为 AssetBundle。

  • 构建结果 :Unity 构建管线检测到 BackBg.png 是 “无主” 依赖,为了保证 Login 和 Shop 包能独立运行,会将 BackBg.png 的完整二进制数据 分别写入 uiloginpanel.bundle 和 uishoppanel.bundle。

  • 代价 :包体变大,内存中存在两份同样的贴图数据(ID 不同)。

3.2 解决方案

在构建管线中引入依赖分析算法

算法逻辑

  1. 收集 :遍历 GameAssets 下所有待打包资源,获取它们的所有依赖项(Dependencies)。

  2. 计数 :建立 资源路径 -> 引用次数 的映射表。

  3. 决策

  • 若引用次数 = 1:无需处理,让其被隐式打入引用者的包中(减少碎片化)。

  • 若引用次数 >= 2:必须显式处理

处理方式对比

  • 方式 A(自动分包) :构建脚本自动将该资源提取到一个新包 shared_backbg_texture.bundle。

    • 缺点 :可能产生数千个碎片化小包,导致 Manifest 极度庞大。
  • 方式 B(强制规范 - 推荐) :构建脚本抛出异常或错误日志 ,中断打包,提示开发者:

“Error: [BackBg.png] is referenced by 2 bundles. Please move it to ‘GameAssets/Textures’!”

  • 优点 :迫使开发者维护清晰的工程结构,避免自动化的不可控。

总结

本章通过工程化的手段解决了资源架构的核心问题:

  1. 目录隔离 :通过 GameResources (原材料) 和 GameAssets (产成品) 的分离,明确了构建管线的输入范围。

  2. 逻辑粒度打包 :拒绝了极端的按文件或按大类打包,采用了基于生命周期的目录映射策略,平衡了 I/O 与内存。

  3. 依赖治理 :通过依赖计数分析,从根源上消除了资源冗余。

我们简单介绍了工程资源的划分和打包策略,主要描述了一些通常情况,真实项目只会更加复杂,一个好的提前规划会带来一个良好的开端,不会让熵增过快最终无法掌控处于混乱中。