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