유니티 셋팅

This commit is contained in:
2026-03-27 16:34:08 +09:00
parent 474bda26cd
commit d76f078a89
1400 changed files with 116263 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6d77f9887d365d44db2e454b56c9b6d5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,141 @@
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
/// <summary>
/// Editor utility to clear _GrayscaleTex from materials.
/// Useful for cleaning up material previews after runtime testing.
/// </summary>
public class ClearGrayscaleTextures : EditorWindow
{
private bool showWarning = true;
[MenuItem("Tools/MesshingAround/Clear GrayscaleTex from Materials")]
static void ShowWindow()
{
GetWindow<ClearGrayscaleTextures>("Clear GrayscaleTex");
}
void OnGUI()
{
EditorGUILayout.Space(10);
EditorGUILayout.LabelField("Clear GrayscaleTex from Materials", EditorStyles.boldLabel);
EditorGUILayout.Space(5);
EditorGUILayout.HelpBox(
"This tool clears the _GrayscaleTex property from all materials.\n\n" +
"This is useful for cleaning up material previews in the Project window. " +
"The grayscale texture will be re-applied automatically at runtime by PotionTextureSetup.",
MessageType.Info
);
EditorGUILayout.Space(10);
// Warning box
if (showWarning)
{
EditorGUILayout.HelpBox(
"⚠️ IMPORTANT WARNINGS:\n\n" +
"• Material Instances: If you have material instances in your scene (created via 'Prepare for Timeline'), " +
"you may need to reset them after running this tool.\n\n" +
"• White Materials: After clearing, materials in the scene may appear white or without the grayscale texture " +
"until you enter Play mode or force a repaint (Play/Stop).\n\n" +
"• This operation affects ALL materials in the project with _GrayscaleTex property.",
MessageType.Warning
);
}
EditorGUILayout.Space(10);
showWarning = EditorGUILayout.ToggleLeft("Show warnings", showWarning);
EditorGUILayout.Space(10);
GUI.backgroundColor = new Color(0.8f, 0.4f, 0.4f);
if (GUILayout.Button("Clear _GrayscaleTex from All Materials", GUILayout.Height(40)))
{
if (EditorUtility.DisplayDialog(
"Clear GrayscaleTex",
"Are you sure you want to clear _GrayscaleTex from all materials?\n\n" +
"This will affect all materials in the project.\n" +
"Material instances may need to be recreated.",
"Yes, Clear",
"Cancel"))
{
ClearAllGrayscaleTextures();
}
}
GUI.backgroundColor = Color.white;
EditorGUILayout.Space(10);
EditorGUILayout.HelpBox(
"💡 TIP: After running this tool:\n" +
"1. Enter Play mode and Stop to refresh materials in the scene\n" +
"2. If materials are still white, re-assign the material to the Renderer\n" +
"3. If using Timeline, you may need to re-run 'Prepare for Timeline' on affected objects",
MessageType.Info
);
}
void ClearAllGrayscaleTextures()
{
string[] guids = AssetDatabase.FindAssets("t:Material");
int clearedCount = 0;
int totalChecked = 0;
EditorUtility.DisplayProgressBar("Clearing GrayscaleTex", "Processing materials...", 0f);
try
{
for (int i = 0; i < guids.Length; i++)
{
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
Material mat = AssetDatabase.LoadAssetAtPath<Material>(path);
if (mat != null)
{
totalChecked++;
if (mat.HasProperty("_GrayscaleTex"))
{
Texture currentTex = mat.GetTexture("_GrayscaleTex");
if (currentTex != null)
{
mat.SetTexture("_GrayscaleTex", null);
EditorUtility.SetDirty(mat);
clearedCount++;
Debug.Log($"✓ Cleared _GrayscaleTex from: {mat.name} (was: {currentTex.name})");
}
}
}
float progress = (float)i / guids.Length;
EditorUtility.DisplayProgressBar("Clearing GrayscaleTex", $"Processing {i + 1}/{guids.Length}", progress);
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
finally
{
EditorUtility.ClearProgressBar();
}
// Show results dialog
string message = $"Operation completed!\n\n" +
$"• Materials checked: {totalChecked}\n" +
$"• Materials cleared: {clearedCount}\n\n" +
$"Remember to:\n" +
$"1. Enter Play mode to refresh scene materials\n" +
$"2. Re-run 'Prepare for Timeline' if needed";
EditorUtility.DisplayDialog("Clear GrayscaleTex - Complete", message, "OK");
Debug.Log($"===== CLEAR GRAYSCALETEX COMPLETE =====");
Debug.Log($"✅ Cleared _GrayscaleTex from {clearedCount}/{totalChecked} material(s)");
}
}
#endif

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: afd91aa4c7bfc4d4cb9593b665e1adc8
AssetOrigin:
serializedVersion: 1
productId: 341020
packageName: Stylized Potions Pack - 110 Colors | Liquid Physics | URP + Built-in
packageVersion: 1.0
assetPath: Assets/MesshingAround/StylizedPotionsPack/Scripts/Editor/ClearGrayscaleTextures.cs
uploadId: 811526

View File

@@ -0,0 +1,146 @@
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(MaterialIndexController))]
public class MaterialIndexControllerEditor : Editor
{
public override void OnInspectorGUI()
{
MaterialIndexController controller = (MaterialIndexController)target;
Renderer rend = controller.GetComponent<Renderer>();
if (rend == null)
{
EditorGUILayout.HelpBox("No Renderer found!", MessageType.Error);
return;
}
Material[] materials = rend.sharedMaterials;
int materialsWithIndex = 0;
// Count materials with _GradientIndex
for (int i = 0; i < materials.Length; i++)
{
if (materials[i] != null && materials[i].HasProperty("_GradientIndex"))
{
materialsWithIndex++;
}
}
EditorGUILayout.Space(5);
EditorGUILayout.LabelField("Material Index Controller", EditorStyles.boldLabel);
string infoMessage = "";
if (materialsWithIndex == 1)
{
infoMessage = "This component is OPTIONAL for single material.\n\n" +
"Provides a convenient slider for _GradientIndex.\n" +
"You can also animate directly: Renderer > Material > _GradientIndex";
}
else if (materialsWithIndex >= 2)
{
infoMessage = "This component is REQUIRED for multiple materials.\n\n" +
"Without it, animating _GradientIndex will affect all materials together.\n" +
"This controller allows independent animation per material slot.";
}
else
{
infoMessage = "No materials with _GradientIndex detected.";
}
EditorGUILayout.HelpBox(infoMessage, materialsWithIndex >= 2 ? MessageType.Warning : MessageType.Info);
EditorGUILayout.Space(5);
// Check if using instances
bool usingInstances = false;
for (int i = 0; i < materials.Length; i++)
{
if (materials[i] != null && materials[i].name.Contains("(Instance)"))
{
usingInstances = true;
break;
}
}
if (!usingInstances && materialsWithIndex > 0)
{
EditorGUILayout.HelpBox(
"⚠️ Material instances not detected!\n\n" +
"For Timeline animation to work:\n" +
"1. Find 'PotionTextureSetup' component\n" +
"2. Click 'Prepare for Timeline'",
MessageType.Warning
);
EditorGUILayout.Space(5);
}
EditorGUILayout.LabelField($"Detected: {materialsWithIndex} material(s) with _GradientIndex", EditorStyles.miniLabel);
EditorGUILayout.Space(10);
EditorGUILayout.LabelField("Individual Material Indices", EditorStyles.boldLabel);
// Show only the sliders for materials that exist and have _GradientIndex
SerializedProperty indexProp0 = serializedObject.FindProperty("indexMaterial0");
SerializedProperty indexProp1 = serializedObject.FindProperty("indexMaterial1");
SerializedProperty indexProp2 = serializedObject.FindProperty("indexMaterial2");
SerializedProperty indexProp3 = serializedObject.FindProperty("indexMaterial3");
for (int i = 0; i < materials.Length && i < 4; i++)
{
if (materials[i] == null) continue;
if (!materials[i].HasProperty("_GradientIndex")) continue;
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField($"Material {i}: {materials[i].name}", EditorStyles.miniLabel);
SerializedProperty prop = null;
switch (i)
{
case 0: prop = indexProp0; break;
case 1: prop = indexProp1; break;
case 2: prop = indexProp2; break;
case 3: prop = indexProp3; break;
}
if (prop != null)
{
EditorGUILayout.PropertyField(prop, new GUIContent($"Index Material {i}"));
}
EditorGUILayout.EndVertical();
EditorGUILayout.Space(3);
}
serializedObject.ApplyModifiedProperties();
EditorGUILayout.Space(10);
// Timeline instructions
if (materialsWithIndex >= 2)
{
EditorGUILayout.HelpBox(
"Timeline Animation (Multiple Materials):\n" +
"1. Add Animation Track\n" +
"2. Drag this GameObject to track\n" +
"3. Add properties: 'Index Material 0', 'Index Material 1', etc.\n" +
"4. Each material animates independently!",
MessageType.None
);
}
else if (materialsWithIndex == 1)
{
EditorGUILayout.HelpBox(
"Timeline Animation (Single Material):\n" +
"1. Add Animation Track\n" +
"2. Drag this GameObject to track\n" +
"3. Add property: 'Index Material 0'\n" +
" (Or animate directly via Renderer > Material > _GradientIndex)",
MessageType.None
);
}
}
}
#endif

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 222b2dfbd04a57449a8d0c9ccd5ef54a
AssetOrigin:
serializedVersion: 1
productId: 341020
packageName: Stylized Potions Pack - 110 Colors | Liquid Physics | URP + Built-in
packageVersion: 1.0
assetPath: Assets/MesshingAround/StylizedPotionsPack/Scripts/Editor/MaterialIndexControllerEditor.cs
uploadId: 811526

View File

@@ -0,0 +1,103 @@
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(PotionTextureSetup))]
public class PotionTextureSetupEditor : Editor
{
public override void OnInspectorGUI()
{
PotionTextureSetup script = (PotionTextureSetup)target;
DrawDefaultInspector();
EditorGUILayout.Space(15);
Renderer rend = script.GetComponent<Renderer>();
int materialCount = rend != null ? rend.sharedMaterials.Length : 0;
if (materialCount > 0)
{
bool isPrepared = false;
if (rend != null)
{
Material[] sharedMats = rend.sharedMaterials;
if (sharedMats != null && sharedMats.Length > 0)
{
foreach (var mat in sharedMats)
{
if (mat != null && mat.name.Contains("(Instance)"))
{
isPrepared = true;
break;
}
}
}
}
EditorGUILayout.LabelField("Timeline Animation Setup", EditorStyles.boldLabel);
if (!isPrepared)
{
EditorGUILayout.HelpBox(
"To animate material properties in Timeline, you need to prepare this object first.\n\n" +
"This creates material instances that allow:\n" +
"• Timeline animations to work properly\n" +
"• Each object to have independent grayscale textures\n" +
"• Properties to animate without affecting other objects",
MessageType.Info
);
GUI.backgroundColor = new Color(0.3f, 0.8f, 0.3f);
if (GUILayout.Button("🎬 Prepare for Timeline", GUILayout.Height(40)))
{
script.PrepareForTimeline();
}
GUI.backgroundColor = Color.white;
}
else
{
string helpText = "✓ Timeline-ready! Material instances created.\n\n";
helpText += "Animate in Timeline:\n";
helpText += "• All properties via: Renderer > Material > [Property]\n";
helpText += "• Colors, emission, smoothness, etc.\n\n";
if (materialCount >= 2)
{
helpText += "⚠️ For _GradientIndex:\n";
helpText += "ADD MaterialIndexController component (REQUIRED)\n";
helpText += "Without it, all materials will animate together.\n\n";
}
else if (materialCount == 1)
{
helpText += "For _GradientIndex:\n";
helpText += "• Animate directly via Renderer > Material, OR\n";
helpText += "• Add MaterialIndexController for a cleaner slider (optional)\n\n";
}
helpText += "💡 Tip: Timeline preview in Edit Mode can be unreliable.\n";
helpText += "Always test in Play Mode for accurate animation results.";
EditorGUILayout.HelpBox(helpText, MessageType.None);
EditorGUILayout.Space(5);
// Undo button
GUI.backgroundColor = new Color(0.9f, 0.5f, 0.3f);
if (GUILayout.Button("↩ Remove Instances (Restore Originals)", GUILayout.Height(30)))
{
if (EditorUtility.DisplayDialog(
"Remove Material Instances?",
"This will restore the original shared materials.\n\nTimeline animations will no longer work until you prepare again.",
"Remove",
"Cancel"))
{
script.RemoveInstances();
}
}
GUI.backgroundColor = Color.white;
}
}
}
}
#endif

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 0d77c97b0aa656e4eba50acfa5350868
AssetOrigin:
serializedVersion: 1
productId: 341020
packageName: Stylized Potions Pack - 110 Colors | Liquid Physics | URP + Built-in
packageVersion: 1.0
assetPath: Assets/MesshingAround/StylizedPotionsPack/Scripts/Editor/PotionTextureSetupEditor.cs
uploadId: 811526

View File

@@ -0,0 +1,456 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// Simulates liquid physics with wobble effects based on object movement and rotation.
/// Supports both Material Property Blocks (for shared materials) and Material Instances (for Timeline animation).
/// </summary>
[ExecuteInEditMode]
[RequireComponent(typeof(MeshFilter), typeof(Renderer))]
public class Liquid : MonoBehaviour
{
/// <summary>
/// Determines which time source to use for wobble calculations.
/// </summary>
public enum UpdateMode
{
/// <summary>Normal game time (affected by Time.timeScale)</summary>
Normal,
/// <summary>Unscaled time (ignores Time.timeScale, useful for pause menus)</summary>
UnscaledTime
}
public UpdateMode updateMode;
[Header("Wobble Settings")]
[Tooltip("Maximum wobble intensity. Higher values = more aggressive liquid movement.")]
[Range(0f, 0.4f)] [SerializeField] float MaxWobble = 0.03f;
[Tooltip("Speed of wobble oscillation. Higher values = faster wobbling.")]
[Range(0f, 1.5f)] [SerializeField] float WobbleSpeedMove = 1f;
[Tooltip("How quickly the wobble returns to rest. Higher values = faster damping.")]
[Range(0f, 2f)] [SerializeField] float Recovery = 1f;
[Tooltip("Minimum velocity magnitude required to trigger wobble. Acts as a threshold.")]
[Range(0f, 5f)] [SerializeField] float Thickness = 1f;
[Header("Fill Settings")]
[Tooltip("Current fill level (0 = empty, 1 = full). Can be animated.")]
[Range(0f, 1f)] public float fillAmount = 0.5f;
[Tooltip("Minimum fill value when fillAmount is 0.")]
[Range(0f, 1f)] public float minFill = 0f;
[Tooltip("Maximum fill value when fillAmount is 1.")]
[Range(0f, 1f)] public float maxFill = 1f;
[Tooltip("Compensates for mesh shape. 0 = uses pivot, 1 = uses lowest vertex point.")]
[Range(0, 1)] public float CompensateShapeAmount;
[Header("References")]
[SerializeField] Mesh mesh;
[SerializeField] Renderer rend;
Vector3 pos;
Vector3 lastPos;
Vector3 velocity;
Quaternion lastRot;
Vector3 angularVelocity;
float wobbleAmountX;
float wobbleAmountZ;
float wobbleAmountToAddX;
float wobbleAmountToAddZ;
float pulse;
float sinewave;
double timeOffset; // Double for better precision across platforms
Vector3 comp;
MaterialPropertyBlock mpb;
private static readonly int GrayscaleTexID = Shader.PropertyToID("_GrayscaleTex");
private static readonly int WobbleXID = Shader.PropertyToID("_WobbleX");
private static readonly int WobbleZID = Shader.PropertyToID("_WobbleZ");
private static readonly int FillAmountID = Shader.PropertyToID("_FillAmount");
private static readonly int GradientIndexID = Shader.PropertyToID("_GradientIndex");
private Texture2D cachedGrayscaleTexture;
private bool grayscaleTextureNeedsUpdate = false;
private bool usesMaterialInstances = false;
// ⚡ OPTIMIZATION: Cache the lowest point of the mesh (constant value)
private float cachedLowestPoint;
private bool lowestPointCached = false;
void Start()
{
// Validate components before initialization
if (!ValidateComponents())
{
enabled = false;
return;
}
GetMeshAndRend();
// ⚡ GPU INSTANCING: Works automatically with MPB if shader has instancing enabled
mpb = new MaterialPropertyBlock();
CheckIfUsingInstances();
CacheLowestPoint();
// Initialize time offset for consistent behavior across platforms
timeOffset = (updateMode == UpdateMode.UnscaledTime) ? Time.unscaledTime : Time.time;
// ⚡ FIX: Limit framerate for consistent physics across platforms
Application.targetFrameRate = 60; // Force 60 FPS everywhere
QualitySettings.vSyncCount = 0; // Disable VSync to use targetFrameRate
}
private void OnValidate()
{
GetMeshAndRend();
if (mpb == null)
{
mpb = new MaterialPropertyBlock();
}
CheckIfUsingInstances();
// Recalculate if mesh changes in editor
lowestPointCached = false;
}
/// <summary>
/// Validates that all required components are present.
/// Logs clear errors if components are missing.
/// </summary>
/// <returns>True if all required components exist, false otherwise.</returns>
bool ValidateComponents()
{
bool isValid = true;
MeshFilter meshFilter = GetComponent<MeshFilter>();
if (meshFilter == null)
{
Debug.LogError($"[Liquid] MeshFilter component missing on '{gameObject.name}'. " +
"Add a MeshFilter component to use Liquid script.", this);
isValid = false;
}
else if (meshFilter.sharedMesh == null)
{
Debug.LogError($"[Liquid] MeshFilter on '{gameObject.name}' has no mesh assigned. " +
"Assign a mesh to the MeshFilter component.", this);
isValid = false;
}
Renderer renderer = GetComponent<Renderer>();
if (renderer == null)
{
Debug.LogError($"[Liquid] Renderer component missing on '{gameObject.name}'. " +
"Add a Renderer component (MeshRenderer, SkinnedMeshRenderer, etc.).", this);
isValid = false;
}
else if (renderer.sharedMaterials == null || renderer.sharedMaterials.Length == 0)
{
Debug.LogWarning($"[Liquid] Renderer on '{gameObject.name}' has no materials assigned. " +
"Assign at least one material for the liquid effect to work.", this);
}
return isValid;
}
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 GetMeshAndRend()
{
if (mesh == null)
{
MeshFilter mf = GetComponent<MeshFilter>();
if (mf != null) mesh = mf.sharedMesh;
}
if (rend == null)
rend = GetComponent<Renderer>();
}
/// <summary>
/// ⚡ OPTIMIZATION: Calculates the lowest point of the mesh ONCE and caches it.
/// This value is constant for a given mesh and doesn't need recalculation every frame.
/// For a mesh with 500 vertices at 60 FPS, this saves 1.8 million iterations per minute.
/// </summary>
void CacheLowestPoint()
{
if (mesh == null || lowestPointCached) return;
float lowestY = float.MaxValue;
Vector3 lowestVert = Vector3.zero;
Vector3[] vertices = mesh.vertices;
for (int i = 0; i < vertices.Length; i++)
{
Vector3 position = transform.TransformPoint(vertices[i]);
if (position.y < lowestY)
{
lowestY = position.y;
lowestVert = position;
}
}
cachedLowestPoint = lowestVert.y;
lowestPointCached = true;
}
/// <summary>
/// Returns the cached lowest point of the mesh in world space.
/// If not cached, calculates and caches it first.
/// </summary>
/// <returns>The Y position of the lowest vertex in world space.</returns>
float GetLowestPoint()
{
if (!lowestPointCached)
{
CacheLowestPoint();
}
return cachedLowestPoint;
}
void Update()
{
float deltaTime = 0;
float currentTime = 0;
switch (updateMode)
{
case UpdateMode.Normal:
deltaTime = Time.deltaTime;
currentTime = (float)(Time.time - timeOffset);
break;
case UpdateMode.UnscaledTime:
deltaTime = Time.unscaledDeltaTime;
currentTime = (float)(Time.unscaledTime - timeOffset);
break;
}
if (deltaTime != 0)
{
wobbleAmountToAddX = Mathf.Lerp(wobbleAmountToAddX, 0, (deltaTime * Recovery));
wobbleAmountToAddZ = Mathf.Lerp(wobbleAmountToAddZ, 0, (deltaTime * Recovery));
pulse = 2 * Mathf.PI * WobbleSpeedMove;
// ⚡ FIX: Use Time.time instead of manual accumulation for platform consistency
sinewave = Mathf.Lerp(sinewave, Mathf.Sin(pulse * currentTime),
deltaTime * Mathf.Clamp(velocity.magnitude + angularVelocity.magnitude, Thickness, 10));
wobbleAmountX = wobbleAmountToAddX * sinewave;
wobbleAmountZ = wobbleAmountToAddZ * sinewave;
velocity = (lastPos - transform.position) / deltaTime;
angularVelocity = GetAngularVelocity(lastRot, transform.rotation);
wobbleAmountToAddX += Mathf.Clamp((velocity.x + (velocity.y * 0.2f) + angularVelocity.z + angularVelocity.y) * MaxWobble, -MaxWobble, MaxWobble);
wobbleAmountToAddZ += Mathf.Clamp((velocity.z + (velocity.y * 0.2f) + angularVelocity.x + angularVelocity.y) * MaxWobble, -MaxWobble, MaxWobble);
}
UpdatePos(deltaTime);
if (usesMaterialInstances)
ApplyToMaterialInstances();
else
ApplyToMaterialPropertyBlock();
lastPos = transform.position;
lastRot = transform.rotation;
}
void ApplyToMaterialInstances()
{
if (rend == null) return;
Material[] materials = rend.sharedMaterials;
// Write directly to material instances (not MPB)
// This allows Timeline to animate other properties freely
for (int i = 0; i < materials.Length; i++)
{
if (materials[i] == null) continue;
if (materials[i].HasProperty(WobbleXID))
materials[i].SetFloat(WobbleXID, wobbleAmountX);
if (materials[i].HasProperty(WobbleZID))
materials[i].SetFloat(WobbleZID, wobbleAmountZ);
if (materials[i].HasProperty(FillAmountID))
materials[i].SetVector(FillAmountID, pos);
if (cachedGrayscaleTexture != null && materials[i].HasProperty(GrayscaleTexID))
{
materials[i].SetTexture(GrayscaleTexID, cachedGrayscaleTexture);
}
}
grayscaleTextureNeedsUpdate = false;
}
void ApplyToMaterialPropertyBlock()
{
if (rend == null || mpb == null) return;
Material[] materials = rend.sharedMaterials;
// ⚡ GPU INSTANCING: Use MPB with instancing enabled for batching
for (int i = 0; i < materials.Length; i++)
{
if (materials[i] == null) continue;
rend.GetPropertyBlock(mpb, i);
if (materials[i].HasProperty(WobbleXID))
mpb.SetFloat(WobbleXID, wobbleAmountX);
if (materials[i].HasProperty(WobbleZID))
mpb.SetFloat(WobbleZID, wobbleAmountZ);
if (materials[i].HasProperty(FillAmountID))
mpb.SetVector(FillAmountID, pos);
if (!Application.isPlaying || grayscaleTextureNeedsUpdate)
{
if (cachedGrayscaleTexture != null && materials[i].HasProperty(GrayscaleTexID))
{
mpb.SetTexture(GrayscaleTexID, cachedGrayscaleTexture);
}
}
rend.SetPropertyBlock(mpb, i);
}
grayscaleTextureNeedsUpdate = false;
}
void UpdatePos(float deltaTime)
{
Vector3 worldPos = transform.TransformPoint(mesh.bounds.center);
float normalizedFill = Mathf.Lerp(minFill, maxFill, fillAmount);
float invertedFill = 1f - normalizedFill;
if (CompensateShapeAmount > 0)
{
// ⚡ Now uses cached value - NO recalculation every frame
if (deltaTime != 0)
comp = Vector3.Lerp(comp, (worldPos - new Vector3(0, GetLowestPoint(), 0)), deltaTime * 10);
else
comp = (worldPos - new Vector3(0, GetLowestPoint(), 0));
pos = worldPos - transform.position - new Vector3(0, invertedFill - (comp.y * CompensateShapeAmount), 0);
}
else
{
pos = worldPos - transform.position - new Vector3(0, invertedFill, 0);
}
}
Vector3 GetAngularVelocity(Quaternion foreLastFrameRotation, Quaternion lastFrameRotation)
{
var q = lastFrameRotation * Quaternion.Inverse(foreLastFrameRotation);
if (Mathf.Abs(q.w) > 1023.5f / 1024.0f) return Vector3.zero;
// Use the deltaTime that respects UpdateMode
float dt = (updateMode == UpdateMode.UnscaledTime) ? Time.unscaledDeltaTime : Time.deltaTime;
if (dt == 0) return Vector3.zero; // Avoid division by zero
float gain;
if (q.w < 0.0f)
{
var angle = Mathf.Acos(-q.w);
gain = -2.0f * angle / (Mathf.Sin(angle) * dt);
}
else
{
var angle = Mathf.Acos(q.w);
gain = 2.0f * angle / (Mathf.Sin(angle) * dt);
}
Vector3 angularVelocity = new Vector3(q.x * gain, q.y * gain, q.z * gain);
if (float.IsNaN(angularVelocity.z)) angularVelocity = Vector3.zero;
return angularVelocity;
}
/// <summary>
/// Sets the grayscale texture for LUT-based gradient coloring.
/// The texture is used by the shader to map grayscale values to gradient colors.
/// </summary>
/// <param name="tex">The grayscale texture to apply. Should be a linear gradient from black to white.</param>
/// <remarks>
/// This method marks the texture as needing update and applies it in the next render cycle.
/// The texture will be applied via MaterialPropertyBlock or Material Instance depending on the current mode.
/// </remarks>
public void SetGrayscale(Texture2D tex)
{
if (rend == null)
{
rend = GetComponent<Renderer>();
if (rend == null)
{
Debug.LogWarning($"[Liquid] Cannot set grayscale texture on '{gameObject.name}': Renderer not found.", this);
return;
}
}
if (mpb == null)
{
mpb = new MaterialPropertyBlock();
}
cachedGrayscaleTexture = tex;
grayscaleTextureNeedsUpdate = true;
CheckIfUsingInstances();
}
/// <summary>
/// Refreshes the 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);
}
}
// Reapply grayscale if needed
if (cachedGrayscaleTexture != null)
{
grayscaleTextureNeedsUpdate = true;
}
}
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: ef390e80db64db64a944b927a65bab7d
AssetOrigin:
serializedVersion: 1
productId: 341020
packageName: Stylized Potions Pack - 110 Colors | Liquid Physics | URP + Built-in
packageVersion: 1.0
assetPath: Assets/MesshingAround/StylizedPotionsPack/Scripts/Liquid.cs
uploadId: 811526

View File

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

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 432f59e3338158d4489ac699c39b67a8
AssetOrigin:
serializedVersion: 1
productId: 341020
packageName: Stylized Potions Pack - 110 Colors | Liquid Physics | URP + Built-in
packageVersion: 1.0
assetPath: Assets/MesshingAround/StylizedPotionsPack/Scripts/MaterialIndexController.cs
uploadId: 811526

View File

@@ -0,0 +1,377 @@
using UnityEngine;
/// <summary>
/// Manages grayscale texture assignment for LUT-based gradient coloring.
/// Supports both MaterialPropertyBlock mode (for shared materials) and Material Instance mode (for Timeline animation).
/// </summary>
[ExecuteInEditMode]
[RequireComponent(typeof(Renderer))]
public class PotionTextureSetup : MonoBehaviour
{
[Header("Grayscale Texture")]
[Tooltip("Grayscale texture used for LUT (Look-Up Table) gradient coloring. Should be a linear gradient from black to white.")]
public Texture2D grayscaleTexture;
private static readonly int GrayscaleTexID = Shader.PropertyToID("_GrayscaleTex");
private Renderer rend;
private Liquid liquidComponent;
private Texture2D lastTexture;
private MaterialPropertyBlock mpb;
private bool usesMaterialInstances = false;
void Awake()
{
InitializeComponents();
}
void OnEnable()
{
InitializeComponents();
ApplyTexture();
}
void Start()
{
// Validate components
if (!ValidateComponents())
{
enabled = false;
return;
}
CheckIfUsingInstances();
ApplyTexture();
}
void OnValidate()
{
InitializeComponents();
ApplyTexture();
}
void Update()
{
// Only update if texture reference changed
if (grayscaleTexture != lastTexture)
{
ApplyTexture();
}
}
/// <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($"[PotionTextureSetup] Renderer component missing on '{gameObject.name}'. " +
"Cannot apply grayscale texture without a Renderer.", this);
return false;
}
if (rend.sharedMaterials == null || rend.sharedMaterials.Length == 0)
{
Debug.LogWarning($"[PotionTextureSetup] Renderer on '{gameObject.name}' has no materials assigned. " +
"Assign materials to use grayscale texture feature.", this);
return false;
}
// Check if any material has the _GrayscaleTex property
bool hasGrayscaleProperty = false;
foreach (var mat in rend.sharedMaterials)
{
if (mat != null && mat.HasProperty("_GrayscaleTex"))
{
hasGrayscaleProperty = true;
break;
}
}
if (!hasGrayscaleProperty)
{
Debug.LogWarning($"[PotionTextureSetup] None of the materials on '{gameObject.name}' have '_GrayscaleTex' property. " +
"This component will have no effect. Use a compatible shader (e.g., Liquid_Effect, Stylized_Tint).", this);
}
return true;
}
/// <summary>
/// Initializes component references and MaterialPropertyBlock with GPU instancing enabled.
/// </summary>
void InitializeComponents()
{
if (rend == null) rend = GetComponent<Renderer>();
if (liquidComponent == null) liquidComponent = GetComponent<Liquid>();
if (mpb == null)
{
mpb = new MaterialPropertyBlock();
}
CheckIfUsingInstances();
}
void CheckIfUsingInstances()
{
if (rend == null) return;
Material[] materials = rend.sharedMaterials;
usesMaterialInstances = false;
foreach (var mat in materials)
{
if (mat != null && mat.name.Contains("(Instance)"))
{
usesMaterialInstances = true;
break;
}
}
}
private void ApplyTexture()
{
if (grayscaleTexture == null) return;
// If Liquid component exists, delegate to it (handles instance detection internally)
if (liquidComponent != null)
{
liquidComponent.SetGrayscale(grayscaleTexture);
lastTexture = grayscaleTexture;
return;
}
if (rend == null) return;
Material[] materials = rend.sharedMaterials;
if (usesMaterialInstances)
{
// Write directly to material instances (for Timeline mode)
for (int i = 0; i < materials.Length; i++)
{
if (materials[i] != null && materials[i].HasProperty("_GrayscaleTex"))
{
materials[i].SetTexture(GrayscaleTexID, grayscaleTexture);
}
}
}
else
{
// ⚡ GPU INSTANCING: Use MPB with instancing enabled
if (mpb == null) return;
for (int i = 0; i < materials.Length; i++)
{
if (materials[i] != null && materials[i].HasProperty("_GrayscaleTex"))
{
rend.GetPropertyBlock(mpb, i);
mpb.SetTexture(GrayscaleTexID, grayscaleTexture);
rend.SetPropertyBlock(mpb, i);
}
}
}
lastTexture = grayscaleTexture;
}
/// <summary>
/// Sets a new grayscale texture and applies it immediately.
/// </summary>
/// <param name="texture">The grayscale texture to apply.</param>
/// <remarks>
/// Use this method to dynamically change the grayscale texture at runtime.
/// The texture will be applied according to the current mode (MPB or Material Instance).
/// </remarks>
public void SetGrayscaleTexture(Texture2D texture)
{
grayscaleTexture = texture;
ApplyTexture();
}
/// <summary>
/// Creates material instances for Timeline animation.
/// This allows Timeline to animate all material properties independently per object.
/// </summary>
/// <remarks>
/// After calling this method:
/// - Each object will have its own material instances (not shared)
/// - Timeline can animate material properties per object
/// - Use MaterialIndexController to animate _GradientIndex independently per material slot
///
/// NOTE: Material instances increase memory usage. Only use when Timeline animation is needed.
/// To revert, use RemoveInstances() method.
/// </remarks>
[ContextMenu("Prepare for Timeline")]
public void PrepareForTimeline()
{
if (rend == null) rend = GetComponent<Renderer>();
Material[] sharedMats = rend.sharedMaterials;
bool alreadyPrepared = true;
// Check if already using instances
for (int i = 0; i < sharedMats.Length; i++)
{
if (sharedMats[i] != null && !sharedMats[i].name.Contains("(Instance)"))
{
alreadyPrepared = false;
break;
}
}
if (alreadyPrepared && sharedMats.Length > 0)
{
Debug.Log($"[PotionTextureSetup] '{gameObject.name}' is already using material instances.", this);
return;
}
// Create material instances
Material[] newInstances = new Material[sharedMats.Length];
for (int i = 0; i < sharedMats.Length; i++)
{
if (sharedMats[i] != null)
{
newInstances[i] = new Material(sharedMats[i]);
newInstances[i].name = sharedMats[i].name + " (Instance)";
}
}
rend.sharedMaterials = newInstances;
ApplyTexture();
// Notify other components to switch from MPB to direct material writes
if (liquidComponent != null)
{
liquidComponent.RefreshInstanceDetection();
}
MaterialIndexController indexController = GetComponent<MaterialIndexController>();
if (indexController != null)
{
indexController.RefreshInstanceDetection();
}
CheckIfUsingInstances();
ApplyTexture();
#if UNITY_EDITOR
// Force editor refresh
UnityEditor.EditorUtility.SetDirty(gameObject);
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(gameObject.scene);
#endif
Debug.Log($"[PotionTextureSetup] Created {newInstances.Length} material instance(s) for '{gameObject.name}'. Timeline-ready!", this);
Debug.Log("Note: Timeline preview in Edit Mode can be unreliable. Always test in Play Mode for accurate results.", this);
if (newInstances.Length >= 2)
{
Debug.Log("IMPORTANT: For multiple materials, add MaterialIndexController component to animate _GradientIndex independently per material.", this);
}
else
{
Debug.Log("Tip: You can optionally add MaterialIndexController for a cleaner _GradientIndex slider (not required for single material).", this);
}
}
/// <summary>
/// Removes material instances and restores the original shared materials.
/// Use this to revert changes made by PrepareForTimeline().
/// </summary>
/// <remarks>
/// After calling this method:
/// - Objects will use shared materials again (better for batching/performance)
/// - Timeline animations will no longer work (need to call PrepareForTimeline again)
/// - Memory usage will be reduced
/// </remarks>
[ContextMenu("Remove Instances (Restore Originals)")]
public void RemoveInstances()
{
if (rend == null) rend = GetComponent<Renderer>();
Material[] currentMats = rend.sharedMaterials;
Material[] originalMats = new Material[currentMats.Length];
bool foundAnyInstances = false;
for (int i = 0; i < currentMats.Length; i++)
{
if (currentMats[i] != null && currentMats[i].name.Contains("(Instance)"))
{
foundAnyInstances = true;
string originalName = currentMats[i].name.Replace(" (Instance)", "").Trim();
Material originalMat = FindOriginalMaterial(originalName);
if (originalMat != null)
{
originalMats[i] = originalMat;
}
else
{
originalMats[i] = currentMats[i];
Debug.LogWarning($"[PotionTextureSetup] Could not find original material '{originalName}' on '{gameObject.name}'. Keeping instance.", this);
}
}
else
{
originalMats[i] = currentMats[i];
}
}
if (foundAnyInstances)
{
rend.sharedMaterials = originalMats;
ApplyTexture();
if (liquidComponent != null)
{
liquidComponent.RefreshInstanceDetection();
}
MaterialIndexController indexController = GetComponent<MaterialIndexController>();
if (indexController != null)
{
indexController.RefreshInstanceDetection();
}
CheckIfUsingInstances();
ApplyTexture();
#if UNITY_EDITOR
UnityEditor.EditorUtility.SetDirty(gameObject);
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(gameObject.scene);
#endif
Debug.Log($"[PotionTextureSetup] Material instances removed from '{gameObject.name}'. Restored original materials.", this);
}
else
{
Debug.Log($"[PotionTextureSetup] No material instances found on '{gameObject.name}'. Nothing to remove.", this);
}
}
/// <summary>
/// Attempts to find the original shared material by name.
/// </summary>
/// <param name="materialName">Name of the material to find.</param>
/// <returns>The original material if found, null otherwise.</returns>
Material FindOriginalMaterial(string materialName)
{
#if UNITY_EDITOR
// Search in project for material with matching name
string[] guids = UnityEditor.AssetDatabase.FindAssets($"t:Material {materialName}");
foreach (string guid in guids)
{
string path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
Material mat = UnityEditor.AssetDatabase.LoadAssetAtPath<Material>(path);
if (mat != null && mat.name == materialName)
{
return mat;
}
}
#endif
return null;
}
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 7de0c1862cc6cdb4baec99f88e37aa84
AssetOrigin:
serializedVersion: 1
productId: 341020
packageName: Stylized Potions Pack - 110 Colors | Liquid Physics | URP + Built-in
packageVersion: 1.0
assetPath: Assets/MesshingAround/StylizedPotionsPack/Scripts/PotionTextureSetup.cs
uploadId: 811526