/// <summary> /// Add the specified widget to the managed list. /// </summary> public void AddWidget(NGUIWidget w) { if (w != null) { #if UNITY_EDITOR if (w.cachedTransform.parent != null) { NGUIWidget parentWidget = NGUITools.FindInParents <NGUIWidget>(w.cachedTransform.parent.gameObject); if (parentWidget != null) { w.cachedTransform.parent = parentWidget.cachedTransform.parent; Debug.LogError("You should never nest widgets! Parent them to a common game object instead. Forcefully changing the parent.", w); // If the error above gets triggered, it means that you parented one widget to another. // If left unchecked, this may lead to odd behavior in the UI. Consider restructuring your UI. // For example, if you were trying to do this: // Widget #1 // | // +- Widget #2 // You can do this instead, fixing the problem: // GameObject (scale 1, 1, 1) // | // +- Widget #1 // | // +- Widget #2 } } #endif NGUINode node = AddTransform(w.cachedTransform); if (node != null) { node.widget = w; w.visibleFlag = 1; if (!mWidgets.Contains(w)) { mWidgets.Add(w); if (!mChanged.Contains(w.material)) { mChanged.Add(w.material); mChangedLastFrame = true; } mDepthChanged = true; mWidgetsAdded = true; } } else { Debug.LogError("Unable to find an appropriate root for " + NGUITools.GetHierarchy(w.gameObject) + "\nPlease make sure that there is at least one game object above this widget!", w.gameObject); } } }
/// <summary> /// Helper function to retrieve the node of the specified transform. /// </summary> NGUINode GetNode(Transform t) { NGUINode node = null; #if USE_SIMPLE_DICTIONARY if (t != null) { mChildren.TryGetValue(t, out node); } #else if (t != null && mChildren.Contains(t)) { node = (NGUINode)mChildren[t]; } #endif return(node); }
/// <summary> /// Remove the specified widget from the managed list. /// </summary> public void RemoveWidget(NGUIWidget w) { if (w != null) { // Do we have this node? Mark the widget's material as having been changed NGUINode pc = GetNode(w.cachedTransform); if (pc != null) { // Mark the material as having been changed if (pc.visibleFlag == 1 && !mChanged.Contains(w.material)) { mChanged.Add(w.material); mChangedLastFrame = true; } // Remove this transform RemoveTransform(w.cachedTransform); } mWidgets.Remove(w); } }
/// <summary> /// Add the specified transform to the managed list. /// </summary> NGUINode AddTransform(Transform t) { NGUINode node = null; NGUINode retVal = null; // Add transforms all the way up to the panel while (t != null && t != cachedTransform) { #if USE_SIMPLE_DICTIONARY if (mChildren.TryGetValue(t, out node)) { if (retVal == null) { retVal = node; } } #else if (mChildren.Contains(t)) { if (retVal == null) { retVal = (NGUINode)mChildren[t]; } } #endif else { // The node is not yet managed -- add it to the list node = new NGUINode(t); if (retVal == null) { retVal = node; } mChildren.Add(t, node); } t = t.parent; } return(retVal); }
/// <summary> /// Convenience function that figures out the panel's correct change flag by searching the parents. /// </summary> int GetChangeFlag(NGUINode start) { int flag = start.changeFlag; if (flag == -1) { Transform trans = start.trans.parent; NGUINode sub; // Keep going until we find a set flag for (;;) { // Check the parent's flag #if USE_SIMPLE_DICTIONARY if (trans != null && mChildren.TryGetValue(trans, out sub)) { #else if (trans != null && mChildren.Contains(trans)) { sub = (NGUINode)mChildren[trans]; #endif flag = sub.changeFlag; trans = trans.parent; // If the flag hasn't been set either, add this child to the hierarchy if (flag == -1) { mHierarchy.Add(sub); } else { break; } } else { flag = 0; break; } } // Update the parent flags for (int i = 0, imax = mHierarchy.size; i < imax; ++i) { NGUINode pc = mHierarchy.buffer[i]; pc.changeFlag = flag; } mHierarchy.Clear(); } return(flag); } /// <summary> /// Update the world-to-local transform matrix as well as clipping bounds. /// </summary> void UpdateTransformMatrix() { if (mUpdateTime == 0f || mMatrixTime != mUpdateTime) { mMatrixTime = mUpdateTime; mWorldToLocal = cachedTransform.worldToLocalMatrix; if (mClipping != NGUIDrawCall.Clipping.None) { Vector2 size = new Vector2(mClipRange.z, mClipRange.w); if (size.x == 0f) { size.x = (mCam == null) ? Screen.width : mCam.pixelWidth; } if (size.y == 0f) { size.y = (mCam == null) ? Screen.height : mCam.pixelHeight; } size *= 0.5f; mMin.x = mClipRange.x - size.x; mMin.y = mClipRange.y - size.y; mMax.x = mClipRange.x + size.x; mMax.y = mClipRange.y + size.y; } } } /// <summary> /// Run through all managed transforms and see if they've changed. /// </summary> void UpdateTransforms() { mChangedLastFrame = false; bool transformsChanged = false; bool shouldCull = false; #if UNITY_EDITOR shouldCull = (clipping != NGUIDrawCall.Clipping.None) && (!Application.isPlaying || mUpdateTime > mCullTime); if (!Application.isPlaying || !widgetsAreStatic || mWidgetsAdded || shouldCull != mCulled) #else shouldCull = (clipping != NGUIDrawCall.Clipping.None) && (mUpdateTime > mCullTime); if (!widgetsAreStatic || mWidgetsAdded || shouldCull != mCulled) #endif { #if USE_SIMPLE_DICTIONARY foreach (KeyValuePair <Transform, NGUINode> child in mChildren) { NGUINode node = child.Value; #else for (int i = 0, imax = mChildren.Count; i < imax; ++i) { NGUINode node = (NGUINode)mChildren[i]; #endif if (node.trans == null) { mRemoved.Add(node.trans); continue; } if (node.HasChanged()) { node.changeFlag = 1; transformsChanged = true; #if UNITY_EDITOR Vector3 s = node.trans.lossyScale; float min = Mathf.Abs(Mathf.Min(s.x, s.y)); if (min == 0f) { Debug.LogError("Scale of 0 is invalid! Zero cannot be divided by, which causes problems. Use a small value instead, such as 0.01\n" + node.trans.lossyScale, node.trans); } #endif } else { node.changeFlag = -1; } } // Clean up the deleted transforms for (int i = 0, imax = mRemoved.Count; i < imax; ++i) { mChildren.Remove(mRemoved[i]); } mRemoved.Clear(); } // If the children weren't culled but should be, check their visibility if (!mCulled && shouldCull) { mCheckVisibility = true; } // If something has changed, propagate the changes *down* the tree hierarchy (to children). // An alternative (but slower) approach would be to do a pc.trans.GetComponentsInChildren<NGUIWidget>() // in the loop above, and mark each one as dirty. if (mCheckVisibility || transformsChanged || mRebuildAll) { #if USE_SIMPLE_DICTIONARY foreach (KeyValuePair <Transform, NGUINode> child in mChildren) { NGUINode pc = child.Value; #else for (int i = 0, imax = mChildren.Count; i < imax; ++i) { NGUINode pc = (NGUINode)mChildren[i]; #endif if (pc.widget != null) { int visibleFlag = 1; // No sense in checking the visibility if we're not culling anything (as the visibility is always 'true') if (shouldCull || transformsChanged) { // If the change flag has not yet been determined... if (pc.changeFlag == -1) { pc.changeFlag = GetChangeFlag(pc); } // Is the widget visible? if (shouldCull) { visibleFlag = (mCheckVisibility || pc.changeFlag == 1) ? (IsVisible(pc.widget) ? 1 : 0) : pc.visibleFlag; } } // If visibility changed, mark the node as changed as well if (pc.visibleFlag != visibleFlag) { pc.changeFlag = 1; } // If the node has changed and the widget is visible (or was visible before) if (pc.changeFlag == 1 && (visibleFlag == 1 || pc.visibleFlag != 0)) { // Update the visibility flag pc.visibleFlag = visibleFlag; Material mat = pc.widget.material; // Add this material to the list of changed materials if (!mChanged.Contains(mat)) { mChanged.Add(mat); mChangedLastFrame = true; } } } } } mCulled = shouldCull; mCheckVisibility = false; mWidgetsAdded = false; } /// <summary> /// Update all widgets and rebuild their geometry if necessary. /// </summary> void UpdateWidgets() { #if USE_SIMPLE_DICTIONARY foreach (KeyValuePair <Transform, NGUINode> c in mChildren) { NGUINode pc = c.Value; #else for (int i = 0, imax = mChildren.Count; i < imax; ++i) { NGUINode pc = (NGUINode)mChildren[i]; #endif NGUIWidget w = pc.widget; // If the widget is visible, update it if (pc.visibleFlag == 1 && w != null && w.UpdateGeometry(this, ref mWorldToLocal, (pc.changeFlag == 1), generateNormals)) { // We will need to refill this buffer if (!mChanged.Contains(w.material)) { mChanged.Add(w.material); mChangedLastFrame = true; } } pc.changeFlag = 0; } } /// <summary> /// Update the clipping rect in the shaders and draw calls' positions. /// </summary> public void UpdateDrawcalls() { Vector4 range = Vector4.zero; if (mClipping != NGUIDrawCall.Clipping.None) { range = new Vector4(mClipRange.x, mClipRange.y, mClipRange.z * 0.5f, mClipRange.w * 0.5f); } if (range.z == 0f) { range.z = Screen.width * 0.5f; } if (range.w == 0f) { range.w = Screen.height * 0.5f; } RuntimePlatform platform = Application.platform; if (platform == RuntimePlatform.WindowsPlayer || platform == RuntimePlatform.WindowsWebPlayer || platform == RuntimePlatform.WindowsEditor) { range.x -= 0.5f; range.y += 0.5f; } Transform t = cachedTransform; for (int i = 0, imax = mDrawCalls.size; i < imax; ++i) { NGUIDrawCall dc = mDrawCalls.buffer[i]; dc.clipping = mClipping; dc.clipRange = range; dc.clipSoftness = mClipSoftness; dc.depthPass = depthPass && mClipping == NGUIDrawCall.Clipping.None; // Set the draw call's transform to match the panel's. // Note that parenting directly to the panel causes unity to crash as soon as you hit Play. Transform dt = dc.transform; dt.position = t.position; dt.rotation = t.rotation; dt.localScale = t.lossyScale; } } /// <summary> /// Set the draw call's geometry responsible for the specified material. /// </summary> void Fill(Material mat) { // Cleanup deleted widgets for (int i = mWidgets.size; i > 0;) { if (mWidgets[--i] == null) { mWidgets.RemoveAt(i); } } // Fill the buffers for the specified material for (int i = 0, imax = mWidgets.size; i < imax; ++i) { NGUIWidget w = mWidgets.buffer[i]; if (w.visibleFlag == 1 && w.material == mat) { NGUINode node = GetNode(w.cachedTransform); if (node != null) { if (generateNormals) { w.WriteToBuffers(mVerts, mUvs, mCols, mNorms, mTans); } else { w.WriteToBuffers(mVerts, mUvs, mCols, null, null); } } else { Debug.LogError("No transform found for " + NGUITools.GetHierarchy(w.gameObject), this); } } } if (mVerts.size > 0) { // Rebuild the draw call's mesh NGUIDrawCall dc = GetDrawCall(mat, true); dc.depthPass = depthPass && mClipping == NGUIDrawCall.Clipping.None; dc.Set(mVerts, generateNormals ? mNorms : null, generateNormals ? mTans : null, mUvs, mCols); } else { // There is nothing to draw for this material -- eliminate the draw call NGUIDrawCall dc = GetDrawCall(mat, false); if (dc != null) { mDrawCalls.Remove(dc); NGUITools.DestroyImmediate(dc.gameObject); } } // Cleanup mVerts.Clear(); mNorms.Clear(); mTans.Clear(); mUvs.Clear(); mCols.Clear(); } /// <summary> /// Main update function /// </summary> void LateUpdate() { mUpdateTime = Time.realtimeSinceStartup; UpdateTransformMatrix(); UpdateTransforms(); // Always move widgets to the panel's layer if (mLayer != gameObject.layer) { mLayer = gameObject.layer; NGUICamera uic = NGUICamera.FindCameraForLayer(mLayer); mCam = (uic != null) ? uic.cachedCamera : NGUITools.FindCameraForLayer(mLayer); SetChildLayer(cachedTransform, mLayer); var cache = drawCalls; for (int i = 0, imax = cache.size; i < imax; ++i) { mDrawCalls.buffer[i].gameObject.layer = mLayer; } } UpdateWidgets(); // If the depth has changed, we need to re-sort the widgets if (mDepthChanged) { mDepthChanged = false; mWidgets.Sort(NGUIWidget.CompareFunc); } var dcInit = drawCalls; // Fill the draw calls for all of the changed materials for (int i = 0, imax = mChanged.size; i < imax; ++i) { Fill(mChanged.buffer[i]); } // Update the clipping rects UpdateDrawcalls(); mChanged.Clear(); mRebuildAll = false; #if UNITY_EDITOR mScreenSize = new Vector2(Screen.width, Screen.height); #endif }