331 lines
10 KiB
C#
331 lines
10 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|