private static T DuplicateVRCAsset <T>(string assetName) where T : UnityEngine.Object { var baseAssetPath = VRCAssetUtility.GetVRCAssetPathForSearch($"{assetName} t:{typeof(T).Name}"); var folder = Selection.activeObject; var folderPath = AssetDatabase.GetAssetPath(folder); var extention = Path.GetExtension(baseAssetPath); var assetPath = AssetDatabase.GenerateUniqueAssetPath(Path.Combine(folderPath, $"{assetName}{extention}")); AssetDatabase.CopyAsset(baseAssetPath, assetPath); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); var asset = AssetDatabase.LoadAssetAtPath <T>(assetPath); Selection.activeObject = asset; return(asset); }
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; }