My problem was simply enumerating the states from my custom editor, which I just figured out
(Actual code for state-switching is yet to be written, but I’ve already done similar in 3D projects.)
That’s how I prefer it too. Make it as easily testable as possible for testers by making it desktop only at first. Think of how controls apply to touch along the way, and actually implement touch controls when you know you have something acceptable
I’ll paste in the code I came up with, in case anybody wants something to build off.
IdleClip.cs:
using UnityEngine;
[System.Serializable]
public class IdleClip : ScriptableObject
{
[SerializeField]
public int weight;
[SerializeField]
public string clip;
[SerializeField]
public int index;
public void OnEnable()
{
if(clip == null) clip = "";
}
}
Just a class that keeps settings for each chosen idle-state. Put this somewhere and forget about it.
IdleController.cs:
using UnityEngine;
using System.Collections.Generic;
public class IdleController : MonoBehaviour
{
[SerializeField]
public string controllerPath;
public List<IdleClip> clips;
public int[] clipIndex;
public string clip
{
get
{
if(clips.Count == 0) return "";
int num = Random.Range(0, clips[clips.Count - 1].weight);
foreach(IdleClip ic in clips)
{
if(num <= ic.weight) return ic.name;
}
return "";
}
}
public static int CompareWeight(IdleClip a, IdleClip b)
{
if(a.weight == b.weight) return 0;
if(a.weight < b.weight) return -1;
return 1;
}
void Awake()
{
if(clips.Count > 0) clips.Sort(CompareWeight);
}
}
Attach to a game object. Maybe not the best name, but IdleController returns the name of a random clip from the property “clip”.
The big one, IdleControllerEditor.cs:
using UnityEngine;
using UnityEditor;
using UnityEditor.Animations;
using System.Collections.Generic;
[CustomEditor(typeof(IdleController))]
public class IdleControllerEditor : Editor
{
public AnimatorController controller;
private List<string> clips;
private IdleController ic;
void OnEnable()
{
ic = (IdleController)target;
if(ic.controllerPath != null && ic.controllerPath != "")
{
controller = AssetDatabase.LoadAssetAtPath<AnimatorController>(ic.controllerPath);
}
clips = new List<string>();
if(controller == null)
{
return;
}
foreach(AnimationClip clip in controller.animationClips)
{
clips.Add(clip.name);
}
UpdateIndices();
}
void UpdateIndices()
{
if(clips.Count == 0) return;
ic.clipIndex = new int[clips.Count];
for(int i = 0; i < clips.Count; i++)
{
ic.clipIndex[i] = ic.clipIndex[i];
}
}
public override void OnInspectorGUI()
{
serializedObject.Update();
if((ic != null) && (!Application.isPlaying))
{
EditorGUI.indentLevel = 0;
controller = (AnimatorController)EditorGUILayout.ObjectField("Animation controller", controller, typeof(AnimatorController), false);
if(controller != null)
{
ic.controllerPath = AssetDatabase.GetAssetPath(controller);
}
// Idle animations for non-carrying state
GUILayout.Label("Idle animations");
EditorGUILayout.Space();
if(ic.clips.Count < clips.Count)
{
if(GUILayout.Button("Add"))
{
IdleClip clip = ScriptableObject.CreateInstance<IdleClip>();
ic.clips.Add(clip);
}
}
EditorGUILayout.BeginHorizontal();
EditorGUILayout.Space();
GUILayout.Label("Clip");
EditorGUILayout.Space();
GUILayout.Label("Weight");
EditorGUILayout.EndHorizontal();
for(int i = 0; i < ic.clips.Count; i++)
{
if(ic.clips[i] == null) continue;
EditorGUILayout.BeginHorizontal();
EditorGUI.indentLevel = 1;
ic.clips[i].index = EditorGUILayout.Popup(ic.clips[i].index, clips.ToArray());
ic.clips[i].name = clips[ic.clips[i].index];
EditorGUILayout.Space();
if(ic.clips[i]) ic.clips[i].weight = EditorGUILayout.IntSlider(ic.clips[i].weight, 0, 100);
if(GUILayout.Button("-")) ic.clips.Remove(ic.clips[i]);
EditorGUI.indentLevel = 0;
EditorGUILayout.EndHorizontal();
}
} else
{
EditorGUI.indentLevel = 0;
}
EditorUtility.SetDirty(target);
serializedObject.ApplyModifiedProperties();
}
}
Put in an Editor folder.
When inspecting an IdleController object this will show a custom inspector where you can drop a Mecanim animation controller and set which animations should play as idle animations, with weights to alter probability. It should only show an add button if you have added fewer clips than the controller has.
This is hardcoded to a 0-100 range at the moment. If you set two clips at 50 and 100, the lowest one will happen when the random number is 0-50, the other one at 51-100. Actually playing animations is left as an exercise to people with time on their hands