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 /// /// Does this plugin get applied during the Standard ApplyDNA pass or in the 'Pre Pass' /// 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!) /// /// 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 /// public abstract Dictionary> IndexesForDnaNames { get; } /// /// Called by the converter this plugin is assigned to. Applys the plugins list of converters to the character /// public abstract void ApplyDNA(UMAData umaData, UMASkeleton skeleton, int dnaTypeHash); #endregion #region PUBLIC MEMBERS /// /// All DynamicDNAPlugins have a 'MasterWeight' this makes it possible to disable them completely, or only enable them when certain dna conditions are met /// [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 /// /// The converterController asset this plugin has been assigned to. This property is set by the converterBehaviour when it is inspected or starts /// 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 /// /// Does this plugin get applied during the Standard ApplyDNA pass or in the 'Pre Pass' /// 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; } } /// /// Gets the height of the 'Help' info that will be drawn by DrawPluginHelp. /// 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 /// /// Standard ImportSettingsMethods are [0]Add [1]Replace /// Override this if your plugins ImportSettings method uses different options /// 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 /// /// Override this method if DynamicDNAPluginInspector is not finding your list of converters automatically /// 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; } /// /// 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 /// public virtual void DrawPluginHelp(Rect position) { //by default this will draw a helpbox that contains the PluginHelp string EditorGUI.HelpBox(position, PluginHelp, MessageType.Info); } /// /// 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 /// /// The full height of the header, override GetListHeaderHeight if you need more lines sent here /// The ScriptableObject representation of the plugin /// True if you want the default elements search bar drawn, false otherwise public virtual bool DrawElementsListHeaderContent(Rect rect, SerializedObject pluginSO) { return true; } /// /// Gets an label for an entry from this plugins list of converters /// /// The SerializedObject representation of this plugin /// The index from this plugins list of converters to draw public virtual GUIContent GetPluginEntryLabel(SerializedProperty entry, SerializedObject pluginSO, int entryIndex) { if (entry != null) { return new GUIContent(entry.displayName); } return GUIContent.none; } /// /// Gets the height for an entry from this plugins list of converters. /// /// The SerializedObject representation of this plugin /// The index from this plugins list of converters to draw 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; } /// /// Draws an entry from this plugins list of converters in the UI. If you override this you may also need to override GetPluginEntryHeight. /// /// The SerializedObject representation of this plugin /// The index from this plugins list of converters to draw /// Whether the entry is currently expanded /// whether the entry is still expanded 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; } /// /// A callback that is called *after* a new entry is added to the plugins list of converters /// /// The SerializedObject representation of this plugin /// The index from this plugins list of converters to that was added public virtual void OnAddEntryCallback(SerializedObject pluginSO, int entryIndex) { //do nothing } /// /// A callback that is called *before* an entry will be deleted from the plugins list of converters /// /// The SerializedObject representation of this plugin /// The index from this plugins list of converters that will be deleted/param> /// Returns true if the entry can safely be deleted public virtual bool OnRemoveEntryCallback(SerializedObject pluginSO, int entryIndex) { return true; } /// /// 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 /// /// The full height of the footer, override GetListFooterHeight if you need more lines sent here /// The ScriptableObject representation of the plugin /// True if you want the default elements '+/-' add/remove controls drawn, false otherwise public virtual bool DrawElementsListFooterContent(Rect rect, SerializedObject pluginSO) { return true; } /// /// Import settings from another plugin. You need to override this method to enable this functionality in your plugin /// /// The sent UnityEngine.Object. Your plugin script should first check that this plugin is the correct type /// True if the settings imported successfully 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 _pluginTypes; #endregion #region PUBLIC STATIC METHODS public static List 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(); 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 } }