This commit is contained in:
ShallowT1Dream
2026-09-10 17:29:24 +08:00
parent 3071ec4b8f
commit 80aa4f0436
20 changed files with 32618 additions and 0 deletions
@@ -0,0 +1,330 @@
using System;
using System.Runtime.CompilerServices;
using Coffee.UIEffectInternal;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Profiling;
using UnityEngine.UI;
using UnityEditor;
#if TIMELINE_ENABLE
using UnityEngine.Timeline;
#endif
[assembly: InternalsVisibleTo("UIEffect")]
[assembly: InternalsVisibleTo("Coffee.UIEffect.Editor")]
namespace Coffee.UIEffects
{
[ExecuteAlways]
[DisallowMultipleComponent]
public abstract class UIEffectBase : UIBehaviour, IMeshModifier, IMaterialModifier, ICanvasRaycastFilter
#if TIMELINE_ENABLE
, ITimeControl
#endif
{
private static readonly InternalObjectPool<UIEffectContext> s_ContextPool =
new InternalObjectPool<UIEffectContext>(() => new UIEffectContext(), x => true, x => x.Reset());
private Graphic _graphic;
private Material _material;
private UIEffectContext _context;
private Action _onBeforeCanvasRebuild;
private Action _onAfterCanvasRebuild;
private bool _canModifyMesh;
private Matrix4x4 _prevTransformHash;
private Canvas _canvas;
private bool _canvasCached = false;
private bool _isContextDirty;
public Material effectMaterial => _material;
public Graphic graphic => _graphic != null ? _graphic : _graphic = GetComponent<Graphic>();
public virtual uint effectId => (uint)GetHashCode();
public virtual float actualSamplingScale => 1;
public virtual bool canModifyShape => true;
protected Canvas canvas
{
get
{
if (_canvasCached) return _canvas;
_canvasCached = true;
_canvas = GetComponentInParent<Canvas>();
if (_canvas == null || !_canvas.isActiveAndEnabled)
{
_canvas = null;
_canvasCached = false;
}
return _canvas;
}
}
public virtual UIEffectContext context
{
get
{
if (_context == null)
{
_context = s_ContextPool.Rent();
UpdateContext(_context);
}
return _context;
}
}
protected Material GetCurrentMaterial()
{
if (!isActiveAndEnabled) return null;
var g = graphic;
if (g == null) return null;
var canvasRenderer = g.canvasRenderer;
if (canvasRenderer == null || canvasRenderer.materialCount == 0) return null;
return canvasRenderer.GetMaterial();
}
public virtual RectTransform transitionRoot => transform as RectTransform;
protected override void OnEnable()
{
_canModifyMesh = true;
_onBeforeCanvasRebuild ??= OnBeforeCanvasRebuild;
_onAfterCanvasRebuild ??= OnAfterCanvasRebuild;
UIExtraCallbacks.onBeforeCanvasRebuild += _onBeforeCanvasRebuild;
UIExtraCallbacks.onAfterCanvasRebuild += _onAfterCanvasRebuild;
UpdateContext(context);
SetMaterialDirty();
SetVerticesDirty();
}
protected override void OnDisable()
{
if (_onBeforeCanvasRebuild != null)
{
UIExtraCallbacks.onBeforeCanvasRebuild -= _onBeforeCanvasRebuild;
}
if (_onAfterCanvasRebuild != null)
{
UIExtraCallbacks.onAfterCanvasRebuild -= _onAfterCanvasRebuild;
}
MaterialRepository.Release(ref _material);
SetMaterialDirty();
SetVerticesDirty();
}
protected override void OnDestroy()
{
_onBeforeCanvasRebuild = null;
_graphic = null;
s_ContextPool.Return(ref _context);
}
private void OnBeforeCanvasRebuild()
{
// If the mesh is not modified in the previous frame, try to modify it if possible.
if (!_canModifyMesh && CanModifyMesh())
{
_canModifyMesh = true;
SetVerticesDirty();
}
}
private void OnAfterCanvasRebuild()
{
if (_material == null || graphic == null || canvas == null || context == null) return;
if (_isContextDirty)
{
_isContextDirty = false;
ApplyContextToMaterial(GetCurrentMaterial());
}
if (!context.useViewMatrix) return;
context.UpdateViewMatrix(GetCurrentMaterial(), transitionRoot, canvas.rootCanvas);
}
protected override void OnCanvasHierarchyChanged()
{
_canvasCached = false;
_canvas = null;
}
public void ModifyMesh(Mesh mesh)
{
}
public virtual void ModifyMesh(VertexHelper vh)
{
if (!isActiveAndEnabled || context == null) return;
_canModifyMesh = CanModifyMesh();
if (_canModifyMesh)
{
context.ModifyMesh(graphic, canvas, transitionRoot, vh, canModifyShape);
}
}
private bool CanModifyMesh()
{
if (graphic == null || !graphic.isActiveAndEnabled) return false;
if (!isActiveAndEnabled) return false;
// The transitionRoot is same as the transform => true.
var root = transitionRoot;
if (transform == root) return true;
// The transitionRoot is not visible => false.
var scale1 = root.lossyScale;
var scale2 = transform.lossyScale;
return !Mathf.Approximately(scale1.x * scale1.y * scale2.x * scale2.y, 0);
}
public virtual Material GetModifiedMaterial(Material baseMaterial)
{
if (baseMaterial == null || !isActiveAndEnabled || context == null || !context.willModifyMaterial)
{
MaterialRepository.Release(ref _material);
return baseMaterial;
}
Profiler.BeginSample("(UIE)[UIEffect] GetModifiedMaterial");
var samplingScaleId = (uint)(Mathf.InverseLerp(0.01f, 100, actualSamplingScale) * uint.MaxValue);
// 使用特效配置哈希作为材质共享 key,相同配置的特效共享同一材质,从而可以合批。
// root 仅在特效需要视图矩阵(transition/gradation/detail/shiny)时才影响材质。
var configId = context.configId;
var rootId = context.useViewMatrix && transitionRoot ? (uint)transitionRoot.GetInstanceID() : 0u;
var hash = new Hash128((uint)baseMaterial.GetInstanceID(), configId, samplingScaleId, rootId);
if (!MaterialRepository.Valid(hash, _material))
{
Profiler.BeginSample("(UIE)[UIEffect] GetModifiedMaterial > Get or create material");
MaterialRepository.Get(hash, ref _material, x => new Material(x)
{
shader = UIEffectProjectSettings.shaderRegistry.FindOptionalShader(x.shader,
"(UIEffect)",
"Hidden/{0} (UIEffect)",
"Hidden/UI/Default (UIEffect)"),
hideFlags = HideFlags.HideAndDontSave
}, baseMaterial);
Profiler.EndSample();
}
_material.CopyPropertiesFromMaterial(baseMaterial);
ApplyContextToMaterial(_material);
Profiler.EndSample();
return _material;
}
#if UNITY_EDITOR
protected override void OnValidate()
{
UpdateContext(context);
SetVerticesDirty();
SetMaterialDirty();
}
protected override void Reset()
{
OnValidate();
}
internal virtual void SetEnablePreviewIfSelected(GameObject[] selection)
{
}
#endif
protected override void OnRectTransformDimensionsChange()
{
if (!isActiveAndEnabled) return;
SetVerticesDirty();
}
protected override void OnDidApplyAnimationProperties()
{
UpdateContext(context);
SetVerticesDirty();
SetMaterialDirty();
}
public virtual void SetVerticesDirty()
{
if (graphic != null)
{
graphic.SetVerticesDirty();
GraphicProxy.Find(graphic).SetVerticesDirty(graphic, enabled);
Misc.QueuePlayerLoopUpdate();
}
}
public virtual void SetMaterialDirty()
{
if (graphic != null)
{
graphic.SetMaterialDirty();
SetMaterialContextDirty();
Misc.QueuePlayerLoopUpdate();
}
}
public virtual void SetMaterialContextDirty()
{
_isContextDirty = true;
}
internal void ReleaseMaterial()
{
MaterialRepository.Release(ref _material);
}
internal abstract void UpdateContext(UIEffectContext c);
public virtual void ApplyContextToMaterial(Material material)
{
if (!isActiveAndEnabled || context == null || material == null) return;
_isContextDirty = false;
context.ApplyToMaterial(material, actualSamplingScale);
#if UNITY_EDITOR
Profiler.BeginSample("(Editor/UIE)[UIEffect] ApplyContextToMaterial Post Process");
if (!Application.isPlaying)
{
SetEnablePreviewIfSelected(Selection.gameObjects);
Misc.QueuePlayerLoopUpdate();
}
UIEffectProjectSettings.shaderRegistry.RegisterVariant(material, "UI > UIEffect");
Profiler.EndSample();
#endif
}
public abstract void SetRate(float rate, UIEffectTweener.CullingMask cullingMask);
public abstract bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera);
public void SetTime(double _)
{
}
public void OnControlTimeStart()
{
if (this != null)
{
enabled = true;
}
}
public void OnControlTimeStop()
{
if (this != null)
{
enabled = false;
}
}
}
}