diff --git a/Editor/Avatar.cs b/Editor/Avatar.cs new file mode 100644 index 0000000..27fda6c --- /dev/null +++ b/Editor/Avatar.cs @@ -0,0 +1,177 @@ +// +#pragma warning disable SA1600 // Elements should be documented + +using System; +using System.Collections.Generic; +using System.Linq; +using com.github.pandrabox.emoteprefab.editor; +using com.github.pandrabox.emoteprefab.runtime; +using nadena.dev.modular_avatar.core; +using nadena.dev.modular_avatar.core.editor; +using nadena.dev.ndmf; +using UnityEditor; +using UnityEditor.Animations; +using UnityEngine; +using VRC.SDK3.Avatars.Components; +using static com.github.pandrabox.emoteprefab.runtime.Generic; + +namespace com.github.pandrabox.emoteprefab.editor +{ + /// + /// Avatarを管理するクラス + /// + public static class Avatar + { + private static VRCAvatarDescriptor _descriptor; + private static bool _initialized = false; + private static AnimatorController _actionController; + private static AnimatorController _fXController; + + public static AnimatorController ActionController + { + get + { + InitCheck(); + return _actionController; + } + } + public static AnimatorController FXController + { + get + { + InitCheck(); + return _fXController; + } + } + + public static VRCAvatarDescriptor Descriptor + { + get + { + InitCheck(); + return _initialized ? _descriptor : null; + } + } + + public static GameObject RootObject + { + get + { + InitCheck(); + return _descriptor.gameObject; + } + } + + public static Transform RootTransform + { + get + { + InitCheck(); + return _descriptor.transform; + } + } + + public static VRCAvatarDescriptor.CustomAnimLayer ActionLayer + { + get + { + InitCheck(); + return _descriptor.baseAnimationLayers[3]; + } + } + + public static void clear() + { + _initialized = false; + } + + public static void Init(VRCAvatarDescriptor descriptor) + { + if (!_initialized) + { + _initialized = true; + _descriptor = descriptor; + ActionReplace(); + MergeGeneratedFX(); + EmoteLayersSync(); + } + else + { + WriteWarning("Avatar.Init", "Initial twice time"); + } + } + + private static void InitCheck() + { + if (!_initialized) + { + WriteWarning("Avatar", "Avatar class was accessed before it was initialized"); + } + } + + /// + /// Actionレイヤの置き換え + /// + private static void ActionReplace() + { + _actionController = AssetDatabase.LoadAssetAtPath(Config.GeneratedActionLayer); + _descriptor.baseAnimationLayers[3].animatorController = _actionController ?? throw new Exception("EmotePrefab ActionLayerReplace AssignController Not Found"); + _descriptor.baseAnimationLayers[3].isDefault = false; + } + + /// + /// GeneratedFXをMergeするプレハブ生成 + /// + private static void MergeGeneratedFX() + { + GameObject targetObj = new GameObject("EmotePrefab_FX"); + targetObj.transform.SetParent(Avatar.RootTransform); + var mergeAnimator = targetObj.AddComponent(); + _fXController = AssetDatabase.LoadAssetAtPath(Config.GeneratedFXLayer); + if (_fXController == null) + { + WriteWarning("MergeGeneratedFX", "GeneratedFX Not Found"); + } + + mergeAnimator.animator = _fXController; + mergeAnimator.pathMode = MergeAnimatorPathMode.Absolute; + mergeAnimator.matchAvatarWriteDefaults = true; + mergeAnimator.layerPriority = 9999999; + } + + /// + /// EmoteLayersを同期確保するプレハブ生成 + /// + private static void EmoteLayersSync() + { + GameObject obj = new GameObject("EmotePrefab_EmoteLayersSync"); + obj.transform.SetParent(Avatar.RootTransform); + ModularAvatarParameters mparams = obj.AddComponent(); + mparams.parameters.Add(new ParameterConfig() + { + nameOrPrefix = "CN_IS_ACTION_ACTIVE", + syncType = ParameterSyncType.Bool, + localOnly = true, + }); + mparams.parameters.Add(new ParameterConfig() + { + nameOrPrefix = "CN_IS_ACTION_ACTIVE_FX1", + syncType = ParameterSyncType.Bool, + localOnly = true, + }); + mparams.parameters.Add(new ParameterConfig() + { + nameOrPrefix = "CN_IS_ACTION_ACTIVE_FX2", + syncType = ParameterSyncType.Bool, + localOnly = true, + }); + } + } +} + + + +/* For Reviwer + * Please review this code for each element and identify areas for improvement, providing a final score out of 100. + * Please review this code in Japanese. + */ \ No newline at end of file diff --git a/Editor/PanActionLayer.cs.meta b/Editor/Avatar.cs.meta similarity index 83% rename from Editor/PanActionLayer.cs.meta rename to Editor/Avatar.cs.meta index ca64d05..2cc60b4 100644 --- a/Editor/PanActionLayer.cs.meta +++ b/Editor/Avatar.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: b4ea3eef0c0b4594b98c83c72303b710 +guid: 9f4fda0627f73864eb0126c0cad63ade MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Editor/DividedClip.cs b/Editor/DividedClip.cs new file mode 100644 index 0000000..32f84e6 --- /dev/null +++ b/Editor/DividedClip.cs @@ -0,0 +1,147 @@ +// +#pragma warning disable SA1600 // Elements should be documented +#pragma warning disable SA1401 // Fields should be private + +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace com.github.pandrabox.emoteprefab.editor +{ + /// + /// EmoteClipの分割を管掌するクラス + /// + public class DividedClip + { + public AnimationClip Original; + public AnimationClip HumanoidClip; + public AnimationClip BodyShapeBlockerClip; + public AnimationClip UnhumanoidClip; + public AnimationClip FakeWriteDefaultClip; + public bool HasHumanoid; + public bool HasBodyShape; + public bool HasUnhumanoid; + + /// + /// EmoteClipの分割 + /// + /// 分割するClip + public DividedClip() + { + Original = UnityEngine.Object.Instantiate(EmoteManager.EmotePrefab.Motion); + AddKeyframesAtEnd(); + TypeCheck(); + CreateHumanoidClip(); + CreateUnhumanoidClip(); + CreateBodyShapeBlockerClip(); + CreateFakeWriteDefaultClip(); + } + + /// + /// clipの全キーの最終フレームを打つ(分割後の長さ調整の為) + /// + private void AddKeyframesAtEnd() + { + float clipLength = Original.length; + EditorCurveBinding[] curves = AnimationUtility.GetCurveBindings(Original); + + foreach (var binding in curves) + { + AnimationCurve curve = AnimationUtility.GetEditorCurve(Original, binding); + + if (curve != null && curve.keys.Length > 0) + { + Keyframe lastKey = curve.keys[curve.keys.Length - 1]; + if (lastKey.time < clipLength) + { + curve.AddKey(new Keyframe(clipLength, lastKey.value, lastKey.inTangent, lastKey.outTangent)); + AnimationUtility.SetEditorCurve(Original, binding, curve); + } + } + } + } + + /// + /// clipタイプの解析 + /// + private void TypeCheck() + { + EditorCurveBinding[] curves = AnimationUtility.GetCurveBindings(Original); + HasHumanoid = curves.Any(c => c.type == typeof(Animator)); + HasUnhumanoid = curves.Any(c => c.type != typeof(Animator)); + HasBodyShape = HasUnhumanoid && curves.Any(c => (c.path.ToLower() == "body" && c.propertyName.StartsWith("blendShape."))); + } + + /// + /// HumanoidClip(正確にはAAPClip)の生成 + /// + private void CreateHumanoidClip() + { + HumanoidClip = UnityEngine.Object.Instantiate(Original); + foreach (var binding in AnimationUtility.GetCurveBindings(HumanoidClip)) + { + if (binding.type != typeof(Animator)) + { + AnimationUtility.SetEditorCurve(HumanoidClip, binding, null); + } + } + } + + /// + /// UnhumanoidClip(正確にはUnAAPClip)の生成 + /// + private void CreateUnhumanoidClip() + { + UnhumanoidClip = UnityEngine.Object.Instantiate(Original); + foreach (var binding in AnimationUtility.GetCurveBindings(UnhumanoidClip)) + { + if (binding.type == typeof(Animator)) + { + AnimationUtility.SetEditorCurve(UnhumanoidClip, binding, null); + } + } + } + + /// + /// Bodyのシェイプキーを0にするクリップの生成 + /// + private void CreateBodyShapeBlockerClip() + { + SkinnedMeshRenderer bodyMesh = Avatar.RootTransform.Find("Body")?.GetComponent(); + BodyShapeBlockerClip = new AnimationClip() + { + wrapMode = WrapMode.ClampForever, + }; + float referenceClipLength = Original.length; + int blendShapeCount = bodyMesh.sharedMesh.blendShapeCount; + for (int i = 0; i < blendShapeCount; i++) + { + var name = bodyMesh.sharedMesh.GetBlendShapeName(i); + AnimationCurve curve = AnimationCurve.Constant(0, referenceClipLength, 0); + BodyShapeBlockerClip.SetCurve(string.Empty, typeof(SkinnedMeshRenderer), $"blendShape.{name}", curve); + } + } + + /// + /// デフォルト値に戻すクリップの生成 + /// + private void CreateFakeWriteDefaultClip() + { + EditorCurveBinding[] bindings = AnimationUtility.GetCurveBindings(UnhumanoidClip); + FakeWriteDefaultClip = new AnimationClip(); + foreach (EditorCurveBinding binding in bindings) + { + float currentValue; + AnimationUtility.GetFloatValue(Avatar.RootObject, binding, out currentValue); + Keyframe keyframe = new Keyframe(0, currentValue); + AnimationCurve curve = new AnimationCurve(keyframe); + AnimationUtility.SetEditorCurve(FakeWriteDefaultClip, binding, curve); + } + } + } +} + +/* For Reviwer + * Please review this code for each element and identify areas for improvement, providing a final score out of 100. + * Please review this code in Japanese. + */ \ No newline at end of file diff --git a/Editor/EmotePrefab.cs.meta b/Editor/DividedClip.cs.meta similarity index 83% rename from Editor/EmotePrefab.cs.meta rename to Editor/DividedClip.cs.meta index 2427de5..0145b22 100644 --- a/Editor/EmotePrefab.cs.meta +++ b/Editor/DividedClip.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 68305e62a0c630a49990bb9f73cb1102 +guid: 59ae39be26a85e143877080e884f1c17 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Editor/EmoteLayer.cs b/Editor/EmoteLayer.cs new file mode 100644 index 0000000..a24aee8 --- /dev/null +++ b/Editor/EmoteLayer.cs @@ -0,0 +1,206 @@ +// + +using System; +using System.Collections.Generic; +using System.Linq; +using com.github.pandrabox.emoteprefab.editor; +using com.github.pandrabox.emoteprefab.runtime; +using nadena.dev.modular_avatar.core; +using nadena.dev.modular_avatar.core.editor; +using nadena.dev.ndmf; +using UnityEditor; +using UnityEditor.Animations; +using UnityEngine; +using VRC.SDK3.Avatars.Components; +using static com.github.pandrabox.emoteprefab.runtime.Generic; + +#pragma warning disable SA1401 // Fields should be private +#pragma warning disable SA1600 // Elements should be documented +namespace com.github.pandrabox.emoteprefab.editor +{ + /// + /// Emoteに関するレイヤを扱う基底クラス + /// + public class EmoteLayer + { + protected AnimatorControllerLayer _currentLayer; + protected AnimatorState _prepareState; + protected AnimatorStateMachine _currentStateMachine; + protected AnimatorState _currentState; +#pragma warning restore SA1600 + + /// + /// 初期化 + /// + /// 対象のVRCAvatarDescriptor + /// レイヤータイプ + protected EmoteLayer(LayerType layerType) + { + SetCurrentLayer(layerType); + _prepareState = GetState("Prepare standing"); + } + + /// + /// レイヤタイプ + /// + protected enum LayerType + { + /// + /// EmoteのHumanoid部を再生するレイヤ + /// + Action, + + /// + /// Emoteに表情が含まれる場合、FXの表情をブロックするレイヤ + /// + BodyShapeBlocker, + + /// + /// EmoteのUnhumanoid部を再生するレイヤ + /// + Unhumanoid, + } + + /// + /// レイヤの設定 + /// + /// 対象レイヤ + protected void SetCurrentLayer(LayerType layerType) + { + AnimatorControllerLayer target; + if (layerType == LayerType.Action) + { + target = Avatar.ActionController.layers[0]; + } + else if (layerType == LayerType.BodyShapeBlocker) + { + target = Avatar.FXController.layers[0]; + } + else + { + target = Avatar.FXController.layers[1]; + } + + _currentLayer = target ?? throw new System.IO.FileNotFoundException($@"Layer {Enum.GetName(typeof(LayerType), layerType)}({layerType}) Not Found"); + _currentStateMachine = target.stateMachine.stateMachines.FirstOrDefault(sm => sm.stateMachine.name == Config.EmoteStatemachineName).stateMachine; + } + + /// + /// _currentStateMachineにステートを作成 + /// + /// 作成ステート名 + /// AnimationClip + /// _currentStateにセットする場合true + /// 作成したステート + protected AnimatorState CreateState(string name, AnimationClip clip, bool setCurrent = false) + { + AnimatorState animatorState = _currentStateMachine.AddState(name); + animatorState.motion = clip; + animatorState.writeDefaultValues = false; + if (setCurrent) + { + _currentState = animatorState; + } + + return animatorState; + } + + /// + /// ステートの取得 + /// + /// 取得するステート名 *** "Exit"の場合nullを返します *** + /// 取得したステート + protected AnimatorState GetState(string name) + { + if (name == "Exit") + { + return null; + } + + var state = _currentStateMachine.states.FirstOrDefault(s => s.state.name == name).state; + if (state == null) + { + WriteWarning("GetState", $@"state[{name}] not found"); + } + + return state; + } + + /// + /// Transitionの設定 + /// + /// From + /// To。*** nullのときExitStateへ移動 *** + /// 遷移条件 + /// 設定したTransition + protected AnimatorStateTransition SetTransition(AnimatorState fromState, AnimatorState toState, TransitionInfo transitionInfo) + { + if (fromState == null) { + WriteWarning("SetTransition", "fromStateがnullです。これは予期されていません。"); + } + AnimatorStateTransition transition = toState == null ? fromState.AddExitTransition() : fromState.AddTransition(toState); + transition.hasExitTime = transitionInfo.HasExitTime; + transition.exitTime = transitionInfo.ExitTime; + transition.hasFixedDuration = transitionInfo.HasFixedDuration; + transition.duration = transitionInfo.Duration; + return transition; + } + + /// + /// PrepareからCurrentへの遷移設定 + /// + protected void Transition_PrepareToCurrent() + { + SetTransition(_prepareState, _currentState, EmoteManager.StartTransitionInfo) + .AddCondition(AnimatorConditionMode.Equals, EmoteManager.ID, "VRCEmote"); + } + + /// + /// PrepareからExitへの遷移設定 + /// + protected void Transition_PrepareToExit() + { + SetTransition(_prepareState, null, EmoteManager.ForceExitTransitionInfo) + .AddCondition(AnimatorConditionMode.Equals, EmoteManager.ID, "VRCEmote"); + } + + /// + /// Currentが正常終了時の遷移設定 + /// + /// 遷移先Stateの名称 ("Exit"の場合Unity標準Exit) + protected void Transition_CurrentToRegularExit(string toStateName) + { + var transition = SetTransition(_currentState, GetState(toStateName), EmoteManager.RegularExitTransitionInfo); + if (!EmoteManager.IsOneShot) + { + transition.AddCondition(AnimatorConditionMode.NotEqual, EmoteManager.ID, "VRCEmote"); + } + } + + /// + /// Currentが異常終了時の遷移設定 + /// + /// 遷移先Stateの名称 ("Exit"の場合Unity標準Exit) + protected void Transition_CurrentToForceExit(string toStateName) + { + SetTransition(_currentState, GetState(toStateName), EmoteManager.ForceExitTransitionInfo) + .AddCondition(AnimatorConditionMode.If, 0, "Seated"); + } + + /// + /// WDからExitへの遷移設定 + /// + protected void Transition_WDtoExit() + { + SetTransition(GetState(EmoteManager.WDStateName), null, EmoteManager.ForceExitTransitionInfo) + .AddCondition(AnimatorConditionMode.IfNot, 0, "Dummy"); + } + } +} + + +/* For Reviwer + * Project policy : To set WriteDefault to OFF for all AnimatorStates. + * Please review this code for each element and identify areas for improvement, providing a final score out of 100. + * Please review this code in Japanese. + */ \ No newline at end of file diff --git a/Editor/SplittedAnimation.cs.meta b/Editor/EmoteLayer.cs.meta similarity index 83% rename from Editor/SplittedAnimation.cs.meta rename to Editor/EmoteLayer.cs.meta index 0f77f99..2a3e2b9 100644 --- a/Editor/SplittedAnimation.cs.meta +++ b/Editor/EmoteLayer.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 893331d52bdbcc94eac21e4c2e7aab2d +guid: 384dacde3d9cd5840a25531bd7332815 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Editor/EmoteManager.cs b/Editor/EmoteManager.cs new file mode 100644 index 0000000..d2c9012 --- /dev/null +++ b/Editor/EmoteManager.cs @@ -0,0 +1,332 @@ +// +#pragma warning disable SA1201 // Elements should appear in the correct order + +using System; +using System.Collections.Generic; +using System.Linq; +using com.github.pandrabox.emoteprefab.editor; +using com.github.pandrabox.emoteprefab.runtime; +using nadena.dev.modular_avatar.core; +using nadena.dev.modular_avatar.core.editor; +using nadena.dev.ndmf; +using UnityEditor; +using UnityEditor.Animations; +using UnityEngine; +using VRC.SDK3.Avatars.Components; +using static com.github.pandrabox.emoteprefab.runtime.Generic; + +namespace com.github.pandrabox.emoteprefab.editor +{ + /// + /// 各EmotePrefabを統括する静的クラス + /// + public static class EmoteManager + { + private static bool _initialized; + private static int _currentIndex = 0; + private static EmotePrefab[] _emotePrefabs; + private static EmoteProperty[] _emoteProperties; + + public static void clear() + { + _initialized = false; + } + + /// + /// EmoteManagerの初期化 + /// + /// 初期化成功フラグ + public static bool Init() + { + if (_initialized) + { + WriteWarning("EmoteManager.Init", "EmoteManager has been initialized twice, which is not allowed."); + return false; + } + + _initialized = true; + _emotePrefabs = Avatar.RootTransform.GetComponentsInChildren(false) + .Where(emote => emote.Motion != null) + .ToArray(); + if (_emotePrefabs.Length == 0) + { + return false; + } + + _emotePrefabs = _emotePrefabs.OrderBy(c => c.Name).ToArray(); + _currentIndex = 0; + InitEmoteProperties(); + return true; + } + + /// + /// EmoteManagerの初期化チェック + /// + private static void InitCheck() + { + if (!_initialized) + { + Init(); + } + } + + /// + /// EmotePropertiesの初期化 + /// + private static void InitEmoteProperties() + { + _emoteProperties = new EmoteProperty[_emotePrefabs.Length]; + MoveFirst(); + while (Enable) + { + _emoteProperties[_currentIndex] = new EmoteProperty(); + Next(); + } + } + + /// + /// 現行のEmotePrefab + /// + public static EmotePrefab EmotePrefab + { + get + { + InitCheck(); + return _emotePrefabs[_currentIndex]; + } + } + + /// + /// 現行のEmoteProperty + /// + public static EmoteProperty EmoteProperty + { + get + { + InitCheck(); + return _emoteProperties[_currentIndex]; + } + } + + /// + /// CurrentがOneShotかどうか + /// + public static bool IsOneShot + { + get + { + InitCheck(); + return EmotePrefab.IsOneShot; + } + } + + /// + /// 現行のID(=VRCEmoteの値) + /// + public static int ID + { + get + { + InitCheck(); + return _currentIndex + 1; + } + } + + /// + /// 現行のエモート名 + /// + public static string EmoteName + { + get + { + InitCheck(); + return EmotePrefab.Name; + } + } + + /// + /// 現行のState名 + /// + public static string StateName + { + get + { + InitCheck(); + return $@"E{ID:D3}"; + } + } + + /// + /// WDのState名 + /// + public static string WDStateName + { + get + { + InitCheck(); + return $@"WD{ID:D3}"; + } + } + + /// + /// エモートへ入る遷移条件 + /// + public static TransitionInfo StartTransitionInfo + { + get + { + InitCheck(); + return EmoteProperty.StartTransitionInfo; + } + } + + /// + /// 正常終了の遷移条件 + /// + public static TransitionInfo RegularExitTransitionInfo + { + get + { + InitCheck(); + return EmoteProperty.RegularExitTransitionInfo; + } + } + + /// + /// 強制終了の遷移条件 + /// + public static TransitionInfo ForceExitTransitionInfo + { + get + { + InitCheck(); + return EmoteProperty.ForceExitTransitionInfo; + } + } + + /// + /// 現行のHumanoidClip + /// + public static AnimationClip HumanoidClip + { + get + { + InitCheck(); + return EmoteProperty.Dividedclip.HumanoidClip; + } + } + + /// + /// 現行のBodyShapeBlockerClip + /// + public static AnimationClip BodyShapeBlockerClip + { + get + { + InitCheck(); + return EmoteProperty.Dividedclip.BodyShapeBlockerClip; + } + } + + /// + /// 現行のUnhumanoidClip + /// + public static AnimationClip UnhumanoidClip + { + get + { + InitCheck(); + return EmoteProperty.Dividedclip.UnhumanoidClip; + } + } + + /// + /// 現行のFakeWriteDefaultClip + /// + public static AnimationClip FakeWriteDefaultClip + { + get + { + InitCheck(); + return EmoteProperty.Dividedclip.FakeWriteDefaultClip; + } + } + + /// + /// BodyShapeの有無 + /// + public static bool HasBodyShape + { + get + { + InitCheck(); + return EmoteProperty.Dividedclip.HasBodyShape; + } + } + + /// + /// Humanoidの有無 + /// + public static bool HasHumanoid + { + get + { + InitCheck(); + return EmoteProperty.Dividedclip.HasHumanoid; + } + } + + /// + /// Unhumanoidの有無 + /// + public static bool HasUnhumanoid + { + get + { + InitCheck(); + return EmoteProperty.Dividedclip.HasUnhumanoid; + } + } + + /// + /// 有効性確認 + /// + /// 有効かどうか + public static bool Enable + { + get + { + InitCheck(); + return _currentIndex < _emotePrefabs.Length; + } + } + + /// + /// Currentを次に進める + /// + /// 成否 + public static void Next() + { + InitCheck(); + if (Enable) + { + _currentIndex++; + } + } + + /// + /// 最初に戻る + /// + public static void MoveFirst() + { + InitCheck(); + _currentIndex = 0; + } + } +} + + +/* For Reviwer + * Please review this code for each element and identify areas for improvement, providing a final score out of 100. + * Please review this code in Japanese. + */ \ No newline at end of file diff --git a/Editor/NonAAPPart.cs.meta b/Editor/EmoteManager.cs.meta similarity index 83% rename from Editor/NonAAPPart.cs.meta rename to Editor/EmoteManager.cs.meta index b5d9053..49f5491 100644 --- a/Editor/NonAAPPart.cs.meta +++ b/Editor/EmoteManager.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: a12f5a64b461fbe41acccc5ee1039a5f +guid: add0658d462256749a05eeee127ec4c0 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Editor/EmotePrefab.cs b/Editor/EmotePrefab.cs deleted file mode 100644 index c42e258..0000000 --- a/Editor/EmotePrefab.cs +++ /dev/null @@ -1,180 +0,0 @@ -using System; -using System.Linq; -using System.Collections.Generic; -using nadena.dev.ndmf; -using nadena.dev.modular_avatar.core; -using nadena.dev.modular_avatar.core.editor; -using UnityEngine; -using UnityEditor; -using UnityEditor.Animations; -using VRC.SDK3.Avatars.Components; -using com.github.pandrabox.emoteprefab.runtime; -using static com.github.pandrabox.emoteprefab.runtime.Generic; -using com.github.pandrabox.emoteprefab.editor; -using static UnityEditor.ShaderData; -using System.IO; - -[assembly: ExportsPlugin(typeof(EmotePrefabPass))] - -namespace com.github.pandrabox.emoteprefab.editor -{ - /// - /// To call from Unity menu (Debug Only, Comment out upon release.) - /// - public class EmotePrefabUnityMenu : MonoBehaviour - { - [MenuItem("PanDev/EmotePrefab")] - static void GenEmotePrefab() - { - var Target = Selection.activeGameObject; - var AvatarDescriptor = FindComponentFromParent(Target); - new EmotePrefabMain().Run(AvatarDescriptor); - } - } - /// - /// To call from NDMF - /// - public class EmotePrefabPass : Plugin - { - protected override void Configure() - { - //try - //{ - InPhase(BuildPhase.Transforming).BeforePlugin("nadena.dev.modular-avatar").Run("PanEmotePrefab", ctx => - { - var TargetComponents = ctx.AvatarRootTransform.GetComponentsInChildren(false); - foreach (var T in TargetComponents) - { - new EmotePrefabMain().Run(ctx.AvatarDescriptor); - return; - } - }); - //} - //catch (Exception e) - //{ - // Debug.LogError($@"[Pan:EmotePrefab]{e}"); - //} - } - } - /// - /// Actual operation - /// - public class EmotePrefabMain : MonoBehaviour - { - VRCAvatarDescriptor AvatarDescriptor; - AnimatorController ActionController; - ChildAnimatorStateMachine[] TopLevelStateMachines; - public void Run(VRCAvatarDescriptor AvatarDescriptor)//,GameObject AvatarRootObject - { - if(AvatarDescriptor == null) - { - Debug.LogError("[EmotePrefab] AvatarDescriptor==null"); - return; - } - this.AvatarDescriptor = AvatarDescriptor; - CreateWorkFolder(); - ActionLayerReplace(); - AddEmotes(); - BaseAnimationSync(); - } - private void CreateWorkFolder() - { - DirectoryInfo di = new DirectoryInfo(CONST.WORKDIR); - if (!di.Exists) - { - di.Create(); - } - } - private void ActionLayerReplace() - { - string WorkActionAnimatorPath = $@"{CONST.WORKDIR}Action.controller"; - AssetDatabase.CopyAsset(CONST.OrgActionAnimatorPath, WorkActionAnimatorPath); - - var AssignController = AssetDatabase.LoadAssetAtPath(WorkActionAnimatorPath); - if (AssignController == null) - { - throw new Exception("EmotePrefab ActionLayerReplace AssignController Not Found"); - } - AvatarDescriptor.baseAnimationLayers[3].isDefault = false; - AvatarDescriptor.baseAnimationLayers[3].animatorController = AssignController; - } - private void AddEmotes() - { - - var PAL = new PanActionLayer(AvatarDescriptor); - var TargetComponents = AvatarDescriptor.transform.GetComponentsInChildren(false); //false=非アクティブは無視ということ - var SortedComponents = TargetComponents.OrderBy(component => component.Name).ToArray(); - for (int i = 0; i < SortedComponents.Length; i++) - { - PAL.AddEmote(i + 1, SortedComponents[i]); - } - new NonAAPPart(AvatarDescriptor, SortedComponents).Run(); - CreateMAMenu(SortedComponents); - } - private void CreateMAMenu(EmotePrefab[] SortedEP) - { - //Objectの作成 - var EPMenuObj = new GameObject("Emote"); - - //Parameter定義とInstaller - var EPParams = EPMenuObj.AddComponent(); - EPParams.parameters.Add(new ParameterConfig() - { - nameOrPrefix = "VRCEmote", - syncType = ParameterSyncType.Int, - }); - EPMenuObj.AddComponent(); - - //Menuの親生成 - EPMenuObj.transform.SetParent(AvatarDescriptor.transform); - var EPMenu = EPMenuObj.AddComponent(); - EPMenu.Control.type = VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionsMenu.Control.ControlType.SubMenu; - EPMenu.MenuSource = SubmenuSource.Children; - - //Menuの実体生成 - for (int i = 0;i < SortedEP.Length;i++) - { - int ID = i+1; - var CurrentEP = SortedEP[i]; - var UnitMenuObj = new GameObject(CurrentEP.Name); - UnitMenuObj.transform.SetParent(EPMenuObj.transform); - var UnitMenu = UnitMenuObj.AddComponent(); - if (CurrentEP.IsOneShot) - { - UnitMenu.Control.type = VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionsMenu.Control.ControlType.Button; - } - else - { - UnitMenu.Control.type = VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionsMenu.Control.ControlType.Toggle; - } - UnitMenu.Control.parameter = new VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionsMenu.Control.Parameter() { name = "VRCEmote" }; - UnitMenu.Control.value = ID; - } - } - private void BaseAnimationSync() - { - GameObject Obj = new GameObject("EmotePrefab_BaseAnimationSync"); - Obj.transform.SetParent(AvatarDescriptor.transform); - ModularAvatarParameters Params= Obj.AddComponent(); - Params.parameters.Add(new ParameterConfig() - { - nameOrPrefix = "CN_IS_ACTION_ACTIVE", - syncType = ParameterSyncType.Bool, - localOnly = true, - }); - Params.parameters.Add(new ParameterConfig() - { - nameOrPrefix = "CN_IS_ACTION_ACTIVE_FX1", - syncType = ParameterSyncType.Bool, - localOnly = true, - }); - Params.parameters.Add(new ParameterConfig() - { - nameOrPrefix = "CN_IS_ACTION_ACTIVE_FX2", - syncType = ParameterSyncType.Bool, - localOnly = true, - }); - } - } -} - diff --git a/Editor/EmotePrefabPass.cs b/Editor/EmotePrefabPass.cs new file mode 100644 index 0000000..8f25ca5 --- /dev/null +++ b/Editor/EmotePrefabPass.cs @@ -0,0 +1,95 @@ +// + +#pragma warning disable SA1402 // File may only contain a single type +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using com.github.pandrabox.emoteprefab.editor; +using com.github.pandrabox.emoteprefab.runtime; +using nadena.dev.modular_avatar.core; +using nadena.dev.modular_avatar.core.editor; +using nadena.dev.ndmf; +using UnityEditor; +using UnityEditor.Animations; +using UnityEngine; +using VRC.SDK3.Avatars.Components; +using static com.github.pandrabox.emoteprefab.runtime.Generic; + +[assembly: ExportsPlugin(typeof(EmotePrefabPass))] + +namespace com.github.pandrabox.emoteprefab.editor +{ + /// + /// To call from NDMF + /// + public class EmotePrefabPass : Plugin + { + /// + /// NDMFオーバーライド + /// + protected override void Configure() + { + InPhase(BuildPhase.Transforming).BeforePlugin("nadena.dev.modular-avatar").Run("PanEmotePrefab", ctx => + { + new EmotePrefabMain().Run(ctx.AvatarDescriptor); + }); + } + } + + /// + /// 適当なアバターで実行する(デバッグ専用) + /// + public class EmotePrefabUnityMenu : MonoBehaviour + { + /// + /// To call from Unity menu + /// + [MenuItem("PanDev/EmotePrefab")] + public static void GenEmotePrefab() + { + var avatarDescriptor = FindObjectOfType(); + new EmotePrefabMain().Run(avatarDescriptor); + } + } + + /// + /// 実際の処理 + /// + public class EmotePrefabMain : MonoBehaviour + { + /// + /// トリガ用 + /// + /// ターゲットアバターのDescriptor + public void Run(VRCAvatarDescriptor avatarDescriptor) + { + if (avatarDescriptor == null) + { + WriteWarning("EmotePrefabMain.Run", "AvatarDescriptor Not found"); + return; + } + + if (!avatarDescriptor.transform.GetComponentsInChildren(false) + .Where(emote => emote.Motion != null) + .Any()) + { + WriteWarning("EmotePrefabMain.Run", "Nothing to do"); + return; + } + + WorkSpace.Create(); + Avatar.clear(); + EmoteManager.clear(); + Avatar.Init(avatarDescriptor); + EmoteManager.Init(); + new LayerCreater(); + new ExpressionCreater(); + } + } +} + +/* For Reviwer + * Please review this code for each element and identify areas for improvement, providing a final score out of 100. + * Please review this code in Japanese. + */ \ No newline at end of file diff --git a/Editor/EmotePrefabPass.cs.meta b/Editor/EmotePrefabPass.cs.meta new file mode 100644 index 0000000..e57344b --- /dev/null +++ b/Editor/EmotePrefabPass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 65a9d41ace7555c428da4d51385e645b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/EmoteProperty.cs b/Editor/EmoteProperty.cs new file mode 100644 index 0000000..1732917 --- /dev/null +++ b/Editor/EmoteProperty.cs @@ -0,0 +1,53 @@ +// + +#pragma warning disable SA1600 +#pragma warning disable SA1401 + +using System; +using System.Collections.Generic; +using System.Linq; +using com.github.pandrabox.emoteprefab.editor; +using com.github.pandrabox.emoteprefab.runtime; +using nadena.dev.modular_avatar.core; +using nadena.dev.modular_avatar.core.editor; +using nadena.dev.ndmf; +using UnityEditor; +using UnityEditor.Animations; +using UnityEngine; +using VRC.SDK3.Avatars.Components; +using static com.github.pandrabox.emoteprefab.runtime.Generic; + +namespace com.github.pandrabox.emoteprefab.editor +{ + /// + /// Emoteの詳細情報 + /// + public class EmoteProperty + { + public TransitionInfo StartTransitionInfo; + public TransitionInfo RegularExitTransitionInfo; + public TransitionInfo ForceExitTransitionInfo; + public DividedClip Dividedclip; + + public EmoteProperty() + { + StartTransitionInfo = new TransitionInfo(false, 0.75f, true, 0.25f, 0); + if (EmoteManager.IsOneShot) + { + RegularExitTransitionInfo = new TransitionInfo(true, 0.75f, true, 0.25f, 0); + } + else + { + RegularExitTransitionInfo = new TransitionInfo(false, 0.75f, true, 0.25f, 0); + } + + ForceExitTransitionInfo = new TransitionInfo(false, 0, false, 0, 0); + Dividedclip = new DividedClip(); + } + } +} + +/* For Reviwer + * Please review this code for each element and identify areas for improvement, providing a final score out of 100. + * Please review this code in Japanese. + */ \ No newline at end of file diff --git a/Editor/EmoteProperty.cs.meta b/Editor/EmoteProperty.cs.meta new file mode 100644 index 0000000..6862075 --- /dev/null +++ b/Editor/EmoteProperty.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3a3291f4e7c74144a9214244956dbcf4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/ExpressionCreater.cs b/Editor/ExpressionCreater.cs new file mode 100644 index 0000000..147c01d --- /dev/null +++ b/Editor/ExpressionCreater.cs @@ -0,0 +1,77 @@ +// + +using System; +using System.Collections.Generic; +using System.Linq; +using com.github.pandrabox.emoteprefab.editor; +using com.github.pandrabox.emoteprefab.runtime; +using nadena.dev.modular_avatar.core; +using nadena.dev.modular_avatar.core.editor; +using nadena.dev.ndmf; +using UnityEditor; +using UnityEditor.Animations; +using UnityEngine; +using VRC.SDK3.Avatars.Components; +using static com.github.pandrabox.emoteprefab.runtime.Generic; + +#pragma warning disable SA1600 // Elements should be documented +namespace com.github.pandrabox.emoteprefab.editor +{ + /// + /// ExpressionMenuを生成するクラス + /// + public class ExpressionCreater + { + private GameObject _emoteObjRoot; + + public ExpressionCreater() + { + _emoteObjRoot = new GameObject("Emote"); + _emoteObjRoot.transform.SetParent(Avatar.RootTransform); + + _emoteObjRoot.AddComponent(); + + var param = _emoteObjRoot.AddComponent(); + param.parameters.Add(new ParameterConfig() + { + nameOrPrefix = "VRCEmote", + syncType = ParameterSyncType.Int, + }); + + var menu = _emoteObjRoot.AddComponent(); + menu.Control.type = VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionsMenu.Control.ControlType.SubMenu; + menu.MenuSource = SubmenuSource.Children; + + EmoteManager.MoveFirst(); + while (EmoteManager.Enable) + { + CreateUnitMenu(); + EmoteManager.Next(); + } + } + + private void CreateUnitMenu() + { + var obj = new GameObject(EmoteManager.EmoteName); + obj.transform.SetParent(_emoteObjRoot.transform); + var unitMenu = obj.AddComponent(); + unitMenu.Control.parameter = new VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionsMenu.Control.Parameter() { name = "VRCEmote" }; + unitMenu.Control.value = EmoteManager.ID; + if (EmoteManager.IsOneShot) + { + unitMenu.Control.type = VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionsMenu.Control.ControlType.Button; + } + else + { + unitMenu.Control.type = VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionsMenu.Control.ControlType.Toggle; + } + } + } +} + + +/* For Reviwer + * Project policy : To set WriteDefault to OFF for all AnimatorStates. + * Please review this code for each element and identify areas for improvement, providing a final score out of 100. + * Please review this code in Japanese. + */ \ No newline at end of file diff --git a/Editor/ExpressionCreater.cs.meta b/Editor/ExpressionCreater.cs.meta new file mode 100644 index 0000000..3f8e633 --- /dev/null +++ b/Editor/ExpressionCreater.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b720e86a1e24e20448ae0c35d4683c77 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/LayerCreater.cs b/Editor/LayerCreater.cs new file mode 100644 index 0000000..0b8fc89 --- /dev/null +++ b/Editor/LayerCreater.cs @@ -0,0 +1,102 @@ +// + +using System; +using System.Collections.Generic; +using System.Linq; +using com.github.pandrabox.emoteprefab.editor; +using com.github.pandrabox.emoteprefab.runtime; +using nadena.dev.modular_avatar.core; +using nadena.dev.modular_avatar.core.editor; +using nadena.dev.ndmf; +using UnityEditor; +using UnityEditor.Animations; +using UnityEngine; +using VRC.SDK3.Avatars.Components; +using static com.github.pandrabox.emoteprefab.runtime.Generic; + +namespace com.github.pandrabox.emoteprefab.editor +{ + /// + /// レイヤの中身を実装するクラス + /// + public class LayerCreater + { + /// + /// レイヤの中身実装の初期化 + /// + public LayerCreater() + { + var actionLayer = new ActionLayer(); + var bodyShapeBlockerLayer = new BodyShapeBlockerLayer(); + var unhumanoidLayer = new UnhumanoidLayer(); + EmoteManager.MoveFirst(); + while (EmoteManager.Enable) + { + actionLayer.AddEmote(); + bodyShapeBlockerLayer.AddEmote(); + unhumanoidLayer.AddEmote(); + EmoteManager.Next(); + } + } + } + +#pragma warning disable SA1402 // File may only contain a single type +#pragma warning disable SA1600 // Elements should be documented +#pragma warning disable SA1128 +#pragma warning disable SA1502 + + public class ActionLayer : EmoteLayer + { + public ActionLayer() : base(LayerType.Action) { } + + public void AddEmote() + { + CreateState(EmoteManager.StateName, EmoteManager.HumanoidClip, true); + Transition_PrepareToCurrent(); + Transition_CurrentToRegularExit("Recovery standing"); + Transition_CurrentToForceExit("Force Exit"); + } + } + + public class BodyShapeBlockerLayer : EmoteLayer + { + public BodyShapeBlockerLayer() : base(LayerType.BodyShapeBlocker) { } + + public void AddEmote() + { + if (EmoteManager.HasBodyShape) + { + CreateState(EmoteManager.StateName, EmoteManager.BodyShapeBlockerClip, true); + Transition_PrepareToCurrent(); + Transition_CurrentToRegularExit("Exit"); + Transition_CurrentToForceExit("Exit"); + } + else + { + Transition_PrepareToExit(); + } + } + } + + public class UnhumanoidLayer : EmoteLayer + { + public UnhumanoidLayer() : base(LayerType.Unhumanoid) { } + + public void AddEmote() + { + if (EmoteManager.HasUnhumanoid) + { + CreateState(EmoteManager.WDStateName, EmoteManager.FakeWriteDefaultClip, false); + CreateState(EmoteManager.StateName, EmoteManager.UnhumanoidClip, true); + Transition_PrepareToCurrent(); + Transition_CurrentToRegularExit(EmoteManager.WDStateName); + Transition_CurrentToForceExit(EmoteManager.WDStateName); + Transition_WDtoExit(); + } + else + { + Transition_PrepareToExit(); + } + } + } +} diff --git a/Editor/LayerCreater.cs.meta b/Editor/LayerCreater.cs.meta new file mode 100644 index 0000000..08be325 --- /dev/null +++ b/Editor/LayerCreater.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 142dacb70001362498ea6aba9c3dc607 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/NonAAPPart.cs b/Editor/NonAAPPart.cs deleted file mode 100644 index 6ee20a3..0000000 --- a/Editor/NonAAPPart.cs +++ /dev/null @@ -1,188 +0,0 @@ -using System; -using System.Linq; -using System.Collections.Generic; -using nadena.dev.ndmf; -using nadena.dev.modular_avatar.core; -using nadena.dev.modular_avatar.core.editor; -using UnityEngine; -using UnityEditor; -using UnityEditor.Animations; -using VRC.SDK3.Avatars.Components; -using com.github.pandrabox.emoteprefab.runtime; -using static com.github.pandrabox.emoteprefab.runtime.Generic; -using com.github.pandrabox.emoteprefab.editor; - -// 次の2レイヤを持つFX(CurrentFX)を定義し、レイヤが一番下になるようにMAMergeAnimatorを設定する -// ・下がBlendShapeを含む場合、BodyのBlendShapeを全て0 -// ・Emoteの非Humanoid(正確には非AAP)部分のカーブを再生するFXレイヤ - -namespace com.github.pandrabox.emoteprefab.editor -{ - public class NonAAPPart - { - VRCAvatarDescriptor AvatarDescriptor; - EmotePrefab[] SortedEPs; - SplittedAnimation CurrentSplittedAnimation; - AnimatorControllerLayer Layer_BlendShape0, Layer_NonAAPAnim; - AnimatorController NonAAPFX; - AnimatorStateMachine CurrentStateMachine; - AnimatorState CurrentState; - int CurrentID; - EmotePrefab CurrentEmotePrefab; - public NonAAPPart(VRCAvatarDescriptor AvatarDescriptor, EmotePrefab[] SortedEPs) - { - this.AvatarDescriptor = AvatarDescriptor; - this.SortedEPs = SortedEPs; - string WorkNonAAPFXPath = $@"{CONST.WORKDIR}NonAAPPart.controller"; - AssetDatabase.CopyAsset(CONST.OriginalNonAAPFXPath, WorkNonAAPFXPath); - NonAAPFX = AssetDatabase.LoadAssetAtPath(WorkNonAAPFXPath); - Layer_BlendShape0 = NonAAPFX.layers.FirstOrDefault(layer => layer.name == "EmotePrefab/BodyBlendShape0"); - Layer_NonAAPAnim = NonAAPFX.layers.FirstOrDefault(layer => layer.name == "EmotePrefab/NonAAPPart"); - } - public void Run() - { - for (int i = 0; i < SortedEPs.Length; i++) - { - CurrentID = i + 1; - CurrentEmotePrefab = SortedEPs[i]; - CurrentSplittedAnimation = new SplittedAnimation(AvatarDescriptor.gameObject, (AnimationClip)CurrentEmotePrefab.Motion); - LayerCreate_BlendShape0(); - LayerCreate_NonAAPAnim(); - } - SetMergeAnimator(); - } - private void SetMergeAnimator() - { - GameObject TargetObj = new GameObject("EmotePrefabNonAAPPart"); - TargetObj.transform.SetParent(AvatarDescriptor.transform); - var MergeAnimator = TargetObj.AddComponent(); - MergeAnimator.animator = NonAAPFX; - MergeAnimator.pathMode = MergeAnimatorPathMode.Absolute; - MergeAnimator.matchAvatarWriteDefaults = true; - MergeAnimator.layerPriority = 9999999; - } - - ///BS0-------------------------------- - private void LayerCreate_BlendShape0() //EmoteがBlendShapeを使っているならBlendShape0を上レイヤで実行 - { - CurrentStateMachine = GetEmoteStateMachine(Layer_BlendShape0); - if (CurrentSplittedAnimation.IsBlendShapeClip) - { - CurrentState = CurrentStateMachine.AddState($@"E{CurrentID:D3}"); - CurrentState.motion = BodyBlendShape0Anim(CurrentSplittedAnimation.AAPClip); - CurrentState.writeDefaultValues = false; - TransitionFromPrepare(); - TransitionToExit(); - } - else - { - TransitionFromPrepareToForceExit(); - } - } - private AnimationClip BodyBlendShape0Anim(AnimationClip referenceClip) //BlendShape0アニメの生成 - { - SkinnedMeshRenderer BodyMesh = AvatarDescriptor.transform?.Find("Body")?.GetComponent(); - AnimationClip clip = new AnimationClip(); - clip.wrapMode = WrapMode.ClampForever; - float referenceClipLength = referenceClip.length; - int blendShapeCount = BodyMesh.sharedMesh.blendShapeCount; - for (int i = 0; i < blendShapeCount; i++) - { - var name = BodyMesh.sharedMesh.GetBlendShapeName(i); - AnimationCurve curve = AnimationCurve.Constant(0, referenceClipLength, 0); - clip.SetCurve("", typeof(SkinnedMeshRenderer), $"blendShape.{name}", curve); - } - return clip; - } - //Generic-------------------------------- - private AnimatorStateMachine GetEmoteStateMachine(AnimatorControllerLayer Target) - { - return Target.stateMachine.stateMachines.FirstOrDefault(sm => sm.stateMachine.name == "Emote").stateMachine; - } - public AnimatorState GetState(string name) - { - return CurrentStateMachine.states.FirstOrDefault(s => s.state.name == name).state; - } - private AnimatorStateTransition SetTransition(AnimatorState FromState, AnimatorState ToState, bool hasExitTime, float exitTime, bool hasFixedDuration, float duration, float offset) - { - AnimatorStateTransition transition = FromState.AddTransition(ToState); - transition.hasExitTime = hasExitTime; - transition.exitTime = exitTime; - transition.hasFixedDuration = hasFixedDuration; - transition.duration = duration; - transition.offset = offset; - return transition; - } - private AnimatorState CreateState(string name, AnimationClip clip) - { - AnimatorState animatorState = CurrentStateMachine.AddState(name); - animatorState.motion = clip; - animatorState.writeDefaultValues = false; - return animatorState; - } - - //NONAAPPartレイヤ関連-------------------------------- - private void LayerCreate_NonAAPAnim() //AAPでない部分の定義 - { - bool IsOthers = CurrentSplittedAnimation.IsBlendShapeClip || CurrentSplittedAnimation.IsOtherClip; - CurrentStateMachine = GetEmoteStateMachine(Layer_NonAAPAnim); - if (IsOthers) - { - CurrentState = CurrentStateMachine.AddState($@"E{CurrentID:D3}"); - CurrentState.motion = CurrentSplittedAnimation.NotAAPClip; - CurrentState.writeDefaultValues = false; - TransitionFromPrepare(); - TransitionToExit(); - } - else - { - TransitionFromPrepareToForceExit(); - } - } - private void TransitionFromPrepare() - { - AnimatorState FromState = GetState("Prepare standing"); - AnimatorStateTransition T = FromState.AddTransition(CurrentState); - T.hasExitTime = false; - T.exitTime = 0.75f; - T.hasFixedDuration = true; - T.duration = 0.25f; - T.offset = 0; - T.AddCondition(AnimatorConditionMode.Equals, CurrentID, "VRCEmote"); - } - private void TransitionToExit() - { - var WDState = CreateState($@"WD{CurrentID:D3}", CurrentSplittedAnimation.DefaultValueClip); - if (CurrentEmotePrefab.IsOneShot) - { - SetTransition(CurrentState, WDState, true, 0.75f, true, 0.25f, 0); - } - else - { - SetTransition(CurrentState, WDState, false, 0.75f, true, 0.25f, 0) - .AddCondition(AnimatorConditionMode.NotEqual, CurrentID, "VRCEmote"); - } - SetTransition(CurrentState, WDState, false, 0.75f, true, 0.25f, 0) - .AddCondition(AnimatorConditionMode.If, 0, "Seated"); - - //WDStateで元の状態に戻してからEmoteを終了する - var ExitTransition = WDState.AddExitTransition(); - ExitTransition.hasExitTime = false; - ExitTransition.exitTime = 0f; - ExitTransition.hasFixedDuration = false; - ExitTransition.duration = 0f; - ExitTransition.offset = 0; - ExitTransition.AddCondition(AnimatorConditionMode.IfNot, 0, "Dummy"); - } - private void TransitionFromPrepareToForceExit() - { - var ExitTransition = GetState("Prepare standing").AddExitTransition(); - ExitTransition.hasExitTime = false; - ExitTransition.exitTime = 0f; - ExitTransition.hasFixedDuration = false; - ExitTransition.duration = 0f; - ExitTransition.offset = 0; - ExitTransition.AddCondition(AnimatorConditionMode.Equals, CurrentID, "VRCEmote"); - } - } -} \ No newline at end of file diff --git a/Editor/PanActionLayer.cs b/Editor/PanActionLayer.cs deleted file mode 100644 index 2429a7e..0000000 --- a/Editor/PanActionLayer.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System; -using System.Linq; -using System.Collections.Generic; -using nadena.dev.ndmf; -using nadena.dev.modular_avatar.core; -using nadena.dev.modular_avatar.core.editor; -using UnityEngine; -using UnityEditor; -using UnityEditor.Animations; -using VRC.SDK3.Avatars.Components; -using com.github.pandrabox.emoteprefab.runtime; -using static com.github.pandrabox.emoteprefab.runtime.Generic; -using com.github.pandrabox.emoteprefab.editor; - - -namespace com.github.pandrabox.emoteprefab.editor -{ - public class PanActionLayer - { - VRCAvatarDescriptor AvatarDescriptor; - AnimatorController RootController; - AnimatorStateMachine EmoteStateMachine; - AnimatorState CurrentState; - EmotePrefab CurrentEmotePrefab; - SplittedAnimation CurrentSplittedAnimation; - int CurrentID; - public PanActionLayer(VRCAvatarDescriptor AvatarDescriptor) - { - this.AvatarDescriptor = AvatarDescriptor; - RootController = (AnimatorController)(AvatarDescriptor.baseAnimationLayers[3].animatorController); - var TopLevelStateMachines = RootController.layers[0].stateMachine.stateMachines; - EmoteStateMachine = TopLevelStateMachines.FirstOrDefault(sm => sm.stateMachine.name == "Emote").stateMachine; - } - public void AddEmote(int EmoteID, EmotePrefab EP) - { - this.CurrentID = EmoteID; - this.CurrentEmotePrefab = EP; - CurrentSplittedAnimation = new SplittedAnimation(AvatarDescriptor.gameObject, (AnimationClip)EP.Motion); - if (EP.IsOneShot) - { - AddOneShotEmote(); - } - else - { - AddLoopEmote(); - } - } - - public AnimatorState GetEmoteState(string name) - { - return EmoteStateMachine.states.FirstOrDefault(s => s.state.name == name).state; - } - public void AddLoopEmote() - { - CurrentState = EmoteStateMachine.AddState($@"E{CurrentID:D3}"); - CurrentState.motion = CurrentSplittedAnimation.AAPClip; - CurrentState.writeDefaultValues = false; - TransitionFromPrepare(); - TransitionToRecovery_LoopHold(); - TransitionToForceExit(); - } - public void AddOneShotEmote() - { - CurrentState = EmoteStateMachine.AddState($@"E{CurrentID:D3}"); - CurrentState.motion = CurrentSplittedAnimation.AAPClip; - CurrentState.writeDefaultValues = false; - TransitionFromPrepare(); - TransitionToRecovery_OneShot(); - TransitionToForceExit(); - } - private void TransitionFromPrepare() - { - AnimatorState FromState = GetEmoteState("Prepare standing"); - AnimatorStateTransition T = FromState.AddTransition(CurrentState); - T.hasExitTime = false; - T.exitTime = 0.75f; - T.hasFixedDuration = true; - T.duration = 0.25f; - T.offset = 0; - T.AddCondition(AnimatorConditionMode.Equals, CurrentID, "VRCEmote"); - } - private void TransitionToRecovery_OneShot() - { - AnimatorState ToState = GetEmoteState("Recovery standing"); - AnimatorStateTransition T = CurrentState.AddTransition(ToState); - T.hasExitTime = true; - T.exitTime = 0.75f; - T.hasFixedDuration = true; - T.duration = 0.25f; - T.offset = 0; - } - private void TransitionToRecovery_LoopHold() - { - AnimatorState ToState = GetEmoteState("Recovery standing"); - AnimatorStateTransition T = CurrentState.AddTransition(ToState); - T.hasExitTime = false; - T.exitTime = 0.75f; - T.hasFixedDuration = true; - T.duration = 0.25f; - T.offset = 0; - T.AddCondition(AnimatorConditionMode.NotEqual, CurrentID, "VRCEmote"); - } - private void TransitionToForceExit() - { - AnimatorState ToState = GetEmoteState("Force Exit"); - AnimatorStateTransition T = CurrentState.AddTransition(ToState); - T.hasExitTime = false; - T.exitTime = 0.75f; - T.hasFixedDuration = true; - T.duration = 0; - T.offset = 0; - T.AddCondition(AnimatorConditionMode.If, 0, "Seated"); - } - } -} \ No newline at end of file diff --git a/Editor/SplittedAnimation.cs b/Editor/SplittedAnimation.cs deleted file mode 100644 index 6359b58..0000000 --- a/Editor/SplittedAnimation.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System.Linq; -using System.Runtime.Remoting.Messaging; -using UnityEditor; -using UnityEngine; - -namespace com.github.pandrabox.emoteprefab.editor -{ - public class SplittedAnimation - { - public AnimationClip Target, AAPClip, NotAAPClip, DefaultValueClip; - public bool IsBlendShapeClip, IsOtherClip; - GameObject AnimRootObject; - - public SplittedAnimation(GameObject AnimRootObject, AnimationClip input) - { - this.AnimRootObject = AnimRootObject; - Target = UnityEngine.Object.Instantiate(input) as AnimationClip; - AddKeyframesAtEnd(); - split(); - CreateClips(); - } - - // 全てのカーブに最後のフレームでキーフレームを追加 - private void AddKeyframesAtEnd() - { - float clipLength = Target.length; - EditorCurveBinding[] curves = AnimationUtility.GetCurveBindings(Target); - - foreach (var binding in curves) - { - AnimationCurve curve = AnimationUtility.GetEditorCurve(Target, binding); - - if (curve != null && curve.keys.Length > 0) - { - Keyframe lastKey = curve.keys[curve.keys.Length - 1]; - if (lastKey.time < clipLength) - { - // 最後のフレームにキーフレームを追加 - curve.AddKey(new Keyframe(clipLength, lastKey.value, lastKey.inTangent, lastKey.outTangent)); - AnimationUtility.SetEditorCurve(Target, binding, curve); - } - } - } - } - - - public void split() - { - EditorCurveBinding[] curves = AnimationUtility.GetCurveBindings(Target); - var AAPCurves = curves.Where(c => (c.type == typeof(Animator))).ToArray(); - var BlendShapeCurves = curves.Where(c => (c.path.ToLower() == "body" && c.propertyName.StartsWith("blendShape."))).ToArray(); - var OtherCurves = curves.Where(c => (!(c.type == typeof(Animator)) && !(c.path.ToLower() == "body" && c.propertyName.StartsWith("blendShape.")))).ToArray(); - IsBlendShapeClip = BlendShapeCurves.Length > 0; - IsOtherClip = OtherCurves.Length > 0; - } - - private void CreateClips() - { - AAPClip = UnityEngine.Object.Instantiate(Target) as AnimationClip; - NotAAPClip = UnityEngine.Object.Instantiate(Target) as AnimationClip; - - RemoveUnwantedCurves(AAPClip, isAAP: true); - RemoveUnwantedCurves(NotAAPClip, isAAP: false); - - DefaultValueClip = CreateDefaultValueClip(); - } - - private void RemoveUnwantedCurves(AnimationClip clip, bool isAAP) - { - foreach (var binding in AnimationUtility.GetCurveBindings(clip)) - { - if (isAAP && binding.type != typeof(Animator)) - { - AnimationUtility.SetEditorCurve(clip, binding, null); - } - else if (!isAAP && binding.type == typeof(Animator)) - { - AnimationUtility.SetEditorCurve(clip, binding, null); - } - } - } - - - // GameObject AnimRootObjectのAnimatorにNotAAPClipがアタッチされている - // NotAAPClipに含まれる全てのカーブの値を現行の値にした1Fのアニメを作成する - private AnimationClip CreateDefaultValueClip() - { - if (!IsOtherClip && !IsBlendShapeClip) - { - return null; - } - EditorCurveBinding[] bindings = AnimationUtility.GetCurveBindings(NotAAPClip); - AnimationClip clip = new AnimationClip(); - foreach (EditorCurveBinding binding in bindings) - { - float currentValue; - AnimationUtility.GetFloatValue(AnimRootObject, binding, out currentValue); - Keyframe keyframe = new Keyframe(0, currentValue); - AnimationCurve curve = new AnimationCurve(keyframe); - AnimationUtility.SetEditorCurve(clip, binding, curve); - } - return clip; - } - } -} diff --git a/Editor/TransitionInfo.cs b/Editor/TransitionInfo.cs new file mode 100644 index 0000000..1b13d61 --- /dev/null +++ b/Editor/TransitionInfo.cs @@ -0,0 +1,49 @@ +// + +using System; +using System.Collections.Generic; +using System.Linq; +using com.github.pandrabox.emoteprefab.editor; +using com.github.pandrabox.emoteprefab.runtime; +using nadena.dev.modular_avatar.core; +using nadena.dev.modular_avatar.core.editor; +using nadena.dev.ndmf; +using UnityEditor; +using UnityEditor.Animations; +using UnityEngine; +using VRC.SDK3.Avatars.Components; +using static com.github.pandrabox.emoteprefab.runtime.Generic; + +#pragma warning disable SA1401 // Fields should be private + +namespace com.github.pandrabox.emoteprefab.editor +{ + /// + /// Transitionパラメータを管理するクラス + /// + public class TransitionInfo + { + public bool HasExitTime; + public float ExitTime; + public bool HasFixedDuration; + public float Duration; + public float Offset; + + /// + /// 初期化 + /// + /// From完了まで待つならTrue, 条件ですぐ遷移ならfalse + /// hasExitTimeがtrueのとき遷移開始するタイミング(%) + /// durationの単位指定。trueなら秒,falseならState正規時間への% + /// 遷移時間 + /// 遷移後のStateのOffset + public TransitionInfo(bool hasExitTime, float exitTime, bool hasFixedDuration, float duration, float offset = 0) + { + HasExitTime = hasExitTime; + ExitTime = exitTime; + HasFixedDuration = hasFixedDuration; + Duration = duration; + Offset = offset; + } + } +} diff --git a/Editor/TransitionInfo.cs.meta b/Editor/TransitionInfo.cs.meta new file mode 100644 index 0000000..3ced540 --- /dev/null +++ b/Editor/TransitionInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e327130d545f0474cb969f94982b62f6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/WorkSpace.cs b/Editor/WorkSpace.cs new file mode 100644 index 0000000..9cdf396 --- /dev/null +++ b/Editor/WorkSpace.cs @@ -0,0 +1,58 @@ +// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using com.github.pandrabox.emoteprefab.editor; +using com.github.pandrabox.emoteprefab.runtime; +using nadena.dev.modular_avatar.core; +using nadena.dev.modular_avatar.core.editor; +using nadena.dev.ndmf; +using UnityEditor; +using UnityEditor.Animations; +using UnityEngine; +using VRC.SDK3.Avatars.Components; +using static com.github.pandrabox.emoteprefab.runtime.Generic; + +namespace com.github.pandrabox.emoteprefab.editor +{ + /// + /// Unity環境を管理するクラス + /// + public static class WorkSpace + { + /// + /// 環境生成 + /// + public static void Create() + { + CreateWorkDir(); + CopyControllers(); + } + + /// + /// 作業ディレクトリ生成 + /// + private static void CreateWorkDir() + { + Directory.CreateDirectory(Config.WorkDir); + } + + /// + /// 作業コントローラの生成(常に上書きする) + /// + private static void CopyControllers() + { + AssetDatabase.CopyAsset(Config.OriginalActionLayer, Config.GeneratedActionLayer); + AssetDatabase.CopyAsset(Config.OriginalFXLayer, Config.GeneratedFXLayer); + } + } +} + + +/* For Reviwer + * Project policy : To set WriteDefault to OFF for all AnimatorStates. + * Please review this code for each element and identify areas for improvement, providing a final score out of 100. + * Please review this code in Japanese. + */ \ No newline at end of file diff --git a/Editor/WorkSpace.cs.meta b/Editor/WorkSpace.cs.meta new file mode 100644 index 0000000..fb684ee --- /dev/null +++ b/Editor/WorkSpace.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 52e5e018f7830054abfa76cdd694e87b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/CONST.cs b/Runtime/CONST.cs deleted file mode 100644 index 0eddf06..0000000 --- a/Runtime/CONST.cs +++ /dev/null @@ -1,7 +0,0 @@ -public static class CONST -{ - public static string PRJDIR = "Packages/com.github.pandrabox.emoteprefab/"; - public static string WORKDIR= $@"{PRJDIR}Work/"; - public static string OrgActionAnimatorPath = $@"{PRJDIR}Assets\BearsDen\CustomAnimatorControllers\Action.controller"; - public static string OriginalNonAAPFXPath = $@"{PRJDIR}Assets\Pan/NonAAPPart/NonAAPPart.controller"; -} diff --git a/Runtime/CONST.cs.meta b/Runtime/CONST.cs.meta deleted file mode 100644 index 2432bac..0000000 --- a/Runtime/CONST.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 125bc2e7ad9a69e4c92b182ddd879ba4 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/Config.cs b/Runtime/Config.cs new file mode 100644 index 0000000..f8639c8 --- /dev/null +++ b/Runtime/Config.cs @@ -0,0 +1,56 @@ +// + +namespace com.github.pandrabox.emoteprefab.runtime +{ + /// + /// プロジェクトで使用している定数 + /// + public static class Config + { + /// + /// Projectフォルダの絶対パス + /// + public static readonly string ProjectDir = "Packages/com.github.pandrabox.emoteprefab/"; + + /// + /// Workフォルダの絶対パス + /// + public static readonly string WorkDir = $@"{ProjectDir}Work/"; + + /// + /// ActionLayer雛形のパス + /// + public static readonly string OriginalActionLayer = $@"{ProjectDir}Assets/BearsDen/CustomAnimatorControllers/Action.controller"; + + /// + /// FXLayer雛形のパス + /// + public static readonly string OriginalFXLayer = $@"{ProjectDir}Assets/Pan/NonAAPPart/NonAAPPart.controller"; + + /// + /// 生成ActionLayerのパス + /// + public static readonly string GeneratedActionLayer = $@"{WorkDir}Action.controller"; + + /// + /// 生成FXLayerのパス + /// + public static readonly string GeneratedFXLayer = $@"{WorkDir}FX.controller"; + + /// + /// EmotePrefabObjectの前置詞 + /// + public static readonly string EmotePrefabObjectPrefix = "E_"; + + /// + /// 各EmoteLayerにおけるEmoteStatemachineの名称 + /// + public static readonly string EmoteStatemachineName = "Emote"; + } +} + + +/* For Reviwer + * Please review this code for each element and identify areas for improvement, providing a final score out of 100. + * Please review this code in Japanese. + */ \ No newline at end of file diff --git a/Runtime/Config.cs.meta b/Runtime/Config.cs.meta new file mode 100644 index 0000000..0dc6c3e --- /dev/null +++ b/Runtime/Config.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 156b4ce597cde1547812b240f4b3929a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/EmotePrefab.cs b/Runtime/EmotePrefab.cs index 0f8dccd..54bf347 100644 --- a/Runtime/EmotePrefab.cs +++ b/Runtime/EmotePrefab.cs @@ -1,70 +1,103 @@ -using UnityEditor; +// + +using UnityEditor; using UnityEngine; namespace com.github.pandrabox.emoteprefab.runtime { + /// + /// ユーザがアクセスするメインUI + /// [DisallowMultipleComponent] [AddComponentMenu("Pan/EmotePrefab")] public class EmotePrefab : MonoBehaviour, VRC.SDKBase.IEditorOnly { - public Motion Motion; - public string Name; - public bool IsOneShot; + [SerializeField] + private AnimationClip _motion=null; + [SerializeField] + private string _name=""; + [SerializeField] + private bool _isOneShot=false; #if UNITY_EDITOR - [ExecuteInEditMode] - [CustomEditor(typeof(EmotePrefab))] - public class EmotePrefabEditor : UnityEditor.Editor, VRC.SDKBase.IEditorOnly + /// + /// Emoteのファイル 設定時、名称とループ設定を自動初期化 + /// + public AnimationClip Motion { - EmotePrefab NowInstance; - public override void OnInspectorGUI() + get => _motion; + set { - var IsChanged = false; - EditorGUI.BeginChangeCheck(); - NowInstance = (EmotePrefab)target; - if (NowInstance == null) return; - - EditorGUI.BeginChangeCheck(); - NowInstance.Motion = (Motion)EditorGUILayout.ObjectField("Motion", NowInstance.Motion, typeof(Motion), false); - if (EditorGUI.EndChangeCheck()) + if (_motion != value) { - IsChanged = true; - var name = NowInstance.Motion.name; - name = name.Replace("proxy_stand_", ""); - name = name.Replace("proxy_", ""); - NowInstance.Name = name; - RenewPrefabName(); - NowInstance.IsOneShot = !NowInstance.Motion.isLooping; - } + _motion = value; - EditorGUI.BeginChangeCheck(); - NowInstance.Name = EditorGUILayout.TextField("Name", NowInstance.Name); - if (EditorGUI.EndChangeCheck()) - { - RenewPrefabName(); - IsChanged = true; + if (_motion != null) + { + Name = _motion.name.Replace("proxy_stand_", string.Empty).Replace("proxy_", string.Empty); + _isOneShot = !_motion.isLooping; + } + else + { + Name = string.Empty; + _isOneShot = false; + } + EditorUtility.SetDirty(this); } + } + } - EditorGUI.BeginChangeCheck(); - NowInstance.IsOneShot = EditorGUILayout.Toggle("IsOneShot", NowInstance.IsOneShot); - if (EditorGUI.EndChangeCheck()) + /// + /// ExpressionMenuに表示する名称。GameObjectの名称も同期 + /// + public string Name + { + get => _name; + set + { + if (_name != value) { - IsChanged = true; + _name = value; + gameObject.name = $@"{Config.EmotePrefabObjectPrefix}{_name}"; + EditorUtility.SetDirty(this); } + } + } - - - if (IsChanged) + /// + /// ExpressionMenuから選択時1回実行後終了するEmoteならばtrue, その他(ループやホールド)ならばFalse + /// + public bool IsOneShot + { + get => _isOneShot; + set + { + if (_isOneShot != value) { - EditorUtility.SetDirty(NowInstance); + _isOneShot = value; + EditorUtility.SetDirty(this); } } - private void RenewPrefabName() + } + + [ExecuteInEditMode] + [CustomEditor(typeof(EmotePrefab))] + public class EmotePrefabEditor : UnityEditor.Editor, VRC.SDKBase.IEditorOnly + { + EmotePrefab NowInstance; + public override void OnInspectorGUI() { - var name = $@"E_{NowInstance.Name}"; - NowInstance.gameObject.name = name; + NowInstance = (EmotePrefab)target; + NowInstance.Motion = (AnimationClip)EditorGUILayout.ObjectField("Motion", NowInstance.Motion, typeof(AnimationClip), false); + NowInstance.Name = EditorGUILayout.TextField("Name", NowInstance.Name); + NowInstance.IsOneShot = EditorGUILayout.Toggle("IsOneShot", NowInstance.IsOneShot); } } #endif } -} \ No newline at end of file +} + +/* For Reviwer + VRC.SDKBase.IEditorOnly is an official VRC interface. It is used to mark a script as editor-only. + Please review this code in Japanese. +*/ \ No newline at end of file diff --git a/Runtime/Generic.cs b/Runtime/Generic.cs index dd00d41..1ab2caf 100644 --- a/Runtime/Generic.cs +++ b/Runtime/Generic.cs @@ -1,185 +1,49 @@ -using System; +// +#pragma warning disable SA1600 // Elements should be documented + +using System; using System.Collections.Generic; using System.Linq; +using nadena.dev.ndmf; +using UnityEditor; +using UnityEditor.Animations; using UnityEngine; -using com.github.pandrabox.emoteprefab.runtime; using VRC.SDK3.Avatars.Components; - namespace com.github.pandrabox.emoteprefab.runtime { + /// + /// 汎用ツール + /// public static class Generic { - /// - /// Searches for a specific component in the self or parent direction. - /// Example of use: var Descriptor = FindComponentFromParent(MyGameObject); - /// - /// Target Component Name - /// GameObject to search from - /// The first component found, or null if none. - public static T FindComponentFromParent(GameObject CurrentObject) where T : Component + public static void WriteWarning(string functionname, string msg) + { + Debug.LogWarning($@"[EmotePrefab][{functionname}][{msg}]"); + } + + public static T FindComponentFromParent(GameObject currentObject) + where T : Component { - while (CurrentObject != null) + while (currentObject != null) { - var component = CurrentObject.GetComponent(); + var component = currentObject.GetComponent(); if (component != null) { return component; } - CurrentObject = CurrentObject.transform.parent?.gameObject; - } - return null; - } - /// - /// Searches for a specific component in the self or parent direction. - /// Example of use: var Descriptor = FindComponentFromParent(MyTransform); - /// - /// Target Component Name - /// Transform to search from - /// The first component found, or null if none. - public static T FindComponentFromParent(Transform CurrentTransform) where T : Component - { - return FindComponentFromParent(CurrentTransform?.gameObject); - } - public static GameObject GetAvatarRootObject(GameObject Target) - { - return FindComponentFromParent(Target)?.gameObject; - } - public static GameObject GetAvatarRootObject(Transform Target) - { - return FindComponentFromParent(Target)?.gameObject; - } - public static Transform GetAvatarRootTransform(GameObject Target) - { - return FindComponentFromParent(Target)?.gameObject?.transform; - } - public static Transform GetAvatarRootTransform(Transform Target) - { - return FindComponentFromParent(Target)?.gameObject?.transform; - } - public static bool IsInAvatar(GameObject Target) - { - return GetAvatarRootObject(Target) != null; - } - public static bool IsInAvatar(Transform Target) - { - return IsInAvatar(Target.gameObject); - } - public static GUIStyle TitleStyle() - { - GUIStyle style = new GUIStyle(GUI.skin.label); - style.normal.background = MakeTex(1, 1, new Color(255f / 255f, 128f / 255f, 0f / 255f, 1f)); - style.normal.textColor = Color.black; - style.fontStyle = FontStyle.Bold; - return style; - } - public static Texture2D MakeTex(int width, int height, Color color) - { - Color[] pix = new Color[width * height]; - for (int i = 0; i < pix.Length; ++i) - { - pix[i] = color; - } - Texture2D result = new Texture2D(width, height); - result.SetPixels(pix); - result.Apply(); - return result; - } - public static bool IsWithinErrorRange(Vector3 vector3, float referenceValue, float errorThreshold) - { - return Mathf.Abs(vector3.x - referenceValue) <= errorThreshold && - Mathf.Abs(vector3.y - referenceValue) <= errorThreshold && - Mathf.Abs(vector3.z - referenceValue) <= errorThreshold; - } - - public static void SetEditorOnly(string TargetName, bool SW, GameObject ParentObject = null) - { - var Targets = GetGameObjectsByName(TargetName, ParentObject); - foreach (var Target in Targets) - { - SetEditorOnly(Target, SW); - } - } - public static void SetEditorOnly(GameObject Target, bool SW) - { - if (SW) - { - Target.tag = "EditorOnly"; - Target.SetActive(false); + currentObject = currentObject.transform.parent?.gameObject; } - else - { - Target.tag = "Untagged"; - Target.SetActive(true); - } - } - public static void SetEditorOnly(Transform Target, bool SW) - { - SetEditorOnly(Target.gameObject, SW); - } - - public static Transform[] GetTransformsByName(string TargetName, Transform ParentTransform = null) - { - Transform[] Transforms; - if (ParentTransform != null) - { - Transforms = ParentTransform.GetComponentsInChildren(true)?.Where(t => t.name == TargetName)?.ToArray(); - } - else - { - Transforms = GameObject.FindObjectsOfType()?.Where(t => t.name == TargetName)?.ToArray(); - } - return Transforms; - } - public static Transform[] GetTransformsByName(string TargetName, GameObject ParentObject = null) - { - return GetTransformsByName(TargetName, ParentObject.transform); - } - public static GameObject[] GetGameObjectsByName(string TargetName, Transform ParentTransform = null) - { - Transform[] Transforms = GetTransformsByName(TargetName, ParentTransform); - GameObject[] GameObjects = new GameObject[Transforms.Length]; - for (int i = 0; i < Transforms.Length; i++) - { - GameObjects[i] = Transforms[i].gameObject; - } - return GameObjects; - } - public static GameObject[] GetGameObjectsByName(string TargetName, GameObject ParentGameObject = null) - { - return GetGameObjectsByName(TargetName, ParentGameObject.transform); + return null; } + } +} - - public static bool IsTargetEditorOnly(string TargetName, GameObject ParentObject = null) - { - Transform[] Transforms = GetTransformsByName(TargetName, ParentObject); - if (Transforms == null || Transforms.Length < 1) { return false; } - return IsTargetEditorOnly(Transforms[0].gameObject); - } - public static bool IsTargetEditorOnly(GameObject target) - { - return target.tag == "EditorOnly" && target.activeSelf == false; - } - - public static float DELTA = 0.00001f; - - public static string[] GestureNames = new string[] { "Neutral", "Fist", "HandOpen", "FingerPoint", "Victory", "RocknRoll", "HandGun", "Thumbsup" }; - public enum Gesture - { - Neutral, - Fist, - HandOpen, - FingerPoint, - Victory, - RocknRoll, - HandGun, - Thumbsup - } - public const int GESTURENUM = 8; - } -} +/* For Reviwer + * Please review this code for each element and identify areas for improvement, providing a final score out of 100. + * Please review this code in Japanese. + */ \ No newline at end of file