using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; namespace Plugins.Animate_UI_Materials { /// /// Used in combination with GraphicMaterialOverride to modify and animate shader properties /// The base class is used for the shared Editor script, and reacting to OnValidate events /// [ExecuteAlways] public abstract class GraphicPropertyOverride : MonoBehaviour, IMaterialPropertyModifier { /// /// The name of the shader property, serialized /// [SerializeField] protected string propertyName; public virtual string DisplayName => propertyName; /// /// The id of the shader property /// /// Should not be serialized, as it can change between game runs protected int PropertyId; // Request a material update whenever the parent changes void OnEnable() { SetMaterialDirty(true); } void OnDisable() { SetMaterialDirty(); } #if UNITY_EDITOR // if in the unity editor, include unity editor callbacks /// /// On editor change, mark as dirty /// void OnValidate() { SetMaterialDirty(true); } #endif /// /// Try to retrieve and apply the default property value /// If the source material cannot be found, reset to sensible defaults /// public abstract void ResetPropertyToDefault(); /// /// Set the material as dirty /// ApplyModifiedProperty will be called by the parent GraphicMaterialProperty /// /// If the GraphicPropertyOverride should try to get the shader property id, just to be safe public void SetMaterialDirty(bool renewId = false) { if (renewId || PropertyId == 0) PropertyId = Shader.PropertyToID(propertyName); GraphicMaterialOverride parent = ParentOverride; if (parent) parent.SetMaterialDirty(); } /// /// Try to apply the GraphicPropertyOverride property to the material /// Does not create a copy, only feed material instances to this /// /// The material to modify public abstract void ApplyModifiedProperty(Material material); /// /// The name of the shader property to override /// Can be invalid if the shader has changed, or if the component was not setup /// public string PropertyName { get => propertyName; set { propertyName = value; SetMaterialDirty(true); } } /// /// Try to get the Graphic component on the parent /// protected Graphic ParentGraphic => transform.parent ? transform.parent.GetComponent() : null; /// /// Try to get the GraphicMaterialOverride component on the parent /// protected GraphicMaterialOverride ParentOverride => transform.parent ? transform.parent.GetComponent() : null; } /// /// Template extension of GraphicPropertyOverride /// Adds LateUpdate function to react to value changes /// Compares value changes using EqualityComparer /// Adds a SerializedField of type T /// /// public abstract class GraphicPropertyOverride : GraphicPropertyOverride { /// /// The serialized value, modified by the inspector or the animator /// [SerializeField] protected T propertyValue; /// /// The last known value, init the the type default (0, null, ...) /// Used to check for changes /// NonSerialized to prevent unity from serializing this /// [NonSerialized] T _previousValue; /// /// If _previousValue was set since last construction /// [NonSerialized] bool _previousValueIsInit; /// /// Checks if any changes happened just before rendering /// Can be removed to optimize, since OnDidApplyAnimationProperties is doing the heavy lifting /// But OnDidApplyAnimationProperties is undocumented, and will potentially change silently in the future /// void LateUpdate() { // If a previous value was recorded // And it perfectly matches the current value // Then ignore this update if (_previousValueIsInit && EqualityComparer.Default.Equals(propertyValue, _previousValue)) return; _previousValueIsInit = true; _previousValue = propertyValue; SetMaterialDirty(); } /// /// Called by the animator system when a value is modified /// public void OnDidApplyAnimationProperties() { _previousValueIsInit = true; _previousValue = propertyValue; SetMaterialDirty(); } /// /// The value of the overriding property /// Will react correctly when changed /// public T PropertyValue { get => propertyValue; set { _previousValueIsInit = true; _previousValue = propertyValue = value; SetMaterialDirty(); } } /// /// Try to retrieve and apply the default property value /// If the source material cannot be found, reset to sensible defaults /// public override void ResetPropertyToDefault() { // Try to get the associated Graphic component Graphic graphic = ParentGraphic; // If successful, get the material Material material = graphic ? graphic.material : null; // init the reset value to default T value = default; bool gotDefaultValue = false; // If material was received, try to get the default value from the material if (material) gotDefaultValue = GetDefaultValue(material, out value); // Log a warning if we failed if (!gotDefaultValue) Debug.LogWarning("Could not retrieve material default value", this); // Set current value to what we managed to retrieve, and update PropertyValue = value; } /// /// Retrieve the default property value from the source material /// /// The source material /// The value from the material /// True if the value could be retrieved public abstract bool GetDefaultValue(Material material, out T defaultValue); } }