ProjectDDD/Assets/Plugins/Animate UI Materials/GraphicPropertyOverride.cs

200 lines
6.5 KiB
C#

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace Plugins.Animate_UI_Materials
{
/// <summary>
/// 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
/// </summary>
[ExecuteAlways]
public abstract class GraphicPropertyOverride : MonoBehaviour, IMaterialPropertyModifier
{
/// <summary>
/// The name of the shader property, serialized
/// </summary>
[SerializeField] protected string propertyName;
public virtual string DisplayName => propertyName;
/// <summary>
/// The id of the shader property
/// </summary>
/// <remarks> Should not be serialized, as it can change between game runs</remarks>
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
/// <summary>
/// On editor change, mark as dirty
/// </summary>
void OnValidate()
{
SetMaterialDirty(true);
}
#endif
/// <summary>
/// Try to retrieve and apply the default property value
/// If the source material cannot be found, reset to sensible defaults
/// </summary>
public abstract void ResetPropertyToDefault();
/// <summary>
/// Set the material as dirty
/// ApplyModifiedProperty will be called by the parent GraphicMaterialProperty
/// </summary>
/// <param name="renewId">If the GraphicPropertyOverride should try to get the shader property id, just to be safe</param>
public void SetMaterialDirty(bool renewId = false)
{
if (renewId || PropertyId == 0) PropertyId = Shader.PropertyToID(propertyName);
GraphicMaterialOverride parent = ParentOverride;
if (parent) parent.SetMaterialDirty();
}
/// <summary>
/// Try to apply the GraphicPropertyOverride property to the material
/// Does not create a copy, only feed material instances to this
/// </summary>
/// <param name="material">The material to modify</param>
public abstract void ApplyModifiedProperty(Material material);
/// <summary>
/// The name of the shader property to override
/// Can be invalid if the shader has changed, or if the component was not setup
/// </summary>
public string PropertyName
{
get => propertyName;
set
{
propertyName = value;
SetMaterialDirty(true);
}
}
/// <summary>
/// Try to get the Graphic component on the parent
/// </summary>
protected Graphic ParentGraphic => transform.parent ? transform.parent.GetComponent<Graphic>() : null;
/// <summary>
/// Try to get the GraphicMaterialOverride component on the parent
/// </summary>
protected GraphicMaterialOverride ParentOverride =>
transform.parent ? transform.parent.GetComponent<GraphicMaterialOverride>() : null;
}
/// <summary>
/// Template extension of GraphicPropertyOverride
/// Adds LateUpdate function to react to value changes
/// Compares value changes using EqualityComparer
/// Adds a SerializedField of type T
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class GraphicPropertyOverride<T> : GraphicPropertyOverride
{
/// <summary>
/// The serialized value, modified by the inspector or the animator
/// </summary>
[SerializeField] protected T propertyValue;
/// <summary>
/// The last known value, init the the type default (0, null, ...)
/// Used to check for changes
/// NonSerialized to prevent unity from serializing this
/// </summary>
[NonSerialized] T _previousValue;
/// <summary>
/// If _previousValue was set since last construction
/// </summary>
[NonSerialized] bool _previousValueIsInit;
/// <summary>
/// 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
/// </summary>
void LateUpdate()
{
// If a previous value was recorded
// And it perfectly matches the current value
// Then ignore this update
if (_previousValueIsInit && EqualityComparer<T>.Default.Equals(propertyValue, _previousValue)) return;
_previousValueIsInit = true;
_previousValue = propertyValue;
SetMaterialDirty();
}
/// <summary>
/// Called by the animator system when a value is modified
/// </summary>
public void OnDidApplyAnimationProperties()
{
_previousValueIsInit = true;
_previousValue = propertyValue;
SetMaterialDirty();
}
/// <summary>
/// The value of the overriding property
/// Will react correctly when changed
/// </summary>
public T PropertyValue
{
get => propertyValue;
set
{
_previousValueIsInit = true;
_previousValue = propertyValue = value;
SetMaterialDirty();
}
}
/// <summary>
/// Try to retrieve and apply the default property value
/// If the source material cannot be found, reset to sensible defaults
/// </summary>
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;
}
/// <summary>
/// Retrieve the default property value from the source material
/// </summary>
/// <param name="material">The source material</param>
/// <param name="defaultValue">The value from the material</param>
/// <returns>True if the value could be retrieved</returns>
public abstract bool GetDefaultValue(Material material, out T defaultValue);
}
}