유니티 셋팅
This commit is contained in:
@@ -0,0 +1,341 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Controls _GradientIndex property per material slot independently.
|
||||
/// Essential for Timeline animation when multiple materials share the same gradient system.
|
||||
/// Detects materials with _GradientIndex and exposes individual sliders for each.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// USAGE:
|
||||
/// - REQUIRED for 2+ materials: Allows independent gradient animation per material slot
|
||||
/// - OPTIONAL for 1 material: Provides a convenient slider (can also animate directly via Renderer > Material)
|
||||
///
|
||||
/// Before using this component, ensure you've called "Prepare for Timeline" on PotionTextureSetup.
|
||||
/// </remarks>
|
||||
[ExecuteInEditMode]
|
||||
[RequireComponent(typeof(Renderer))]
|
||||
public class MaterialIndexController : MonoBehaviour
|
||||
{
|
||||
[Header("Material Indices (Animate These in Timeline)")]
|
||||
[Tooltip("Gradient index for material slot 0. Animatable in Timeline.")]
|
||||
[Range(0, 109)] public float indexMaterial0 = 0f;
|
||||
|
||||
[Tooltip("Gradient index for material slot 1. Animatable in Timeline.")]
|
||||
[Range(0, 109)] public float indexMaterial1 = 0f;
|
||||
|
||||
[Tooltip("Gradient index for material slot 2. Animatable in Timeline.")]
|
||||
[Range(0, 109)] public float indexMaterial2 = 0f;
|
||||
|
||||
[Tooltip("Gradient index for material slot 3. Animatable in Timeline.")]
|
||||
[Range(0, 109)] public float indexMaterial3 = 0f;
|
||||
|
||||
[Header("Info")]
|
||||
[SerializeField] private int detectedMaterialsWithIndex = 0;
|
||||
|
||||
private static readonly int GradientIndexID = Shader.PropertyToID("_GradientIndex");
|
||||
private Renderer rend;
|
||||
private MaterialPropertyBlock mpb;
|
||||
private float[] lastIndices = new float[4];
|
||||
private bool usesMaterialInstances = false;
|
||||
private bool hasReadInitialValues = false;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
Initialize();
|
||||
|
||||
// Validate before proceeding
|
||||
if (!ValidateComponents())
|
||||
{
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
DetectMaterialsWithIndex();
|
||||
CheckIfUsingInstances();
|
||||
ReadCurrentIndicesFromMaterials();
|
||||
|
||||
// Info message when component is first added
|
||||
if (!hasReadInitialValues && Application.isPlaying)
|
||||
{
|
||||
if (detectedMaterialsWithIndex > 0)
|
||||
{
|
||||
Debug.Log($"[MaterialIndexController] Detected {detectedMaterialsWithIndex} material(s) with _GradientIndex on '{gameObject.name}'. Current values loaded.", this);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[MaterialIndexController] No materials with _GradientIndex found on '{gameObject.name}'. " +
|
||||
"This component will have no effect. Use a compatible shader (e.g., Liquid_Effect, Stylized_Tint).", this);
|
||||
}
|
||||
}
|
||||
|
||||
ApplyIndices();
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
DetectMaterialsWithIndex();
|
||||
CheckIfUsingInstances();
|
||||
ReadCurrentIndicesFromMaterials();
|
||||
ApplyIndices();
|
||||
}
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
if (rend == null) rend = GetComponent<Renderer>();
|
||||
DetectMaterialsWithIndex();
|
||||
CheckIfUsingInstances();
|
||||
// Don't read values in OnValidate - user might be editing sliders
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
// Only update if values changed (performance optimization)
|
||||
if (HasChanged())
|
||||
{
|
||||
ApplyIndices();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates that all required components are present and properly configured.
|
||||
/// </summary>
|
||||
/// <returns>True if valid, false otherwise.</returns>
|
||||
bool ValidateComponents()
|
||||
{
|
||||
if (rend == null)
|
||||
{
|
||||
Debug.LogError($"[MaterialIndexController] Renderer component missing on '{gameObject.name}'. " +
|
||||
"Cannot control material indices without a Renderer.", this);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rend.sharedMaterials == null || rend.sharedMaterials.Length == 0)
|
||||
{
|
||||
Debug.LogWarning($"[MaterialIndexController] Renderer on '{gameObject.name}' has no materials assigned. " +
|
||||
"Assign materials to use gradient index control.", this);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes component references and MaterialPropertyBlock with GPU instancing enabled.
|
||||
/// </summary>
|
||||
void Initialize()
|
||||
{
|
||||
if (rend == null)
|
||||
rend = GetComponent<Renderer>();
|
||||
|
||||
if (mpb == null)
|
||||
{
|
||||
mpb = new MaterialPropertyBlock();
|
||||
}
|
||||
}
|
||||
|
||||
void CheckIfUsingInstances()
|
||||
{
|
||||
if (rend == null) return;
|
||||
|
||||
Material[] materials = rend.sharedMaterials;
|
||||
|
||||
// Check if ANY material is an instance
|
||||
usesMaterialInstances = false;
|
||||
foreach (var mat in materials)
|
||||
{
|
||||
if (mat != null && mat.name.Contains("(Instance)"))
|
||||
{
|
||||
usesMaterialInstances = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DetectMaterialsWithIndex()
|
||||
{
|
||||
if (rend == null) return;
|
||||
|
||||
detectedMaterialsWithIndex = 0;
|
||||
Material[] materials = rend.sharedMaterials;
|
||||
|
||||
for (int i = 0; i < materials.Length && i < 4; i++)
|
||||
{
|
||||
if (materials[i] != null && materials[i].HasProperty(GradientIndexID))
|
||||
{
|
||||
detectedMaterialsWithIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads current gradient index values from materials and syncs controller values.
|
||||
/// Only reads once during initialization to avoid overwriting user changes.
|
||||
/// </summary>
|
||||
void ReadCurrentIndicesFromMaterials()
|
||||
{
|
||||
if (rend == null) return;
|
||||
if (hasReadInitialValues) return; // Only read once on first initialization
|
||||
|
||||
Material[] materials = rend.sharedMaterials;
|
||||
|
||||
// Read current index values from materials
|
||||
for (int i = 0; i < materials.Length && i < 4; i++)
|
||||
{
|
||||
if (materials[i] == null) continue;
|
||||
if (!materials[i].HasProperty(GradientIndexID)) continue;
|
||||
|
||||
float currentValue = materials[i].GetFloat(GradientIndexID);
|
||||
|
||||
// Set the controller values to match current material values
|
||||
switch (i)
|
||||
{
|
||||
case 0: indexMaterial0 = currentValue; break;
|
||||
case 1: indexMaterial1 = currentValue; break;
|
||||
case 2: indexMaterial2 = currentValue; break;
|
||||
case 3: indexMaterial3 = currentValue; break;
|
||||
}
|
||||
}
|
||||
|
||||
hasReadInitialValues = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if any index value has changed since last update.
|
||||
/// </summary>
|
||||
/// <returns>True if any value changed, false otherwise.</returns>
|
||||
bool HasChanged()
|
||||
{
|
||||
return !Mathf.Approximately(indexMaterial0, lastIndices[0]) ||
|
||||
!Mathf.Approximately(indexMaterial1, lastIndices[1]) ||
|
||||
!Mathf.Approximately(indexMaterial2, lastIndices[2]) ||
|
||||
!Mathf.Approximately(indexMaterial3, lastIndices[3]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies gradient index values to materials.
|
||||
/// Uses Material Instances (direct write) or MaterialPropertyBlock depending on current mode.
|
||||
/// </summary>
|
||||
void ApplyIndices()
|
||||
{
|
||||
if (rend == null) return;
|
||||
|
||||
Material[] materials = rend.sharedMaterials;
|
||||
float[] currentIndices = { indexMaterial0, indexMaterial1, indexMaterial2, indexMaterial3 };
|
||||
|
||||
if (usesMaterialInstances)
|
||||
{
|
||||
// Write directly to material instances
|
||||
for (int i = 0; i < materials.Length && i < 4; i++)
|
||||
{
|
||||
if (materials[i] == null) continue;
|
||||
if (!materials[i].HasProperty(GradientIndexID)) continue;
|
||||
|
||||
materials[i].SetFloat(GradientIndexID, currentIndices[i]);
|
||||
lastIndices[i] = currentIndices[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// ⚡ GPU INSTANCING: Use MPB with instancing enabled
|
||||
if (mpb == null) return;
|
||||
|
||||
for (int i = 0; i < materials.Length && i < 4; i++)
|
||||
{
|
||||
if (materials[i] == null) continue;
|
||||
if (!materials[i].HasProperty(GradientIndexID)) continue;
|
||||
|
||||
rend.GetPropertyBlock(mpb, i);
|
||||
mpb.SetFloat(GradientIndexID, currentIndices[i]);
|
||||
rend.SetPropertyBlock(mpb, i);
|
||||
|
||||
lastIndices[i] = currentIndices[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the gradient index for a specific material slot.
|
||||
/// </summary>
|
||||
/// <param name="materialSlot">The material slot index (0-3).</param>
|
||||
/// <param name="value">The gradient index value (0-109).</param>
|
||||
/// <remarks>
|
||||
/// Use this method to programmatically change gradient indices at runtime.
|
||||
/// The value is automatically clamped to the valid range (0-109).
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Set gradient index 45 for material slot 0
|
||||
/// materialIndexController.SetIndex(0, 45f);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public void SetIndex(int materialSlot, float value)
|
||||
{
|
||||
value = Mathf.Clamp(value, 0, 109);
|
||||
|
||||
switch (materialSlot)
|
||||
{
|
||||
case 0: indexMaterial0 = value; break;
|
||||
case 1: indexMaterial1 = value; break;
|
||||
case 2: indexMaterial2 = value; break;
|
||||
case 3: indexMaterial3 = value; break;
|
||||
}
|
||||
|
||||
ApplyIndices();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current gradient index for a specific material slot.
|
||||
/// </summary>
|
||||
/// <param name="materialSlot">The material slot index (0-3).</param>
|
||||
/// <returns>The gradient index value for the specified slot.</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// float currentIndex = materialIndexController.GetIndex(0);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public float GetIndex(int materialSlot)
|
||||
{
|
||||
switch (materialSlot)
|
||||
{
|
||||
case 0: return indexMaterial0;
|
||||
case 1: return indexMaterial1;
|
||||
case 2: return indexMaterial2;
|
||||
case 3: return indexMaterial3;
|
||||
default: return 0f;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes instance detection and clears property blocks.
|
||||
/// Call this after using "Prepare for Timeline" to switch between MPB and Material Instance modes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method is called automatically by PotionTextureSetup when creating or removing material instances.
|
||||
/// You typically don't need to call this manually.
|
||||
/// </remarks>
|
||||
public void RefreshInstanceDetection()
|
||||
{
|
||||
CheckIfUsingInstances();
|
||||
|
||||
// Clear any existing property blocks when switching modes
|
||||
if (rend != null)
|
||||
{
|
||||
Material[] materials = rend.sharedMaterials;
|
||||
for (int i = 0; i < materials.Length; i++)
|
||||
{
|
||||
rend.SetPropertyBlock(null, i);
|
||||
}
|
||||
}
|
||||
|
||||
// Read values again after switching to instances
|
||||
hasReadInitialValues = false;
|
||||
ReadCurrentIndicesFromMaterials();
|
||||
|
||||
ApplyIndices();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user