Files
aibis-dream/Assets/Scripts/MiniGame/HuoShan/Language/TMPRectClipper.cs
T

117 lines
3.5 KiB
C#

using TMPro;
using UnityEngine;
namespace AibisDream.MiniGame.Language
{
/// <summary>
/// Gives one TMP object its own hard rectangular clip material. The screen rect is
/// converted from world corners into the TMP object's local coordinate system every
/// frame, so clipping remains correct while the text moves, scales or rotates.
/// </summary>
[DisallowMultipleComponent]
public sealed class TMPRectClipper : MonoBehaviour
{
private static readonly int ClipRectId = Shader.PropertyToID("_ClipRect");
private TMP_Text target;
private RectTransform screenRect;
private Material sourceMaterial;
private Material runtimeMaterial;
public void Initialize(TMP_Text text, RectTransform rect)
{
target = text;
screenRect = rect;
RebuildMaterial();
UpdateClipRect();
}
public void SetScreenRect(RectTransform rect)
{
screenRect = rect;
UpdateClipRect();
}
private void LateUpdate()
{
UpdateClipRect();
}
public void UpdateClipRect()
{
if (target == null || screenRect == null)
return;
if (runtimeMaterial == null)
RebuildMaterial();
if (runtimeMaterial == null)
return;
runtimeMaterial.SetVector(ClipRectId, CalculateLocalClipRect(target.transform, screenRect));
}
public static Vector4 CalculateLocalClipRect(Transform textTransform, RectTransform rect)
{
if (textTransform == null || rect == null)
return new Vector4(-32767f, -32767f, 32767f, 32767f);
Vector3[] corners = new Vector3[4];
rect.GetWorldCorners(corners);
float minX = float.PositiveInfinity;
float minY = float.PositiveInfinity;
float maxX = float.NegativeInfinity;
float maxY = float.NegativeInfinity;
for (int i = 0; i < corners.Length; i++)
{
Vector3 local = textTransform.InverseTransformPoint(corners[i]);
minX = Mathf.Min(minX, local.x);
minY = Mathf.Min(minY, local.y);
maxX = Mathf.Max(maxX, local.x);
maxY = Mathf.Max(maxY, local.y);
}
return new Vector4(minX, minY, maxX, maxY);
}
public void ReleaseMaterial()
{
if (target != null && sourceMaterial != null)
target.fontSharedMaterial = sourceMaterial;
if (runtimeMaterial != null)
{
if (Application.isPlaying)
Destroy(runtimeMaterial);
else
DestroyImmediate(runtimeMaterial);
}
runtimeMaterial = null;
sourceMaterial = null;
}
private void RebuildMaterial()
{
ReleaseMaterial();
if (target == null || target.fontSharedMaterial == null)
return;
sourceMaterial = target.fontSharedMaterial;
runtimeMaterial = new Material(sourceMaterial)
{
name = $"{sourceMaterial.name} (Screen Clip)",
hideFlags = HideFlags.DontSave
};
runtimeMaterial.EnableKeyword("MASK_HARD");
target.fontSharedMaterial = runtimeMaterial;
}
private void OnDestroy()
{
ReleaseMaterial();
}
}
}