private void OnGUI() { using (var check = new EditorGUI.ChangeCheckScope()) { avatarPrefab = EditorGUILayout.ObjectField("2.0 Avatar Prefab", avatarPrefab, typeof(GameObject), false) as GameObject; if (ObjectSelectorWrapper.isVisible) { ObjectSelectorWrapper.SetFilterString("t:prefab"); } if (check.changed && avatarPrefab != null) { avatar2Info = GetAvatar2Info(avatarPrefab); } } if (avatarPrefab != null && avatar2Info != null) { using (var scroll = new EditorGUILayout.ScrollViewScope(scrollPos)) { scrollPos = scroll.scrollPosition; EditorGUILayout.LabelField("Prefab Name", avatarPrefab.name); showViewInfo = EditorGUILayout.Foldout(showViewInfo, "View"); if (showViewInfo) { using (new EditorGUI.IndentLevelScope()) { EditorGUILayout.LabelField("ViewPosition", avatar2Info.ViewPosition.ToString()); EditorGUILayout.LabelField("ScaleIPD", avatar2Info.ScaleIPD.ToString()); } } showLipSyncInfo = EditorGUILayout.Foldout(showLipSyncInfo, "LipSync"); if (showLipSyncInfo) { using (new EditorGUI.IndentLevelScope()) { EditorGUILayout.LabelField("FaceMeshPath", avatar2Info.faceMeshRendererPath); } } showEyeLookInfo = EditorGUILayout.Foldout(showEyeLookInfo, "EyeLook"); if (showEyeLookInfo) { using (new EditorGUI.IndentLevelScope()) { EditorGUILayout.LabelField("Eyes.LeftEyeBone", LEFT_EYE_PATH); EditorGUILayout.LabelField("Eyes.RightEyeBone", RIGHT_EYE_PATH); EditorGUILayout.LabelField("EyelidType", "None"); EditorGUILayout.HelpBox("If use this, change type after convert", MessageType.Info); EditorGUILayout.LabelField("Eyelids.FyelidsMesh", EYELIDS_MESH_PATH); EditorGUILayout.LabelField("Eyelids.BlendShapeStates", "<Unimplemented>"); EditorGUILayout.HelpBox("Set LeftEyeBone, RightEyeBone and EyelidsMesh if found them", MessageType.Warning); } } showAnimationLayersInfo = EditorGUILayout.Foldout(showAnimationLayersInfo, "AnimationLayers"); if (showAnimationLayersInfo) { using (new EditorGUI.IndentLevelScope()) { EditorGUILayout.LabelField("StandingOverrideController", avatar2Info.standingOverrideControllerPath); EditorGUILayout.LabelField("SittingOverrideController", "<Unimplemented>"); using (new EditorGUI.IndentLevelScope()) { foreach (var animationClipInfo in avatar2Info.OverrideAnimationClips) { if (animationClipInfo is null) { continue; } EditorGUILayout.LabelField(animationClipInfo.Type, animationClipInfo.Path); } } } } } } using (new EditorGUI.DisabledGroupScope(avatarPrefab is null || avatar2Info is null)) { if (GUILayout.Button("Convert Avatar To 3.0")) { var avatar3Obj = ConvertAvatarTo3(avatarPrefab, avatar2Info); Selection.activeObject = avatar3Obj; } } EditorGUILayout.Space(); EditorGUILayout.HelpBox("Remove missing component after convert", MessageType.Warning); EditorGUILayout.Space(); }
private VRCAvatarDescripterDeserializedObject GetAvatar2Info(GameObject avatarPrefab2) { var avatar2Info = new VRCAvatarDescripterDeserializedObject(); var filePath = AssetDatabase.GetAssetPath(avatarPrefab2); var yaml = new YamlStream(); using (var sr = new StreamReader(filePath, System.Text.Encoding.UTF8)) { yaml.Load(sr); } // コンポーネントレベルでDocumentが存在する foreach (var document in yaml.Documents) { var node = document.RootNode; // MonoBehaiviour以外は処理しない if (node.Tag != "tag:unity3d.com,2011:114") { continue; } var mapping = (YamlMappingNode)node; var vrcAvatarDescripter = (YamlMappingNode)mapping.Children["MonoBehaviour"]; // VRCAvatarDescripter以外は処理しない if (((YamlScalarNode)((YamlMappingNode)vrcAvatarDescripter["m_Script"]).Children["guid"]).Value != "f78c4655b33cb5741983dc02e08899cf") { continue; } avatar2Info.Name = ((YamlScalarNode)vrcAvatarDescripter["Name"]).Value; // [View] // ViewPosition var viewPosition = (YamlMappingNode)vrcAvatarDescripter["ViewPosition"]; avatar2Info.ViewPosition = new Vector3( float.Parse(((YamlScalarNode)viewPosition["x"]).Value), float.Parse(((YamlScalarNode)viewPosition["y"]).Value), float.Parse(((YamlScalarNode)viewPosition["z"]).Value) ); // ScaleIPD avatar2Info.ScaleIPD = ((YamlScalarNode)vrcAvatarDescripter["ScaleIPD"]).Value == "1"; // [LipSync] // Mode var lipSyncTypeIndex = int.Parse(((YamlScalarNode)vrcAvatarDescripter["lipSync"]).Value); avatar2Info.lipSync = (VRC.SDKBase.VRC_AvatarDescriptor.LipSyncStyle)Enum.ToObject(typeof(VRC.SDKBase.VRC_AvatarDescriptor.LipSyncStyle), lipSyncTypeIndex); // FaceMesh var faceMeshRendererGuid = ((YamlScalarNode)((YamlMappingNode)vrcAvatarDescripter["VisemeSkinnedMesh"]).Children["fileID"]).Value; var path = GetSkinnedMeshRendererPathFromGUID(yaml.Documents, faceMeshRendererGuid); avatar2Info.faceMeshRendererPath = path; // VisemeBlendShapes avatar2Info.VisemeBlendShapes = new string[15]; var visemeBlendShapes = ((YamlSequenceNode)vrcAvatarDescripter["VisemeBlendShapes"]); for (int i = 0; i < 15; i++) { avatar2Info.VisemeBlendShapes[i] = ((YamlScalarNode)visemeBlendShapes[i]).Value; } // [AnimationLayers] // CustomStaindingAnims var standingOverrideControllerGuid = ((YamlScalarNode)((YamlMappingNode)vrcAvatarDescripter["CustomStandingAnims"]).Children["guid"]).Value; avatar2Info.standingOverrideControllerPath = AssetDatabase.GUIDToAssetPath(standingOverrideControllerGuid); var yamlController = new YamlStream(); using (var sr = new StreamReader(avatar2Info.standingOverrideControllerPath, System.Text.Encoding.UTF8)) { yaml.Load(sr); } var controllerNode = (YamlMappingNode)yaml.Documents[0].RootNode; var overrideController = (YamlMappingNode)controllerNode.Children["AnimatorOverrideController"]; var clips = (YamlSequenceNode)overrideController.Children["m_Clips"]; avatar2Info.OverrideAnimationClips = new AnimationClipInfo[clips.Count()]; for (int i = 0; i < clips.Count(); i++) { var clip = clips[i]; var clipPair = (YamlMappingNode)clip; var originalClip = (YamlMappingNode)clipPair.Children["m_OriginalClip"]; var originalClipFileID = ((YamlScalarNode)originalClip.Children["fileID"]).Value; var overrideClip = (YamlMappingNode)clipPair.Children["m_OverrideClip"]; if (!overrideClip.Children.TryGetValue("guid", out YamlNode overrideClipGuidNode)) { continue; } var overrideClipGuid = ((YamlScalarNode)overrideClipGuidNode).Value; avatar2Info.OverrideAnimationClips[i] = new AnimationClipInfo { Type = animationTypes[originalClipFileID], Path = AssetDatabase.GUIDToAssetPath(overrideClipGuid) }; } break; } return(avatar2Info); }
private VRCAvatarDescripterDeserializedObject GetAvatar2Info(GameObject avatarPrefab2) { var avatar2Info = new VRCAvatarDescripterDeserializedObject(); var filePath = AssetDatabase.GetAssetPath(avatarPrefab2); // fbxが選択されている場合何も返さない if (Path.GetExtension(filePath).ToLower() == ".fbx") { selectFbx = true; return null; } else selectFbx = false; var yaml = new YamlStream(); using (var sr = File.OpenText(filePath)) { var yamlText = sr.ReadToEnd(); // 改変アバターは削除したTransformの部分に謎の文字列が入っており // これがあるとLoadに失敗するので削除する yamlText = yamlText.Replace(" stripped", string.Empty); using (var stream = new StringReader(yamlText)) { yaml.Load(stream); } } // コンポーネントレベルでDocumentが存在する foreach (var document in yaml.Documents) { var node = document.RootNode; // MonoBehaiviour以外は処理しない if (node.Tag != "tag:unity3d.com,2011:114") continue; var mapping = (YamlMappingNode)node; var vrcAvatarDescripter = (YamlMappingNode)mapping.Children["MonoBehaviour"]; // VRCAvatarDescripter以外は処理しない if (((YamlScalarNode)((YamlMappingNode)vrcAvatarDescripter["m_Script"]).Children["guid"]).Value != "f78c4655b33cb5741983dc02e08899cf") continue; avatar2Info.Name = ((YamlScalarNode)vrcAvatarDescripter["Name"]).Value; // [View] // ViewPosition var viewPosition = (YamlMappingNode)vrcAvatarDescripter["ViewPosition"]; avatar2Info.ViewPosition = new Vector3( float.Parse(((YamlScalarNode)viewPosition["x"]).Value), float.Parse(((YamlScalarNode)viewPosition["y"]).Value), float.Parse(((YamlScalarNode)viewPosition["z"]).Value) ); // ScaleIPD avatar2Info.ScaleIPD = ((YamlScalarNode)vrcAvatarDescripter["ScaleIPD"]).Value == "1"; // Default Animation Set avatar2Info.DefaultAnimationSet = (VRCAvatarDescripterDeserializedObject.AnimationSet)Enum.Parse(typeof(VRCAvatarDescripterDeserializedObject.AnimationSet), ((YamlScalarNode)vrcAvatarDescripter["Animations"]).Value); // [LipSync] // Mode var lipSyncTypeIndex = int.Parse(((YamlScalarNode)vrcAvatarDescripter["lipSync"]).Value); avatar2Info.lipSync = (VRC.SDKBase.VRC_AvatarDescriptor.LipSyncStyle)Enum.ToObject(typeof(VRC.SDKBase.VRC_AvatarDescriptor.LipSyncStyle), lipSyncTypeIndex); // FaceMesh var faceMeshRendererGuid = ((YamlScalarNode)((YamlMappingNode)vrcAvatarDescripter["VisemeSkinnedMesh"]).Children["fileID"]).Value; var path = GetSkinnedMeshRendererPathFromGUID(yaml.Documents, faceMeshRendererGuid); avatar2Info.faceMeshRendererPath = path; // VisemeBlendShapes avatar2Info.VisemeBlendShapes = new string[15]; var visemeBlendShapes = ((YamlSequenceNode)vrcAvatarDescripter["VisemeBlendShapes"]); for (int i = 0; i < 15; i++) { avatar2Info.VisemeBlendShapes[i] = ((YamlScalarNode)visemeBlendShapes[i]).Value; } // [AnimationLayers] // CustomStaindingAnims var standingOverrideControllerGuid = ((YamlScalarNode)((YamlMappingNode)vrcAvatarDescripter["CustomStandingAnims"]).Children["guid"]).Value; avatar2Info.standingOverrideControllerPath = AssetDatabase.GUIDToAssetPath(standingOverrideControllerGuid); var yamlController = new YamlStream(); using (var sr = new StreamReader(avatar2Info.standingOverrideControllerPath, System.Text.Encoding.UTF8)) { yaml.Load(sr); } var controllerNode = (YamlMappingNode)yaml.Documents[0].RootNode; var overrideController = (YamlMappingNode)controllerNode.Children["AnimatorOverrideController"]; var clips = (YamlSequenceNode)overrideController.Children["m_Clips"]; if (!clips.Any()) break; avatar2Info.OverrideAnimationClips = new AnimationClipInfo[clips.Count()]; for (int i = 0; i < clips.Count(); i++) { var clip = clips[i]; var clipPair = (YamlMappingNode)clip; var originalClip = (YamlMappingNode)clipPair.Children["m_OriginalClip"]; var originalClipFileID = ((YamlScalarNode)originalClip.Children["fileID"]).Value; var overrideClip = (YamlMappingNode)clipPair.Children["m_OverrideClip"]; if (!overrideClip.Children.TryGetValue("guid", out YamlNode overrideClipGuidNode)) { continue; } var overrideClipGuid = ((YamlScalarNode)overrideClipGuidNode).Value; if (!animationTypes.TryGetValue(originalClipFileID, out string animationType)) { Debug.Log($"Don't Exist {originalClipFileID}"); continue; } avatar2Info.OverrideAnimationClips[i] = new AnimationClipInfo { Type = animationType, Path = AssetDatabase.GUIDToAssetPath(overrideClipGuid) }; } break; } return avatar2Info; }
private GameObject ConvertAvatarTo3(GameObject avatarPrefab2, VRCAvatarDescripterDeserializedObject avatar2Info) { var avatarObj3 = PrefabUtility.InstantiatePrefab(avatarPrefab2) as GameObject; avatarObj3.name = GameObjectUtility.GetUniqueNameForSibling(avatarObj3.transform.parent, $"{ avatarObj3.name}_3.0"); var avatar = avatarObj3.AddComponent <VRCAvatarDescriptor>(); avatar.Name = avatar2Info.Name; avatar.ViewPosition = avatar2Info.ViewPosition; avatar.ScaleIPD = avatar2Info.ScaleIPD; avatar.lipSync = avatar2Info.lipSync; avatar.VisemeSkinnedMesh = avatarObj3.transform.Find(avatar2Info.faceMeshRendererPath)?.GetComponent <SkinnedMeshRenderer>() ?? null; avatar.VisemeBlendShapes = avatar2Info.VisemeBlendShapes; avatar.enableEyeLook = true; avatar.customEyeLookSettings = new VRCAvatarDescriptor.CustomEyeLookSettings { leftEye = avatarObj3.transform.Find(LEFT_EYE_PATH), rightEye = avatarObj3.transform.Find(RIGHT_EYE_PATH), // TODO: 設定が未完了なのでアバターが鏡に写らなくなってしまう //eyelidType = VRCAvatarDescriptor.EyelidType.Blendshapes, eyelidsSkinnedMesh = avatarObj3.transform.Find(EYELIDS_MESH_PATH)?.GetComponent <SkinnedMeshRenderer>() ?? null }; if (avatar.customEyeLookSettings.eyelidsSkinnedMesh is null) { avatar.customEyeLookSettings.eyelidType = VRCAvatarDescriptor.EyelidType.None; } if (avatar.customEyeLookSettings.leftEye is null && avatar.customEyeLookSettings.rightEye is null && avatar.customEyeLookSettings.eyelidType == VRCAvatarDescriptor.EyelidType.None) { avatar.enableEyeLook = false; } avatar.customizeAnimationLayers = true; avatar.baseAnimationLayers = new VRCAvatarDescriptor.CustomAnimLayer[] { new VRCAvatarDescriptor.CustomAnimLayer { type = VRCAvatarDescriptor.AnimLayerType.Base, isDefault = true }, new VRCAvatarDescriptor.CustomAnimLayer { type = VRCAvatarDescriptor.AnimLayerType.Additive, isDefault = true }, new VRCAvatarDescriptor.CustomAnimLayer { type = VRCAvatarDescriptor.AnimLayerType.Gesture, isDefault = true }, new VRCAvatarDescriptor.CustomAnimLayer { type = VRCAvatarDescriptor.AnimLayerType.Action, isDefault = true }, new VRCAvatarDescriptor.CustomAnimLayer { type = VRCAvatarDescriptor.AnimLayerType.FX, isDefault = true } }; avatar.specialAnimationLayers = new VRCAvatarDescriptor.CustomAnimLayer[] { new VRCAvatarDescriptor.CustomAnimLayer { type = VRCAvatarDescriptor.AnimLayerType.Sitting, isDefault = true }, new VRCAvatarDescriptor.CustomAnimLayer { type = VRCAvatarDescriptor.AnimLayerType.TPose, isDefault = true }, new VRCAvatarDescriptor.CustomAnimLayer { type = VRCAvatarDescriptor.AnimLayerType.IKPose, isDefault = true } }; // FaceEmotion var originalHandLayerControllerPath = GetAssetPathForSearch("vrc_AvatarV3HandsLayer t:AnimatorController"); var fxController = DuplicateAnimationLayerController( originalHandLayerControllerPath, Path.GetDirectoryName(avatar2Info.standingOverrideControllerPath), avatarPrefab2.name); avatar.baseAnimationLayers[(int)AnimationLayerType.FX].isDefault = false; avatar.baseAnimationLayers[(int)AnimationLayerType.FX].isEnabled = true; avatar.baseAnimationLayers[(int)AnimationLayerType.FX].animatorController = fxController; avatar.baseAnimationLayers[(int)AnimationLayerType.FX].mask = null; foreach (var layer in fxController.layers) { if (layer.name != "Left Hand" && layer.name != "Right Hand") { continue; } for (int i = 0; i < avatar2Info.OverrideAnimationClips.Length; i++) { var animationInfo = avatar2Info.OverrideAnimationClips[i]; if (animationInfo is null || string.IsNullOrEmpty(animationInfo.Path) || animationInfo.Type.StartsWith("Emote")) { continue; } var animClip = AssetDatabase.LoadAssetAtPath(animationInfo.Path, typeof(AnimationClip)) as AnimationClip; var state = GetAnimatorStateFromStateName(layer.stateMachine, animationInfo.Type); if (state is null) { continue; } state.motion = animClip; } } if (HasEmoteAnimation(avatar2Info.OverrideAnimationClips)) { // Emote var originalActionLayerController = Resources.Load <AnimatorController>("Controllers/vrc_AvatarV3ActionLayer_VRCAV3T"); var originalActionLayerControllerPath = AssetDatabase.GetAssetPath(originalActionLayerController); var actionController = DuplicateAnimationLayerController( originalActionLayerControllerPath, Path.GetDirectoryName(avatar2Info.standingOverrideControllerPath), avatarPrefab2.name); avatar.baseAnimationLayers[(int)AnimationLayerType.Action].isDefault = false; avatar.baseAnimationLayers[(int)AnimationLayerType.Action].isEnabled = true; avatar.baseAnimationLayers[(int)AnimationLayerType.Action].animatorController = actionController; avatar.baseAnimationLayers[(int)AnimationLayerType.Action].mask = null; var actionLayer = actionController.layers[0]; for (int i = 0; i < avatar2Info.OverrideAnimationClips.Length; i++) { var animationInfo = avatar2Info.OverrideAnimationClips[i]; if (animationInfo is null || string.IsNullOrEmpty(animationInfo.Path) || !animationInfo.Type.StartsWith("Emote")) { continue; } var animClip = AssetDatabase.LoadAssetAtPath(animationInfo.Path, typeof(AnimationClip)) as AnimationClip; var state = GetAnimatorStateFromStateName(actionLayer.stateMachine, animationInfo.Type); if (state is null) { continue; } state.motion = animClip; } avatar.customExpressions = true; var exMenu = CreateInstance <VRCExpressionsMenu>(); AssetDatabase.CreateAsset( exMenu, AssetDatabase.GenerateUniqueAssetPath( Path.Combine( Path.GetDirectoryName(avatar2Info.standingOverrideControllerPath), $"ExMenu_{avatarPrefab2.name}.asset"))); var subMenuEmotes = CreateInstance <VRCExpressionsMenu>(); AssetDatabase.CreateAsset( subMenuEmotes, AssetDatabase.GenerateUniqueAssetPath( Path.Combine( Path.GetDirectoryName(avatar2Info.standingOverrideControllerPath), $"ExMenu_Emotes_{avatarPrefab2.name}.asset"))); var exParameters = CreateInstance <VRCExpressionParameters>(); AssetDatabase.CreateAsset( exParameters, AssetDatabase.GenerateUniqueAssetPath( Path.Combine( Path.GetDirectoryName(avatar2Info.standingOverrideControllerPath), $"ExParams_{avatarPrefab2.name}.asset"))); avatar.expressionsMenu = exMenu; avatar.expressionParameters = exParameters; var emoteIconPath = GetAssetPathForSearch("person_dance t:texture"); var emoteIcon = AssetDatabase.LoadAssetAtPath <Texture2D>(emoteIconPath); exMenu.controls.Add(new VRCExpressionsMenu.Control { name = "Emotes", icon = emoteIcon, type = VRCExpressionsMenu.Control.ControlType.SubMenu, subMenu = subMenuEmotes }); for (int i = 0; i < avatar2Info.OverrideAnimationClips.Length; i++) { var animationInfo = avatar2Info.OverrideAnimationClips[i]; if (animationInfo is null || string.IsNullOrEmpty(animationInfo.Path) || !animationInfo.Type.StartsWith("Emote")) { continue; } subMenuEmotes.controls.Add(new VRCExpressionsMenu.Control { name = Path.GetFileNameWithoutExtension(animationInfo.Path), icon = emoteIcon, type = VRCExpressionsMenu.Control.ControlType.Button, parameter = new VRCExpressionsMenu.Control.Parameter { name = "VRCEmote" }, value = int.Parse(animationInfo.Type.Replace("Emote", string.Empty)) }); } Selection.activeObject = exParameters; } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); return(avatarObj3); }
private GameObject ConvertAvatarTo3(GameObject avatarPrefab2, VRCAvatarDescripterDeserializedObject avatar2Info) { var avatarObj3 = PrefabUtility.InstantiatePrefab(avatarPrefab2) as GameObject; avatarObj3.name = GameObjectUtility.GetUniqueNameForSibling(avatarObj3.transform.parent, $"{ avatarObj3.name}_3.0"); var avatar = avatarObj3.AddComponent<VRCAvatarDescriptor>(); { avatar.Name = avatar2Info.Name; avatar.ViewPosition = avatar2Info.ViewPosition; avatar.ScaleIPD = avatar2Info.ScaleIPD; avatar.lipSync = avatar2Info.lipSync; avatar.VisemeSkinnedMesh = avatarObj3.transform.Find(avatar2Info.faceMeshRendererPath)? .GetComponent<SkinnedMeshRenderer>() ?? null; avatar.VisemeBlendShapes = avatar2Info.VisemeBlendShapes; } // TODO: アバターによってはRotationStatesを設定しないと白目になってしまうのでenableEyeLook=falseにしておく avatar.customEyeLookSettings = new VRCAvatarDescriptor.CustomEyeLookSettings { leftEye = avatarObj3.transform.Find(LEFT_EYE_PATH), rightEye = avatarObj3.transform.Find(RIGHT_EYE_PATH), // TODO: 設定が未完了なのでアバターが鏡に写らなくなってしまう //eyelidType = VRCAvatarDescriptor.EyelidType.Blendshapes, eyelidsSkinnedMesh = avatarObj3.transform.Find(EYELIDS_MESH_PATH)? .GetComponent<SkinnedMeshRenderer>() ?? null }; if (avatar.customEyeLookSettings.eyelidsSkinnedMesh is null) { avatar.customEyeLookSettings.eyelidType = VRCAvatarDescriptor.EyelidType.None; } // TODO: 足りない場合falseにする(現状必ずfalseなので一旦コメントアウトする) //if (avatar.customEyeLookSettings.leftEye is null && avatar.customEyeLookSettings.rightEye is null && // avatar.customEyeLookSettings.eyelidType == VRCAvatarDescriptor.EyelidType.None) //{ // avatar.enableEyeLook = false; //} InitializeCusomizeAnimationLayers(avatar); // CustomStandingAnimsが未設定ならPlayableLayerを設定しない if (avatar2Info.OverrideAnimationClips is null) return avatarObj3; // FaceEmotion var faceBlendShapes = new List<EditorCurveBinding>(); string searchTargetHandsLayer; if (avatar2Info.DefaultAnimationSet == VRCAvatarDescripterDeserializedObject.AnimationSet.Male) { searchTargetHandsLayer = "vrc_AvatarV3HandsLayer t:AnimatorController"; } else { searchTargetHandsLayer = "vrc_AvatarV3HandsLayer2 t:AnimatorController"; } var originalHandLayerControllerPath = VRCAssetUtility.GetVRCAssetPathForSearch(searchTargetHandsLayer); var fxController = AnimatorControllerUtility.DuplicateAnimationLayerController( originalHandLayerControllerPath, Path.GetDirectoryName(avatar2Info.standingOverrideControllerPath), avatarPrefab2.name); SetControllerToAnimationLayer(avatar, AnimationLayerType.FX, fxController); var emptyAnimation = new AnimationClip(); AssetDatabase.CreateAsset( emptyAnimation, AssetDatabase.GenerateUniqueAssetPath( Path.Combine( Path.GetDirectoryName(avatar2Info.standingOverrideControllerPath), $"EmptyClip_{avatarPrefab2.name}.anim"))); AssetDatabase.SaveAssets(); foreach (var layer in fxController.layers) { if (layer.name != "Left Hand" && layer.name != "Right Hand") continue; foreach (var childState in layer.stateMachine.states) { if (childState.state is null) continue; var animationInfo = avatar2Info.OverrideAnimationClips .Where(c => c != null && childState.state.name.Contains(c.Type)) .SingleOrDefault(); if (animationInfo != null) { var animClip = AssetDatabase.LoadAssetAtPath<AnimationClip>(animationInfo.Path); if (childState.state != null && animClip != null) childState.state.motion = animClip; var bindings = AnimationUtility.GetCurveBindings(animClip); faceBlendShapes.AddRange(bindings.Where(x => x.type == typeof(SkinnedMeshRenderer))); } // まばたき干渉防止 var control = childState.state.AddStateMachineBehaviour<VRCAnimatorTrackingControl>(); if (childState.state.name.StartsWith("Idle")) { control.trackingEyes = VRC.SDKBase.VRC_AnimatorTrackingControl.TrackingType.Tracking; // WriteDefaultsオフ対策でResetFaceレイヤーで初期化しているので // BlendShapeを何も適用しないように空のClipを設定する childState.state.motion = emptyAnimation; } else { control.trackingEyes = VRC.SDKBase.VRC_AnimatorTrackingControl.TrackingType.Animation; } } } // 表情をResetするためのLayerを追加(WriteDefaultオフだとIdleで表情が元に戻らない) var defaultFaceAnimation = CreateDefaultFaceAnimation( avatarObj3, avatarPrefab2.name, faceBlendShapes.Distinct(), Path.GetDirectoryName(avatar2Info.standingOverrideControllerPath)); var resetFaceStateMachine = new AnimatorStateMachine(); var resetState = resetFaceStateMachine.AddState("Reset"); { resetState.writeDefaultValues = false; resetState.motion = defaultFaceAnimation; } var fxControllerPath = AssetDatabase.GetAssetPath(fxController); AssetDatabase.AddObjectToAsset(resetState, fxControllerPath); AssetDatabase.AddObjectToAsset(resetFaceStateMachine, fxControllerPath); var resetFaceLayer = new AnimatorControllerLayer { defaultWeight = 1, name = "ResetFace", stateMachine = resetFaceStateMachine }; InsertLayer(fxController, resetFaceLayer, 1); if (HasEmoteAnimation(avatar2Info.OverrideAnimationClips)) { // Emote var originalActionLayerController = Resources.Load<AnimatorController>("Controllers/vrc_AvatarV3ActionLayer_VRCAV3T"); var originalActionLayerControllerPath = AssetDatabase.GetAssetPath(originalActionLayerController); var actionController = AnimatorControllerUtility.DuplicateAnimationLayerController( originalActionLayerControllerPath, Path.GetDirectoryName(avatar2Info.standingOverrideControllerPath), avatarPrefab2.name); SetControllerToAnimationLayer(avatar, AnimationLayerType.Action, actionController); var actionLayer = actionController.layers.First(); for (int i = 0; i < avatar2Info.OverrideAnimationClips.Length; i++) { var animationInfo = avatar2Info.OverrideAnimationClips[i]; if (animationInfo is null || string.IsNullOrEmpty(animationInfo.Path) || !animationInfo.Type.StartsWith("Emote")) continue; var animClip = AssetDatabase.LoadAssetAtPath(animationInfo.Path, typeof(AnimationClip)) as AnimationClip; var state = GetAnimatorStateFromStateName(actionLayer.stateMachine, animationInfo.Type); if (state is null) continue; state.motion = animClip; } avatar.customExpressions = true; var exMenu = CreateInstance<VRCExpressionsMenu>(); AssetDatabase.CreateAsset( exMenu, AssetDatabase.GenerateUniqueAssetPath( Path.Combine( Path.GetDirectoryName(avatar2Info.standingOverrideControllerPath), $"ExMenu_{avatarPrefab2.name}.asset"))); var subMenuEmotes = CreateInstance<VRCExpressionsMenu>(); AssetDatabase.CreateAsset( subMenuEmotes, AssetDatabase.GenerateUniqueAssetPath( Path.Combine( Path.GetDirectoryName(avatar2Info.standingOverrideControllerPath), $"ExMenu_Emotes_{avatarPrefab2.name}.asset"))); var exParameters = CreateInstance<VRCExpressionParameters>(); AssetDatabase.CreateAsset( exParameters, AssetDatabase.GenerateUniqueAssetPath( Path.Combine( Path.GetDirectoryName(avatar2Info.standingOverrideControllerPath), $"ExParams_{avatarPrefab2.name}.asset"))); avatar.expressionsMenu = exMenu; avatar.expressionParameters = exParameters; var emoteIconPath = VRCAssetUtility.GetVRCAssetPathForSearch("person_dance t:texture"); var emoteIcon = AssetDatabase.LoadAssetAtPath<Texture2D>(emoteIconPath); exMenu.controls.Add(new VRCExpressionsMenu.Control { name = "Emotes", icon = emoteIcon, type = VRCExpressionsMenu.Control.ControlType.SubMenu, subMenu = subMenuEmotes }); for (int i = 0; i < avatar2Info.OverrideAnimationClips.Length; i++) { var animationInfo = avatar2Info.OverrideAnimationClips[i]; if (animationInfo is null || string.IsNullOrEmpty(animationInfo.Path) || !animationInfo.Type.StartsWith("Emote")) continue; subMenuEmotes.controls.Add(new VRCExpressionsMenu.Control { name = Path.GetFileNameWithoutExtension(animationInfo.Path), icon = emoteIcon, type = VRCExpressionsMenu.Control.ControlType.Button, parameter = new VRCExpressionsMenu.Control.Parameter { name = "VRCEmote" }, value = int.Parse(animationInfo.Type.Replace("Emote", string.Empty)) }); } Selection.activeObject = exParameters; } // Sitting Animation string searchTargetSittingLayer; if (avatar2Info.DefaultAnimationSet == VRCAvatarDescripterDeserializedObject.AnimationSet.Male) { searchTargetSittingLayer = "vrc_AvatarV3SittingLayer t:AnimatorController"; } else { searchTargetSittingLayer = "vrc_AvatarV3SittingLayer2 t:AnimatorController"; } var originalSittingLayerControllerPath = VRCAssetUtility.GetVRCAssetPathForSearch(searchTargetSittingLayer); var sittingController = AnimatorControllerUtility.DuplicateAnimationLayerController( originalSittingLayerControllerPath, Path.GetDirectoryName(avatar2Info.standingOverrideControllerPath), avatarPrefab2.name); SetControllerToAnimationLayer(avatar, SpecialAnimationLayerType.Sitting, sittingController); // 元Avatars2.0のVRCAvatarDescriptorを削除 // TODO: 現状だとMissingなスクリプトすべて消してしまうのでVRCAvatarDescriptorだけ消したい var components = avatar.GetComponents<Component>(); int count = 0; for (int i = 0; i < components.Length; i++) { if (components[i] is null) { var serializedObject = new SerializedObject(avatar.gameObject); var property = serializedObject.FindProperty("m_Component"); property.DeleteArrayElementAtIndex(i - count); count++; serializedObject.ApplyModifiedProperties(); } } PrefabUtility.UnpackPrefabInstance(avatar.gameObject, PrefabUnpackMode.OutermostRoot, InteractionMode.AutomatedAction); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); return avatarObj3; }