02 · 资产管理与管线自动化
02 · 资产管理与管线自动化
管线不是"流程文档",是能自动拒绝脏数据的机器。 独立游戏没有 QA,校验器就是你的 QA。
1. 目录结构(一次定死,永不改动)
Project/
├── Art/
│ ├── _Source/ # 源文件(Blender/PS/Substance)—— 不进引擎
│ │ ├── Characters/
│ │ ├── Environment/
│ │ └── Props/
│ ├── _Working/ # WIP,允许脏,不进构建
│ ├── Characters/
│ │ ├── Hero/
│ │ │ ├── Mesh/ Textures/ Material/ Anim/ Rig/
│ │ └── Enemy_01/
│ ├── Environment/
│ │ ├── Modular_Kit_A/ # 模块化套件
│ │ ├── Props/
│ │ └── Tileables/ # 可平铺纹理
│ ├── VFX/
│ │ ├── Textures/ Flipbooks/ Meshes/ Systems/
│ ├── UI/
│ └── Shared/ # 跨项目共享:母材质、噪声、LUT、预设
├── Scenes/
│ ├── _Template/ # 光照模板场景(所有场景从此复制)
│ ├── _Test/ # 测试场景
│ └── Act1/
└── Tools/ # TA 工具与脚本
规则
_前缀目录 = 不参与构建_Source不进引擎(避免引擎生成 .meta 污染源文件)- 同类资产只在一个位置(禁止"这个石头在三个文件夹里各有一份")
2. 命名规范(写进校验器强制执行)
通用格式
<类型>_<类别>_<对象>_<变体>_<后缀>
前缀表
| 前缀 | 类型 | 示例 |
|---|---|---|
SM_ |
Static Mesh 静态网格 | SM_Env_Rock_Cliff_01 |
SK_ |
Skeletal Mesh 骨骼网格 | SK_Chr_Hero_Body |
T_ |
Texture 纹理 | T_Env_Rock_Cliff_01_BC |
M_ |
Material 材质 | M_Env_Standard |
MI_ |
Material Instance 实例 | MI_Env_Rock_Cliff_01 |
SMI_ |
共享实例 | SMI_Env_Wood_All |
VFX_ |
特效 | VFX_Combat_Hit_Slash |
FB_ |
Flipbook | FB_Fire_Explosion_8x8 |
BP_/Prefab_ |
预制体 | BP_Prop_Barrel_01 |
UI_ |
UI | UI_Icon_Sword_01 |
SKEL_ |
骨架 | SKEL_Hero |
PHYS_ |
物理资产 | PHYS_Hero |
ANIM_ |
动画 | ANIM_Hero_Run_Fwd |
DA_ |
数据资产 | DA_HitFeedbackConfig |
纹理后缀
| 后缀 | 含义 | 色彩空间 |
|---|---|---|
_BC |
Base Color | sRGB |
_N |
Normal | Linear |
_ORM |
AO/Roughness/Metallic 打包 | Linear |
_M |
Mask(自定义遮罩) | Linear |
_E |
Emissive | sRGB |
_H |
Height | Linear |
_D |
Detail / Noise | Linear |
_LUT |
Lookup Table | Linear |
动画命名
ANIM_<角色>_<动作>_<方向?>_<变体?>
ANIM_Hero_Run_Fwd
ANIM_Hero_Attack_Combo01
ANIM_Enemy_Goblin_Death_A
命名规范的唯一目的是让脚本能自动处理。 任何无法被脚本解析的命名都是失败。
3. 导入预设自动化(省人天的第一大户)
目标:美术把文件拖进引擎,一切设置自动正确。
Unity:AssetPostprocessor
public class ArtImportSettings : AssetPostprocessor
{
void OnPreprocessTexture()
{
var importer = (TextureImporter)assetImporter;
string name = System.IO.Path.GetFileNameWithoutExtension(assetPath);
// 按后缀判定用途
if (name.EndsWith("_BC")) importer.sRGBTexture = true;
else importer.sRGBTexture = false;
if (name.EndsWith("_N")) importer.textureType = TextureImporterType.NormalMap;
// 尺寸封顶
importer.maxTextureSize = name.Contains("Hero") ? 2048 : 1024;
// 平台压缩
var pc = importer.GetPlatformTextureSettings("Standalone");
pc.overridden = true; pc.format = TextureImporterFormat.BC7;
importer.SetPlatformTextureSettings(pc);
var mob = importer.GetPlatformTextureSettings("Android");
mob.overridden = true; mob.format = TextureImporterFormat.ASTC_6x6;
importer.SetPlatformTextureSettings(mob);
importer.mipmapEnabled = !name.Contains("_UI");
importer.textureCompression = TextureImporterCompression.Compressed;
}
void OnPreprocessModel()
{
var importer = (ModelImporter)assetImporter;
importer.globalScale = 1.0f; // 统一单位
importer.importBlendShapes = false; // 默认关,按需开
importer.meshCompression = ModelImporterMeshCompression.Medium;
importer.generateSecondaryUV = false;
importer.materialImportMode = ModelImporterMaterialImportMode.None; // 用母材质
}
}
UE5:Editor Utility / Python + DataAsset
import unreal
# 批量设置纹理
for asset in selected_textures:
asset.set_editor_property("srgb", name.endswith("_BC"))
asset.set_editor_property("compression_settings", unreal.TextureCompressionSettings.TC_DEFAULT)
asset.set_editor_property("lod_group", unreal.TextureGroup.TEXTUREGROUP_World)
asset.set_editor_property("max_texture_size", 1024)
Godot:.import 预设 + 导入脚本
在 project.godot 或导入脚本中按路径规则批量设 compress_mode / filter / mipmaps。
4. 资产校验器(你的 QA)
必检项目
| 类别 | 检查项 | 严重度 |
|---|---|---|
| 命名 | 符合 类型_类别_对象_变体 |
P1 |
| 纹理 | 尺寸是 2 的幂;≤ 规格上限;压缩格式正确;sRGB 正确;有 Mip | P0 |
| 网格 | 三角面 ≤ 上限;材质槽 ≤ 上限;UV 存在且无重叠;无退化三角形 | P1 |
| 网格 | Pivot 在合理位置(底部/中心,不是世界原点) | P1 |
| 网格 | 缩放为 1,旋转归零(无负缩放) | P1 |
| 网格 | 法线朝外;无翻转面 | P2 |
| 材质 | 使用的是母材质实例,不是独立材质 | P0 |
| 材质 | 纹理引用完整,无丢失 | P0 |
| 角色 | 骨骼数 ≤ 上限;命名符合规范;根骨骼存在 | P1 |
| 角色 | 蒙皮权重 ≤ 4 骨骼/顶点;无零权重顶点 | P1 |
| 动画 | 帧率一致;无冗余关键帧;根运动设置正确 | P2 |
| 场景 | 无重叠物件;无漏光;无缝隙 | P2 |
校验器实现要点
- 入库前跑(Git pre-commit hook / 编辑器保存时 / 手动一键)
- 输出可操作的报告:
文件 / 问题 / 严重度 / 一键修复按钮 - P0 阻止提交,P1 警告,P2 记录
- 能自动修的就自动修(如压缩格式、尺寸封顶),不能修的才报错
最低可用版本(1 天能写完)
def validate_asset(path):
issues = []
if not NAME_RE.match(basename(path)): issues.append(("命名不规范", "P1"))
if is_texture(path):
if not is_pot(size): issues.append(("非2的幂", "P0"))
if size > LIMIT: issues.append(("超出尺寸上限", "P0"))
if compression != EXPECTED: issues.append(("压缩格式错误", "P0"))
if srgb != EXPECTED: issues.append(("色彩空间错误", "P0"))
if is_mesh(path):
if tris > LIMIT: issues.append(("面数超标", "P1"))
if material_slots > LIMIT: issues.append(("材质槽过多", "P1"))
if pivot_is_world_origin: issues.append(("Pivot 在世界原点", "P1"))
if uses_standalone_material: issues.append(("非母材质实例", "P0"))
return issues
5. 单位与坐标系统(一次定死)
| 项 | 标准 |
|---|---|
| 单位 | 1 unit = 1 meter |
| 轴向 | Y-up 右手 / 或引擎默认(UE: Z-up 左手)——全项目统一 |
| 角色高度 | 1.8m(人类)—— 所有比例以此为基准 |
| 网格模数 | 例如 0.25m(环境模块),所有模块按此对齐 |
| 门高 | 2.2m · 台阶 0.15–0.2m · 栏杆 1.1m |
| 导出缩放 | Blender 导出 FBX 时 scale = 1.0(或用统一导出预设) |
| 轴向转换 | 在 DCC 端修正,不要在引擎里靠旋转补丁修 |
最常见的坑:Blender 导出到 Unity/UE 出现 -90° X 旋转 + 100 倍缩放。 解法:写一个统一的导出预设/脚本,所有人用同一个,而不是每人手动改。
6. 版本控制与二进制
| 项 | 方案 |
|---|---|
| 代码/文本 | Git |
| 二进制资产 | Git LFS 或 Perforce / Plastic SCM |
| 源文件(_Source) | 单独仓库或云盘,不进主仓库(太占空间) |
| 锁机制 | Perforce 有文件锁;Git 靠命名分区 + 沟通 |
| 分支 | 美术不要开长分支,直接主干 + 频繁提交 |
| 提交前 | 跑校验器(pre-commit hook) |
LFS 配置示例
git lfs track "*.fbx" "*.psd" "*.png" "*.tga" "*.blend" "*.wav" "*.mp4"
git lfs track "*.unity" "*.prefab" "*.asset" # 视情况
红线:二进制文件没进 LFS → 仓库膨胀到几十 GB → 团队崩溃。这是独立游戏最容易犯的致命错误之一。
7. 批量处理脚本库(按 ROI 排序)
| 脚本 | 省多少 | 人天 |
|---|---|---|
| 批量导出 FBX(Blender,应用统一导出预设) | 每个资产 3 分钟 | 0.5 |
| 批量重命名(符合规范) | 导入后整理时间 | 0.5 |
| 批量生成 LOD | 手做 LOD 的时间 × 资产数 | 1 |
| 批量生成材质实例(按贴图自动匹配) | 大量 | 0.5 |
| 批量 ORM 通道打包(PS/脚本) | 大量 | 1 |
| 批量纹理压缩/尺寸调整 | 大量 | 0.5 |
| 场景资产清单导出(统计面数/材质/内存) | 每周审计 | 0.5 |
| 查重(内容重复的资产) | 防止仓库膨胀 | 0.5 |
| 未使用资产扫描 | 减小包体 | 0.5 |
| 批量贴图色板重映射(素材包统一) | 素材包统一 | 1 |
8. 模块化套件(Modular Kit)—— 环境量产的核心
设计原则
| 原则 | 说明 |
|---|---|
| 模数统一 | 所有模块基于同一网格(如 0.25m / 1m / 4m) |
| 接缝可预测 | 模块边缘保持一致轮廓,任意组合都不漏 |
| 材质共用 | 整个套件用 1–2 个母材质 |
| UV 策略 | 可平铺 UV + 少量独特 UV;用三平面解决不规则面 |
| 变体靠参数 | 同一个墙模块,改 _Wear/_Wetness 变出不同年代 |
| 数量控制 | 一套套件 20–40 个模块足够撑起一个章节 |
检查:套件够不够用?
拿套件去搭 3 个完全不同的场景。搭不出来 → 缺模块,补;搭出来很多废模块 → 砍。
9. 资产台账(Asset Ledger)
必须有一张表,记录所有资产的来源与状态。独立游戏常因为缺这张表而在上架时踩法律雷。
| 字段 | 说明 |
|---|---|
| 资产名 | |
| 来源 | 自制 / 外包 / 素材包 / AI 生成 |
| 授权类型 | 商用授权 / CC0 / 编辑器专属 / 需署名 |
| 许可证文件位置 | 路径 |
| 是否可商用 | ✅/❌ |
| 是否需署名 | 是(署名内容)/ 否 |
| 已做风格统一 | ✅/❌ |
| 使用的母材质 |
红线:免费版/教育版素材不得用于商业发行;AI 生成素材需确认平台条款与版权状态;任何授权不明的资产在发货前必须替换。
10. 管线成熟度自查
| 级别 | 特征 |
|---|---|
| L0 混乱 | 靠人肉纪律;命名随意;经常丢材质;不知道哪个是最新版 |
| L1 有文档 | 有命名规范和文档,但靠人执行 |
| L2 有工具 | 导入预设 + 校验器 + 批量脚本,大部分自动 |
| L3 有门禁 | 校验器接入提交流程,不合规无法入库;有 CI 性能回归 |
| 独立游戏目标:L2~L3(最少投入:1–3 人天) |
11. 自检清单
- 目录结构定了吗?
_Source和引擎资产分开了吗? - 命名规范能被脚本解析吗?校验器在跑吗?
- 导入预设生效了吗?美术还需要手动改设置吗?
- 单位/轴向统一了吗?导出预设是同一个吗?
- 二进制进 LFS 了吗?仓库多大?
- 母材质覆盖率 ≥ 70% 吗?有没有资产用了独立材质?
- 资产台账有吗?所有素材包的授权都确认了吗?
- 有未使用资产堆积吗?
- 模块化套件能搭出 3 个不同场景吗?
- 新成员能在 30 分钟内完成"建模→导出→导入→可用"全流程吗?不能 → 管线还不够成熟。