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);
}
}