Search Unity

SpriteManager - draw lots of sprites in a single draw call!

Discussion in 'iOS and tvOS' started by Brady, Jan 15, 2009.

  1. RBerg

    RBerg

    Joined:
    Aug 14, 2008
    Posts:
    10
    AWESOME BRADY! Thank you!
     
  2. seon

    seon

    Joined:
    Jan 10, 2007
    Posts:
    1,441
    Code (csharp):
    1.     public void TransformBillboarded(Sprite sprite)
    2.     {
    3.         Vector3 pos = sprite.clientTransform.position;
    4.  
    5.         vertices[sprite.mv1] = pos + t.TransformDirection(sprite.v1);
    6.         vertices[sprite.mv2] = pos + t.TransformDirection(sprite.v2);
    7.         vertices[sprite.mv3] = pos + t.TransformDirection(sprite.v3);
    8.         vertices[sprite.mv4] = pos + t.TransformDirection(sprite.v4);
    9.  
    10.         vertsChanged = true;
    11.     }
    Where is t defined? This errors out when compiling.
     
  3. RBerg

    RBerg

    Joined:
    Aug 14, 2008
    Posts:
    10
    Seon,

    It looks like t should be defined in that method as:

    Code (csharp):
    1. Transform t = Camera.main.transform;
    ...might have been a copy/paste oversight.
     
  4. seon

    seon

    Joined:
    Jan 10, 2007
    Posts:
    1,441
    Thanks RBerg :)

    I'm not using billboards anyway, so had just commented out that function contents, but thought I would put it out there so it could be fixed.

    Brady, I have added a few extra functions here to make it easy to remap sprite UV's on the fly... how do I go about getting it added for the community?

    Here is the code if anyone is interested :)


    Code (csharp):
    1.  
    2. public void RemapSprite(Sprite sprite, int leftPixelX, int bottomPixelY)
    3. {
    4.     RemapSprite(sprite, PixelCoordToUVCoord(leftPixelX, bottomPixelY));
    5. }
    6.  
    7. public void RemapSprite(Sprite sprite, Vector2 newLowerLeftUV) {
    8.  
    9.     UVs[sprite.uv1] = newLowerLeftUV + Vector2.up * sprite.uvDimensions.y;
    10.     UVs[sprite.uv2] = newLowerLeftUV;                          
    11.     UVs[sprite.uv3] = newLowerLeftUV + Vector2.right * sprite.uvDimensions.x;
    12.     UVs[sprite.uv4] = newLowerLeftUV + sprite.uvDimensions;
    13.  
    14.     uvsChanged = true;
    15. }
    16.  
    Basically, it assumes that you have a sequence of images on your texture all the same size, so it lets you easily change the lower left start position for the image in either pixels or UV coordinates.

    I use it a lot for change of health bar visual without needing to remove and read sprites. I have them all 32x8 pixels stacked above each other, and use the RemapSprite to chance the start lower left using a switch statement.

    Exampe:-

    Code (csharp):
    1. function updateHealthUV() {
    2.     if (myHealthSprite) {
    3.         switch (myHealthValue) {
    4.             case 10:
    5.                 SM.RemapSprite(myHealthSprite, 480, 512);
    6.                 break;
    7.             case 9:
    8.                 SM.RemapSprite(myHealthSprite, 480, 504);
    9.                 break;
    10.             case 8:
    11.                 SM.RemapSprite(myHealthSprite, 480, 496);
    12.                 break;
    13.             case 7:
    14.                 SM.RemapSprite(myHealthSprite, 480, 488);
    15.                 break;
    16.             case 6:
    17.                 SM.RemapSprite(myHealthSprite, 480, 480);
    18.                 break;
    19.             case 5:
    20.                 SM.RemapSprite(myHealthSprite, 480, 472);
    21.                 break;
    22.             case 4:
    23.                 SM.RemapSprite(myHealthSprite, 480, 464);
    24.                 break;
    25.             case 3:
    26.                 SM.RemapSprite(myHealthSprite, 480, 456);
    27.                 break;
    28.             case 2:
    29.                 SM.RemapSprite(myHealthSprite, 480, 448);
    30.                 break;
    31.             case 1:
    32.                 SM.RemapSprite(myHealthSprite, 480, 440);
    33.                 break;
    34.             case 0:
    35.                 SM.RemapSprite(myHealthSprite, 480, 432);
    36.                 break;
    37.         }
    38.     }
    39. }
    You might have already had functionality for this somewhere, but this seemed like an easy addition that is easy to understand and use.

    Cheers :)
     
  5. CedarParkDad

    CedarParkDad

    Joined:
    May 28, 2008
    Posts:
    98
    Finally, a place where I can truly help! One quick speedup for billboards will triple the transform speed:

    Old
    Code (csharp):
    1.  
    2.     void TransformSprites()
    3.     {
    4.         for(int i=0; i<activeBlocks.Count; ++i)
    5.         {
    6.             ((Sprite)activeBlocks[i]).Transform();
    7.         }
    8.  
    9.         // Handle any billboarded sprites:
    10.         if(activeBillboards.Count > 0)
    11.         {
    12.             Transform t = Camera.main.transform;
    13.             Vector3 pos;
    14.             Sprite s;
    15.  
    16.             for(int i=0; i<activeBillboards.Count; ++i)
    17.             {
    18.                 s = (Sprite)activeBillboards[i];
    19.                 pos = s.clientTransform.position;
    20.  
    21.                 vertices[s.mv1] = pos + t.TransformDirection(s.v1);
    22.                 vertices[s.mv2] = pos + t.TransformDirection(s.v2);
    23.                 vertices[s.mv3] = pos + t.TransformDirection(s.v3);
    24.                 vertices[s.mv4] = pos + t.TransformDirection(s.v4);
    25.             }
    26.         }
    27.     }
    28.  
    New
    Code (csharp):
    1.  
    2.     void TransformSprites()
    3.     {
    4.         for(int i=0; i<activeBlocks.Count; ++i)
    5.         {
    6.             ((Sprite)activeBlocks[i]).Transform();
    7.         }
    8.  
    9.         // Handle any billboarded sprites:
    10.         if(activeBillboards.Count > 0)
    11.         {
    12.             Transform t = Camera.main.transform;
    13.             Vector3 pos;
    14.             Vector3 setValue;
    15.             Vector3 transformedValue;
    16.             Sprite s;
    17.  
    18.             for(int i=0; i<activeBillboards.Count; ++i)
    19.             {
    20.                 s = (Sprite)activeBillboards[i];
    21.                 pos = s.clientTransform.position;
    22.  
    23.                 // v1 to mv1
    24.                 setValue = vertices[s.mv1];
    25.                 transformedValue = t.TransformDirection(s.v1);
    26.                 setValue.x = pos.x + transformedValue.x;
    27.                 setValue.y = pos.y + transformedValue.y;
    28.                 setValue.z = pos.z + transformedValue.z;
    29.  
    30.                 // v2 to mv2
    31.                 setValue = vertices[s.mv2];
    32.                 transformedValue = t.TransformDirection(s.v2);
    33.                 setValue.x = pos.x + transformedValue.x;
    34.                 setValue.y = pos.y + transformedValue.y;
    35.                 setValue.z = pos.z + transformedValue.z;
    36.  
    37.                 // v3 to mv3
    38.                 setValue = vertices[s.mv3];
    39.                 transformedValue = t.TransformDirection(s.v3);
    40.                 setValue.x = pos.x + transformedValue.x;
    41.                 setValue.y = pos.y + transformedValue.y;
    42.                 setValue.z = pos.z + transformedValue.z;
    43.  
    44.                 // v4 to mv4
    45.                 setValue = vertices[s.mv4];
    46.                 transformedValue = t.TransformDirection(s.v4);
    47.                 setValue.x = pos.x + transformedValue.x;
    48.                 setValue.y = pos.y + transformedValue.y;
    49.                 setValue.z = pos.z + transformedValue.z;
    50.             }
    51.         }
    52.     }
    53.  
    This is because there is a Vector3 at each index which will get replaced with a Vector3 which gets created in the "operator +" of the Vector3. You not only have allocation time for the new one but deallocation time and garbage collection for the old. This simply reuses the one that is already there. It can be done throughout the code. Yes, I only do this when I'm done with a section of code and I'm optimizing because it is not fun to read ;-)

    I added the setValue variable because I'm not sure that Mono optimizes repeat uses in a stack frame, whereas Microsoft C# does.
     
  6. seon

    seon

    Joined:
    Jan 10, 2007
    Posts:
    1,441
    @CedarParkDad: So, this code optimisation WILL increase performance, or MIGHT increase performance? You have tested this and it works that much faster?
     
  7. CedarParkDad

    CedarParkDad

    Joined:
    May 28, 2008
    Posts:
    98
    Yes, I tested it. It is that much faster as I timed 100000 vector3 adds. But remember this is just on the time spent doing the transforms. Is that why billboards have the extra time? I don't know that, but it "feels" faster. This can be done more cleanly, but by adding a stack frame, by having a fastVector3Add() function which takes the 3 vectors.

    There are a few other places where I believe some time can be saved, but I haven't done any testing beyond this hit. I'll spend more time this weekend on C#-specific optimizations, as it looks like everything else has been minimized nicely.
     
  8. seon

    seon

    Joined:
    Jan 10, 2007
    Posts:
    1,441
    @CedarParkDad: Awesome work :)
     
  9. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    Thanks for all the input, guys!

    Seon: I don't know how that one got past me, but I'll get that fixed right away. I'm writing this quickly as I'm short on time, but I'll give your UV funds a look soon.

    CedarParkDad: Thanks for the optimization. I'll do some tests to see the kind of gain it yields. I was worried about the "temporary" vars that get created by the current code too and actually have a version that deals with the components the way you did directly, but for some reason, it wasn't working so I just went ahead with the current version. The main performance difference should be from the vector add and temporary variable handling since other than that, the transform process is almost identical to the conventional method.
     
  10. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    Okay, here's an update:

    Seon: After reviewing things, it occurred to me that this functionality should already exist through directly modifying the Sprite's lowerLeftUV property. To use pixel values, you'd just need to do:

    sprite.lowerLeftUV = PixelCoordToUVCoord(x, y);

    Let me know if I'm missing something or if the above doesn't produce the desired behavior.


    CedarParkDad: Okay, I'm not sure why my earlier attempt to directly add components didn't work, but I tried your code, as well as a version that removed the setValue assignment in favor of direct access to the indexed vertex values. Both gave me very interesting results:

    First, for some strange reason that I've not yet figured out, your version (using setValue) would not update the positions of the sprites according to their client objects nor would it billboard them. My best guess is that after assigning setValue, somehow its components weren't actually referencing the components of the vertices on the vertex list. I can't figure out why this would be, but it definitely wasn't writing to the original vertices at all. The version that used the actual indexed vertices worked fine, however.

    Now here's the second interesting thing: I do know that it was doing work (there wasn't something causing it to skip that block of code) because it was actually significantly slower than my original implementation. The version directly assigning to the vertex buffer performed the same as the setValue version.

    I'm testing on an iPod Touch 1st gen, and I have about 100 sprites - all billboarded. With my original implementation, I get a pretty consistent 20fps (no bounds recalculation). With both versions of the direct component access method I got between 12-18fps. For whatever reason, it would give me a pretty consistent 15fps for the first second or two, and then would fluctuate between 16 and 18fps.

    When I was originally working on using the direct component access method, I had considered the possibility that it could be slower for the following reason: though my final method created the overhead of a temporary variable, it relies on mults and adds, etc, that are all being done in optimized machine code by Unity internally. Whereas the direct component method is doing almost ALL of the operations through script (though compiled to ARM instructions, it is certainly not as optimized). That is my best guess as to why I received the results I did, and it sounds like a reasonable hypothesis.

    Incidentally, I had implemented several other methods, including one which was extremely simple and COMPLETELY did away with temporary variables (no transform call), had no calling overhead, and used only 1 mult and 2 adds per vector component, and this one gave me a consistent 12fps. I had concluded then that this was due to having it all done in script, and this further reinforces my above theory.

    So I guess the moral of the story is, call into optimized engine code as much as possible and minimize what you do in script even if it means a bit of temporary variable overhead.
     
  11. CedarParkDad

    CedarParkDad

    Joined:
    May 28, 2008
    Posts:
    98
    Hmmm...I'll have to play with it myself over the weekend. I see what you mean about direct engine calls. I am going to do testing purely with changing vectors and not within your code so that I get the absolute answers. Of course, any insight by Unity folks as to which calls are into the engine and which calls are actually using script-related functionality would be a great help in speeding this script up.

    Once I get my tests done I'll post the times. Thank you for trying it directly.
     
  12. seon

    seon

    Joined:
    Jan 10, 2007
    Posts:
    1,441
    Hey Brady, I didn't even know I could use that functionality from your script, thats why I rolled my own functions :) Thanks for letting me know of an easier way :)

    Edit: Swapped over to your suggested way and works like a charm :)

    Also, is there any way of making a sprite invisible/visible... like a show/hide feature, without resorting shrinking its UV's or removing and then re-adding?

    I would love a sprite.visible = true/false option.
     
  13. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    Hmmm... well, besides removing or shrinking its size, you could assign it a transparent color. Like:

    sprite.color = new Color(1,1,1,0);

    I'll think about adding a Hide() function. That's a good idea. Though I think the only really good way to implement it would be to just reduce the vertices to 0.

    That's a good suggestion, thanks!
     
  14. seon

    seon

    Joined:
    Jan 10, 2007
    Posts:
    1,441
    I guess internally however you do it doesn't really matter so long as it is fast, but externally a sprite.visible = true/false or sprite.hide = true/false would be fantastic !

    Thanks again for a wonderful contribution to the community, and for being so open to enhancements and discussion.
     
  15. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    Okay, I've added a HideSprite() method to SpriteManager, and also added a "hidden" property to Sprite which should allow you to simply do something like this:

    sprite.hidden = true;

    or

    sprite.hidden = !sprite.hidden; // toggles the sprite as hidden/unhidden

    Thanks for the suggestion, Seon!
     
  16. briwil

    briwil

    Joined:
    Jan 23, 2009
    Posts:
    10
    I know I'm way behind you guys here, and I'm sorry, because I'm sure someone is going to chew me out for asking and tell me to look through the threads some more (I don't know what else to search for), but can someone just give me a sample of what exactly this script should say-

    "Add a script to each of these objects that contains a reference to the instance of the SpriteManager script you created in step 1."


    I understand the concept, I'm just not sure exactly the language thats needed.
    I'm sorry, I'm trying to learn this, its destroying my day, any help is kindly appreciated. Thanks
     
  17. CedarParkDad

    CedarParkDad

    Joined:
    May 28, 2008
    Posts:
    98
    @briwill: Here's a simple one I've been using for testing speed. The "LinkedSpriteManager" you see at the top is what it references. You just drag the GameObject which contains the LinkedManagerScript to that variable in the Unity editor. As shown, you then use it to add your GameObject to the manager. No, those hard-coded numbers are not good programming ;-)

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class BillboardMovement : MonoBehaviour
    6. {
    7.     public LinkedSpriteManager manager;
    8.     public float movementSpeed;
    9.    
    10.     private Vector3 _movement;
    11.     private float _direction;
    12.     private float _time;
    13.  
    14.     // Use this for initialization
    15.     void Start ()
    16.     {
    17.         manager.AddSprite( transform.gameObject, 10.0f, 10.0f, new Vector2( 0.0f, 0.0f ), new Vector2( 1.0f, 1.0f ), false );
    18.         _movement = new Vector3( 1.0f, 1.0f, 1.0f );
    19.         _direction = 1.0f;
    20.         _time = 0;
    21.     }
    22.    
    23.     // Update is called once per frame
    24.     void Update ()
    25.     {
    26.         Transform t = transform;
    27.         t.position += ( _movement * _direction * Time.deltaTime * movementSpeed );
    28.         _time += Time.deltaTime;
    29.         if ( _time > 1.0f )
    30.         {
    31.             _movement *= -1.0f;
    32.             _time = 0.0f;
    33.         }
    34.         manager.UpdatePositions();
    35.     }
    36. }
    37.  
     
  18. briwil

    briwil

    Joined:
    Jan 23, 2009
    Posts:
    10
    Ahh, thank you so much... I feel my headache subsiding as I write!
     
  19. seon

    seon

    Joined:
    Jan 10, 2007
    Posts:
    1,441
    @Brady: Awesome work, thanks :)

    @briwil:Ok, it goes something like....

    You will need to have a main GO in your scene that has either the SpriteManager or LinkedSpriteManager script on it. That is what needs to be referenced. We will assume in this case you called it SpriteManagerGameObject.

    So on each of your objects that you want a sprite on (I am going to assume that (a) you are using LinkedSpriteManager and (b) each object (we will call enemy) is instantiated at runtime) you need to this to the top of your enemy script...

    Code (csharp):
    1. var LSP : LinkedSpriteManager;
    Then inside your Awake() functio to need to hook that up to your SpriteManagerGameObject

    Code (csharp):
    1. // grab reference to teh GO that has the LinkedSpriteManager script on it
    2. var SMGO : GameObject = GameObject.Find("SpriteManagerGameObject");
    3. // now grab reference to the actual LinkedSPriteManager script
    4. var LSP = SMGO.GetComponent("LinkedSpriteManager");
    So now inside your enemy script you can do stuff like...

    Code (csharp):
    1. LSP.AddSprite(......);
    Did all that make sense?
     
  20. briwil

    briwil

    Joined:
    Jan 23, 2009
    Posts:
    10
    Seon: I appreciate it, but I guess maybe this is beyond my understanding, because as I create a new enemyScript, and enter that first variable "var LSP : LinkedSpriteManager" at the top, I immediately get this error:
    Assets/enemyScript.js(1,11): BCE0018: The name 'LinkedSpriteManager' does not denote a valid type.

    Even though 'LinkedSpriteManager' is the name of the script it should be trying to reference.
    [/i]
     
  21. seon

    seon

    Joined:
    Jan 10, 2007
    Posts:
    1,441
    Do you have the SpriteManager and LinkedSpriteManager scripts in you rproject INSIDE a folder called Plugins that is in the root of the Assets folder?

    That error means it cannot find the script.
     
  22. seon

    seon

    Joined:
    Jan 10, 2007
    Posts:
    1,441
    @Brady: I am getting those messy "Shader wants normals, but the mesh _GameLogic doesn't have them" errors again with this new script (that has the hide stuff in it).

    I am using the LinkedSpriteManager and the second it starts running as my scene starts, i get stax of these errors.

    EDIT: And they never stop :(

    EDIT: This is in Unity 2.1 BTW... Not Unity iPhone.
     
  23. briwil

    briwil

    Joined:
    Jan 23, 2009
    Posts:
    10
    Seon: I didn't have them in a Plugins folder, I would've never guessed; seemed to work, thanks.
    so I know I'm really asking you to hold my hand here, and I promise this will be the last thing I ask, but can you give an example of the exact scripting you would use for LSP.AddSprite(......);? I'm reading the WIKI, but still don't know exactly how to write it.

    I got CedarParkDad's C# script above to work, but I know I can't combine his AddSprite line with yours, and I'd prefer to do this with js.

    I'm sorry for being a pain in the ass
     
  24. seon

    seon

    Joined:
    Jan 10, 2007
    Posts:
    1,441
    Np Problems:)

    Mine is like this...

    Code (csharp):
    1. myHealthSprite = SM.AddSprite(myHealthBar, 2, 0.5,  0, 176, 64, 16, false);
    Which is using the pixel position version, rather than the UV position version.

    Code (csharp):
    1. myHealthBar = the object the sprite is sitting on
    2. 2, 0.5 is the width and heigh in real work space I want it to be
    3. 0,176 is teh lower left hand pixels of the image in the material I am using
    4. 64,16 is the width and height in pixels of the image for the sprite.
     
  25. CedarParkDad

    CedarParkDad

    Joined:
    May 28, 2008
    Posts:
    98
    Seon, that error occurs when the "Particles/Vertex Lit (Blended)" shader is used. At least for me ;-)
     
  26. briwil

    briwil

    Joined:
    Jan 23, 2009
    Posts:
    10
    I think I have it setup correctly, but now I'm getting this error:
    ArgumentOutOfRangeException: Index is less than 0 or more than or equal to the list count.
    Parameter name: index


    Anyone have any ideas?
     

    Attached Files:

  27. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    Yeah, regarding the normals warning, it depends on the shader you're using. If you're using a shader that requires normals, then it will definitely complain. Nothing has changed in the script with regard to that, however, so it should have been giving you that error all along, unless you recently changed the shader.

    If it is absolutely necessary to use a shader that requires normals, you'll have to uncomment out the normals buffer declaration in SpriteManager and then add code in EnlargeArrays() similar to the other buffer allocations for the normals, then add code in LateUpdate() which assigns the normals to the mesh object. And if you truly need accurate normals, you'll also want to call RecalculateNormals() on the mesh object after assigning everything to it.

    I hadn't implemented this at the moment since sprites traditionally don't require any kind of normals for lighting purposes, etc. and adding normals to the mix would only further degrade performance since it's yet another buffer to be copied and recalculated. So if you don't actually need the functionality that normals give you (such as dynamic lighting), I'd choose a shader that doesn't require them.
     
  28. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    briwil, which line of which script is generating the error?
     
  29. MadMac

    MadMac

    Joined:
    Dec 30, 2008
    Posts:
    61
    Hello,
    well i also try'ed to implement it test it out....
    Have the same problems with the shader etc.
    hmmm i think it would be a great idea to set up a Demo Project just to show everybody how the basic works. For me is it much easy to understand when i see it in a Project and can then play with that.
    Thxx anyway for this great pice of code ! :eek:
     
  30. briwil

    briwil

    Joined:
    Jan 23, 2009
    Posts:
    10
    I agree, a dummy project would be remarkable and much appreciated, as I still don't know what is causing the problem on my end.
     
  31. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    Okay, I've attached a demo package to the first post of this thread. Let me know what you think. Also, briwil, I never heard back from you about the line of that error.
     
  32. MadMac

    MadMac

    Joined:
    Dec 30, 2008
    Posts:
    61
    wow amazing how that works...
    many many thxxx for the quick demo
    I understand now how it works !
    Will make some Test this weekend and let you know :)
    thxxx
    Stefan
     
  33. hellomrjoyboy

    hellomrjoyboy

    Joined:
    Feb 2, 2009
    Posts:
    6
    Hello:
    I am having difficulty identifying a sprite and its client upon a collision. Has anyone had any luck with this?
    Thanks.
     
  34. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    SpriteManager has nothing to do with collision. You'll need to use conventional methods to check for collisions. Then, if you want to do something with a sprite based upon that collision, you'll need to have stored a reference to that sprite somewhere where it can be readily matched to the game object in question. For instance, in your client game object, have a script that stores a local copy of its associated sprite. Or have an object that has a script that serves as a central list of gameobjects and their sprites.
     
  35. seon

    seon

    Joined:
    Jan 10, 2007
    Posts:
    1,441
    Brady, I have a feature request... its big, no doubt about it, but would address an issue you have brought up before about how the system works.

    I would like a flag for each sprite to say wether it is Static or Dynamic, then then 2 different Update procedures.

    A standard one that will ONLY update Dynamic Sprites, and one that will update ALL sprites (a true/false would suffice in one script).

    So this was we don't have to re-draw sprites every frame that are not moving.

    I would do this myself, but I don't code in C#. Though if you like the idea, but cant get to it at the moment, I might give it a go myself.
     
  36. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    I think the easiest solution for this in the short-term would simply be to have two SpriteManager objects. If you want one that always updates, use LinkedSpriteManager, and for the one that you want to update manually/periodically, use SpriteManager.

    Let me know if you need more of an explanation than that.
     
  37. seon

    seon

    Joined:
    Jan 10, 2007
    Posts:
    1,441
    That adds an extra Update or LateUpdate cycle to the loop, that is a killer for the iPhone.

    I am exploring other options at the moment... I will keep you posted.
     
  38. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    I don't think it would be all that big of a hit. Actually, I think the bigger hit would just be the additional draw call.

    I think the best way to create the sort of change you're referring to would be to have another list, like the billboarded and active lists. Have one that is for "static" (or manual-update) sprites, and another for dynamic (or auto-update) sprites.

    I'm still not sure it would yield that much of an improvement performance-wise over using a separate object though. The reason being, if you have a large number of static objects and just a few dynamic, the entire mesh still has to get re-copied each frame to pick up the changes in the few dynamic objects. So all those static vertices still get copied over again even though they haven't changed at all. That's a lot of cycles wasted where if you had them separated into two separate managers, the few dynamics get updated each frame as they must, but the statics don't, thereby saving you a bunch of overhead.

    You have to keep in mind that when you do: mesh.vertices = myVertices, it isn't just doing a single assignment. It's actually copying each element of myVertices to mesh.vertices. And there's no way to just update the changed parts from what I'm told. You have to re-copy the entire array. So better to keep your dynamic and statics separate so you don't copy more than you have to.

    A single additional LateUpdate() shouldn't make any real difference in performance by itself. It never has for me. And it seems you'd likely make up for what little it cost you by avoiding copying a large vertex array containing lots of unchanged elements.
     
  39. CedarParkDad

    CedarParkDad

    Joined:
    May 28, 2008
    Posts:
    98
    Sorry this took so long, but I only have one or two nights a week to work on this:
    I've finally finished a simplistic UI system based on the SpriteManager. Below are two classes. The first is the UIManager interface which will need to be implemented to be sent the events for buttons. The second is the SpriteUI system which actually runs the show. If you want the UI system then just add the SpriteUI script to an object which is at 0,0,0 (as you would the SpriteManager. You will need to set the resolution because it didn't seem like the resolution was set for the design tool. UILayer is the layer which you wish to use for the UI. ZeroLocation is where you would like 0,0 to be: LowerLeft or UpperLeft.

    You then use AddElement to add a static UI element (not a button). Use AddButton to add a button, and pass in the UIManager implementation which you would like to get the events. Both return the sprites, as with SpriteManager. RemoveElement and RemoveSprite are the same, but RemoveButton must be called for buttons. Z is used to determine which button shows on top as well as precedence.

    What is not in this one:
    • - Activate/Deactivate
      - Any rollover support (though you can roll your own by having the rollovers on the same atlas and adding them on getting the OnMouseDown event and removing them on the OnMouseExit and OnMouseUp events)
      - Multi-touch support
      - Ignore alpha on rounded buttons (not sure how to do this yet)
      - Comments (live with it, this works like SpriteManager)
    I do plan on adding these in the future, but I figured some people would find this useful right now.

    First, there is one change to the SpriteManager that needed to be made:
    Change the Awake() function declaration to:
    Code (csharp):
    1. protected virtual void Awake()
    2.  
    This is since I'm inheriting from SpriteManager, but I need to call SpriteManager's Awake. Now the interface and class:

    UIManager
    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public interface UIManager {
    5.     void OnMouseDown(Sprite sprite);
    6.     void OnMouseUp(Sprite sprite);
    7.     void OnMouseMoved(Sprite sprite);
    8.     void OnMouseExit(Sprite sprite);
    9. }
    10.  
    SpriteUI
    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Specialized;
    4.  
    5. public class SpriteButton
    6. {
    7.     private Vector2 _screenPosition;
    8.     private Vector2 _lowerRight;
    9.     private UIManager _uiManager;
    10.     private Sprite _sprite;
    11.    
    12.     public Sprite sprite
    13.     {
    14.         get { return _sprite; }
    15.         set { _sprite = value; }
    16.     }
    17.     public Vector2 screenPosition
    18.     {
    19.         get { return _screenPosition; }
    20.         set { _screenPosition = value; }
    21.     }
    22.     public Vector2 lowerRight
    23.     {
    24.         get { return _lowerRight; }
    25.         set { _lowerRight = value; }
    26.     }
    27.     public UIManager uiManager
    28.     {
    29.         get { return _uiManager; }
    30.         set { _uiManager = value; }
    31.     }
    32.    
    33.     public float left
    34.     {
    35.         get { return _screenPosition.x; }
    36.     }
    37.     public float right
    38.     {
    39.         get { return _lowerRight.x; }
    40.     }
    41.     public float top
    42.     {
    43.         get { return _screenPosition.y; }
    44.     }
    45.     public float bottom
    46.     {
    47.         get { return _lowerRight.y; }
    48.     }
    49. }
    50.  
    51. public class SpriteUI : SpriteManager
    52. {
    53.    
    54.     public enum ZeroLocationEnum : sbyte
    55.     {
    56.         LowerLeft = -1,
    57.         UpperLeft = 1
    58.     }
    59.    
    60.     public LayerMask UILayer = 0;
    61.     public Vector2 ScreenResolution = new Vector2( 320.0f, 480.0f );
    62.     public ZeroLocationEnum ZeroLocation = ZeroLocationEnum.LowerLeft;
    63.    
    64.     public bool HitButton = false;
    65.    
    66.     private Camera _uiCamera;
    67.     private GameObject _uiCameraHolder;
    68.     private float _xOffset;
    69.     private float _yOffset;
    70.     private bool _uiInitialUpdate = false;
    71.     private SpriteButton _spriteSelected = null;
    72.    
    73.     private ListDictionary _buttons = new ListDictionary();
    74.  
    75.     protected override void Awake()
    76.     {
    77.         base.Awake();
    78.         _uiCameraHolder = new GameObject();
    79.         _uiCameraHolder.AddComponent( "Camera" );
    80.         _uiCamera = _uiCameraHolder.camera;
    81.         _uiCamera.name = "__UICamera";
    82.         _uiCamera.clearFlags = CameraClearFlags.Depth;
    83.         _uiCamera.nearClipPlane = 0.3f;
    84.         _uiCamera.farClipPlane = 100.0f;
    85.         _uiCamera.depth = 100;
    86.         _uiCamera.rect = new Rect(0.0f, 0.0f, 1.0f, 1.0f );
    87.         _uiCamera.orthographic = true;
    88.         _uiCamera.orthographicSize = 120.0f;
    89.         _uiCamera.cullingMask = UILayer;
    90.         _uiCamera.transform.position = new Vector3( 0.0f, 0.0f, -10.0f );
    91.         UpdateUISize();
    92.     }
    93.    
    94.     public void UpdateUISize()
    95.     {
    96.         _xOffset = -ScreenResolution.x / 2.0f;
    97.         _yOffset = ScreenResolution.y / 2.0f;
    98.     }
    99.    
    100.     public Sprite AddElement( Vector2 upperLeft, float width, float height, float depth, int leftPixelX, int bottomPixelY, int pixelWidth, int pixelHeight)
    101.     {
    102.         return AddElement(upperLeft, width, height, depth, PixelCoordToUVCoord(leftPixelX, bottomPixelY), PixelSpaceToUVSpace(pixelWidth, pixelHeight));
    103.     }
    104.    
    105.     public Sprite AddButton( Vector2 upperLeft, float width, float height, float depth, int leftPixelX, int bottomPixelY, int pixelWidth, int pixelHeight, UIManager manager)
    106.     {
    107.         return AddButton(upperLeft, width, height, depth, PixelCoordToUVCoord(leftPixelX, bottomPixelY), PixelSpaceToUVSpace(pixelWidth, pixelHeight), manager);
    108.     }
    109.  
    110.     public Sprite AddElement( Vector2 upperLeft, float width, float height, float depth, Vector2 lowerLeftUV, Vector2 uvSize )
    111.     {
    112.         GameObject element = new GameObject();
    113.         Transform t = element.transform;
    114.         float xPos = ( upperLeft.x + _xOffset + ( width / 2.0f ) );
    115.         float yPos = (int)ZeroLocation * ( -upperLeft.y + _yOffset - ( height / 2.0f ) );
    116.         t.position = new Vector3( xPos, yPos, depth );
    117.         int actualLayer = (int)Mathf.Sqrt(UILayer.value) / 2; // Item layer is 1 shifted left by this number
    118.         element.layer = actualLayer;
    119.         Sprite sprite = AddSprite( element, width, height, lowerLeftUV, uvSize, false );   
    120.         return sprite;
    121.     }
    122.    
    123.     public Sprite AddButton( Vector2 upperLeft, float width, float height, float depth, Vector2 lowerLeftUV, Vector2 uvSize, UIManager manager )
    124.     {
    125.         Sprite sprite = AddElement( upperLeft, width, height, depth, lowerLeftUV, uvSize );
    126.         ArrayList list = null;
    127.         int iDepth = (int)depth;
    128.         if ( _buttons.Contains( iDepth ) )
    129.         {
    130.             list = (ArrayList)_buttons[iDepth];
    131.         }
    132.         else
    133.         {
    134.             list = new ArrayList();
    135.             _buttons.Add( iDepth, list );
    136.         }
    137.         SpriteButton spriteButton = new SpriteButton();
    138.         spriteButton.uiManager = manager;
    139.         spriteButton.sprite = sprite;
    140.         spriteButton.screenPosition = upperLeft;
    141.         spriteButton.lowerRight = new Vector2( upperLeft.x + width, upperLeft.y + height );
    142.         list.Add( spriteButton );
    143.         return sprite;
    144.     }
    145.    
    146.     public void RemoveElement( Sprite sprite )
    147.     {
    148.         RemoveSprite( sprite );
    149.     }
    150.    
    151.     public void RemoveButton( Sprite sprite )
    152.     {
    153.         GameObject obj = sprite.client;
    154.         int iDepth = (int)obj.transform.position.z;
    155.         ArrayList list = (ArrayList)_buttons[iDepth];
    156.         if ( list != null )
    157.         {
    158.             SpriteButton selected = null;
    159.             foreach ( SpriteButton button in list )
    160.             {
    161.                 if ( button.sprite == sprite )
    162.                 {
    163.                     selected = button;
    164.                     break;
    165.                 }
    166.             }
    167.             if ( selected != null )
    168.             {
    169.                 list.Remove( selected );
    170.             }
    171.         }
    172.         if ( list.Count == 0 )
    173.         {
    174.             _buttons.Remove( iDepth );
    175.         }
    176.         RemoveSprite( sprite );
    177.         GameObject.Destroy( obj );
    178.     }
    179.    
    180.     void PreUpdate()
    181.     {
    182.         HitButton = false;
    183.     }
    184.    
    185.     // Update is called once per frame
    186.     void Update ()
    187.     {
    188.         if ( Input.GetMouseButtonDown (0) )
    189.         {
    190.             SpriteButton button = getButtonForScreenPosition( Input.mousePosition.x, Input.mousePosition.y );
    191.             if ( button != null )
    192.             {
    193.                 if ( _spriteSelected != null  button != _spriteSelected )
    194.                 {
    195.                     _spriteSelected.uiManager.OnMouseExit( _spriteSelected.sprite );
    196.                 }
    197.                 _spriteSelected = button;
    198.                 _spriteSelected.uiManager.OnMouseDown( _spriteSelected.sprite );
    199.             }
    200.             else if ( _spriteSelected != null )
    201.             {
    202.                 _spriteSelected.uiManager.OnMouseExit( _spriteSelected.sprite );
    203.                 _spriteSelected = null;
    204.             }
    205.         }
    206.         else if ( Input.GetMouseButton(0) )
    207.         {
    208.             SpriteButton button = getButtonForScreenPosition( Input.mousePosition.x, Input.mousePosition.y );
    209.             if ( button != null )
    210.             {
    211.                 if ( _spriteSelected == null )
    212.                 {
    213.                     // For any non-UI related logic
    214.                     button.uiManager.OnMouseDown( button.sprite );
    215.                 }
    216.                 else if ( button != _spriteSelected )
    217.                 {
    218.                     _spriteSelected.uiManager.OnMouseExit( _spriteSelected.sprite );
    219.                     // For any non-UI related logic
    220.                     button.uiManager.OnMouseDown( button.sprite );
    221.                 }
    222.                 _spriteSelected = button;
    223.                 _spriteSelected.uiManager.OnMouseMoved( _spriteSelected.sprite );
    224.             }
    225.             else if ( _spriteSelected != null )
    226.             {
    227.                 _spriteSelected.uiManager.OnMouseExit( _spriteSelected.sprite );
    228.                 _spriteSelected = null;
    229.             }
    230.         }
    231.         else if ( Input.GetMouseButtonUp (0) )
    232.         {
    233.             SpriteButton button = getButtonForScreenPosition( Input.mousePosition.x, Input.mousePosition.y );
    234.             if ( button != null )
    235.             {
    236.                 if ( _spriteSelected != null  button != _spriteSelected )
    237.                 {
    238.                     _spriteSelected.uiManager.OnMouseExit( _spriteSelected.sprite );
    239.                     // For any non-UI related logic
    240.                     button.uiManager.OnMouseDown( button.sprite );
    241.                 }
    242.                 button.uiManager.OnMouseUp( button.sprite );
    243.                 _spriteSelected = null;
    244.             }
    245.             else if ( _spriteSelected != null )
    246.             {
    247.                 _spriteSelected.uiManager.OnMouseExit( _spriteSelected.sprite );
    248.                 _spriteSelected = null;
    249.             }
    250.         }
    251.         else if ( _spriteSelected != null )
    252.         {
    253.             _spriteSelected = null;
    254.         }
    255.     }
    256.    
    257.     private SpriteButton getButtonForScreenPosition( float xValue, float yValue )
    258.     {
    259.         foreach ( DictionaryEntry entry in _buttons )
    260.         {
    261.             if ( ZeroLocation == ZeroLocationEnum.UpperLeft )
    262.             {
    263.                 yValue = -yValue + ScreenResolution.y;
    264.             }
    265.             ArrayList list = (ArrayList)entry.Value;
    266.             foreach ( System.Object obj in list )
    267.             {
    268.                 SpriteButton button = (SpriteButton)obj;
    269.                 if ( button.left <= xValue  xValue <= button.right
    270.                      button.top <= yValue  yValue <= button.bottom )
    271.                 {
    272.                     return button;
    273.                 }
    274.             }
    275.         }
    276.         return null;
    277.     }
    278. }
    279.  
    You will notice a camera in there. This is created orthographic and only seeing the layer set as the UILayer. So you don't have to create your own camera for this as that was part of what I wanted to do in making this easy. I can answer questions about this, but I can't do any updates until next weekend at the earliest. I'm just plain booked ;-)
     
  40. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    Cool! I'll have to give 'er a try when I get some time. Thanks a bunch! This is the next logical use of SpriteManager, indeed.
     
  41. seon

    seon

    Joined:
    Jan 10, 2007
    Posts:
    1,441
    @CedarParkDad: Thanks for this... very cool.

    Question... could you add an option to pass it a font, and have it use the font material to manage text for us too as sprites?

    I know, that sounds like a big ask, but if you are up for it, it would save many drawcalls from all the GUIText elements.
     
  42. CedarParkDad

    CedarParkDad

    Joined:
    May 28, 2008
    Posts:
    98
    @Seon,

    I've been down all week with the flu, so I'm just now reading this. It would be possible, yes. But I wouldn't have time to make it work for ideographic languages, as they would require intelligent font-texture handling to help reduce draw calls. Thankfully they are fixed-width unless they fallback to Hiragana, Bopomofo, or Hangul. I also wouldn't initially handle right-to-left languages.

    Now, if you would be willing to provide a font material consisting of only the characters appearing in a scene then it might be possible to fit those characters on a single texture. But then the user of the system would need to provide x/y offsets for each character value (or range of values if done correctly). So, while possible, fonts and text are always something more complicated.

    In the end I will look at what it takes to provide this through Unity. From what I've seen so far, I may actually recommend what is against my very soul: putting text in images. Given the small amount of text in a normal iPhone game it should be fine.

    What I can provide is a simple offset function which gives automatic UV coordinates and size based on an index into an image. That could be used for most syllabic languages, but wouldn't be useful for any ideographic languages. Then I could build the simplistic UV and Material offset based on an index into an image. The later would need multiple materials for a single language, and may not help in the number of draw calls depending on the language. I see to the first part this weekend.

    UNRELATED NOTE:
    One last thing in looking at what I did: I didn't test this on landscape (480x320). I'm guessing it won't work correctly, but the fix is easy. Where the orthographicSize of the created camera is set you would change it to be the ratio of the Screen.size and the resolution you want. The '120.0' is the resolution that Unity has in it (which is a 4:5 ratio) to the iPhone portrait (3:2 ratio). Which is 1.2 or 120%. That is because the ortho camera appears to be set according to the screen resolution at creation and as I had stated in my post it doesn't appear to be correct, but I haven't thoroughly tested that.
     
  43. arton

    arton

    Joined:
    Jun 11, 2005
    Posts:
    38
    I'd just like to the chorus of thanks for this!
    It is a huge help, and the demo project makes it so much easier to get started.
    Thank You! :D
     
  44. arton

    arton

    Joined:
    Jun 11, 2005
    Posts:
    38
    So, I'm having good luck using a sprite client script to add objects to a scene procedurally like the example file, but I'm having trouble with objects adding themselves to the SpriteManager.

    I'd like to use code attached to a prototype that adds itself to the SpriteManager with all appropriate settings. That way I can just drop prefabs into the scene and position them, and they'll automatically take care of the sprite part.

    It seems like this would be best handled with Javascript, but I'm running into a problem accessing the SpriteManager component directly (not a valid type).
    When I try it with C#, I'm having trouble referencing the object it's been attached to to add it.

    It would be great if someone could share a very basic script which could be attached to a prefab to add it to the SpriteManager.

    Thanks!

    :)
     
  45. Dark-Table

    Dark-Table

    Joined:
    Nov 25, 2008
    Posts:
    315
    This is working for me (in C#):

    Code (csharp):
    1. public class Blah : BlahParent {
    2.  
    3.     public SpriteManager SpriteMgr;
    4.    
    5.     public Sprite me;
    6.    
    7.     void Awake() {
    8.         SpriteMgr = (SpriteManager) GameObject.Find("SpriteManager").GetComponent(typeof(SpriteManager));
    9.     }
    10.  
    11.     void Start () {        
    12.         me = SpriteMgr.AddSprite(gameObject, // The game object to associate the sprite to
    13.                                     1f,         // The width of the sprite
    14.                                     0.25f,      // The height of the sprite
    15.                                     0,      // Left pixel
    16.                                     64,         // Bottom pixel
    17.                                     64,         // Width in pixels
    18.                                     64,         // Height in pixels
    19.                                     false);     // Billboarded?
    20. }
    21.  
    I have an object in the level called "SpriteManager".
     
  46. arton

    arton

    Joined:
    Jun 11, 2005
    Posts:
    38
    That's just what I needed.

    I just want to mention for anyone confused, since it confused me, where it says "BlahParent", put "MonoBehaviour".

    Thanks again!


     
  47. _mikey

    _mikey

    Joined:
    Nov 6, 2008
    Posts:
    12
    CedarParkDad: thanks for the code!

    unfortunately i havent been able to get it to work... :p

    has anyone else been able to get it to work?

    i am trying to use AddElement() but all i get is a huge plane offscreen that does not move with the camera

    well, if i manage to get it working ill post the example
    [/code]
     
  48. CedarParkDad

    CedarParkDad

    Joined:
    May 28, 2008
    Posts:
    98
    I'll have to put an example somewhere. It creates its own camera if you attach it to an object at 0,0,0. So there should be no need to have it move with the camera. I'm just recently out of the hospital, but I should be able to get a simple sample and the fix for resolution done this weekend.
     
  49. RBerg

    RBerg

    Joined:
    Aug 14, 2008
    Posts:
    10
    Hey Everyone,

    Good news! I am very close to finalizing some cool sprite animation code to contribute based on the requirements posed in the wiki. I actually posted it the other night, but I found a bug and removed the post until I find time to fix it.

    On an unrelated note: What shader have you found works best for the sprites? I am using Particles/Alpha Blended and it works really well except I am finding it hard to create a nice smooth alpha gradient (imagine a radial gradient starting from solid in the middle to transparent at the edge). Any advice?

    Thanks.
    -Ryan
     
  50. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    Are you using texture compression? If so, switch to uncompressed RGBA, because neither DXT nor PVRTC do smooth gradients.

    --Eric