private void OnGUI() { srcController = EditorGUILayout.ObjectField("Src AnimatorController", srcController, typeof(AnimatorController), true) as AnimatorController; using (new EditorGUI.IndentLevelScope()) { EditorGUILayout.LabelField("Copy ↓"); } dstController = EditorGUILayout.ObjectField("Dst AnimatorController", dstController, typeof(AnimatorController), true) as AnimatorController; using (new EditorGUI.DisabledGroupScope(!srcController || !dstController)) { if (GUILayout.Button("Combine")) { AnimatorControllerUtility.CombineAnimatorController(srcController, dstController); } } }
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; }
private void OnGUI() { using (var check = new EditorGUI.ChangeCheckScope()) { srcController = EditorGUILayout.ObjectField("Src AnimatorController", srcController, typeof(AnimatorController), true) as AnimatorController; if (check.changed) { if (srcController != null) { isCopyLayers = Enumerable.Range(1, srcController.layers.Length) .Select(i => true) .ToArray(); isCopyParameters = Enumerable.Range(1, srcController.parameters.Length) .Select(i => true) .ToArray(); } } } if (srcController != null) { using (new EditorGUI.IndentLevelScope()) using (var scroll = new EditorGUILayout.ScrollViewScope(srcControllerScrollPos, new GUIStyle(), new GUIStyle("verticalScrollbar"))) { srcControllerScrollPos = scroll.scrollPosition; using (new EditorGUILayout.HorizontalScope()) { using (new EditorGUILayout.VerticalScope()) { EditorGUILayout.LabelField("Layers", EditorStyles.boldLabel); for (int i = 0; i < srcController.layers.Length; i++) { var layer = srcController.layers[i]; using (new EditorGUILayout.HorizontalScope()) { isCopyLayers[i] = EditorGUILayout.ToggleLeft(layer.name, isCopyLayers[i]); } } } GUILayout.FlexibleSpace(); using (new EditorGUILayout.VerticalScope()) { EditorGUILayout.LabelField("Parameters", EditorStyles.boldLabel); for (int i = 0; i < srcController.parameters.Length; i++) { var parameter = srcController.parameters[i]; using (new EditorGUILayout.HorizontalScope()) { isCopyParameters[i] = EditorGUILayout.ToggleLeft($"[{parameter.type}]{parameter.name}", isCopyParameters[i]); } } } } EditorGUILayout.Space(); } } EditorGUILayout.Space(); using (new EditorGUI.IndentLevelScope()) using (new EditorGUILayout.HorizontalScope()) { GUILayout.FlexibleSpace(); EditorGUILayout.LabelField("Copy ↓", GUILayout.Width(60f)); GUILayout.FlexibleSpace(); } EditorGUILayout.Space(); dstController = EditorGUILayout.ObjectField("Dst AnimatorController", dstController, typeof(AnimatorController), true) as AnimatorController; if (dstController != null) { using (new EditorGUI.IndentLevelScope()) using (var scroll = new EditorGUILayout.ScrollViewScope(dstControllerScrollPos, new GUIStyle(), new GUIStyle("verticalScrollbar"))) { dstControllerScrollPos = scroll.scrollPosition; using (new EditorGUILayout.HorizontalScope()) { using (new EditorGUILayout.VerticalScope()) { EditorGUILayout.LabelField("Layers", EditorStyles.boldLabel); foreach (var layer in dstController.layers) { using (new EditorGUILayout.HorizontalScope()) { EditorGUILayout.LabelField(layer.name); } } } GUILayout.FlexibleSpace(); using (new EditorGUILayout.VerticalScope()) { EditorGUILayout.LabelField("Parameters", EditorStyles.boldLabel); foreach (var parameter in dstController.parameters) { using (new EditorGUILayout.HorizontalScope()) { EditorGUILayout.LabelField($"[{parameter.type}]{parameter.name}"); } } } } EditorGUILayout.Space(); } } EditorGUILayout.Space(); using (new EditorGUI.DisabledGroupScope(!srcController || !dstController)) { if (GUILayout.Button("Combine")) { for (int i = 0; i < srcController.layers.Length; i++) { if (!isCopyLayers[i]) { continue; } AnimatorControllerUtility.AddLayer(dstController, srcController.layers[i], i == 0); } for (int i = 0; i < srcController.parameters.Length; i++) { if (!isCopyParameters[i]) { continue; } AnimatorControllerUtility.AddParameter(dstController, srcController.parameters[i]); } } } EditorGUILayout.Space(); }