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 s_ContextPool = new InternalObjectPool(() => 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(); 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(); 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; } } } }