extended EZGUI features (implemented and requested)

This is the place to post add-on scripts you have written that use EZ GUI. Please remember only to post your add-on content and not the original EZ GUI scripts.
Forum rules
Only post your own add-on content and not any of the original EZ GUI scripts.

extended EZGUI features (implemented and requested)

Postby harleywinks » Mon Jun 13, 2011 8:16 pm

Hi Brady,

First off, thank you for EZGUI, which has proved to be a vital tool in our Unity games. We've recently completed a project that definitely pushes EZGUI's limits, and I want to share the problems we faced. Some of the problems, we were able to solve, and where that's the case, I've included our solution in the post. For problems that we were not able to solve ourselves, I'm hoping you can help us out with some updated features.

As a point of reference, you can download our app, Lyric Legend from the Android Market (it's free). I can also share the source code for the whole app with you privately if that's of any use.

============
BACKGROUND
============

Lyric Legend is a music game that has a fairly extensive app for purchasing and managing songs and virtual currency. There is no way we could have finished this game on schedule using Unity if we hadn't had EZGUI. That said there were a number of extensions we had to make to EZGUI to cover all the features of the app, and as I think the problems we were grappling with are fairly common issues, I want to share both the problems and our solutions, in the hopes that you will incorporate our features into a future release of EZGUI.

=============================================================================================
ISSUE #1: UIScrollList exhibits very poor load performance when you dynamically add a large number of items.
=============================================================================================

We found that loading a large set of items into a UIScrollList (say, 100 or more) causes a long pause. Digging through the profiler, the main culprit turned out to be that adding n items to a UIScrollList triggers n calls to UIScrollList::UpdateContentExtents and many more calls for item clipping, etc.

========================================================================
SOLUTION TO ISSUE #1: Extend UIScrollList and add a batch-add mode to the subclass
========================================================================

Below is the subclass BatchInsertUIScrollList and a short pseudocode example that illustrates its use

Code: Select all
BatchInsertUIScrollList list = ...//;
list.BeginBatch();
// add a bunch of items
// no UpdateContentExtents calls will happen
list.EndBatch(); // UpdateContents and all other necessary calls happen here


Here's the code for BatchInsertUIScrollList

Code: Select all
using UnityEngine;
using System.Collections;

public class BatchInsertUIScrollList : UIScrollList
{
   public void BeginBatch()
   {
      m_batchContentDelta   = 0;
      m_inBatch = true;
   }
   
   public void EndBatch()
   {
      m_inBatch = false;
      UpdateContentExtents(m_batchContentDelta);
      ClipItems();
      m_batchContentDelta = 0f;
   }
   
   override protected void UpdateContentExtents(float contentDelta)
   {
      if(m_inBatch)
      {
         m_batchContentDelta += contentDelta;
      }
      else
      {
         base.UpdateContentExtents(contentDelta);
      }
   }
   
   override protected void ClipItems()
   {
      if(!m_inBatch)
      {
         base.ClipItems();
      }
   }
   
   private bool m_inBatch = false;
   private float m_batchContentDelta = 0f;
}



===========================================================================================================
ISSUE #2: Need to add dynamic images to scroll lists (e.g. album covers, user profile pictures) and have scroll clipping still work
===========================================================================================================

Since the image content is dynamic, I couldn't see how we could use something like Sprite or PackedSprite.

==============================================================================================================
SOLUTION to ISSUE #2: add an interface for clippable components and a special version of UIListButton that can manage clippables
==============================================================================================================

Our solution was to add 3 new classes to EZGUI: 1) a Clippable interface 2) a ContainerClippingUIListButton that manages/contains a set of Clippable components and 3) a ClippableMesh component that can render an image and also implements Clippable. Code for the three aforementioned classes is below.

Clippable.cs
Code: Select all
public interface Clippable
{
   Rect3D ClippingRect
   {
      get;
      set;
   }
   
   void Unclip();
}


ContainerClippingUIListButton
// NOTE: this impl makes the user explicitly set all child clippables on parent; they could be discovered instead
Code: Select all
using UnityEngine;
using System.Collections;

public class ContainerClippingUIListButton : UIListButton
{
   public SpriteRoot[] m_childSprites;
   public AutoSpriteControlBase[] m_childControls;
   public SpriteText[] m_childLabels;
   public Clippable[] m_childClippables;
   
   public void Reset()
   {
      Unclip();
      m_childSprites = null;
      m_childControls = null;
      m_childLabels = null;
      m_childClippables = null;
   }
   
   public Clippable[] ChildClippables
   {
      get
      {
         return m_childClippables;
      }
      set
      {
          m_childClippables = value;
      }
   }
   
   public SpriteRoot[] ChildSprites
   {
      get
      {
         return m_childSprites;
      }
      set
      {
          m_childSprites = value;
      }
   }
   
   public AutoSpriteControlBase[] ChildControls
   {
      get
      {
         return m_childControls;
      }
      set
      {
          m_childControls = value;
      }
   }
   
   public SpriteText[] ChildLabels
   {
      get
      {
         return m_childLabels;
      }
      set
      {
          m_childLabels = value;
      }
   }
   
   override public Rect3D ClippingRect
   {
      get
      {
         return base.ClippingRect;
      }
      set
      {
         base.ClippingRect = value;
         
         if(m_childSprites != null)
         {
            foreach(SpriteRoot c in m_childSprites)
            {
               c.ClippingRect = value;
            }
         }
         
         if(m_childControls != null)
         {
            foreach(AutoSpriteControlBase c in m_childControls)
            {
               c.ClippingRect = value;
            }
         }
         
         if(m_childLabels != null)
         {
            foreach(SpriteText c in m_childLabels)
            {
               c.ClippingRect = value;
            }
         }
         
         if(m_childClippables != null)
         {
            foreach(Clippable c in m_childClippables)
            {
               c.ClippingRect = value;
            }
         }
      }
   }
   
   override public void Unclip()
   {
      base.Unclip();
      if(m_childSprites != null)
      {
         foreach(SpriteRoot c in m_childSprites)
         {
            c.Unclip();
         }
      }
      
      if(m_childControls != null)
      {
         foreach(AutoSpriteControlBase c in m_childControls)
         {
            c.Unclip();
         }
      }
      
      if(m_childLabels != null)
      {
         foreach(SpriteText c in m_childLabels)
         {
            c.Unclip();
         }
      }
      
      if(m_childClippables != null)
      {
         foreach(Clippable c in m_childClippables)
         {
            c.Unclip();
         }
      }
   }
   
}


ClippableMesh
// NOTE: this could definitely be written better. Seems like there are bugs with repeated calls to SetTexture...
Code: Select all
using UnityEngine;
using System.Collections;

public class ClippableMesh : Clippable
{
   public MeshFilter m_meshFilter;
   public MeshRenderer m_meshRenderer;
   
        public void Reset()
   {
      Init();
   }
   
   public void UpdateTexture(Texture2D t)
   {
      m_meshRenderer.material.mainTexture = t;
   }
   
   public virtual Rect3D ClippingRect
   {
      get { return m_clippingRect; }
      set
      {
         Init();
         m_clippingRect = value;

         m_localClipRect = Rect3D.MultFast(m_clippingRect, this.transform.worldToLocalMatrix).GetRect();

         Clip(m_localClipRect);
      }
   }
   
   
   
   public virtual void Unclip()
   {
      Init();
      
      UpdateMesh(m_topLeft, m_bottomRight, 0f, 0f, 0f, 0f);
   }
   
   protected void SetSize(float w, float h)
   {
      m_width = w;
      m_height = h;
      
      m_topLeft.x = w * -0.5f;
      m_topLeft.y = h * 0.5f;
      
      m_bottomRight.x = w * 0.5f;
      m_bottomRight.y = h * -0.5f;
      

      UpdateMesh(m_topLeft, m_bottomRight, 0f, 0f, 0f, 0f);
      
   }
   
   private void Clip(Rect clip)
   {
      
      float clipLeft = clip.xMin, clipTop = Mathf.Max(clip.yMin, clip.yMax), clipRight = clip.xMax, clipBottom = Mathf.Min(clip.yMin, clip.yMax);
      
      float clipLeftPct = 0f, clipRightPct = 0f, clipTopPct = 0f, clipBottomPct = 0f;
      
      Vector2 tl = new Vector2(m_topLeft.x, m_topLeft.y), br = new Vector2(m_bottomRight.x, m_bottomRight.y);
      
      if(clipLeft > tl.x)
      {
         tl.x = clipLeft;
         clipLeftPct = (clipLeft - m_topLeft.x) / m_width;
      }
      
      
      if(clipTop < tl.y)
      {
         tl.y = clipTop;
         clipTopPct = (m_topLeft.y - clipTop) / m_height;
      }
      
      
      if(clipRight < br.x)
      {
         br.x = clipRight;
         clipRightPct = (m_bottomRight.x - clipRight) / m_width;
      }
      
      
      if(clipBottom > br.y)
      {
         br.y = clipBottom;
         clipBottomPct = (clipBottom - m_bottomRight.y) / m_height;
      }
      
      UpdateMesh(tl, br, clipLeftPct, clipRightPct, clipTopPct, clipBottomPct);
   }
   
   private void UpdateMesh(Vector2 topLeft, Vector2 bottomRight, float clipLeftPct, float clipRightPct, float clipTopPct, float clipBottomPct)
   {
      
      // Upper-left
      m_vertices[0].x = topLeft.x;
      m_vertices[0].y = topLeft.y;
      m_vertices[0].z = 0f;
      
      // Lower-left
      m_vertices[1].x = topLeft.x;
      m_vertices[1].y = bottomRight.y;
      m_vertices[1].z = 0f;
      
      // Lower-right
      m_vertices[2].x = bottomRight.x;
      m_vertices[2].y = bottomRight.y;
      m_vertices[2].z = 0f;

      // Upper-right
      m_vertices[3].x = bottomRight.x;
      m_vertices[3].y = topLeft.y;
      m_vertices[3].z = 0f;
      
      m_mesh.vertices = m_vertices;
      
      Vector2[] uvs = new Vector2[4];
      
      uvs[0] = new Vector2(0f + clipLeftPct, 1f - Mathf.Clamp(clipTopPct, 0f, 1f));
      uvs[1] = new Vector2(0f + clipLeftPct, 0f + Mathf.Clamp(clipBottomPct, 0f, 1f));
      uvs[2] = new Vector2(1f - clipRightPct, 0f + Mathf.Clamp(clipBottomPct, 0f, 1f));
      uvs[3] = new Vector2(1f - clipRightPct, 1f - Mathf.Clamp(clipTopPct, 0f, 1f));
      
      m_mesh.uv = uvs;
      
      InitFaces();
      
      m_mesh.RecalculateNormals();
      m_mesh.RecalculateBounds();
   }
   
   
   private void Init()
   {
      if(!m_init)
      {
         SetTexture(m_meshRenderer.material.mainTexture);
         m_init = true;
      }
   }
   
   private void InitMesh()
   {
      m_mesh = new Mesh();
      m_meshFilter.mesh = m_mesh;
   }
   
   private void InitFaces()
   {
      if(!m_facesInit)
      {
         int[] faces = new int[6];
         faces[0] = 0;   //   0_ 1         0 ___ 3
         faces[1] = 3;   //  | /      Verts:    |   /|
         faces[2] = 1;   // 2|/            1|/__|2
   
         faces[3] = 3;   //     3
         faces[4] = 2;   //   /|
         faces[5] = 1;   // 5/_|4
      
         m_mesh.triangles = faces;
         
         m_facesInit = true;
      }
   }
   
   // make private for now, because for some reason repeat call make texture disappear :-/
   private void SetTexture(Texture t)
   {
      if(t != null)
      {
         InitMesh();
         
         Camera c = (FindObjectOfType(typeof(Camera)) as Camera);
         float pixelRatio = (c.orthographicSize * 2f ) / c.pixelHeight;
         
         SetSize(t.width * pixelRatio, t.height * pixelRatio);
      }
   }
   
   private Vector3[] m_vertices = new Vector3[4];
   
   private bool m_init = false;
   private float m_width;
   private float m_height;
   private bool m_facesInit = false;
   private Rect3D m_clippingRect;
   private Rect m_localClipRect;
   private Vector2 m_topLeft;
   private Vector2 m_bottomRight;
   private Mesh m_mesh;
}



=============================
ISSUE #3: Shrink to fit Sprite Text
=============================

Dynamic text is all over our app, and when the text is too long, we need to try to shrink it to make it fit. I've seen other posts for SpriteText subclasses that cutoff long text and add elipses. In our case, we needed to shrink the text to fit (if possible) and then do the cutoff, only if shrinking to some preset minsize doesn't achieve the configured minwidth for the text.

============================================
SOLUTION for ISSUE #3: DynamicFontSizeSpriteText
============================================

Code is below. Note that this version deals with a single line of text and a max width; it would be even better to have an option to set a maxHeight for multiline text.

DynamicFontSizeSpriteText
Code: Select all
using UnityEngine;
using System.Collections;

public class DynamicFontSizeSpriteText : SpriteText
{
   public bool m_shrinkToFit = true;
   public float m_minCharacterSize = 0.2f;
   public float m_maxWidthSingleLine;
   private float? m_preferedCharacterSize;

   public float MaxWidthSingleLine
   {
      get
      {
         return m_maxWidthSingleLine;
      }
      set
      {
         m_maxWidthSingleLine = value;
      }
   }
   
   override public string Text
   {
      get
      {
         return base.Text;
      }
      set
      {
         if(!m_preferedCharacterSize.HasValue)
         {
            m_preferedCharacterSize = characterSize;
         }
         else if(characterSize != m_preferedCharacterSize.Value)
         {
            SetCharacterSize(m_preferedCharacterSize.Value);
         }
         
         base.Text = value;
         
         if(m_shrinkToFit && m_maxWidthSingleLine > 0)
         {
            float width = Mathf.Abs(UnclippedBottomRight.x - UnclippedTopLeft.x);
            if(width > m_maxWidthSingleLine)
            {
               string displayStr = value;
               
               float pct = m_maxWidthSingleLine/width;
               float charSize = m_preferedCharacterSize.Value * pct;
               
               if(charSize < m_minCharacterSize)
               {
                  int keepChars = Mathf.Clamp(Mathf.FloorToInt((displayStr.Length * (charSize/m_minCharacterSize))) - 1, 0, displayStr.Length);
                  displayStr = displayStr.Substring(0, keepChars) + "...";
                  charSize = m_minCharacterSize;
               }
               
               
               SetCharacterSize(charSize);
            }
            
         }
      }
   }
   
   
}


===========================================================================
ISSUE #4: UIScroll lists with many items load slowly even with batch-insert improvement
===========================================================================

It's for looking at this issue, that I suggest you download Lyric Legend android. I'm not talking about first-load performance (which in our app takes time because data is loading from the web), but rather subsequent loads of, say, our Song Store screen, where all the data is loaded, and the only 'work' that's happening is EZGUI constructing the UIScrollList and populating it with item objects. In these cases, we still frequently see that it takes 1-2 seconds to load a UIScrollList on even relatively high end Android hardware, say, an HTC Evo.

===================================================
Proposed Solution for ISSUE #4: Learn from the iPhone model
===================================================

Seems like the iPhone/CocoaTouch model might be the way to go, e.g. a UIScrollList could have template methods for when an item's data comes into the unclipped/viewable window of the list and then items can be dynamically pulled from a pool/returned to the pool as the user scrolls.

Do you think something like the feature above could make it into an upcoming EZGUI release?

Thanks again for the great product, and again, feel free to contact me directly if you want to.

--Larry
harleywinks
 
Posts: 10
Joined: Wed Dec 15, 2010 5:01 am

Re: extended EZGUI features (implemented and requested)

Postby Brady » Sat Jun 18, 2011 9:44 pm

Thanks for your feedback. Regarding the issues you encountered:

1) This is actually already addressed in the current beta as scroll lists now queue item additions up which are then processed in a single batch at the conclusion of the frame in which they were added. This is all done automatically without having to alter the way the scroll list is used.

2) This issue has actually been solved for some time via the use of UIListItemContainer -- a special container class that lets you use any "regular" sprite-based control or SpriteText in a scroll list. There is also a work-around for UIListItem and UIListButton that just involves adding the child sprites to the item's "Layers" array.

3) Great! Thanks for sharing that one. This will doubtless come in handy for many users.

4) I'm not sure what you mean here. Pooling/unpooling (which is already supported insofar as RemoveItem(index, false) removes the item from the list, but doesn't destroy it) isn't really the problem. The "work" that has to be done when items are added are the positioning and clipping that must be done since the layout of the list changes. However, I still don't believe this to be the cause of the issue you're experiencing. Though I'm not seeing this performance impact in my tests, I have had another report of long item addition times, and after doing some deep profiling as well as searching around, they discovered that, in Unity in general (not just EZ GUI), there is sometimes a very significant overhead incurred when parenting object transforms for some strange reason.

So I suppose one possible way to see if this is the source of the delay would be to comment out the line in RemoveItem() where it un-parents the item being removed, and then add a line in InsertItem() where you check the existing parent of the item being added, and if it is already equal to the "mover" transform, then skip the re-parenting. If you decide to give this a shot, let me know your results as I'm not seeing this slowness in my tests. But I'm interested to see if this one change makes the difference for you.
Brady
 
Posts: 5361
Joined: Tue Jul 06, 2010 11:33 pm

Re: extended EZGUI features (implemented and requested)

Postby harleywinks » Mon Jun 20, 2011 8:56 pm

Hey Brady,

Thanks for the reply. That's great that I can get rid of my BatchInsert scroll list class. When do you expect to release the current beta versions of EZGUI and SM2?

Re slow loading of large lists:

I will look into the parenting issue you called out. To clarify the point I was trying to make about the iPhone API though, say you have a list that has 1000 items in its data model where the visible window shows 4 items at a time. The common iPhone model would create view objects only for the 4 visible items, creating and destroying item view objects as the user scrolls (for efficiency, most implementations would use a pool, and check-in/check-out the list-items views rather than create and destroy them). With the EZGUI UIScrollList on the other hand, the only out-of-the-box option is to instantiate the views for all 1000 list items up front.

The EZGUI UIScrollList is definitely the simpler and better way to go for the common game case where scrolling lists mostly contain a small number of items, but as the number of items in a list grows larger, creating all the item view objects up front and keeping them all in memory becomes a performance bottleneck.

I'm not a super experienced iPhone programmer myself, but as a reference, here is an entry point to their APIs for tables/lists:

http://developer.apple.com/library/ios/ ... eView.html#//apple_ref/doc/uid/TP40007451-CH6-SW10

Keep in mind that the iOS lib is trying to support tables generically which forces their API to be slightly more complex than if it had just needed to support lists. The aspect of the API that's most relevant is how they decompose a table into a UITableView and and UITableViewDataSource.

Thanks again for you time and help.

Larry
harleywinks
 
Posts: 10
Joined: Wed Dec 15, 2010 5:01 am

Re: extended EZGUI features (implemented and requested)

Postby Brady » Mon Jun 20, 2011 9:40 pm

Regarding the beta "going gold", I still have a couple of issues to check into before I'm satisfied that things are all working as expected. So until those are resolved, I really hate to estimate when it might be ready.

Regarding not instancing items until they come into view, the problem is that since EZ GUI can make no assumptions as to the physical size, etc, of the items until they actually exist, there is no way to properly calculate the scroll area, etc. What's more, I think you might find the performance implications of instantiating items at runtime as they come into view just might be as bad or worse since scrolling might not be smooth, but instead would feel really choppy and unresponsive as Unity has to pause for a fraction of a second to instantiate, and possibly load new textures, etc, as items come into view.

Remember, iOS itself can make assumptions and optimize in ways that EZ GUI cannot because of things like item uniformity and the fact that it is rendered in what amounts to "immediate mode". By contrast, EZ GUI must render everything in "retained mode" since everything must be an object instance that physically exists in the scene, and so it cannot wait until the last instant to do some of these types of things. And since list items can be "rich" in that they can contain multiple nested objects of various types (including non-list types), there is no way to centrally aggregate the physical dimensions of an item in advance. It has to be directly measured once the item exists, and this size can change at runtime based on the content of the item.

But if you want to try the allocate-on-the-fly approach, you should be able to approximate it by constantly checking the scroll position, and if it reaches 1 (the end) or more before all items have been added to the list, add another item until you've added all items. That should work except that the scroll coasting might be interrupted when it temporarily reaches the end. While it may appear smooth for a case where all the needed assets (textures, etc) are already in memory, I think it would lead to bad stuttering in cases where the items use additional assets that Unity must load into memory upon instantiation.
Brady
 
Posts: 5361
Joined: Tue Jul 06, 2010 11:33 pm

Re: extended EZGUI features (implemented and requested)

Postby harleywinks » Tue Jun 21, 2011 1:56 am

Tangent: I discovered a minor bug in the new frame-based batch insert that UIScrollList does:

The variable 'itemsAdded' that UIScrollList keeps and then uses in LateUpdate to batch the inserts is not reset in UIScrollList::ClearList().

So if you have a frame that adds items to a list, then clears the list, then adds more items, you end up with ArrayIndexOutOfBounds exceptions in UIScrollList::PositionNewItems(). I found one place in our app that was doing the above, and cleaned it up to not load twice, but UIScrollList should also be fixed not to crash in that circumstance.

Back to issue of lazy item creation, I definitely hear your points.

With regards to the issue of list-item dimensions, you're right that implementing lazy list-item creation implies that the list impl needs some way to know the dimensions of list items without actually creating the associated view objects.

I think the way iPhone deals with this is via the UITableViewDelegate::heightForRowAtIndexPath protocol method. This would be limiting, but for the case of very large lists, I think this would be a more acceptable limit than having to keep all of a list's item-view objects in memory.

I also right that lists stuttering as the user scrolls will likely be a problem. I was hoping this could be mitigated by pooling list-view objects (which would at least save the cost of repeatedly instantiating and destroying game objects). It's definitely possible that even with pooling, the churn of activating/deactivating pooled list objects would still cause choppy scroll performance, but it's hard really know without trying it.
harleywinks
 
Posts: 10
Joined: Wed Dec 15, 2010 5:01 am

Re: extended EZGUI features (implemented and requested)

Postby Rafe » Tue Jun 21, 2011 4:24 pm

Hi,

I have a scroll list running off a pooling system. I'll edit this and post the code when I get some time at home (probably tomorrow late-night (This post is my reminder). It is using PoolManager but if you don't have it then I'm sure the pattern will still come in handy. It was tricky getting it to work. Mine isn't interactive though (it is triggered by code to change positions). I'm not sure if that will matter or not.

Remember that with Pooling you still have everything loaded in memory (which is where the advantage comes from), it just doesn't hit performance. Still, this is far better than creating and destroying things, unless you have memory issues rather than performance issues.

I implemented pooling with my scroll list because I needed it to be dynamic, but if you know what your content is, is it really not better to just put it all up and let the camera rendering ignore off-screen items? Or is the scroll list processing off-screen items?
- Rafe
PoolManager Developer and EZ[everything] fan
Check out the Path-o-logical Games Asset Packages.
Rafe
 
Posts: 174
Joined: Mon Mar 07, 2011 1:45 pm

Re: extended EZGUI features (implemented and requested)

Postby Brady » Tue Jun 21, 2011 7:08 pm

@harleywinks
Regarding the batching bug, yes, that issue has been fixed in the internal version that will be going out soon.

Rafe is correct though, you definitely don't want to create and then destroy items all the time. So even a "lazy" system would still need to leave items in memory once instantiated for performance reasons, so keeping the full list out of memory wouldn't really be one of the benefits, at least not once you scrolled to the bottom of the list. The main benefit I see if that the list could get up-and-running faster, but with the terrible potential for glitchy and stuttering scrolling, which is especially the case if the items being instantiated draw upon texture or audio assets which have not, as yet, been loaded.

That said, I think in a highly specific use case, it might work okay, but would have to either forgo the use of a few of the scroll list's features or require that all items be of the same extent, and would need all items to draw upon already-loaded assets. However, if it turns out that transform parenting is, indeed, one of the bottlenecks, then even those things wouldn't prevent stuttering while scrolling.

Regarding getting up-and-running quickly with a large item list, another alternative might instead be to add items a few at a time over several frames. So let's say your list can only display 10 items at once. So you could just add 10 items in the first frame, 10 more in the next frame, and so on until your entire list is loaded. It still probably won't be smooth as silk if you try to scroll while it is still loading items, but it would be smooth after a couple of seconds when the list is finished being loaded.
Brady
 
Posts: 5361
Joined: Tue Jul 06, 2010 11:33 pm

Re: extended EZGUI features (implemented and requested)

Postby Rafe » Fri Jun 24, 2011 3:12 am

OK, I stripped this down and didn't test it (the Update() function contains some pseudo class/types), but the code in the rest of the functions should help anyone who is looking to run a scroll list dynamically and from code. I hope this helps...

(This code would require PoolManager, but again, you can substitute your own pooling system if you have one.)

Basically, you would set it up and then during game-play run ScrollListSample.Instance.MoveNext() to trigger the update

The tricky part was getting the co-routine to work and not error out if ran two times really fast (the second is running while the first is not yet done). This is where I hope this pattern really helps someone.

The last function is where the real meat is. This is the co-routine itself. How you trigger it is more case-specific.


Code: Select all
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

/// <description>
/// Controls a dynamic scroll list that displays a queue of icons and when
///   advanced, it pops the last one off and animates the rest slidding in to
///   place
/// </description>
public class ScrollListSample : MonoBehaviour
{
   #region Public Properties
    public EZAnimation.EASING_TYPE scrollEasing = EZAnimation.EASING_TYPE.Linear;
    public float scrollDuration = 1f;
    public UIScrollList scrollList;

    public List<Transform> icons = new List<Transform>();

    public Transform spacer;
    #endregion Public Properties


    /// <summary>
    /// Static access to run MoveNext() from an external script.
    /// </summary>
    private static ScrollListSample _instance;
    public static ScrollListSample Instance
    {
        get { return _instance; }
    }


    /// <summary>
    /// Run to trigger the update of the scroll list
    /// </summary>
    /// <param name="enqueueIcon">The icon prefab to add</param>
    public static void MoveNext(Transform enqueueIcon)
    {
        Instance.StartCoroutine(Instance.DoSendNext(enqueueIcon));
    }   


    #region Private Properties
    private SpawnPool pool;
    private bool isIconsInit = false;
    #endregion Private Properties


    /// <summary>
    /// Init Static Access
    /// </summary>
    private void Awake()
    {
        // Init the static property Instance to a reference of this instance
        _instance = this;
    }


    /// <summary>
    /// Init class stuff
    /// </summary>
   private void Start()
    {
        // Grab a reference to the PoolManager SpawnPool
        this.pool = PoolManager.Pools["ScrollListIcons"];
    }


    /// <summary>
    /// Includes a singleton to finish init after Start()
    /// </summary>
    private void Update()
   {
        // Singleton to be sure this happens ONCE after Start()
        //    Icons may be populated during start by an outside script, so this makes sure this is done after
        #region SINGLETON Init Icons from Pathing
        // Cheap check first - should always be true if not init yet
        if (!this.isIconsInit && this.scrollList.Count == 0)
        {
            this.isIconsInit = true;

            // Init the queue (reference to the queue holding items to scroll)
            List<QueueItems> items = QueueClass.Queue;
            if (items.Count < 10)
            {
                Debug.LogError("Can't have less than 10!");
                return;
            }


            for (int i = 0; i < 10; i++)
            {
                Transform item = items[items.Count - 1 - i];
                this.scrollList.AddItem(this.Spawn(item));
            }

            // Add a spacer
            this.scrollList.AddItem(this.Spawn(this.spacer));
        }
        #endregion SINGLETON Init Icons from Pathing
    }


    /// <summary>
    /// Instances using SpawnPool.Spawn and connects the UI Manger and returns
    /// a GameObject, which is more friendly with EZGUI's ScrollList methods.
    /// </summary>
    /// <param name="prefab">The prefab to instance</param>
    /// <returns>GameObject</returns>
    private GameObject Spawn(Transform prefab)
    {
        Transform inst = this.pool.Spawn(prefab);
        this.scrollList.manager.AddSprite(inst.gameObject);
        return inst.gameObject;
    }


    /// <summary>
    /// Triggers the update to pop an icon off the bottom and
    /// play the animation to slide the icons down and the new icon on-screen.
    /// THIS IS RUN AS A COROUTINE by the static function MoveNext()
    /// </summary>
    /// <param name="enqueueIcon">The icon prefab to add</param>
    /// <returns>void</returns>
    private IEnumerator DoSendNext(Transform enqueueIcon)
    {
        // REMOVE THE LAST ICON. The space off-screen will move in to the gap
        //   The spacer is a UIList item with no texture but set to be the
        //   same size as the other icons.
        IUIListObject remove;
        remove = this.scrollList.GetItem(this.scrollList.Count - 2);
        this.scrollList.RemoveItem(remove, false);  // ScrolList
        remove.transform.parent = this.pool.group;  // Put back in Pool Group
        this.pool.Despawn(remove.transform);        // Pool despawn
       

        // ADD THE NEW ICON off-screen to be scrolled on
        //   Add an item then set the list to the end so the item is clipped
        //   and ready to come on screen.
        if (enqueueIcon == null) enqueueIcon = this.spacer;
        GameObject newIconInst = this.Spawn(enqueueIcon);
        UIListItem newIcon = newIconInst.GetComponent<UIListItem>();
        this.scrollList.InsertItem(newIcon, 0);  //<--THE NEW ITEM
           
        // Set the scroll to the starting point and trigger the animation
        this.scrollList.ScrollListTo(1);
        this.scrollList.ScrollToItem(newIcon,
                                     this.scrollDuration,
                                     this.scrollEasing);

        yield return null;
    }

}


Edit: That update is ugly for a single use situation. It should probably be a co-routine.
Last edited by Rafe on Mon Jul 18, 2011 6:17 am, edited 1 time in total.
- Rafe
PoolManager Developer and EZ[everything] fan
Check out the Path-o-logical Games Asset Packages.
Rafe
 
Posts: 174
Joined: Mon Mar 07, 2011 1:45 pm

Re: extended EZGUI features (implemented and requested)

Postby Brady » Fri Jun 24, 2011 6:58 pm

Cool! Thanks for sharing. I'll have to give this a try when I get the chance.
Brady
 
Posts: 5361
Joined: Tue Jul 06, 2010 11:33 pm

Re: extended EZGUI features (implemented and requested)

Postby Rafe » Sun Jun 26, 2011 7:31 am

I just noticed I'm doing some parenting that is really only organizational for development and could be commented out if it is a performance hit. I read in another thread that parenting can be intense, though in this case it is only done rarely and with a very shallow hierarchy (one item in my case) it might be fine.
- Rafe
PoolManager Developer and EZ[everything] fan
Check out the Path-o-logical Games Asset Packages.
Rafe
 
Posts: 174
Joined: Mon Mar 07, 2011 1:45 pm

Next

Return to EZ GUI Add-ons

Who is online

Users browsing this forum: No registered users and 4 guests

cron