Skip to content
This repository was archived by the owner on Jul 16, 2021. It is now read-only.

Commit c06767a

Browse files
committed
Add basic script
- Add basic inventory generation script
1 parent 54773c9 commit c06767a

File tree

1 file changed

+317
-0
lines changed

1 file changed

+317
-0
lines changed
Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+

2+
#if UNITY_EDITOR
3+
using System.Collections.Generic;
4+
using System.IO;
5+
using UnityEditor;
6+
using UnityEditor.Animations;
7+
using UnityEngine;
8+
using VRC.SDK3.Components;
9+
using VRC.SDK3.ScriptableObjects;
10+
11+
namespace Merlin
12+
{
13+
[AddComponentMenu("Merlin/Inventory Descriptor")]
14+
public class InventoryDescriptor : MonoBehaviour
15+
{
16+
[System.Serializable]
17+
public class InventorySlot
18+
{
19+
public string slotName;
20+
public Texture2D slotIcon;
21+
public GameObject[] slotToggleItems;
22+
public bool startEnabled;
23+
}
24+
25+
public bool advancedMode = false;
26+
public AnimatorController basisAnimator;
27+
public VRCExpressionsMenu basisMenu;
28+
public VRCStageParameters basisStageParameters;
29+
public VRCAvatarDescriptor.AnimLayerType inventoryAnimLayer = VRCAvatarDescriptor.AnimLayerType.Gesture;
30+
public InventorySlot[] inventorySlots;
31+
public string descriptorGUID;
32+
33+
private void Reset()
34+
{
35+
inventoryAnimLayer = VRCAvatarDescriptor.AnimLayerType.Gesture;
36+
basisAnimator = AssetDatabase.LoadAssetAtPath<AnimatorController>("Assets/VRCSDK/Examples3/Animation/Controllers/vrc_AvatarV3HandsLayer.controller");
37+
descriptorGUID = GUID.Generate().ToString();
38+
hideFlags = HideFlags.DontSaveInBuild;
39+
}
40+
41+
public void GenerateInventory()
42+
{
43+
string generatedDirPath = $"Assets/Merlin/Inventory/_generated/{descriptorGUID}";
44+
45+
if (!Directory.Exists(generatedDirPath))
46+
{
47+
Directory.CreateDirectory(generatedDirPath);
48+
}
49+
50+
// Generate the stage parameters for the inventory toggles
51+
VRCStageParameters inventoryStageParams;
52+
string stageParameterPath = $"{generatedDirPath}/customStageParams.asset";
53+
54+
if (basisStageParameters != null)
55+
{
56+
AssetDatabase.CopyAsset(AssetDatabase.GetAssetPath(basisStageParameters), stageParameterPath);
57+
inventoryStageParams = AssetDatabase.LoadAssetAtPath<VRCStageParameters>(stageParameterPath);
58+
}
59+
else
60+
{
61+
inventoryStageParams = ScriptableObject.CreateInstance<VRCStageParameters>();
62+
AssetDatabase.CreateAsset(inventoryStageParams, $"{generatedDirPath}/customStageParams.asset");
63+
}
64+
65+
List<VRCStageParameters.Parameter> originalParams = new List<VRCStageParameters.Parameter>();
66+
67+
foreach (VRCStageParameters.Parameter param in inventoryStageParams.stageParameters)
68+
{
69+
if (!string.IsNullOrEmpty(param.name))
70+
{
71+
originalParams.Add(new VRCStageParameters.Parameter() { name = param.name, valueType = param.valueType });
72+
}
73+
}
74+
75+
if (inventorySlots.Length + originalParams.Count > 16)
76+
{
77+
Debug.LogError($"Cannot have more than {16 - originalParams.Count} inventory slots");
78+
return;
79+
}
80+
81+
VRCStageParameters.Parameter[] basisParameters = inventoryStageParams.stageParameters;
82+
inventoryStageParams.stageParameters = new VRCStageParameters.Parameter[16];
83+
84+
for (int i = 0; i < originalParams.Count; ++i) inventoryStageParams.stageParameters[i] = originalParams[i];
85+
86+
for (int i = originalParams.Count; i < inventorySlots.Length + originalParams.Count; ++i)
87+
inventoryStageParams.stageParameters[i] = new VRCStageParameters.Parameter() { name = $"GenInventorySlot{i - originalParams.Count}", valueType = VRCStageParameters.Parameter.ValyeType.Int };
88+
89+
for (int i = originalParams.Count + inventorySlots.Length; i < 16; ++i) // Clear out empty params
90+
inventoryStageParams.stageParameters[i] = new VRCStageParameters.Parameter() { name = "", valueType = VRCStageParameters.Parameter.ValyeType.Float };
91+
92+
// Generate menu asset
93+
VRCExpressionsMenu menuAsset;
94+
string menuPath = $"{generatedDirPath}/expressionMenu.asset";
95+
if (basisMenu)
96+
{
97+
AssetDatabase.CopyAsset(AssetDatabase.GetAssetPath(basisMenu), menuPath);
98+
menuAsset = AssetDatabase.LoadAssetAtPath<VRCExpressionsMenu>(menuPath);
99+
}
100+
else
101+
{
102+
menuAsset = ScriptableObject.CreateInstance<VRCExpressionsMenu>();
103+
AssetDatabase.CreateAsset(menuAsset, menuPath);
104+
}
105+
106+
menuAsset.stageParameters = inventoryStageParams;
107+
108+
for (int i = 0; i < inventorySlots.Length; ++i)
109+
{
110+
menuAsset.controls.Add(new VRCExpressionsMenu.Control()
111+
{
112+
icon = inventorySlots[i].slotIcon,
113+
name = inventorySlots[i].slotName,
114+
parameter = new VRCExpressionsMenu.Control.Parameter() { name = $"GenInventorySlot{i}" },
115+
type = VRCExpressionsMenu.Control.ControlType.Toggle,
116+
value = 1,
117+
});
118+
}
119+
120+
// Generate controller
121+
AnimatorController controller;
122+
string controllerPath = $"{generatedDirPath}/inventoryController.controller";
123+
124+
if (basisAnimator)
125+
{
126+
AssetDatabase.CopyAsset(AssetDatabase.GetAssetPath(basisAnimator), controllerPath);
127+
128+
controller = AssetDatabase.LoadAssetAtPath<AnimatorController>(controllerPath);
129+
}
130+
else
131+
{
132+
controller = AnimatorController.CreateAnimatorControllerAtPath(controllerPath);
133+
}
134+
135+
AnimationClip[] inventoryClips = new AnimationClip[inventorySlots.Length];
136+
137+
// Generate layer mask
138+
AvatarMask maskEverything = new AvatarMask();
139+
for (int i = 0; i < (int)AvatarMaskBodyPart.LastBodyPart; ++i)
140+
maskEverything.SetHumanoidBodyPartActive((AvatarMaskBodyPart)i, false);
141+
142+
maskEverything.name = "maskEverythingMask";
143+
AssetDatabase.AddObjectToAsset(maskEverything, controller);
144+
145+
// Generate animation clips
146+
for (int i = 0; i < inventorySlots.Length; ++i)
147+
{
148+
InventorySlot slot = inventorySlots[i];
149+
150+
// Set initial object state
151+
foreach (GameObject toggleObject in slot.slotToggleItems)
152+
if (toggleObject)
153+
toggleObject.SetActive(slot.startEnabled);
154+
155+
string animationClipPath = $"{generatedDirPath}/Animations/_toggle{i}.anim";
156+
AnimationClip toggleClip = GenerateToggleClip(slot.slotToggleItems, !slot.startEnabled);
157+
158+
//AssetDatabase.CreateAsset(toggleClip, animationClipPath);
159+
160+
inventoryClips[i] = toggleClip;
161+
162+
toggleClip.name = $"toggleAnim{i}";
163+
AssetDatabase.AddObjectToAsset(toggleClip, controller);
164+
}
165+
166+
// Generate controller layers
167+
for (int i = 0; i < inventorySlots.Length; ++i)
168+
{
169+
string paramName = $"GenInventorySlot{i}";
170+
controller.AddParameter(paramName, AnimatorControllerParameterType.Int);
171+
172+
string layerName = $"GenToggleLayer{i}";
173+
174+
AnimatorControllerLayer toggleLayer = new AnimatorControllerLayer();
175+
toggleLayer.name = layerName;
176+
toggleLayer.defaultWeight = 1f;
177+
toggleLayer.stateMachine = new AnimatorStateMachine();
178+
toggleLayer.stateMachine.name = toggleLayer.name;
179+
toggleLayer.stateMachine.hideFlags = HideFlags.HideInHierarchy;
180+
toggleLayer.avatarMask = maskEverything;
181+
182+
if (AssetDatabase.GetAssetPath(controller) != "")
183+
AssetDatabase.AddObjectToAsset(toggleLayer.stateMachine, AssetDatabase.GetAssetPath(controller));
184+
185+
AnimatorStateMachine stateMachine = toggleLayer.stateMachine;
186+
187+
AnimatorState nullState = stateMachine.AddState("Null State", stateMachine.entryPosition + new Vector3(200f, 0f));
188+
AnimatorState toggleState = stateMachine.AddState("Toggle Triggered", stateMachine.entryPosition + new Vector3(500f, 0f));
189+
toggleState.motion = inventoryClips[i];
190+
191+
AnimatorStateTransition toToggle = nullState.AddTransition(toggleState);
192+
toToggle.exitTime = 0f;
193+
toToggle.hasExitTime = false;
194+
toToggle.hasFixedDuration = true;
195+
toToggle.duration = 0f;
196+
197+
AnimatorStateTransition toNull = toggleState.AddTransition(nullState);
198+
toNull.exitTime = 0f;
199+
toNull.hasExitTime = false;
200+
toNull.hasFixedDuration = true;
201+
toNull.duration = 0f;
202+
203+
toToggle.AddCondition(AnimatorConditionMode.Greater, 0f, paramName);
204+
toNull.AddCondition(AnimatorConditionMode.Equals, 0f, paramName);
205+
206+
controller.AddLayer(toggleLayer);
207+
}
208+
209+
// Setup layers on the avatar descriptor
210+
VRCAvatarDescriptor descriptor = GetComponent<VRCAvatarDescriptor>();
211+
212+
descriptor.expressionsMenu = menuAsset;
213+
214+
VRCAvatarDescriptor.CustomAnimLayer layer = new VRCAvatarDescriptor.CustomAnimLayer();
215+
layer.isDefault = false;
216+
layer.animatorController = controller;
217+
layer.type = inventoryAnimLayer;
218+
219+
for (int i = 0; i < descriptor.baseAnimationLayers.Length; ++i)
220+
{
221+
if (descriptor.baseAnimationLayers[i].type == inventoryAnimLayer)
222+
{
223+
descriptor.baseAnimationLayers[i] = layer;
224+
break;
225+
}
226+
}
227+
228+
AssetDatabase.SaveAssets();
229+
}
230+
231+
private string GetGameObjectAnimPath(GameObject gameObject)
232+
{
233+
string path = gameObject.name;
234+
235+
Transform currentTransform = gameObject.transform.parent;
236+
while (currentTransform != null && currentTransform.parent != null)
237+
{
238+
path = $"{currentTransform.gameObject.name}/{path}";
239+
currentTransform = currentTransform.parent;
240+
}
241+
242+
return path;
243+
}
244+
245+
private AnimationClip GenerateToggleClip(GameObject[] gameObjects, bool enableToggle)
246+
{
247+
AnimationClip clip = new AnimationClip();
248+
249+
for (int i = 0; i < gameObjects.Length; ++i)
250+
{
251+
if (gameObjects[i])
252+
{
253+
AnimationCurve enableCurve = new AnimationCurve(new Keyframe[] { new Keyframe(0f, enableToggle ? 1f : 0f) });
254+
AnimationUtility.SetEditorCurve(clip, new EditorCurveBinding() { path = GetGameObjectAnimPath(gameObjects[i]), propertyName = "m_IsActive", type = typeof(GameObject) }, enableCurve);
255+
}
256+
}
257+
258+
return clip;
259+
}
260+
}
261+
262+
[CustomEditor(typeof(InventoryDescriptor))]
263+
public class InventoryDescriptorEditor : Editor
264+
{
265+
public override void OnInspectorGUI()
266+
{
267+
InventoryDescriptor descriptor = (InventoryDescriptor)target;
268+
if (descriptor.GetComponent<VRCAvatarDescriptor>() == null)
269+
{
270+
EditorGUILayout.HelpBox("Inventory Descriptor must be on the same object as the VRC Avatar Descriptor", MessageType.Error);
271+
return;
272+
}
273+
274+
SerializedObject descriptorObject = new SerializedObject(descriptor);
275+
SerializedProperty advProperty = descriptorObject.FindProperty(nameof(InventoryDescriptor.advancedMode));
276+
SerializedProperty inventoryProperty = descriptorObject.FindProperty(nameof(InventoryDescriptor.inventorySlots));
277+
278+
EditorGUI.BeginChangeCheck();
279+
280+
281+
EditorGUILayout.PropertyField(advProperty);
282+
if (descriptor.advancedMode)
283+
{
284+
SerializedProperty basisAnimator = descriptorObject.FindProperty(nameof(InventoryDescriptor.basisAnimator));
285+
SerializedProperty basisMenu = descriptorObject.FindProperty(nameof(InventoryDescriptor.basisMenu));
286+
SerializedProperty basisStageParameters = descriptorObject.FindProperty(nameof(InventoryDescriptor.basisStageParameters));
287+
SerializedProperty inventoryAnimLayer = descriptorObject.FindProperty(nameof(InventoryDescriptor.inventoryAnimLayer));
288+
289+
EditorGUILayout.PropertyField(basisAnimator);
290+
EditorGUILayout.PropertyField(basisMenu);
291+
EditorGUILayout.PropertyField(basisStageParameters);
292+
EditorGUILayout.PropertyField(inventoryAnimLayer);
293+
294+
}
295+
296+
EditorGUILayout.PropertyField(inventoryProperty, true);
297+
298+
EditorGUI.BeginDisabledGroup(true);
299+
EditorGUILayout.Space();
300+
EditorGUILayout.TextField("Inventory GUID", descriptor.descriptorGUID);
301+
EditorGUI.EndDisabledGroup();
302+
303+
304+
if (EditorGUI.EndChangeCheck())
305+
descriptorObject.ApplyModifiedProperties();
306+
307+
EditorGUILayout.Space();
308+
309+
if (GUILayout.Button("Generate Inventory"))
310+
{
311+
descriptor.GenerateInventory();
312+
}
313+
}
314+
}
315+
}
316+
317+
#endif

0 commit comments

Comments
 (0)