name: vrchat-simplified-avatar-creation category: vrchat description: 从完整版VRChat Avatar创建精简版变体的完整工作流 — 场景内Instantiate、移除多余子对象、创建独立Params/Menu、处理共享材质、设置taozhuang默认值 trigger: 用户说"创建精简版Avatar"、"只保留一套衣服"、"做个简化版"、"从XX复制一个只保留YY"
VRChat 精简版 Avatar 创建工作流
基于 Cazalis ZG_03 项目实战总结(2026-06-02)
适用场景
从已有完整换衣系统 Avatar 创建一个只保留固定服装的精简版,用于: - 降低参数预算(移除换衣相关参数) - 简化菜单(移除换衣/配饰子菜单) - 减少下载体积(移除多余服装 GameObject)
前提条件
- 源 Avatar 已有完整的换衣系统(taozhuang Int 参数 + 4S_ 层)
- 多 Avatar 共享同一个 FX Controller
- 每个 Avatar 有独立的 ExpressionParameters 和 ExpressionMenu
完整操作流程
Step 1: 场景内 Instantiate
// MCP execute_code
var src = GameObject.Find("Cazalis_ZG_02");
var clone = Object.Instantiate(src);
clone.name = "Cazalis_ZG_03";
clone.transform.position = new Vector3(0.71f, 0.92f, 0.34f); // 偏移避免重叠
⚠️ Instantiate 后骨骼自动重绑到新 Avatar,不需要手动修复。
Step 2: 从层级移除不需要的子对象
仅断开引用,绝对不删源文件!
移除的对象类型: - 其他服装套装(Cloud, Blanchir_Cazalis, SilentSkinPeek 等) - 不需要的内衣部件(如 Cloth_Under_Bra) - 其他配饰
保留的对象: - 目标服装(如 01_Black) - 指甲等装饰配件(obake nail — 不需要菜单但必须保留!) - 保留的内衣(如 Cloth_Under_Shorts) - Armature, Body, Body_Base, 头发, GoGo Loco 等 obake nail — 不需要菜单但必须保留!) - 保留的内衣(如 Cloth_Under_Shorts) - Armature, Body, Body_Base, 头发, GoGo Loco 等### Step 3: 创建独立 ExpressionParameters 副本
// 复制到专属目录
AssetDatabase.CopyAsset("Assets/Cazalis_ZG_02_Assets/Cazalis_Parameter_ZG_02.asset",
"Assets/Cazalis_ZG_03_Assets/Cazalis_Parameter_ZG_03.asset");
精简参数 — 删除换衣/配饰相关参数,但必须保留: - ✅ taozhuang — default=对应值(如 6=HonmeiKnit/01_Black),否则 VRChat 默认 0 会让衣服被溶解隐藏! - ✅ VRCEmote, VRCFaceBlendH/V — VRChat 内置参数 - ✅ 保留的装饰/功能参数(Butterfly_ON, Breasts_Size 等)
可删除的参数: - 换衣相关:Origin_ON, Lujian_ON, Casual_ON, Cardigan_ON 等 - 配饰参数:WardrobeParts_0/1, Wardrobe_Int1 - 内衣开关:Bra_ON, Shorts_ON, Dress_ON 等 - 鞋袜开关:Pumps_ON, Socks_ON, DressSkirt_ON
Step 4: 创建独立 ExpressionMenu 副本
AssetDatabase.CopyAsset("Assets/Cazalis_ZG_02_Assets/Cazalis_Menu_ZG_02.asset",
"Assets/Cazalis_ZG_03_Assets/Cazalis_Menu_ZG_03.asset");
精简菜单 — 删除换衣和配饰子菜单,保留功能菜单。
Step 5: 指向新副本
var desc = clone.GetComponent<VRC.SDK3.Avatars.Components.VRCAvatarDescriptor>();
var newParams = AssetDatabase.LoadAssetAtPath<VRCExpressionParameters>("Assets/Cazalis_ZG_03_Assets/Cazalis_Parameter_ZG_03.asset");
var newMenu = AssetDatabase.LoadAssetAtPath<VRCExpressionsMenu>("Assets/Cazalis_ZG_03_Assets/Cazalis_Menu_ZG_03.asset");
desc.expressionParameters = newParams;
desc.expressionsMenu = newMenu;
Step 6: 移除 MA ObjectToggle
精简版不需要切换逻辑,移除衣服上的 ModularAvatarObjectToggle 组件。 Menu = newMenu;
### Step 6: 移除 MA ObjectToggle
精简版不需要切换逻辑,移除衣服上的 ModularAvatarObjectToggle 组件。### Step 7: 处理共享材质(关键!)
⚠️ **最大陷阱**:修改共享材质会影响所有使用该材质的 Avatar!
**必须先创建独立材质副本**,再修改溶解参数:
```csharp
// 1. 复制材质到专属目录
AssetDatabase.CopyAsset("Assets/.../Cazalis_Underwear_Shorts.mat",
"Assets/Cazalis_ZG_03_Assets/Cazalis_Underwear_Shorts_ZG03.mat");
// 2. 修改副本的溶解参数
var mat = AssetDatabase.LoadAssetAtPath<Material>("Assets/Cazalis_ZG_03_Assets/Cazalis_Underwear_Shorts_ZG03.mat");
mat.SetFloat("_DissolveParams.z", -0.5f); // 常驻显示(原版1.7=动画控制)
// 3. 将SMR指向新材质
smr.sharedMaterials = new Material[] { mat };
需要独立材质的情况: - 精简版需要常驻显示(z=-0.5),原版由动画控制(z=1.7) - 任何两个 Avatar 对同一材质有不同溶解状态需求
Step 8: 验证
- 精简版子对象正确(含 obake nail 等配件)
- Params 包含 taozhuang 且默认值正确
- Menu 不含已删除的子菜单
- VRCAvatarDescriptor 指向独立副本
- 独立材质副本存在且溶解参数正确
- 源 Avatar 未受影响(参数/菜单/材质全部不变)
-
保存场景
-
VRCAvatarDescriptor 指向独立副本
- 独立材质副本存在且溶解参数正确
- 源 Avatar 未受影响(参数/菜单/材质全部不变)
- 保存场景## Step 2.5: 删除前外部引用扫描(强烈建议)
⚠️ 删 GameObject 前必须扫一遍是否被树内其他组件引用(MA 组件、Constraint、PhysBone 骨骼引用等),否则删完留下空引用导致运行时报错。
// 扫整个 Avatar 树,找出所有指向待删节点(或其子节点)的 ObjectReference
var toDelete = new List<GameObject>{ /* 待删节点 */ };
var allComps = avatar.GetComponentsInChildren<Component>(true);
int refCount = 0;
foreach (var comp in allComps) {
if (comp==null) continue;
// 跳过待删节点上的组件(它们会一起被删)
bool isInDel = false;
foreach (var del in toDelete) if (comp.transform.IsChildOf(del.transform)) { isInDel = true; break; }
if (isInDel) continue;
var so = new SerializedObject(comp);
var prop = so.GetIterator();
while (prop.NextVisible(true)) {
if (prop.propertyType != SerializedPropertyType.ObjectReference) continue;
var refObj = prop.objectReferenceValue;
if (refObj==null) continue;
GameObject refGo = (refObj as GameObject) ?? (refObj as Component)?.gameObject;
if (refGo==null) continue;
foreach (var del in toDelete) {
if (refGo == del || refGo.transform.IsChildOf(del.transform)) {
refCount++;
Debug.Log(comp.GetType().Name+" on "+comp.gameObject.name+" -> "+prop.propertyPath+" -> "+refGo.name);
break;
}
}
}
}
// refCount == 0 才安全删
实战经验:Cazalis ZG_04 项目 2383 个组件,扫描结果 0 引用 → 安全删除 4 个套装节点。
}
} } // refCount == 0 才安全删 ```
实战经验:Cazalis ZG_04 项目 2383 个组件,扫描结果 0 引用 → 安全删除 4 个套装节点。## Menu 精简的两种模式
精简版的 Expression Menu 有两套思路,按用户期望选择:
| 模式 | 主菜单 | 套装子菜单 | 适用 |
|---|---|---|---|
| ZG_03 模式(彻底砍) | 删除"套装"等不需要的入口 | 不存在 | 极简、只玩一套 |
| ZG_02/ZG_04 模式(保留入口) | 完整 8 项主菜单 | 仅含保留套装的子菜单(如 2 项) | 想保留菜单结构、未来还能加套装 |
实战中 ZG_04 用 ZG_02 模式:主菜单完整克隆,仅替换 [6]套装 指向独立的 套装_ZG_04.asset(2 项:value=6/7)。
复制源选择:场景实例 vs Project Prefab
| 源类型 | 是否需 PrefabUtility.UnpackPrefabInstance | 备注 |
|---|---|---|
| 场景内裸 GameObject(已 unpack) | ❌ 不需要 | ZG_04 走此路径,Object.Instantiate 即可 |
| 场景内 Prefab Instance(蓝色) | ✅ 需要 unpack 后才能改子节点 | 否则 prefab override 难管理 |
| Project 中 .prefab | 建议先 Instantiate 到场景再 unpack | 直接 PrefabUtility.InstantiatePrefab 出来是 instance |
检查方法:PrefabUtility.IsPartOfPrefabInstance(go) → false 就是裸的。
关键陷阱汇总
| # | 陷阱 | 后果 | 解决方案 |
|---|---|---|---|
| 1 | 删 taozhuang 参数 | 衣服被溶解隐藏 | 保留 taozhuang,设对默认值 |
| 2 | 修改共享材质 | 影响其他 Avatar | 先创建独立副本再修改 |
| 3 | 移除 obake nail | 指甲消失 | 不需要菜单≠不需要保留 |
| 4 | VRCExpressionParameters.Parameter 没有 localOnly | 编译报错 | 字段只有:name, valueType, saved, defaultValue, networkSynced |
| 5 | 不创建独立 Params/Menu | 修改精简版影响原版 | 每个 Avatar 必须有独立副本 |