//---------------------------------------------- // NGUI: Next-Gen UI kit // Copyright © 2011-2015 Tasharen Entertainment //---------------------------------------------- #define FUNCELL_MODIFIED using System; using UnityEngine; /// /// Abstract UI rectangle containing functionality common to both panels and widgets. /// A UI rectangle contains 4 anchor points (one for each side), and it ensures that they are updated in the proper order. /// public abstract class UIRect : MonoBehaviour { #if FUNCELL_MODIFIED //用于记录自增的ID数据 private static int IncID = 0; //当前UIRect的ID private int _id = 0; public int ID { get { return _id; } } public Action AnchorChanged = null; #endif [System.Serializable] public class AnchorPoint { public Transform target; public float relative = 0f; public int absolute = 0; [System.NonSerialized] public UIRect rect; [System.NonSerialized] public Camera targetCam; public AnchorPoint () { } public AnchorPoint (float relative) { this.relative = relative; } /// /// Convenience function that sets the anchor's values. /// public void Set (float relative, float absolute) { this.relative = relative; this.absolute = Mathf.FloorToInt(absolute + 0.5f); } /// /// Convenience function that sets the anchor's values. /// public void Set (Transform target, float relative, float absolute) { this.target = target; this.relative = relative; this.absolute = Mathf.FloorToInt(absolute + 0.5f); } /// /// Set the anchor's value to the nearest of the 3 possible choices of (left, center, right) or (bottom, center, top). /// public void SetToNearest (float abs0, float abs1, float abs2) { SetToNearest(0f, 0.5f, 1f, abs0, abs1, abs2); } /// /// Set the anchor's value given the 3 possible anchor combinations. Chooses the one with the smallest absolute offset. /// public void SetToNearest (float rel0, float rel1, float rel2, float abs0, float abs1, float abs2) { float a0 = Mathf.Abs(abs0); float a1 = Mathf.Abs(abs1); float a2 = Mathf.Abs(abs2); if (a0 < a1 && a0 < a2) Set(rel0, abs0); else if (a1 < a0 && a1 < a2) Set(rel1, abs1); else Set(rel2, abs2); } /// /// Set the anchor's absolute coordinate relative to the specified parent, keeping the relative setting intact. /// public void SetHorizontal (Transform parent, float localPos) { if (rect) { Vector3[] sides = rect.GetSides(parent); float targetPos = Mathf.Lerp(sides[0].x, sides[2].x, relative); absolute = Mathf.FloorToInt(localPos - targetPos + 0.5f); } else { Vector3 targetPos = target.position; if (parent != null) targetPos = parent.InverseTransformPoint(targetPos); absolute = Mathf.FloorToInt(localPos - targetPos.x + 0.5f); } } /// /// Set the anchor's absolute coordinate relative to the specified parent, keeping the relative setting intact. /// public void SetVertical (Transform parent, float localPos) { if (rect) { Vector3[] sides = rect.GetSides(parent); float targetPos = Mathf.Lerp(sides[3].y, sides[1].y, relative); absolute = Mathf.FloorToInt(localPos - targetPos + 0.5f); } else { Vector3 targetPos = target.position; if (parent != null) targetPos = parent.InverseTransformPoint(targetPos); absolute = Mathf.FloorToInt(localPos - targetPos.y + 0.5f); } } /// /// Convenience function that returns the sides the anchored point is anchored to. /// public Vector3[] GetSides (Transform relativeTo) { if (target != null) { if (rect != null) return rect.GetSides(relativeTo); #if UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7 if (target.camera != null) return target.camera.GetSides(relativeTo); #else if (target.GetComponent() != null) return target.GetComponent().GetSides(relativeTo); #endif } return null; } } /// /// Left side anchor. /// public AnchorPoint leftAnchor = new AnchorPoint(); /// /// Right side anchor. /// public AnchorPoint rightAnchor = new AnchorPoint(1f); /// /// Bottom side anchor. /// public AnchorPoint bottomAnchor = new AnchorPoint(); /// /// Top side anchor. /// public AnchorPoint topAnchor = new AnchorPoint(1f); public enum AnchorUpdate { OnEnable, OnUpdate, OnStart, } /// /// Whether anchors will be recalculated on every update. /// public AnchorUpdate updateAnchors = AnchorUpdate.OnEnable; [System.NonSerialized] protected GameObject mGo; [System.NonSerialized] protected Transform mTrans; [System.NonSerialized] protected ObjDisOrderList mChildren = new ObjDisOrderList(); [System.NonSerialized] protected bool mChanged = true; [System.NonSerialized] protected bool mParentFound = false; [System.NonSerialized] bool mUpdateAnchors = true; [System.NonSerialized] int mUpdateFrame = -1; [System.NonSerialized] bool mAnchorsCached = false; [System.NonSerialized] UIRoot mRoot; [System.NonSerialized] UIRect mParent; [System.NonSerialized] bool mRootSet = false; [System.NonSerialized] protected Camera mCam; // Marking it as NonSerialized will cause widgets to disappear when code recompiles in edit mode #if FUNCELL_MODIFIED [System.NonSerialized] public bool mStarted = false; #else protected bool mStarted = false; #endif /// /// Final calculated alpha. /// [System.NonSerialized] public float finalAlpha = 1f; #if FUNCELL_MODIFIED private static ObjDisOrderList _updateCacheList = new ObjDisOrderList(1024); private int _cacheIndex = -1; private bool _isCached = false; public static int updateRate = 0; private static void OnIndexChanged(UIRect rect, int index) { rect._cacheIndex = index; } #endif private bool _isCachedGo = false; /// /// Game object gets cached for speed. Can't simply return 'mGo' set in Awake because this function may be called on a prefab. /// public GameObject cachedGameObject { get { if (!_isCachedGo) { mGo = gameObject; _isCachedGo = true; } return mGo; } } private bool _isCachedTrans = false; /// /// Transform gets cached for speed. Can't simply return 'mTrans' set in Awake because this function may be called on a prefab. /// public Transform cachedTransform { get { if(!_isCachedTrans) { mTrans = transform; _isCachedTrans = true; } return mTrans; } } /// /// Camera used by anchors. /// public Camera anchorCamera { get { if (!mAnchorsCached) ResetAnchors(); return mCam; } } /// /// Whether the rectangle is currently anchored fully on all sides. /// public bool isFullyAnchored { get { return leftAnchor.target && rightAnchor.target && topAnchor.target && bottomAnchor.target; } } /// /// Whether the rectangle is anchored horizontally. /// public virtual bool isAnchoredHorizontally { get { return leftAnchor.target || rightAnchor.target; } } /// /// Whether the rectangle is anchored vertically. /// public virtual bool isAnchoredVertically { get { return bottomAnchor.target || topAnchor.target; } } /// /// Whether the rectangle can be anchored. /// public virtual bool canBeAnchored { get { return true; } } /// /// Get the rectangle's parent, if any. /// public UIRect parent { get { if (!mParentFound) { mParentFound = true; mParent = NGUITools.FindInParents(cachedTransform.parent); } return mParent; } } /// /// Get the root object, if any. /// public UIRoot root { get { if (!mRootSet) { mRootSet = true; if (parent != null) { mRoot = mParent.root; } else { mRoot = NGUITools.FindInParents(cachedTransform); } } return mRoot; } } /// /// Returns 'true' if the widget is currently anchored on any side. /// public bool isAnchored { get { return (leftAnchor.target || rightAnchor.target || topAnchor.target || bottomAnchor.target) && canBeAnchored; } } /// /// Local alpha, not relative to anything. /// public abstract float alpha { get; set; } /// /// Get the final cumulative alpha. /// public abstract float CalculateFinalAlpha (int frameID); /// /// Local-space corners of the UI rectangle. The order is bottom-left, top-left, top-right, bottom-right. /// public abstract Vector3[] localCorners { get; } /// /// World-space corners of the UI rectangle. The order is bottom-left, top-left, top-right, bottom-right. /// public abstract Vector3[] worldCorners { get; } /// /// Helper function that returns the distance to the camera's directional vector hitting the panel's plane. /// protected float cameraRayDistance { get { if (anchorCamera == null) return 0f; #if UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7 if (!mCam.isOrthoGraphic) #else if (!mCam.orthographic) #endif { Transform t = cachedTransform; Transform ct = mCam.transform; Plane p = new Plane(t.rotation * Vector3.back, t.position); Ray ray = new Ray(ct.position, ct.rotation * Vector3.forward); float dist; if (p.Raycast(ray, out dist)) return dist; } return Mathf.Lerp(mCam.nearClipPlane, mCam.farClipPlane, 0.5f); } } /// /// Sets the local 'changed' flag, indicating that some parent value(s) are now be different, such as alpha for example. /// public virtual void Invalidate (bool includeChildren, bool onlyAlphaChange = false) { if(!onlyAlphaChange) { mChanged = true; } if (includeChildren) for (int i = 0; i < mChildren.size; ++i) mChildren.buffer[i].Invalidate(true, onlyAlphaChange); } // Temporary variable to avoid GC allocation static protected Vector3[] mSides = new Vector3[4]; /// /// Get the sides of the rectangle relative to the specified transform. /// The order is left, top, right, bottom. /// public virtual Vector3[] GetSides (Transform relativeTo) { if (anchorCamera != null) return mCam.GetSides(cameraRayDistance, relativeTo); Vector3 pos = cachedTransform.position; for (int i = 0; i < 4; ++i) mSides[i] = pos; if (relativeTo != null) { for (int i = 0; i < 4; ++i) mSides[i] = relativeTo.InverseTransformPoint(mSides[i]); } return mSides; } /// /// Helper function that gets the specified anchor's position relative to the chosen transform. /// protected Vector3 GetLocalPos (AnchorPoint ac, Transform trans) { if (anchorCamera == null || ac.targetCam == null) return cachedTransform.localPosition; Rect rect = ac.targetCam.rect; Vector3 viewPos = ac.targetCam.WorldToViewportPoint(ac.target.position); Vector3 pos = new Vector3((viewPos.x * rect.width) + rect.x, (viewPos.y * rect.height) + rect.y, viewPos.z); pos = mCam.ViewportToWorldPoint(pos); if (trans != null) pos = trans.InverseTransformPoint(pos); pos.x = Mathf.Floor(pos.x + 0.5f); pos.y = Mathf.Floor(pos.y + 0.5f); return pos; } #if UNITY_EDITOR [System.NonSerialized] bool mEnabled = false; #endif /// /// Automatically find the parent rectangle. /// protected virtual void OnEnable () { #if UNITY_EDITOR mEnabled = true; #endif mUpdateFrame = -1; if (updateAnchors == AnchorUpdate.OnEnable) { mAnchorsCached = false; mUpdateAnchors = true; } if (mStarted) OnInit(); mUpdateFrame = -1; #if FUNCELL_MODIFIED _updateCacheList.SetIndexChangeFunc(OnIndexChanged); if (!_isCached) { _updateCacheList.Add(this); _isCached = true; } #endif } /// /// Automatically find the parent rectangle. /// protected virtual void OnInit () { mChanged = true; mRootSet = false; mParentFound = false; _isCachedTrans = false; _isCachedGo = false; if (parent != null) mParent.mChildren.Add(this); } /// /// Clear the parent rectangle reference. /// protected virtual void OnDisable () { #if UNITY_EDITOR mEnabled = false; #endif if (mParent) mParent.mChildren.Remove(this); mParent = null; mRoot = null; mRootSet = false; mParentFound = false; _isCachedTrans = false; _isCachedGo = false; #if FUNCELL_MODIFIED if (_isCached) { _updateCacheList.RemoveAt(_cacheIndex); _isCached = false; } #endif } #if FUNCELL_MODIFIED protected virtual void OnDestroy() { if (_isCached) { _updateCacheList.RemoveAt(_cacheIndex); _isCached = false; } } #endif /// /// Reset 'mStarted' as Unity remembers its value. It can't be marked as [NonSerialized] because then /// Unity edit mode stops working properly and code recompile causes widgets to disappear. /// protected virtual void Awake () { #if FUNCELL_MODIFIED IncID++; _id = IncID; #endif mStarted = false; mGo = gameObject; mTrans = transform; } /// /// Set anchor rect references on start. /// protected void Start () { mStarted = true; OnInit(); OnStart(); } #if FUNCELL_MODIFIED private static int _curFrameCount = 0; public static int CurFrameCount { get { return _curFrameCount; } set { _curFrameCount = value; } } public bool SelfUpdate() { if (!mAnchorsCached) ResetAnchors(); #if UNITY_EDITOR if (updateAnchors == AnchorUpdate.OnUpdate || mUpdateAnchors || !Application.isPlaying) #else if (updateAnchors == AnchorUpdate.OnUpdate || mUpdateAnchors) #endif UpdateAnchorsInternal(_curFrameCount); // Continue with the update return OnUpdate(); } public static void AllUpdate() { if(updateRate > 0 && CurFrameCount % updateRate != 0) { return; } for (int i = 0, max = _updateCacheList.size; i < max; ) { var succ = _updateCacheList[i].SelfUpdate(); if(!succ) { _updateCacheList.RemoveAt(i); --max; } else { ++i; } } } #else /// /// Rectangles need to update in a specific order -- parents before children. /// When deriving from this class, override its OnUpdate() function instead. /// public void Update() { if (!mAnchorsCached) ResetAnchors(); #if UNITY_EDITOR int frame = Time.frameCount; if (mUpdateFrame != frame || !Application.isPlaying) #else if (mUpdateFrame != frame) #endif { #if UNITY_EDITOR if (updateAnchors == AnchorUpdate.OnUpdate || mUpdateAnchors || !Application.isPlaying) #else if (updateAnchors == AnchorUpdate.OnUpdate || mUpdateAnchors) #endif UpdateAnchorsInternal(frame); // Continue with the update OnUpdate(); } } #endif /// /// Update anchors. /// protected void UpdateAnchorsInternal (int frame) { mUpdateFrame = frame; mUpdateAnchors = false; bool anchored = false; if (leftAnchor.target) { anchored = true; #if FUNCELL_MODIFIED if (leftAnchor.rect != null && leftAnchor.rect.mUpdateFrame != frame) leftAnchor.rect.SelfUpdate(); #else if (leftAnchor.rect != null && leftAnchor.rect.mUpdateFrame != frame) leftAnchor.rect.Update(); #endif } if (bottomAnchor.target) { anchored = true; #if FUNCELL_MODIFIED if (bottomAnchor.rect != null && bottomAnchor.rect.mUpdateFrame != frame) bottomAnchor.rect.SelfUpdate(); #else if (bottomAnchor.rect != null && bottomAnchor.rect.mUpdateFrame != frame) bottomAnchor.rect.Update(); #endif } if (rightAnchor.target) { anchored = true; #if FUNCELL_MODIFIED if (rightAnchor.rect != null && rightAnchor.rect.mUpdateFrame != frame) rightAnchor.rect.SelfUpdate(); #else if (rightAnchor.rect != null && rightAnchor.rect.mUpdateFrame != frame) rightAnchor.rect.Update(); #endif } if (topAnchor.target) { anchored = true; #if FUNCELL_MODIFIED if (topAnchor.rect != null && topAnchor.rect.mUpdateFrame != frame) topAnchor.rect.SelfUpdate(); #else if (topAnchor.rect != null && topAnchor.rect.mUpdateFrame != frame) topAnchor.rect.Update(); #endif } // Update the dimensions using anchors if (anchored) OnAnchor(); } /// /// Manually update anchored sides. /// public void UpdateAnchors () { if (isAnchored) { mUpdateFrame = -1; mUpdateAnchors = true; UpdateAnchorsInternal(Time.frameCount); } } /// /// Update the dimensions of the rectangle using anchor points. /// protected abstract void OnAnchor (); /// /// Anchor this rectangle to the specified transform. /// Note that this function will not keep the rectangle's current dimensions, but will instead assume the target's dimensions. /// public void SetAnchor (Transform t) { leftAnchor.target = t; rightAnchor.target = t; topAnchor.target = t; bottomAnchor.target = t; ResetAnchors(); UpdateAnchors(); } /// /// Anchor this rectangle to the specified transform. /// Note that this function will not keep the rectangle's current dimensions, but will instead assume the target's dimensions. /// public void SetAnchor (GameObject go) { Transform t = (go != null) ? go.transform : null; leftAnchor.target = t; rightAnchor.target = t; topAnchor.target = t; bottomAnchor.target = t; ResetAnchors(); UpdateAnchors(); } /// /// Anchor this rectangle to the specified transform. /// public void SetAnchor (GameObject go, int left, int bottom, int right, int top) { Transform t = (go != null) ? go.transform : null; leftAnchor.target = t; rightAnchor.target = t; topAnchor.target = t; bottomAnchor.target = t; leftAnchor.relative = 0f; rightAnchor.relative = 1f; bottomAnchor.relative = 0f; topAnchor.relative = 1f; leftAnchor.absolute = left; rightAnchor.absolute = right; bottomAnchor.absolute = bottom; topAnchor.absolute = top; ResetAnchors(); UpdateAnchors(); } /// /// Ensure that all rect references are set correctly on the anchors. /// public void ResetAnchors () { mAnchorsCached = true; leftAnchor.rect = (leftAnchor.target) ? leftAnchor.target.GetComponent() : null; bottomAnchor.rect = (bottomAnchor.target) ? bottomAnchor.target.GetComponent() : null; rightAnchor.rect = (rightAnchor.target) ? rightAnchor.target.GetComponent() : null; topAnchor.rect = (topAnchor.target) ? topAnchor.target.GetComponent() : null; mCam = NGUITools.FindCameraForLayer(cachedGameObject.layer,root); FindCameraFor(leftAnchor); FindCameraFor(bottomAnchor); FindCameraFor(rightAnchor); FindCameraFor(topAnchor); mUpdateAnchors = true; } /// /// Convenience method that resets and updates the anchors, all at once. /// public void ResetAndUpdateAnchors () { ResetAnchors(); UpdateAnchors(); } /// /// Set the rectangle manually. /// public abstract void SetRect (float x, float y, float width, float height); /// /// Helper function -- attempt to find the camera responsible for the specified anchor. /// void FindCameraFor (AnchorPoint ap) { // If we don't have a target or have a rectangle to work with, camera isn't needed if (ap.target == null || ap.rect != null) { ap.targetCam = null; } else { // Find the camera responsible for the target object ap.targetCam = NGUITools.FindCameraForLayer(ap.target.gameObject.layer,root); } } /// /// Call this function when the rectangle's parent has changed. /// public virtual void ParentHasChanged () { mParentFound = false; UIRect pt = NGUITools.FindInParents(cachedTransform.parent); if (mParent != pt) { if (mParent) mParent.mChildren.Remove(this); mParent = pt; if (mParent) mParent.mChildren.Add(this); mRootSet = false; } } /// /// Abstract start functionality, ensured to happen after the anchor rect references have been set. /// protected abstract void OnStart (); /// /// Abstract update functionality, ensured to happen after the targeting anchors have been updated. /// protected virtual bool OnUpdate () { return true; } #if FUNCELL_MODIFIED public virtual void SelfCalculateFinalAlpha(float parentAlpha){ } #endif #if UNITY_EDITOR /// /// This callback is sent inside the editor notifying us that some property has changed. /// protected virtual void OnValidate () { _isCachedGo = false; _isCachedTrans = false; mRootSet = false; if (mEnabled && NGUITools.GetActive(this)) { if (!Application.isPlaying) ResetAnchors(); Invalidate(true); } } #endif }