using UnityEngine;
using UnityEngine.UI;
namespace Plugins.Animate_UI_Materials
{
///
/// Implements IMaterialModifier while avoiding the creation of too many garbage materials
/// WARNING: will destroy the modified material if the source material changes shader or on its own destruction
///
public abstract class BufferedMaterialModifier : MonoBehaviour, IMaterialModifier
{
private static readonly int Stencil = Shader.PropertyToID("_Stencil");
///
/// Hold the last modified material, to be re-used if possible
///
Material _bufferedMaterial;
///
/// Holds the base material used to create _bufferedMaterial
///
Material _bufferedMaterialSource;
///
/// From IMaterialModifier
/// Receives a material to be modified before display, and returns a new material
/// Only called once per frame per Graphic if changed, as Graphic is well optimized
///
///
/// A new material object, or the reset previous return value if possible
public Material GetModifiedMaterial(Material baseMaterial)
{
// Return the base material if invalid or if this component is disabled
if (!enabled || baseMaterial == null) return baseMaterial;
if (!_bufferedMaterial || _bufferedMaterial.shader != baseMaterial.shader || baseMaterial != _bufferedMaterialSource)
{
DestroyBuffer();
// Create a child material of the original
_bufferedMaterial = CreateNewMaterial(baseMaterial, "OVERRIDE");
_bufferedMaterialSource = baseMaterial;
}
_bufferedMaterial.CopyPropertiesFromMaterial(baseMaterial);
ModifyMaterial(_bufferedMaterial);
return _bufferedMaterial;
}
private int? GetStencilId(Material baseMaterial)
{
if (baseMaterial == null) return null;
// Check if material has stencil prop to avoid warning
if (!baseMaterial.HasInt(Stencil)) return null;
int id = baseMaterial.GetInt(Stencil);
return id > 0 ? id : null;
}
///
/// Create a new material variant of the base material.
/// Tries to set parent value from the source material for prettier editing.
/// Sets flags to avoid saving the material in assets.
/// Used for creating new buffered material or for the fake editor screen.
///
/// IMaterialModifier argument
/// Suffix to append to the original material name
///
private Material CreateNewMaterial(Material baseMaterial, string suffix)
{
Material realSource;
// Try to retrieve real base Material
if (TryGetComponent(out Graphic graphic))
{
realSource = graphic.material ? graphic.material : Canvas.GetDefaultCanvasMaterial();
}
else
{
Debug.LogWarning("No graphic found");
realSource = baseMaterial;
}
// Add mask info to the Material
if (GetStencilId(baseMaterial) is {} stencilId)
{
suffix = $"{suffix} MASKED {stencilId}";
}
Material modifiedMaterial = new (baseMaterial.shader)
{
// Set a new name, to warn about editor modifications
name = $"{realSource.name} {suffix}",
hideFlags = HideFlags.DontSaveInBuild | HideFlags.DontSaveInEditor,
};
// Set parent if supported
#if UNITY_2022_1_OR_NEWER && UNITY_EDITOR
modifiedMaterial.parent = realSource;
#endif
return modifiedMaterial;
}
void DestroyBuffer()
{
if (Application.isPlaying) Destroy(_bufferedMaterial);
else DestroyImmediate(_bufferedMaterial);
}
///
/// Child class implement this class, modifying directly the buffered material
///
///
protected abstract void ModifyMaterial(Material modifiedMaterial);
///
/// Destroy the buffered material
///
void OnDestroy()
{
DestroyBuffer();
}
public Material GetEditorMaterial(Material baseMaterial)
{
// Create a child material of the original
Material modifiedMaterial = CreateNewMaterial(baseMaterial, "EDITOR");
modifiedMaterial.CopyPropertiesFromMaterial(baseMaterial);
ModifyMaterial(modifiedMaterial);
return modifiedMaterial;
}
}
}