跳转至

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 必须有独立副本