Files
SimUL/Assets/UMA/Core/Scripts/DynamicDNAPlugin.cs
T
2025-01-07 18:54:46 +02:00

537 lines
18 KiB
C#

using System;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace UMA
{
[System.Serializable]
public abstract class DynamicDNAPlugin : ScriptableObject
{
//=====================================================================//
//A DynamicDNAPlugin is always a LIST of a type of 'DNA Converter' (an abstract concept- it can be any type)
//A 'DNA Converter' converts dna values into modifications to the character
//For example in UMA currently we have a SkeletonModifier that converts dna values into modifications to the skeletons bones
//Or a DNAMorphset converts dna values into weights for UMABonePoses and Blendshapes
//So those are examples of a 'DNA Converter' and a DynamicDNAPlugin is most basically a list of one of those kinds of things,
//along with an ApplyDNA method to apply them
//The plugin concept places no restrictions on what those 'DNA Converters' might be or do.
//But it does expect that a DynamicDNAPlugin contain a list of them and requires one method and one property in order to integrate them into the system.
//DynamicDNAPlugins only have to derive from DynamicDNAPlugin and they do not need an associated inspector.
//Any new plugins are automatically found and are available to add to the DynamicDNAConverterControllerAsset via its inspector
//DynamicDNAPlugins also have a MasterWeight. Setting this to zero disables the plugin, but the master weight can itself be hooked up to a dna value.
//By doing this different characters can control how much a set of Skeleton Modifiers or MorphSets should apply to them in their current state.
//For example a charcaters 'Claws' dna might do nothing while its human, but alot when it is a 'werewolf' (i.e. its 'werewolf' dna is turned up)
//The system also introduces DNAEvaluator, which is a super flexible field that performs math calculations on a dna value using a customizable animation curve.
//This is so that there is no need to have any extra behaviours or code in order to interpret dna values in a certain way.
//So the user is not overwhelmed by the complexity of using animation curves for math, DNAEvaluator uses DNAEvaluationGraphs which have preset curves
//with nice friendly names and tool tips. As coders we can set DNAEvaluationGraph fields to use one of our predefined defaults.
//DynamicDNAPlugins are assigned to a DynamicDNAConverterControllerAsset which calls ApplyDNA on each plugin in turn at runtime,
//and which at edit time looks after the creation of the Plugin Assets and its own Plugins list
//A DynamicDNAConverterControllerAsset is assigned to a DynamicDNAConverterBehaviour which triggers the ApplyDNA action on the DynamicDNAConverterControllerAsset
/// <summary>
/// Does this plugin get applied during the Standard ApplyDNA pass or in the 'Pre Pass'
/// </summary>
public enum ApplyPassOpts
{
PrePass,
Standard,
PostPass
}
#region ABSTRACT PROPERTIES AND METHODS
//It is REQUIRED that all DynamicDNAPlugins have these two Propeties and Methods (and thats it!)
/// <summary>
/// Returns a dictionary of all the dna names in use by the plugin and the indexes of the entries in its converter list that reference them
/// </summary>
public abstract Dictionary<string, List<int>> IndexesForDnaNames { get; }
/// <summary>
/// Called by the converter this plugin is assigned to. Applys the plugins list of converters to the character
/// </summary>
public abstract void ApplyDNA(UMAData umaData, UMASkeleton skeleton, int dnaTypeHash);
#endregion
#region PUBLIC MEMBERS
/// <summary>
/// All DynamicDNAPlugins have a 'MasterWeight' this makes it possible to disable them completely, or only enable them when certain dna conditions are met
/// </summary>
[Tooltip("The master weight controls how much all the converters in this group are applied. You can disable a set of converters by making the master weight zero. Or you can hook the master weight up to a characters dna so the converters only apply when that dna has a certain value.")]
[SerializeField]
public MasterWeight masterWeight = new MasterWeight();
#endregion
#region PRIVATE MEMBERS
[SerializeField]
private DynamicDNAConverterController _converterController;
#endregion
#region PUPLIC PROPERTIES
/// <summary>
/// The converterController asset this plugin has been assigned to. This property is set by the converterBehaviour when it is inspected or starts
/// </summary>
public DynamicDNAConverterController converterController
{
get { return _converterController; }
set { _converterController = value; }
}
public DynamicUMADnaAsset DNAAsset
{
get
{
if (_converterController != null)
{
return _converterController.DNAAsset;
}
return null;
}
}
#endregion
#region VIRTUAL PROPERTIES
//Its is OPTIONAL for any DynamicDNAPlugin to override these properties / methods
/// <summary>
/// Does this plugin get applied during the Standard ApplyDNA pass or in the 'Pre Pass'
/// </summary>
public virtual ApplyPassOpts ApplyPass { get { return ApplyPassOpts.Standard; } }
#if UNITY_EDITOR
public virtual string PluginHelp { get { return ""; } }
public virtual float GetListHeaderHeight
{
get
{
return 0f;
}
}
public virtual float GetListFooterHeight
{
get
{
return 13f;
}
}
/// <summary>
/// Gets the height of the 'Help' info that will be drawn by DrawPluginHelp.
/// </summary>
public virtual float GetPluginHelpHeight
{
get
{
//by default this will return the height of a help box that contains the PluginHelp string
return EditorStyles.helpBox.CalcHeight(new GUIContent(PluginHelp, EditorGUIUtility.FindTexture("console.infoicon")), Screen.width - EditorGUIUtility.FindTexture("console.infoicon").width) + (EditorGUIUtility.singleLineHeight / 2);
}
}
//This is a string array because different plugins might want to make different import methods.
//the plugins ImportSettings method will just be sent the index of the choice, then its up to the method what to do
/// <summary>
/// Standard ImportSettingsMethods are [0]Add [1]Replace
/// Override this if your plugins ImportSettings method uses different options
/// </summary>
public virtual string[] ImportSettingsMethods
{
get
{
return new string[]
{
"Add",
"Replace"
};
}
}
#endif
#endregion
#region VIRTUAL METHODS
public virtual void Reset()
{
}
//these all show when anything else gets a DynamicDNAPlugin - I'd prefer them to be protaected but also available to the pluginDrawer..How??
#if UNITY_EDITOR
/// <summary>
/// Override this method if DynamicDNAPluginInspector is not finding your list of converters automatically
/// </summary>
public virtual SerializedProperty GetConvertersListProperty(SerializedObject pluginSO)
{
//if overidden you should do something like
//return pluginSO.FindPropertyRelative("nameOfMyConverterList");
//By default gets the first kind of valid array in the plugin.
//Since plugins should always be a list of converters first and foremost this should usually work
SerializedProperty it = pluginSO.GetIterator();
it.Next(true);
while (it.Next(false))
{
if (it.propertyType != SerializedPropertyType.String && it.isArray && it.name != "Array" && it.name != "_masterWeight")
{
return it;
}
}
Debug.LogWarning("Could not find the Converters list for " + this.name + ". Please override 'GetConvertersListProperty' in your plugin");
return null;
}
/// <summary>
/// Draws the plugins 'Help' info using the value from the plugins 'PluginHelp' property. If you override this method you will also need to override the GetPluginHeight method
/// </summary>
public virtual void DrawPluginHelp(Rect position)
{
//by default this will draw a helpbox that contains the PluginHelp string
EditorGUI.HelpBox(position, PluginHelp, MessageType.Info);
}
/// <summary>
/// Override this to draw your own content in the Elements list header
/// Use pluginSO for to find properties to pass to standard EditorGUI.Property methods etc
/// You may also want to override GetListHeaderHeight if you need more lines
/// </summary>
/// <param name="rect">The full height of the header, override GetListHeaderHeight if you need more lines sent here</param>
/// <param name="pluginSO">The ScriptableObject representation of the plugin</param>
/// <returns>True if you want the default elements search bar drawn, false otherwise</returns>
public virtual bool DrawElementsListHeaderContent(Rect rect, SerializedObject pluginSO)
{
return true;
}
/// <summary>
/// Gets an label for an entry from this plugins list of converters
/// </summary>
/// <param name="pluginSO">The SerializedObject representation of this plugin</param>
/// <param name="entryIndex">The index from this plugins list of converters to draw</param>
public virtual GUIContent GetPluginEntryLabel(SerializedProperty entry, SerializedObject pluginSO, int entryIndex)
{
if (entry != null)
{
return new GUIContent(entry.displayName);
}
return GUIContent.none;
}
/// <summary>
/// Gets the height for an entry from this plugins list of converters.
/// </summary>
/// <param name="pluginSO">The SerializedObject representation of this plugin</param>
/// <param name="entryIndex">The index from this plugins list of converters to draw</param>
public virtual float GetPluginEntryHeight(SerializedObject pluginSO, int entryIndex, SerializedProperty entry)
{
if(entry != null)
{
if (entry.isExpanded)
{
return EditorGUI.GetPropertyHeight(entry, true);
}
else
{
return EditorGUIUtility.singleLineHeight;
}
}
return EditorGUIUtility.singleLineHeight;
}
/// <summary>
/// Draws an entry from this plugins list of converters in the UI. If you override this you may also need to override GetPluginEntryHeight.
/// </summary>
/// <param name="pluginSO">The SerializedObject representation of this plugin</param>
/// <param name="entryIndex">The index from this plugins list of converters to draw</param>
/// <param name="isExpanded">Whether the entry is currently expanded</param>
/// <returns>whether the entry is still expanded</returns>
public virtual bool DrawPluginEntry(Rect rect, SerializedObject pluginSO, int entryIndex, bool isExpanded, SerializedProperty entry)
{
if (entry != null)
{
EditorGUI.PropertyField(rect, entry, GetPluginEntryLabel(entry, pluginSO, entryIndex), true);
return entry.isExpanded;
}
return false;
}
/// <summary>
/// A callback that is called *after* a new entry is added to the plugins list of converters
/// </summary>
/// <param name="pluginSO">The SerializedObject representation of this plugin</param>
/// <param name="entryIndex">The index from this plugins list of converters to that was added</param>
public virtual void OnAddEntryCallback(SerializedObject pluginSO, int entryIndex)
{
//do nothing
}
/// <summary>
/// A callback that is called *before* an entry will be deleted from the plugins list of converters
/// </summary>
/// <param name="pluginSO">The SerializedObject representation of this plugin</param>
/// <param name="entryIndex">The index from this plugins list of converters that will be deleted/param>
/// <returns>Returns true if the entry can safely be deleted</returns>
public virtual bool OnRemoveEntryCallback(SerializedObject pluginSO, int entryIndex)
{
return true;
}
/// <summary>
/// Override this to draw your own content in the Elements list footer
/// Use pluginSO for to find properties to pass to standard EditorGUI.Property methods etc
/// You may also want to override GetListFooterHeight if you need more lines
/// </summary>
/// <param name="rect">The full height of the footer, override GetListFooterHeight if you need more lines sent here</param>
/// <param name="pluginSO">The ScriptableObject representation of the plugin</param>
/// <returns>True if you want the default elements '+/-' add/remove controls drawn, false otherwise</returns>
public virtual bool DrawElementsListFooterContent(Rect rect, SerializedObject pluginSO)
{
return true;
}
/// <summary>
/// Import settings from another plugin. You need to override this method to enable this functionality in your plugin
/// </summary>
/// <param name="pluginToImport">The sent UnityEngine.Object. Your plugin script should first check that this plugin is the correct type</param>
/// <returns>True if the settings imported successfully</returns>
public virtual bool ImportSettings(UnityEngine.Object pluginToImport, int importMethod)
{
Debug.LogWarning("Import Settings was not implimented for this plugin");
return false;
}
#endif
#endregion
#region PRIVATE STATIC FIELDS
private static readonly Type baseDynamicDNAPluginType = typeof(DynamicDNAPlugin);
private static List<Type> _pluginTypes;
#endregion
#region PUBLIC STATIC METHODS
public static List<Type> GetAvailablePluginTypes()
{
if (_pluginTypes == null)
{
CompilePluginTypesList();
}
return _pluginTypes;
}
public static bool IsValidPluginType(Type type)
{
return PluginDerivesFromBase(type);
}
public static bool IsValidPlugin(UnityEngine.Object asset)
{
try
{
return PluginDerivesFromBase(asset.GetType());
}
catch
{
return false;
}
}
#endregion
#region PRIVATE STATIC METHODS
private static bool PluginDerivesFromBase(Type type)
{
if (type == baseDynamicDNAPluginType)
{
return false;
}
else
{
return baseDynamicDNAPluginType.IsAssignableFrom(type);
}
}
private static void CompilePluginTypesList()
{
var list = new List<Type>();
System.Reflection.Assembly[] array = AppDomain.CurrentDomain.GetAssemblies();
for (int i = 0; i < array.Length; i++)
{
System.Reflection.Assembly assembly = array[i];
try
{
if (assembly != null)
{
Type[] array1 = assembly.GetTypes();
for (int i1 = 0; i1 < array1.Length; i1++)
{
Type type = array1[i1];
if (type.IsAbstract)
{
continue;
}
if (PluginDerivesFromBase(type))
{
list.Add(type);
}
}
}
}
catch
{
Debug.Log("An exception occurred loading assemblies. this can happen when an invalid assembly is present in the project and it cannot be loaded.");
}
}
_pluginTypes = list;
}
#endregion
#region SPECIAL TYPES
[System.Serializable]
public class MasterWeight
{
public enum MasterWeightType
{
UseGlobalValue,
UseDNAValue
}
[Tooltip("Choose whether to use a global value for all characters that use this converter, or a dnaValue that characters can change.")]
[SerializeField]
private MasterWeightType _masterWeightType = MasterWeightType.UseGlobalValue;
[Tooltip("The global weight to use for this set of converters. Applies to all characters that use the converter behaviour this resides in. Override this with DNAForWeight for 'per character' control")]
[Range(0f, 1f)]
[SerializeField]
private float _globalWeight = 1f;
[Tooltip("If set, the weight value will be controlled by the given dna on the character.")]
[SerializeField]
[DNAEvaluator.Config(true, true)]
private DNAEvaluator _DNAForWeight = new DNAEvaluator("", DNAEvaluationGraph.Raw, 1);
public MasterWeightType masterWeightType
{
get { return _masterWeightType; }
set { _masterWeightType = value; }
}
public float globalWeight
{
get { return _globalWeight; }
set { _globalWeight = value; }
}
public string dnaName
{
get { return _DNAForWeight.dnaName; }
set { _DNAForWeight.dnaName = value; }
}
public DNAEvaluationGraph dnaEvaluationGraph
{
get { return _DNAForWeight.evaluator; }
set { _DNAForWeight.evaluator = value; }
}
public float dnaMultiplier
{
get { return _DNAForWeight.multiplier; }
set { _DNAForWeight.multiplier = value; }
}
public MasterWeight()
{
_masterWeightType = MasterWeightType.UseGlobalValue;
_globalWeight = 1f;
}
public MasterWeight(MasterWeight other)
{
_masterWeightType = other._masterWeightType;
_globalWeight = other._globalWeight;
_DNAForWeight = new DNAEvaluator(other._DNAForWeight);
}
public MasterWeight(MasterWeightType masterWeightType = MasterWeightType.UseGlobalValue, float defaultWeight = 1f, string dnaForWeightName = "", DNAEvaluationGraph dnaForWeightGraph = null, float dnaForWeightMultiplier = 1f)
{
_masterWeightType = masterWeightType;
_globalWeight = defaultWeight;
if (!string.IsNullOrEmpty(dnaForWeightName))
{
_DNAForWeight = new DNAEvaluator(dnaForWeightName, dnaForWeightGraph, dnaForWeightMultiplier);
}
else
{
_DNAForWeight = new DNAEvaluator("", DNAEvaluationGraph.Raw, 1);
}
}
public float GetWeight(UMADnaBase umaDna = null)
{
if (_masterWeightType == MasterWeightType.UseDNAValue)
{
return _DNAForWeight.Evaluate(umaDna);
}
else
{
return _globalWeight;
}
}
//TODO check if this still screws up the incoming dnas values
public UMADnaBase GetWeightedDNA(UMADnaBase incomingDna)
{
if (_masterWeightType == MasterWeightType.UseGlobalValue)
{
return incomingDna;
}
var masterWeight = GetWeight(incomingDna);
var weightedDNA = new DynamicUMADna();
if (masterWeight > 0)
{
weightedDNA._names = new string[incomingDna.Names.Length];
Array.Copy(incomingDna.Names, weightedDNA._names, incomingDna.Names.Length);
weightedDNA._values = new float[incomingDna.Values.Length];
Array.Copy(incomingDna.Values, weightedDNA._values, incomingDna.Values.Length);
for (int i = 0; i < incomingDna.Count; i++)
{
weightedDNA.SetValue(i, weightedDNA.GetValue(i) * masterWeight);
}
}
return weightedDNA;
}
}
#endregion
}
}