Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

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

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

  1. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    [NOTE: I'm no longer updating the documentation in this post, so please see the wiki entry for up-to-date documentation on how to use this.]

    I think you will all agree that the method I present here for drawing 2D sprites will allow you to make certain 2D games with Unity iPhone that you otherwise would think twice about making due to the poor performance resulting from having too many draw calls. I'm providing this code for all to use on one condition: that you keep the notice at the top of each script intact and unmodified, and that if you make any improvements to the code, that you share them here so everyone (including the author! :)) can benefit from them. Oh, and I'd love if you could drop me a note if you use this in your project. I'd like to see what you're able to do with it. I really would like to see the community take off with this and continue to add functionality. It has not yet been thoroughly tested for stability, but the code is pretty simple and I expect that it should be pretty stable. The next step I see for this approach is to allow for simple 3D meshes instead of just quads. This would come in handy for 3D games, whereas the current solution is most useful for 2D games.

    Okay, on to the code! (See attached files for the sourcecode.)

    Summary:
    Drawing lots of simple, independently-moving sprites for a 2D game can be performance prohibitive in Unity iPhone because the engine was designed with 3D in mind. For each object that has its own transform, another draw call is normally required. The significant overhead of a draw call quickly adds up and will cause framerate problems with only a modest number of objects on-screen. To address this, my SpriteManager class builds a single mesh containing the sprite "quads" to be displayed, and then "manually" transforms the vertices of these quads at runtime to create the appearance of multiple, independently moving objects - all in a single draw call! This dramatically increases the number of independently moving objects allowed on-screen at a time while maintaining a decent framerate.

    Usage Overview:
    1. Create an empty GameObject (or you may use any other GameObject so long as it is located at the origin (0,0,0) with no rotations or scaling) and attach the SpriteManager or LinkedSpriteManager script to it. (NOTE: It is vital that the object containing the SpriteManager script be at the origin and have no rotations or scaling or else the sprites will be drawn out of alignment with the positions of the GameObjects they are intended to represent! This gets forced in the Awake() method of SpriteManager so that you don't have to worry about it in the editor. But do not relocate the object containing SpriteManager at run-time unless you have a very good reason for doing so!) Fill in the allocBlockSize and material values in the Unity editor. The SpriteManager is now ready to use.

    2. To use it, create GameObjects which you want to represent using sprites at run-time. Add a script to each of these objects that contains a reference to the instance of the SpriteManager script you created in step 1.

    3. In Start() of each such GameObject, place code calling the appropriate initialization routines of the SpriteManager object to add the sprite you want to represent this GameObject to the SpriteManager. Depending on the animation techniques used, you may also need to add code to Update() to manually inform the SpriteManager of changes you have made to the sprite at run-time. (In a later revision, all the necessary update calls could be made automatically to the SpriteManager through the Sprite class's own property accessors.)

    The Sprite Class
    The Sprite class contains all the relevant information to describe a single quad sprite (two coplanar triangles that form a quadrilateral). Each sprite has a width and height to indicate world-space dimensions. It also has the location of the lower-left UV offset (which can be changed at runtime to create UV animations) as well as the width and height of the UV (m_UVDimensions).

    Each sprite contains four vertices which define the shape of its "quad" in local space. These vertices will be transformed by the SpriteManager class at runtime to orient the quad in world-space.

    Finally, each sprite is associated with a GameObject referred to as the "client". This client object is the object to be represented by the quad. The quad will be transformed according to the client's transform. So when the client moves, the quad will follow, exactly as if the quad were simply part of the client GameObject.

    The SpriteManager class
    This class manages a list of sprites and associated GameObjects.

    Memory management:
    Currently, as sprites are added, the list (and associated vertex, uv, and triangle buffers) increase in size. As sprites are removed from the manager, the lists remain the same size, but the "open slots" are flagged and are re-used when new sprites are added again, removing the performance penalty of re-allocating all the buffers and copying their contents over again. This approach was taken not only for the aformentioned performance reasons, but also because it would add significant complexity to reduce the size of the buffers since client GameObjects hold the indices of their associated sprites, and if the buffers were sized down, those indices could then point to invalid offsets. The only way to resolve this would be to add either additional complexity to the design, or less performant ways of keeping track of sprites, or both.

    allocBlockSize
    Since allocating large new buffers and copying their contents can be a big performance hit at runtime, SpriteManager allows the developer to choose how many sprites should be pre-allocated at a time. If, for example, you expect your game to never use more than 100 sprites, you should probably set this value to 100, resulting in a one-time allocation of sprites so the player does not experience a "hiccup" mid-game as the buffer is re-allocated and new contents are copied over during gameplay. If you pre-allocate 100 sprites and have filled up the sprite buffer, then find yourself having to create one more sprite (for a total of 101), if you have set allocBlockSize to 100, then another 100 sprites will be allocated even though you have added only 1. So use caution in the value you assign to allocBlockSize. Try to balance memory waste with frequency of having to re-allocate new buffers at runtime. In the above case, using an allocBlockSize of 25, if you created 101 sprites, you would only have an "overage" of 24 sprites, but the buffers would have to be re-allocated and re-copied 5 times.

    material
    Simply assign the materal you wish to use for your sprites here. It is strongly advised that for sprites, you use one of the particle shaders so that backface culling is not an issue. All the sprites for this SpriteManager will use this material. So for a typical application, you would want to combine as many of your sprites as possible into a single texture atlas and assign that material to the SpriteManager.

    plane
    The plane in which the sprites are to be created. The options are XY, XZ, or YZ. For example, an Asteroids type game might typically use sprites created in the XZ plane, while a Tetris-like game would probably use the XY plane.

    AddSprite()
    This method will add a sprite to the SpriteManager's list and will associate it with the specified GameObject. The sprite list as well as the vertex, UV, and triangle buffers will all be reallocated and copied if no available "slots" can be found. The buffers will be increased according to allocBlockSize. Performance note: Will cause the vertex, UV, and triangle buffers to be re-copied to the mesh object.

    Arguments:
    client - The GameObject that is to be associated with this sprite. The sprite will be transformed using this object's transform.

    width and height - The width and height of the sprite in world space units. (This assumes that you have not applied scaling to the object containing the SpriteManager script - which you probably should not do unless you really know why you're doing it.)

    lowerLeftUV - The UV coordinate of the lower-left corner of the quad.

    UVDimensions - The width and height of how much of the texture to use. This is a scalar value. Ex: if lowerLeftUV is 0.5,0.5 and UVDimensions is 0.5,0.5, the quad will display the associated texture from the center extending out to the extreme top and right edges.

    Return value: the index of the sprite added. This is the ID that will be used in the future to access the sprite.

    RemoveSprite()
    "Removes" the sprite specified by i - the index of the sprite in the sprite array. (It actually just flags the sprite as available and reduces its dimensions to 0 so that it is invisible when rendered.)

    Arguments:
    i - The index of the sprite to remove. This should be the value returned by AddSprite().
    Performance note: Will cause the vertex buffer to be re-copied to the mesh object.

    GetSprite()
    This method returns a reference to the specified sprite so that the sprite can be directly manipulated if need be.

    Arguments:
    i - Index of the sprite in question.

    Transform()
    This method transforms the vertices associated with the specified sprite by the transform of its client GameObject. In plain English, if a GameObject wants to manually synch a sprite up with its current orientation, it should call this method. This method will transform that sprite, and that sprite alone, leaving all the other sprites un-updated. Performance note: Will cause the vertex buffer to be re-copied to the mesh object.

    Arguments:
    i - The index of the sprite to transform.

    UpdatePosition()
    Transforms the vertices of the specified sprite and forces the vertices of the mesh to be re-copied in the next frame. This is used if a GameObject has made changes to a sprite (such as changing its dimensions) and its vertices should be re-copied to the mesh to reflect these changes. For now, it basically does the same thing as Transform(), but may have somewhat different functionality in the future. Performance note: Will cause the vertex buffer to be re-copied to the mesh object.

    Arguments:
    i - The index of the sprite to update.

    UpdateUV()
    Updates the UVs of the sprite in the local UV buffer (which mirrors that of the mesh object), and forces the UVs of the entire mesh to be re-copied to the mesh object. Use this when you manually change the UV offset or dimensions of the sprite between frames and want to inform the SpriteManager of the change so that it may update its UV buffer. Performance note: Will cause the UV buffer to be re-copied to the mesh object.

    The LinkedSpriteManager class
    This class inherits from SpriteManager and adds the functionality of automatically transforming the vertices of all sprites each frame, removing the need to call "Transform()" whenever the position of a GameObject is changed. The trade-off is that if you have lots of sprites that do not move most of the time, you will be transforming the vertices of these sprites needlessly each frame. If you have lots of sprites, this could impact performance noticably. If, however, the typical case is that your sprites will be in almost constant motion, it will be faster to use LinkedSpriteManager since all transformations are handled under a single function call (TransformSprites()) rather than having each GameObject call Transform() separately, thereby reducing call overhead.

    In closing:
    Please report all bugs you find or improvements you make to these classes. I put a bit of work into creating these and am sharing with everyone in the hope that more brains working on this will result in a more robust, efficient, and stable solution than I have time to commit to making happen here by myself. I truly believe that this approach will unlock game types and other possibilities that have, until now, been out of the question for Unity iPhone because of the overhead of the required draw calls.

    Possible features to be implemented by the community in the future:
    * Some way of having a more automated, but flexible, UV animation system that is encapsulated in the SpriteManager or Sprite classes to simplify the code needed in the client GameObjects to perform UV animation. The characteristics of an ideal system would be: A) support for an arbitrary number of animation sequences for a single sprite, B) support for different animation framerates for each sprite, C) a simple interface for defining, playing, pausing, and otherwise controlling animation playback on a per-sprite basis, D) it would not impose any undue restrictions or rigid conventions on the artist when creating the texture that contains the animation frames.
    * Devise a method for reducing the size of the sprite and other buffers without adding significant complexity or performance overhead and without compromising stability.
    * Create a 3D version of SpriteManager for use with fully 3D objects.
    * Anything else you can think of!

    Well, that's it! I hope you benefit greatly from the use of these classes and that you can, in turn, help us all to improve upon them.
     

    Attached Files:

  2. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
  3. ugur

    ugur

    Joined:
    Jun 3, 2008
    Posts:
    692
    this sounds great, funnily i was just thinking about a solution for the problem getting lots of drawcalls for 2D stuff needed, too :) (Yours seems to go towards a similar direction as particle system, nice)
    Do you have an example project where one can see it in action?
     
  4. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    Hmmm, no, not at the moment. I've just implemented it into a project in production that I can't post here. But it isn't too difficult to throw something together (but after writing up all those docs, I'm all spent out on time at the moment). Just create a new GameObject, drag LinkedSpriteManager on to it, choose a material (use a particle shader), set allocBlockSize to whatever (anything but 0), then create a blank script that calls AddSprite() on the SpriteScript object above. Then create a cube with a rigidbody, attach the "blank" script I mentioned above to it, duplicate it a bunch of times, and then hit play. It should draw quads where each cube is and rotate them along with the cubes as they tumble.
     
  5. IPete

    IPete

    Joined:
    May 15, 2008
    Posts:
    414
    Brady,

    Thanks for this - I will take a longer look at it soon, but it sounds fab.
     
  6. 3dgrinder

    3dgrinder

    Joined:
    Oct 21, 2008
    Posts:
    249
    I only can say thumbs UP bro :)
     
  7. Andrej-Vojtas

    Andrej-Vojtas

    Joined:
    Jan 12, 2009
    Posts:
    67
    This is sooo awesome! I was helplessly trying to find a 2D documentation / tutorial something and now you come and present a ready to use package!!!

    A million thanks!
     
  8. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    Well, I'm not sure if it's all that ready-to-use... well, it is, but the most natural setup to use it I've found is to have the camera looking along the positive Z axis, then orient your 2D game to play out in the XY plane. There's no reason you couldn't use it differently, of course. But that's how the quads are oriented by default.

    I suppose that's another possible improvement - adding a simple setting that would dictate which world-space plane the quads were constructed on. That would help in cases where you may want to put the camera "overhead" (looking down the negative Y) and have the game play out on the XZ plane. I could see something like that for an Asteroids-type game. But of course, that's all personal preference, since there's no reason why an Asteroids game couldn't be written to use the XY plane instead.

    I hope you find it useful! But to have a complete 2D solution, you'll probably also want to look into, for example, using joints, etc, to restrict object movement to a certain axis. Of course you'd only need that if you're using rigid body physics. And I'd use that sparingly on the iPhone.

    BTW, there is a 2D gameplay Unity tutorial here:
    http://unity3d.com/support/resources/tutorials/2d-gameplay-tutorial
     
  9. ugur

    ugur

    Joined:
    Jun 3, 2008
    Posts:
    692
    I gave it a try now and while it overall seems to be quite nice all sprites are rotated wrong, i guess its due to my cam/ world space plane directions.
    (I have it as you said on the xz plane).
     
  10. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    Yep, that would do it. That needs to be high on the list of features to add. I may do that here soon when I get the chance.
     
  11. cheezorg

    cheezorg

    Joined:
    Jun 5, 2008
    Posts:
    394
    Really great stuff, Brady. This is fantastic.
     
  12. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    Thanks, everyone for the kind comments.

    Okay, I couldn't stand it anymore :), so I went ahead and implemented a plane option. So now you should just have to choose which plane you want the sprites to be created in in the editor and it should work. I've updated the attached scripts as well as the wiki and documentation. Let me know if that works for you, tommosaur.
     
  13. ugur

    ugur

    Joined:
    Jun 3, 2008
    Posts:
    692
    Thanks for this :)
    It has all sprites rotated the right way it seems when i choose xz as plane. Now the problem with that is the texture is invisible unless i set the shader of the material to one of the particle ones. I guess they are somehow flipped and not shown then with the other shaders since they are not double sided. Its just a guess though :)
     
  14. jerrodputman

    jerrodputman

    Joined:
    Jun 4, 2008
    Posts:
    181
    Awesome, Brady! We've got a game in the pipeline that could really use something like this, except with 3d meshes. I'll try to see if we can't tinker around with it and come up with something.
     
  15. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    tommosaur, yeah, that's why in the docs I said it's highly recommended to use a particle shader. Otherwise, you may have to reverse the winding depending on where your camera is. That should be easy to do by modifying the Sprite class. I show which line is upper-left, lower right, etc, so it would just be a matter of moving those around to flip the winding.

    Jarrod, to do it with 3D meshes, you basically need a cross between this and CombineChildren. But there's no reason it can't work. The ceiling on how many objects you can have before performance decreases noticeably would just be lower since each object would have more vertices, and transforming each vertex each frame is the real bottleneck. But I imagine, depending on how high-poly your meshes are, it would be faster than adding a drawcall.
     
  16. mindlube

    mindlube

    Joined:
    Oct 3, 2008
    Posts:
    993
    Thanks Brady, I can't wait to dig into this!
     
  17. ugur

    ugur

    Joined:
    Jun 3, 2008
    Posts:
    692
    if the sprites don´t move/rotate around is it then possible with any of the plane settings and a cam view position combination to get em to display without being set to a particle shader?
    I tried several combinations now and always either the texture is not visible at all or is displayed mirrored.
     
  18. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    Hmmm... I guess if you change the winding, you'll also have to change the UVs to match...

    You know, I'm not sure why it would be doing that, since the quads are wound counter-clockwise from the perspective of looking down the positive Z in XY, negative Y in XZ, and positive X in YZ. Anyone have ideas?

    EDIT: tommosaur, I just realized I didn't completely answer your question. Yeah, if they aren't showing up, you could reverse the position of your camera. i.e. if it's above the XZ plane, put it below it and look up instead. That's sort of inconvenient though since I'm sure you're used to thinking of your game's coordinate space a certain way. Is there a particular reason you can't use a particle shader? Perhaps the shader side of things would be the easiest workaround? But I'm still hoping someone can help shed light as to why CCW-wound tris would face the wrong way.
     
  19. HiggyB

    HiggyB

    Unity Product Evangelist

    Joined:
    Dec 8, 2006
    Posts:
    6,183
    Kudos and thanks for sharing your work like this!
     
  20. jeffcraighead

    jeffcraighead

    Joined:
    Nov 15, 2006
    Posts:
    740
    Nice! I'm glad I didn't invest too much time in my implementation of a sprite drawer! (It wasn't nearly as nice/robust as yours)

    Thanks!
     
  21. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    Thanks, Higgy and Jeff!

    tommosaur, I just tested this and there's no reason it shouldn't work without having to redo the UVs: in EnlargeArrays(), look for where the triangle indices are set up. Just swap the last two vertex offsets for each triangle. That is, you should see this:
    Code (csharp):
    1.  
    2. triIndices[i * 6 + 0] = i * 4 + 0;
    3. triIndices[i * 6 + 1] = i * 4 + 1;
    4. triIndices[i * 6 + 2] = i * 4 + 3;
    5.  
    6. triIndices[i * 6 + 3] = i * 4 + 3;
    7. triIndices[i * 6 + 4] = i * 4 + 1;
    8. triIndices[i * 6 + 5] = i * 4 + 2;
    9.  
    Change it to this:
    Code (csharp):
    1.  
    2. triIndices[i * 6 + 0] = i * 4 + 0;
    3. triIndices[i * 6 + 1] = i * 4 + 3; // 3 swapped with 1
    4. triIndices[i * 6 + 2] = i * 4 + 1;
    5.  
    6. triIndices[i * 6 + 3] = i * 4 + 3;
    7. triIndices[i * 6 + 4] = i * 4 + 2; // 2 swapped with 1
    8. triIndices[i * 6 + 5] = i * 4 + 1;
    9.  
    That's the easiest way to reverse the winding order.

    Now depending on what shader you're using, it may still not look right because we're not giving the mesh normals. If I understand correctly, this is as simple as allocating a buffer of normals (Vector3s) the same length as the vertex buffer (one normal per vertex) and assigning it to the normals member of the mesh object, then calling RecalculateNormals() after everything's been updated. But since the winding order will have been reversed, it's anyone's guess if it'll look right or not. Try it and see if you wind up needing normals for the shader you're using.

    I still swear that SpriteManager is winding the verts correctly. I just don't get why they're acting like they're reversed.
     
  22. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    Brady, thanks a lot for this.

    This really seems like the kind of thing that should be built into Unity, though, does it not? Is there a reason something like it has not been implemented yet? Are you perhaps a mad scientist who has invented something not done before? :eek:
     
  23. HiggyB

    HiggyB

    Unity Product Evangelist

    Joined:
    Dec 8, 2006
    Posts:
    6,183
    With finite resources and limited time there will always be some things that some people think are "must haves" that haven't been done by us yet. That leaves room for the community to step in and create items of value like this.

    Again, nice work and this is something I'm gonna play with this coming weekend. :D
     
  24. MatthewW

    MatthewW

    Joined:
    Nov 30, 2006
    Posts:
    1,356
    Nice work!

    In glancing at the code, you should limit the amount of .transform references you're using. IE, something like this:

    Code (csharp):
    1.  
    2.         vertices[spriteIndex * 4 + 0] = client.transform.TransformPoint(newSprite.verts[0]);
    3.         vertices[spriteIndex * 4 + 1] = client.transform.TransformPoint(newSprite.verts[1]);
    4.         vertices[spriteIndex * 4 + 2] = client.transform.TransformPoint(newSprite.verts[2]);
    5.         vertices[spriteIndex * 4 + 3] = client.transform.TransformPoint(newSprite.verts[3]);
    6.  
    That client.transform part is just shorthand for client.GetComponent(Transform)--Unity isn't actually caching anything for you. Pull the Transform from the GameObject once and then use it multiple times, or simply pass around Transform instances in the first place...
     
  25. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    Thanks, Jessy.

    I'm honored, Higgy. :) It still has some holes in it of course, no normals (I made it just with the intention of doing basic 2D sprites), and of course the mystery of the face culling even though from all I can tell, the tris are propertly wound CCW.

    Matthew, thanks for the tip. I still don't have a handle on all Unity is doing internally and I didn't realize that. That's good information. I'll add that as an optimization first thing tomorrow.
     
  26. ugur

    ugur

    Joined:
    Jun 3, 2008
    Posts:
    692
    several things :)

    1.

    yeah, not sure either, maybe it would be best if someone from the UT tech magicians could try this to see if its a bug in the code or unity that the textures are mirrored.
    Your changed code fixed the problem for my cam positioning/ plane setup, thanks for that, dunno if it works for all now though, still strange.

    2.
    I don´t like it a lot that AddSprite() returns an index instead of the newly created Sprite (and that would have an index property anyway).
    What do you think about changing that?
    I could change that in my copy myself, but yeah, if the idea is to have this be a generally used thing i think it would be good to change such things in the public version :)

    3.
    I couldn´t get it to create the sprites in wanted dimensions with wanted UV dimensions at wanted positions. I´m not sure what to pass in the AddSprite method: When i pass the gameObject i want to get turned into a sprite as client it seems to put the created sprite at seemingly right positions but the texture/sprite dimensions or uv setup is wrong; when i pass the parent container gameobject (which is at default 0,0,0 position and has no funky transforms applied) of the gameobject i want as sprite as client then positioning of the created sprite is wrong but textures/uvs look better.
    Have you made something like a tilebased map grid setup where you have lots of textured planes and pass those as clients in AddSprite calls to see if the transforms and are all fine? Not sure if its my setup or something on SpriteManager side.
     
  27. ugur

    ugur

    Joined:
    Jun 3, 2008
    Posts:
    692
    woot, regarding point 3 i got that sorted now and it looks good now :)
    I had the width and height dimensions arguments in the AddSprite call wrong :)

    I don´t think its that intuitive to have to pass those like that and also not sure about how one passes UVDimensions, i´m too sleepy now to think of some other solution now though.
    Overall once one gets around that mirroring problem and gets it set up well its pretty sweet, i tried it with something where the drawcalls are down from before over 140 to 1 :D
    (performance on iphone raises from below 15 to over 40 :D )
     
  28. ugur

    ugur

    Joined:
    Jun 3, 2008
    Posts:
    692
    looking at it again after some relaxing i think it would be better if instead of passing
    lowerLeftUV one would pass topLeftUV
    and regarding passing UVDimensions i think it would be better if one passed width and height instead of a scalar value. Thoughts?
     
  29. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    First of all, thanks a bunch for the feedback. I'll try to clarify some things here in answer to your issues:

    1. The textures shouldn't be mirrored if you just use the triangle index swapping method I mentioned to reverse the polygon winding. That is, unless you're viewing the whole thing from the negative Y (in YZ), negative X (in XZ), or positive Z (in XY). Otherwise, leave the Sprite class intact as-is, and just change the triangle index assignment and all should work correctly.

    2. Hmmm... it actually wasn't until late yesterday that I decided to add an index to the Sprite class, so my code had, up to that point, relied entirely on the client having the index. It wouldn't be a bad idea to return a reference instead. I'll think about that a bit just to make sure I wouldn't be harming anything, and probably will implement it. Good suggestion!

    3. Let me describe here for everyone in more detail how dimensions and UVs work with SpriteManager:

    A) The width and height of the sprite is given in worldspace units, and works the same as Unity's primitives. For example, just like a Unity cube primitive, a sprite of height 1 extends 0.5 units above the center point and 0.5 units below, yielding a total height of 1. In other words, the sprite is centered on the client GameObject. I've added it to my ToDo list, however, to allow for an offset should you want the sprite to be offset from the center of the object for some reason.

    B) SpriteManager uses the lower-left UV coordinate because UV coordinates originate at the lower-left corner and increase upward and to the right. It is upside-down from what you're used to with screen coordinates. That's not an issue with SpriteManager, that's just how UV coordinate space is oriented. Using the upper-left coordinate would make the code messier and would be potentially confusing if you're familiar with UV coordinate space.

    C) UVDimensions is a scalar value because UV coordinates are scalar values and because you don't want to closely tie your texture coordinate definitions to actual pixel dimensions. Using anything other than scalar values for this would be confusing if you are familiar with UV coordinate space. UV coordinates work like so: bottom-left == 0,0. Top-right == 1,1. So a UVDimenions value of 0.5,0.5 results in a sprite that displays starting from the specified lower-left UV coordinate, a width and height of exactly half the width and height of the texture. So to display the whole texture, you would pass a lower-left UV of 0,0, and a UVDimensions of 1,1. To display a tiled texture such that the texture tiles twice, you would pass a UVDimensions of 2,2. To display only the upper-right quarter of a texture, you would pass a lower-left UV of 0.5,0.5, and a UVDimensions of 0.5,0.5.

    All of that said, it may be helpful to some to provide a utility function that would convert pixel positions into UV coordinates for the given texture. If called each frame though, it would be a bit slower since it would have to convert the pixel positions to UV scalar values before Unity could use them, and that could add up if you've got a LOT of sprites all with UV animation occurring each frame. But it would make things simpler for people who are unfamiliar with UV coordinate space. I'll put that on my ToDo list.

    Anyway, I hope that helps. And I'm thrilled to hear you got a huge speed boost from it! I experienced the same. Draw calls, for whatever reason, incur a HUGE amount of overhead. Without something like this, drawing hundreds of independently-moving objects is going to be S...L...O...W. I'm pretty excited about the possibilities this opens up!
     
  30. ugur

    ugur

    Joined:
    Jun 3, 2008
    Posts:
    692
    np, this has potential so i´d like to give input :)


    yeah, the index swapping fixed the problem, would be cool though if this could be automated.

    :)

    Thanks for the explanation and yeah, i agree the setup makes sense by itself, just thought when its about actually doing 2D 2D stuff it might be handy to have a method where one passes dimensions of a tile/ sprite graphic in pixel deimensions and have the SpriteManager automatically get the UV dimensions based on that (and reuse that for future access).

    I see and yeah, cool.



    yeah :)
    I before tried it with a material with a texture just showing a single graphic and that worked fine.
    Now i wanted to do it with a spritesheet texture that features many sprite graphics next to each other in several rows/columns and i couldn´t get lowerLeftUV to do anything, i mean no matter what i pass it always copies the same area of the texture.
     
  31. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    I'm now working on a MAJOR update to the system, and will allow selection of CW or CCW winding.

    lowerLeftUV was working just fine in my tests, but I've changed a couple of things since then... not things that I would have thought affected this behavior, but I'll look into it.
     
  32. ugur

    ugur

    Joined:
    Jun 3, 2008
    Posts:
    692
    very nice :)

    thanks, dunno either. Could it be related to the swapping of indexes to fix the mirroring problem?
     
  33. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    Okay, I've updated the SpriteManager in a big way. But first I need to ask a question hopefully somebody has an answer for: Would it be faster to call Sprite.Transform(), or to inline the code found in that routine but have to call sprite.v1, sprite.v2, etc, to access each of its vertices? I'm not sufficiently familiar with how C# compiles. If it was C++ I'd know the answer, but I just want to know for sure with C#.

    See the bottom of the wiki page to see a list of changes made. But to summarize the big ones:
    * AddSprite now returns a reference as suggested
    * Incorporated the optimization suggested about caching the client object's transform, as well as some other optimizations
    * Added an offset member to the Sprite class, allowing you to offset the sprite from its client GameObject
    * Added the ability to specify texture coordinates in AddSprite using pixel-space values
    * Added an option to easily select which winding order you want to use

    There are some things I would have normally written differently for the sake of elegance, but for the sake of optimized performance (which is critical and was the whole point of writing this class in the first place), I did them differently.

    Anyway, try this latest version unmodified and let me know if you still have the lowerleftUV problem, tommosaur.
     
  34. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    BTW, I'm now adding color support, allowing you to change the color (and transparency) of each sprite at runtime. I already have it implemented, but want to just make sure everything's right before I do another release. It's working great so far though. I should have it online by next week.
     
  35. potan

    potan

    Joined:
    Nov 2, 2008
    Posts:
    177
    this is awesome Brady !!
    thanks for sharing it :D
     
  36. 3dgrinder

    3dgrinder

    Joined:
    Oct 21, 2008
    Posts:
    249
    Hi all,

    Thanks to Brady for his new version. :). I don't know any one found that problem or not. The case is like this: if you add LikedSpriteManager and don't create any Sprite by calling AddSprite() function then it's showing following information:

    I added this script to my game controller and I want to create the Sprite on demand. Also I found that, if I commented the LateUpdate() LinkedSpriteManager then this info is not coming.

    Brday, any idea bro?

    Thnaks
     
  37. 3dgrinder

    3dgrinder

    Joined:
    Oct 21, 2008
    Posts:
    249
    Hi all,

    Thanks to Brady for his new version. :). I don't know any one found that problem or not. The case is like this: if you add LikedSpriteManager and don't create any Sprite by calling AddSprite() function then it's showing following information:

    I added this script to my game controller and I want to create the Sprite on demand. Also I found that, if I commented the LateUpdate() LinkedSpriteManager then this info is not coming.

    Brday, any idea bro?

    Thnaks
     
  38. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    Hmmm... I'm glad you mentioned this. I did some looking and I think I found the cause of it. #1, EnlargeArrays() wasn't setting the uvsChanged flag to make sure the UVs get copied over in the initial batch creation (this would normally happen after a sprite was created, but since you hadn't created any, they weren't getting copied). #2, the logic to check for UVs being changed wasn't quite right in LinkedSpriteManager. I fixed that as well.

    I'll post the updated version in the next day or two along with the other additions I've made. Thanks again for spotting this!
     
  39. 3dgrinder

    3dgrinder

    Joined:
    Oct 21, 2008
    Posts:
    249
    You are welcome Brady. :)

    By the way, I was trying to make Billboard effect using following changes in SpriteManager:
    Code (csharp):
    1.     public void LookAtTarget ( Transform target ){
    2.         m_clientTransform.LookAt ( target );
    3.     }
    4.    
    5.     public void MoveClient ( float movingSpeed ){
    6.        
    7.         m_clientTransform.position +=  dirToMove * movingSpeed * Time.deltaTime;
    8.    
    9.     }
    10.  
    11.  
    And to effect this I used following code in LinkedSpriteManager:
    Code (csharp):
    1.     void TransformSprites()
    2.     {
    3.         //vertsChanged = false;
    4.         for(int i=0; i<activeBlocks.Count; ++i)
    5.         {
    6.             Sprite sp = (Sprite)activeBlocks[i];
    7.             sp.MoveClient ( 100.0f );
    8.             sp.LookAtTarget ( Camera.main.transform );
    9.             sp.Transform();
    10.         }
    11.        
    12.         if ( activeBlocks.Count > 0 ){
    13.             vertsChanged = true;
    14.         }
    15.     }
    16.  
    17.  
    I should mentioned that, I'm trying to use it in 3D space as a Billboard and I created the Plane in XY plane. But it doesn't work properly except in XY plane. If camera look at the XY plane then Billboard visible otherwise not. May be I'm missing something. :(. I'm still trying what I'm missing.

    Thanks
     
  40. RBerg

    RBerg

    Joined:
    Aug 14, 2008
    Posts:
    10
    Wow Brady, what a great addition this functionality is to the community! Thanks for all of your hard work. I was trying to do something very similar to this by manipulating individual particles from a particle system, but this is just great. It would be really awesome to have the ability to billboard the sprites for use in 3D space, would that be a hard addition? (My knowledge about manually drawing meshes and UV mapping is severely limited, or else I would give it a shot)

    Grinder, I believe your approach isn't working because the SpriteManager does not use rotation information from the client object to draw the quads.

    Thanks!
    -Ryan
     
  41. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    Thanks, RBerg. Actually, when a quad is transformed, it does rotate the quad as well. So if the client GameObject is facing the right direction, it should work. However, if they're not showing up you may need to try a different winding order. Or else you may need to "LookAt" the opposite direction than you would think.

    A note on my overall design: I didn't include any routines that modify the client object because I wanted the SpriteManager to be a low-profile solution. By that I mean that most people already have games coded either in Unity or Unity iPhone that use separate drawcalls for each object, and the behaviors of those objects are already defined. So my thought was, modifying those objects through SpriteManager could produce unexpected behavior since the object is being modified by two sources. As such, SpriteManager was just meant to be a way to render what's in the game.

    That said, being able to flag a sprite as billboarded is a good idea. I'll add that to my todo list, but I think I'll try to find a way to implement it without modifying the client object. For example, if the client object is under physics control, you can get some bad effects when you manually modify its position/rotation rather than applying forces.
     
  42. Andrej-Vojtas

    Andrej-Vojtas

    Joined:
    Jan 12, 2009
    Posts:
    67
    Thanks Brady, you are doing an awesome job here.

    I'm completely new in Unity. Can someone answer how this fits into the bigger picture? If I got it right from Brady's last post: you use all the Unity behaviors, physics, it's slick editor... and this just renders the sprites super fast?

    I hope I don't ask to much, but what would be the natural path to understand how to work with this be? I imagine and answer like: first study tutorial: 2d gameplay, study documentation for this and that, ...

    I must admit I almost dropped Unity3d for it's lack of 2d sprite support. But you made me a believer again.

    Keep the fire.
     
  43. UVRadiation

    UVRadiation

    Joined:
    Jul 21, 2008
    Posts:
    183
    Thanks! this is exactly what I need!
    I'm gonna try it on my game this weekend. :D
     
  44. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    Thanks, Andrej. Actually, it would be more accurate to say this helps remove the overhead of rendering lots of independently-moving sprites by taking care of the vertex transformations.

    There's not much to implementing this in your project. The "hard part" is going to be the usual stuff involved in making a game. Basically, you just make your game like you would normally, but instead of attaching a mesh (such as a quad or "plane") directly to the GameObject in question, you put just a couple of lines in a script that register that GameObject with SpriteManager, and whala, it gets rendered.

    The major drawback I see with this currently is that your sprites won't be visible until you run the game. So if you're setting up objects in the editor and it's important to see them as they will appear at runtime, I'd recommend developing them with their own meshes, and then when you're ready to optimize, remove/disable the mesh on the object and enable the script that adds it to the SpriteManager.

    If anyone has any tips as to how to write an editor script that would make this work at edit-time, that'd be awesome.
     
  45. seon

    seon

    Joined:
    Jan 10, 2007
    Posts:
    1,441
    Ok, I am trying to implement this SpriteManager in my ALL JavaScript code project and I am at a loss as to why I cannot reference SpriteManager as a Type for variable assignment.

    My understanding is that JavaScript and CS scripts can be accessed via each other.

    I have a GO called GameLogic that has the SpriteManager script on it and I am using this to access it in my Start() function.

    Code (csharp):
    1. var GL : GameObject = gameObject.Find("_GameLogic");
    2. var SM : SpriteManager = GL.GetComponent("SpriteManager");
    but I am getting the following error:-

    Code (csharp):
    1. BCE0018: The name 'SpriteManager' does not denote a valid type.
    My Script is called SpriteManager and of course the public class inside the script is called SpriteManager, so what else do I have to do to get it to talk?
     
  46. seon

    seon

    Joined:
    Jan 10, 2007
    Posts:
    1,441
    Don't worry, forgot to put the scripts in my plugins directory!
     
  47. mklaurence

    mklaurence

    Joined:
    Dec 26, 2008
    Posts:
    104
    +1 for any billboarding tricks you can devise :)

    I got it working at certain viewing angles, but that was the extent of my fiddling. If I get really desperate I might try again, but anything I produced would probably be an inefficient hack...

    Very nice work so far!
     
  48. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    Okay, I think I have billboarding working okay now... it is a fair bit slower than just doing the straight-up transform, unfortunately however. Optimization suggestions are welcome. But I'll clean up the code a bit and roll the billboarding in to the next update I'll be posting soon.
     
  49. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    Okay, I've updated the scripts. You can see the latest docs and such on the wiki entry.

    Please, anyone, let me know if there's any way to further optimize the billboarding. It is significantly slower than regular transforming at the moment. But it shouldn't be a problem as long as you don't have a whole bunch of billboarded sprites. Only billboard sprites that absolutely MUST be billboarded.
     
  50. Andrej-Vojtas

    Andrej-Vojtas

    Joined:
    Jan 12, 2009
    Posts:
    67
    Thank you Brady for your explanation. It's a very smart solution ;)