Search Unity

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

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

  1. se7en

    se7en

    Joined:
    Dec 3, 2008
    Posts:
    232
    I don't have time to get into the package and fully test it out yet but it sounds amazing. I have a few "stupid" questions in the meantime...

    1. Real World iPhone Test - how many moving small sprites (say 64x) will run at say 40 frames per second? ( I am leaving 10 fps buffer for a scrolling bg, HUD, etc that would need to be used)

    2. Is the interface all C# or can it controlled and implemented with Javascript?

    3. How is movement handled X, Y type coord each frame like Flash or can it use Physics/Transform on the sprites?

    This might be a great option for my game but it mostly means tearing out about half of what I have already done to use this system. It may be a huge performance gain or it might not be worth a major undertaking. Any upfront info will certainly help me decide if this is worth pursuing or not.

    Also, Brady great job on this!! Its cool to see people adding stuff to the Unity Engine that it is clearly lacking imo ; )
     
  2. se7en

    se7en

    Joined:
    Dec 3, 2008
    Posts:
    232
    Bump. Has anyone done some iPhone test builds yet!?... how many objects, size of objects, framerate, what shaders, etc. This system looks really promising.
     
  3. seon

    seon

    Joined:
    Jan 10, 2007
    Posts:
    1,441
    Se7en,

    It TOTALLY depends on what else your game logic is doing. The sprites will move and rotate based on the GO Transform they are attached to, so the more complicated their movement (physics, rigidbodies etc) the slower your game will perform.

    In CD iPhone, I get about 40-60 with no problems.
     
  4. se7en

    se7en

    Joined:
    Dec 3, 2008
    Posts:
    232
    Seon thanks - Hmm... okay so it must be completely opposite of what I was thinking. I was assuming that you would put this on a GO and NOT move it at all but move the sprites. What your describing sounds more like a "flock" behavior.

    I guess I will have to try it out, once I get some downtime : )
     
  5. CedarParkDad

    CedarParkDad

    Joined:
    May 28, 2008
    Posts:
    98
    The SpriteManager associates each sprite with a 3D object. The object is then moved in 3D and the sprite associated with it is moved accordingly. Billboarded sprites will also be rotated to face the camera's forward vector. It allows manipulation of objects through everything Unity already gives you, but handles display of a sprite for the object instead of a separate draw call. Unless you connected each object's mesh and they all used the same material then you are saving a draw call for each object beyond the first. This is a HUGE savings on the iPhone.

    If the objects are relatively static then you can fit more using the base SpriteManager. The more they move, the fewer you can have. Obviously, anything else you do to use processor time will also affect count. Using the LinkedSpriteManager you can get some more in if they are all moving since there is no individual check per object.

    As Seon says, it depends on what you are doing as to how many sprites you can fit. Using the LinkedSpriteManager with billboarding, all sprites moving, and nothing else happening I could get to about 75 before it started collapsing under the weight on a 1st Gen touch. 2nd Gen touch could get to about 100 under the same conditions. Billboarding adds the extra expense of rotation, so I'm sure you could get more with just sprites.
     
  6. Bububear

    Bububear

    Joined:
    Mar 1, 2009
    Posts:
    106
    Hi,

    I looked around for where I can download this SpriteManager, but was unable to find it. Can somebody please point me to where the download link is?

    Thanks,
     
  7. se7en

    se7en

    Joined:
    Dec 3, 2008
    Posts:
    232
    Dude Bububear what the hell?... page 1 of this thread or the wiki ; )

    CedarParkDad - Excellent info. I checked out the package and it is amazing. It will definitely help out and looks to be well worth the time investment to get it running in my app.

    The only thing that I am concerned about is the C#. I don't really know the first thing about it. I am assuming that we can call all of these functions from javascript? Please say yes : )
     
  8. Langaert

    Langaert

    Joined:
    Mar 2, 2009
    Posts:
    21
    First off, thanks for putting SpriteManager together, I have implemented it for my most recent project. Can you fill me in on what kind of support exists for depth management of sprites being drawn within the SpriteManager?

    [EDIT:]
    To elaborate, what I mean is that the SpriteManager seems to assign depth values purely based on when a Sprite was added to the Manager, and not based on any other factor. Before implementing the SpriteManager (that is, with a bunch of seperate mesh objects for each sprite) I had been handling depth management by assigning different Y values (I use an XZ plane). This method doesn't work with the SpriteManager.
     
  9. GamesByJerry

    GamesByJerry

    Joined:
    Oct 27, 2008
    Posts:
    71
    If I'm understanding you correct Langaert I believe your issue is due to the shader your using. I had to modify my shader to change ZWrite from Off to On.

    I noticed sprites appearing infront of objects when they should have been behind them and this fixed my issues. I'm not entirely sure if this is the best method, it was the first thing I saw in the shader (warning: no experience when it comes to shaders at all!) and decided to try my luck :)
     
  10. CedarParkDad

    CedarParkDad

    Joined:
    May 28, 2008
    Posts:
    98
    @Langaert
    Did you set the SPRITE_PLANE of the SpriteManager to be XZ?
     
  11. dock

    dock

    Joined:
    Jan 2, 2008
    Posts:
    605
    It's a real shame that there is no Javascript demo code for this. It's proving to be a bit of an uphill struggle to figure out how to get it working in a javascript based project.
     
  12. seon

    seon

    Joined:
    Jan 10, 2007
    Posts:
    1,441
    dock... it's extremely easy to get it working.

    Create a directory in your Assets folder called plugins and put both of the .CS files in there.

    Now just call the methods in javascript... done.

    Example:

    var SM : SpriteManager;
    var mySprite : Sprite;

    mySprite = SM.AddSprite(............);
     
  13. se7en

    se7en

    Joined:
    Dec 3, 2008
    Posts:
    232
    I am not finding it all that easy yet : ) I have the SpriteManager working and displaying a sprite with my set UVs - so far so good. When I put a rigidbody on my child GameObject and I move it around on the same plane the sprite does not follow.

    In Step 2 it says create a reference to the Sprite Manager but I am unclear as to wether I need to call the AddSprite function from the child GO or from the SM?

    I just tried it another way and that doesn't appear to work for me either. I'm not sure what I'm missing but I would guess that A. it's obvious and B. that I am an idiot : )

    Here is my Code if it helps:

    On the Sprite Manager GameObject and Script

    Code (csharp):
    1. var SM : SpriteManager;
    2. var mySprite : Sprite;
    3. var spriteObject : GameObject;
    4.  
    5. function Start () {
    6. mySprite = SM.AddSprite(spriteObject, 3, 3, 0, 128, 64, 64, false);
    7. // GameObject to follow, W World Units, H World Units, Left Pixel, Bottom Pixel, Width Pixels, Height Pixels, bilboard
    8. }
    On a GO called Sprite_1 with rigidbody
    Code (csharp):
    1. var SM : SpriteManager;
    2.  
    3. function Update () {
    4. rigidbody.AddRelativeForce(0, 0.05, 0);
    5. rigidbody.AddTorque(0, 0, 0.05);   
    6. }
     
  14. RBerg

    RBerg

    Joined:
    Aug 14, 2008
    Posts:
    10
    @se7en

    In my opinion, the easiest way to get this working is to call AddSprite within the Start function of the GameObject that represents the sprite. Right now you are passing in another GameObject and using that as the client object. Try this:

    Code (csharp):
    1.  
    2. var SM : SpriteManager;
    3. var mySprite : Sprite;
    4.  
    5. function Start () {
    6. mySprite = SM.AddSprite(this.gameObject, 3, 3, 0, 128, 64, 64, false);
    7. }
    8.  
    9. function Update () {
    10. rigidbody.AddRelativeForce(0, 0.05, 0);
    11. rigidbody.AddTorque(0, 0, 0.05);  
    12. }
    13.  
    This approach should work.
     
  15. se7en

    se7en

    Joined:
    Dec 3, 2008
    Posts:
    232
    RBerg - thx. I actually had just dragged a copy of itself in there when I was trying to get it to work : )

    But... still no go. I cannot drag or select a sprite manager script to the child script. I thought maybe creating an instance would be better but I can't seam to "make a reference" that way either.

    How do I make this connection in JS because...
    Code (csharp):
    1. SM : SpriteManager;
    doesn't appear to work on an instance or 100% if the child object is on the stage. This is why I asked if it had a javascript interface or not ; ) And even though I may act like at times, I am not a complete idiot.[/code]
     
  16. ugur

    ugur

    Joined:
    Jun 3, 2008
    Posts:
    692
    RBerg and all, since i´m working on the animation side now i wondered, what´s the state with animation handling here?
     
  17. Langaert

    Langaert

    Joined:
    Mar 2, 2009
    Posts:
    21
    CedarParkDad: Yes, the Sprite Plane is set to XZ. Short of that, I wouldn't really be seeing anything at all!

    Tagged: Thanks for the advice, sounds like that might be what I need to do, but how do I edit a shader? I'm using Particles/ Alpha Blended and it doesn't show up in the project view so how do I access it? Can't find any documentation on this. Thanks.
     
  18. Brackhar

    Brackhar

    Joined:
    Feb 3, 2009
    Posts:
    7
    Is there any particular reason why one should not use the object transform when resizing a sprite? The doc says not to do this unless you "really understand why".
     
  19. se7en

    se7en

    Joined:
    Dec 3, 2008
    Posts:
    232
    Brackhar - he's saying don't resize the sprite manager itself. It should be scale 1.0 and sit at Vector3.zero.

    Langaert - The shader is part of the material that you apply to the sprite manager.
     
  20. Brackhar

    Brackhar

    Joined:
    Feb 3, 2009
    Posts:
    7
    Oh, it's fine to resize the objects the sprites are attached to then?

    On the same lines, is there a decent way of getting a preview of the sprite in the editor window? I'm trying to use the sprite manager to create a 2D foreground, and it'd be nice if I could see what I'm doing easily without having to constantly starting the game.
     
  21. Langaert

    Langaert

    Joined:
    Mar 2, 2009
    Posts:
    21
    Of course it is. That wasn't my question. The material editor only lets me select a shader from a drop down. The edit shader option is shaded out when using built-in shaders such as Particles/ Alpha Blended. Unless I can use the edit shader option, I don't know how to dig into the shader itself and specify the ZWrite. THAT was my question.

    Perhaps a better route is simply to ask if someone has a good basic shader for use with the SpriteManager that they would like to share with everyone? It would be nice if this became part of the documentation for the SpriteManager, since the current suggestion of using "one of the Particles shaders" doesn't seem to really pass muster. The built in particle shaders don't do adequate depth sorting.
     
  22. se7en

    se7en

    Joined:
    Dec 3, 2008
    Posts:
    232
    Sorry Langaert I didn't realize what you were asking. There is a lot of info in the shader section on here and in the wiki... I'm pretty sure that it all applies.

    FYI - I solved the object not following rigidbody problem... I just needed to use the LinkedSpriteManager.cs and I applied the configurable joint. Back in business!
     
  23. imparare

    imparare

    Joined:
    Jun 24, 2008
    Posts:
    369
    I have a shader that replaces particles alpha/blended but it was given to me by jfrisby so if its ok with him then I will post it (you can use it and then set up the z order/index which works fine). If he sees this and ok's it then I will post it but have not seen him around for a while on the irc.


    On another issue I changed my camera today from perspective to ortho and a certain points (reproducible) the sprite disappears. In fact all sprites disappear. I know of someone else who had the opposite problem in that he changed from ortho to perspective. Everything works fine then bam it does not seem to render at a certain position (2 positions actually in my game). Anyone else had this issue and maybe has a workaround ?

    http://img26.imageshack.us/my.php?image=image1etj.png scene view where you can see the sprite

    http://img22.imageshack.us/my.php?image=image2mhs.png game view where you do not see the sprite (red cube in there for reference)

    http://img17.imageshack.us/my.php?image=image3gzk.png ortho cam moved slightly can now see the sprite

    this was using particles a/b

    edit: NCarter (irc) pointed me in the right direction ScheduleBoundsUpdate(float seconds);
     
  24. se7en

    se7en

    Joined:
    Dec 3, 2008
    Posts:
    232
    False Alarm - it is not working when I try to instantiate a game object. The only way I can get it to work is if the Sprite is placed in the scene and then I drag the SpriteManager also placed in the scene to the SpriteManger var in the editor. That appears to be the only way to make an actual reference in javascript.

    I'm sure I can do some cool stuff with it but if it doesn't work with dynamic instances in JS then it is a waste of time I guess.

    I really wish someone that has a solid understanding of the system would step up and answer some of these questions. I get that it was made available freely and without warranty but quite a few people here could use some help.

    ie - Has anyone actually used this system with JS and how? I am seriously frustrated right now, as this looks like it could be amazing but I can't even get it started.
     
  25. se7en

    se7en

    Joined:
    Dec 3, 2008
    Posts:
    232
    Not sure if this has been mentioned or not but it looks like while this saves a metric ton of draw calls it trades that in for eating massive amounts of memory. Adding 8 non moving non-linked sprites cause my App to crash after 15 seconds. It would not even load the App with 8 moving, linked sprites.

    I'm not bashing, I just thought I should throw it out there. It might save someone else potential wasted time.
     
  26. seon

    seon

    Joined:
    Jan 10, 2007
    Posts:
    1,441
    se7en,

    Its not a memory issue, its your code issue. Sorry. I have over 100 sprites on screen at once in CDi, all moving and many animating.

    Thee are too many people using this successfully to have it be a SpriteManager issue.

    On the point of your Instantiated objects/Sprites not working... please provide code snippets of your Start() and Awake() code in your Instantiated object, so we can help you out.

    There is NO difference between using the SM in C# or JavaScript. just a lack of understanding on implementation on your behalf, and that wold be the same in C#.
     
  27. se7en

    se7en

    Joined:
    Dec 3, 2008
    Posts:
    232
    Seon - I agree that my App is pushing the memory envelope already but, adding the 8 sprites causes it to crash vs. adding 8 more objects/draw calls it doesn't.

    I did give samples of my code but I guess I can do it again. The problem seams to be that I can only get it to work if the sprites and the manager are in the scene before it starts. Even if I duplicate a sprite I have to re-link it with the placed manager or the sprite doesn't register.

    GameObject 1: SpriteManger
    Script: SpriteManger.cs (located in a directory call "plugins")

    GameObject 2: Sprite_1

    Code (csharp):
    1. var SM : SpriteManager;
    2.  
    3. function Start() {
    4. SM.AddSprite(gameObject, 2, 2, 192, 64, 64, 64, false);
    5. }
    I have tried about 6 different way so far. Same results.

    I agree with the lack of understanding... thats why I have been asking ; )
     
  28. seon

    seon

    Joined:
    Jan 10, 2007
    Posts:
    1,441
    Ok you are not assigning the SM variable on object instantiation.

    Code (csharp):
    1. var SM : SpriteManager;
    2.  
    3. function Awake() {
    4. var GO : GameObject = GameObject.Find("your sprite manager GO name goes here");
    5. SM = GO.GetComponent("SpriteManager");
    6. }
    7.  
    8. function Start() {
    9. SM.AddSprite(gameObject, 2, 2, 192, 64, 64, 64, false);    
    10. }
    Your instantiated object has to link its reference to the main SpriteManager for it to be able to use SM. whatever.

    This is the code version of you dragging it onto the GO in the editor.

    Cheers :)
     
  29. se7en

    se7en

    Joined:
    Dec 3, 2008
    Posts:
    232
    This was one of the ways that I tried before except I wasn't using both Awake and Start just one or the other.

    I am still getting this error:
    NullReferenceException: Object reference not set to an instance of an object
     
  30. seon

    seon

    Joined:
    Jan 10, 2007
    Posts:
    1,441
    If you double click on the error, what line is it pointing to. That error alone is not very helpful.

    Also, are you using the SpriteManager, or LinkedSpriteManager?

    if you are using the LinkedSpriteManager, this code wont work... you need to change the code to be LinkedSpriteManager, not SpriteManager.
     
  31. se7en

    se7en

    Joined:
    Dec 3, 2008
    Posts:
    232
    The error is pointing to the AddSprite:

    SM.AddSprite(gameObject, 2, 2, 192, 64, 64, 64, false);

    I have also tried
    Code (csharp):
    1. this.gameObject
    with the same results and the following (with and without quotes)

    Code (csharp):
    1. SM = GO.GetComponent("SpriteManager");
    I really appreciate your help! I still feel like its a JS issue. More specifically it looks like a timing issue where the SpriteManger is not really loaded prior to calling the AddSprite. Are you using using JS to run yours in CDi... I am guessing not ; )
     
  32. se7en

    se7en

    Joined:
    Dec 3, 2008
    Posts:
    232
    Hey Seon... did you give up then? Or maybe you are asleep... 20 hr time gap I think.

    Anyone on here and I mean anyone with CS and JS knowledge, please come forward...
     
  33. RBerg

    RBerg

    Joined:
    Aug 14, 2008
    Posts:
    10
    @se7en

    There is a sample project earlier in this thread that i suggest you take a look at. It demonstrates how everything gets "wired" together.

    The basic breakdown is:

    1) You need an empty game object in your scene that has the SpriteManager (I use LinkedSpriteManager) script attached to it.

    2) You need other game objects in your scene that represent the sprites. Those sprite game objects have your own script attached to them. In this script is where you make calls to the sprite manager such as AddSprite, RemoveSprite, etc.... You get the reference to the sprite manager in one of two ways. You either a) pass the game object that has the sprite manager script on it into your sprite game objects through the editor, or b) use the Find method to find the game object that has the sprite manager script attached to it. If using Find be sure to pass the name of the game object, not the name of the script.

    Hope this helps.
    -Ryan
     
  34. RBerg

    RBerg

    Joined:
    Aug 14, 2008
    Posts:
    10
    @tommosaur

    I can post my animation code tonight. It isn't as "refined" as I would like it to be, but perhaps you or someone else can pick up where I left off.

    Thanks.
    -Ryan
     
  35. dock

    dock

    Joined:
    Jan 2, 2008
    Posts:
    605
    Code (csharp):
    1. //This shifts the UVs
    2. (mySprites[11] as Sprite).lowerLeftUV = SM.PixelCoordToUVCoord(83, 253);
    3.  
    4. //This line hides the sprite
    5. (mySprites[11] as Sprite).hidden = true;
    6.  
    7. //This line of code does nothing!
    8. (mySprites[11] as Sprite).SetColor(new Color(0,0,0,1));
    I can't seem to get Sprite 'SetColor' working in javascript no matter what syntax I used. Is there some sort of trick to using this? I realise it works easily in C#, but I don't see why it fails in JS.
     
  36. se7en

    se7en

    Joined:
    Dec 3, 2008
    Posts:
    232
    Thanks RBerg I appreciate the input. What you suggest is exactly what I'm doing. I did DL and import the package and the CS version works fine. I get the null error when trying to create sprites dynamically no matter what with JS.

    Hey Dock - can you help out here? Are you instantiating sprites at runtime through JS and how?

    I have a feeling that answer is super simple, i just can't seam to find it.
     
  37. seon

    seon

    Joined:
    Jan 10, 2007
    Posts:
    1,441
    Sorry, was at the hospital with my dad all day yesterday.

    Ok, have you checked that at runtime, the var SM is actually set?

    Click run, leave it running. find your GO in the scene, click on it and see if the var SM is set. It sounds like it isnt, which means your GameObject.Find isnt working.
     
  38. dock

    dock

    Joined:
    Jan 2, 2008
    Posts:
    605
    se7en, yes I am creating sprites at runtime. It's actually easier that way, as then you can create the gameobjects (based on prefab colliders).

    I use two arrays:
    var myCans = new Array();
    var mySprites = new Array();

    and in my Start function I have this:
    Code (csharp):
    1.    
    2. myCans.Add(Instantiate(canPrefab, new Vector3( 10, -8, 0), Quaternion.identity));
    3.     myCans.Add(Instantiate(canPrefab, new Vector3( -8, -8, 0), Quaternion.identity));
    4.     myCans.Add(Instantiate(canPrefab, new Vector3( 1, 1, 0), Quaternion.identity));
    5.     myCans.Add(Instantiate(canPrefab, new Vector3( 10, 1, 0), Quaternion.identity));
    6.     myCans.Add(Instantiate(canPrefab, new Vector3( -8.2, 1, 0), Quaternion.identity));
    7.     myCans.Add(Instantiate(canPrefab, new Vector3( 1.2, 10, 0), Quaternion.identity));
    8.     myCans.Add(Instantiate(canPrefab, new Vector3( 10.2, 10, 0), Quaternion.identity));
    9.     myCans.Add(Instantiate(canPrefab, new Vector3( -8, 10, 0), Quaternion.identity));
    10.     myCans.Add(Instantiate(canPrefab, new Vector3( 1.5, 20, 0), Quaternion.identity));
    11.     myCans.Add(Instantiate(canPrefab, new Vector3( 10.5, 20, 0), Quaternion.identity));
    12.     myCans.Add(Instantiate(canPrefab, new Vector3( -8.5, 20, 0), Quaternion.identity));
    13.     myCans.Add(Instantiate(canPrefab, new Vector3( 1.5, 30, 0), Quaternion.identity));
    14.  
    15.     for(i=0; i<10; ++i)
    16.     {
    17.         s = SM.AddSprite(myCans[i]  ,10.00  ,10.00  ,256    ,256    ,256    ,256, false);
    18.         mySprites.Add(s);
    19.     }  
    20.  
    I think this SpriteManager could really do with a demo project in Javascript. I would make one myself, but I haven't been able to get all the features working via JS.
     
  39. ugur

    ugur

    Joined:
    Jun 3, 2008
    Posts:
    692
    cool, yeah, i´ve been working some on mine, too and also chatted with [xform]Zero whos also working on animation stuff for SpriteManager. I think it would be cool if we join forces and there´s the best of all thing at the end :)

    I started off with a UV animation example from the wiki
    (
    http://unifycommunity.com/wiki/index.php?title=Animating_Tiled_texture_-_Extended
    ) and added functionality to define multiple anims, switch between them and have simple playback control.

    I hacked it down quickly so it surely could be improved a lot, but yeah, maybe its useful for comparing and combining what´s good :)

    I always make a small seperate test project when i work on such stuff, here´s the code:

    (You can see how to define Anims and play one in the Awake fn)
    Code (csharp):
    1.  
    2.  
    3. //vars for the whole sheet
    4. private var colCount    : int =  16;
    5. private var rowCount    : int =  8;
    6.  
    7. //vars for animation
    8. var rowNumber   : int =  0; //Zero Indexed
    9. var colNumber   : int =  0; //Zero Indexed
    10. var totalCells  : int =  4;
    11.  var size: Vector2;
    12. var fps  : int;
    13. var looping: boolean= false;
    14. private var offset  : Vector2;  
    15.  
    16. var chosenSpriteAnimationName: String;
    17. var spriteAnimations: Hashtable= new Hashtable();
    18.  
    19. var playing: boolean= false;
    20. var chosenSpriteAnimation: SpriteAnimationInfoObject=null;
    21. //time moment at which playing a different animation was triggered (used to make surte non looping anims play once with right frame offset)
    22. private var offsetTime: float;
    23.  
    24. function Awake(){
    25.    
    26.     // addSpriteAnimation(incomingAnimationName: String,incomingRowNumber: int,incomingColNumber: int,incomingTotalCells: int,incomingFps: int,incomingLooping: boolean)
    27.    
    28.      // Size of every cell
    29.     size = Vector2 (1.0 / colCount, 1.0 / rowCount);
    30.    
    31.    
    32.  
    33.    
    34.     addSpriteAnimation("sheepWait",0,0,61,25,true);
    35.    
    36.     addSpriteAnimation("wink",0,3,36,15,true);
    37.    
    38.     addSpriteAnimation("down",3,13,4,15,true);
    39.    
    40.     addSpriteAnimation("up",4,2,4,15,true);
    41.    
    42.     addSpriteAnimation("left",4,7,4,15,true);
    43.    
    44.     addSpriteAnimation("right",4,12,4,15,true);
    45.    
    46.     addSpriteAnimation("drown",0,82,31,15,false);
    47.    
    48.     addSpriteAnimation("sheepWaitNoLoop",0,0,61,25,false);
    49.     addSpriteAnimation("winkNoLoop",0,3,36,15,false);
    50.     addSpriteAnimation("eatNoLoop",2,14,14,15,false);
    51.    
    52.  
    53.  
    54.  
    55.         var randomNumber: int= Random.Range(1, 3);
    56.         var chosenSpriteAnimationName: String= "sheepWaitNoLoop";
    57.         if(randomNumber==1){
    58.             chosenSpriteAnimationName="winkNoLoop";
    59.        
    60.         }
    61.         if(randomNumber==2){
    62.             chosenSpriteAnimationName="eatNoLoop";
    63.            
    64.         }      
    65.            
    66.         PlayAnimationByName(chosenSpriteAnimationName);            
    67.        
    68.  
    69.    
    70.    
    71.    
    72.    
    73. }
    74.  
    75.  
    76. //Update
    77. function Update () {
    78.     if(playing  chosenSpriteAnimation.fps!=0){
    79.        
    80.         //Debug.Log("colCount:"+colCount+",rowCount:"+rowCount+",rowNumber:"+rowNumber+",colNumber:"+colNumber+",totalCells:"+totalCells+",fps:"+fps);
    81.        
    82.         PlayCurrentSpriteAnimation(colCount,rowCount,rowNumber,colNumber,totalCells,fps, looping);  
    83.        
    84.     }
    85. }
    86.  
    87.  
    88. public function PlayAnimationByName(animationName: String){
    89.     if((animationName=="sheepWait" || animationName=="eatNoLoop"  || animationName=="winkNoLoop")  chosenSpriteAnimationName!=animationName){
    90.         var randomNumber: int= Random.Range(1, 3);
    91.         if(randomNumber==1){
    92.             chosenSpriteAnimationName="winkNoLoop";
    93.             PlayAnimationByName(chosenSpriteAnimationName);
    94.         }
    95.         if(randomNumber==2){
    96.             chosenSpriteAnimationName="eatNoLoop";
    97.             PlayAnimationByName(chosenSpriteAnimationName);
    98.         }      
    99.     }
    100.    
    101.    
    102.     var targetAnimation: SpriteAnimationInfoObject= spriteAnimations[animationName];
    103.     if(targetAnimation){
    104.         offsetTime= Time.time;
    105.         chosenSpriteAnimationName= animationName;
    106.         chosenSpriteAnimation= targetAnimation;
    107.         rowNumber=chosenSpriteAnimation.rowNumber;
    108.         colNumber=chosenSpriteAnimation.colNumber;
    109.         totalCells=chosenSpriteAnimation.totalCells;
    110.         fps=chosenSpriteAnimation.fps;
    111.         looping= chosenSpriteAnimation.looping;
    112.         offset= Vector2(0,0);
    113.         playing= true;
    114.     }
    115. }
    116.  
    117. public function Stop(){
    118.     playing= false;
    119. }
    120.  
    121.  
    122. public function Play(){
    123.     playing= true;
    124. }
    125.  
    126. //adding of new animations is done with this fn:
    127. public function addSpriteAnimation(incomingAnimationName: String,incomingRowNumber: int,incomingColNumber: int,incomingTotalCells: int,incomingFps: int,incomingLooping: boolean){
    128.     var newSpriteAnimation: SpriteAnimationInfoObject= new  SpriteAnimationInfoObject(incomingAnimationName,incomingRowNumber,incomingColNumber,incomingTotalCells,incomingFps,incomingLooping);
    129.     spriteAnimations[newSpriteAnimation.animationName]= newSpriteAnimation;
    130. }
    131.  
    132. //internal fn used to play current animation:
    133. private function PlayCurrentSpriteAnimation(colCount : int,rowCount : int,rowNumber : int,colNumber : int,totalCells : int,fps : int, looping: boolean){
    134.  
    135.     // Calculate index
    136.     var index : int;
    137.    
    138.         var uIndex: float;
    139.         var vIndex: float;    
    140.    
    141.     // Repeat when exhausting all cells
    142.     if(looping){
    143.         index = (Time.time) * fps;
    144.         index = index % totalCells;
    145.        
    146.         // split into horizontal and vertical index
    147.         //was buggy in wikie version, fixed these two lines here:
    148.         uIndex = (index+colNumber) % colCount;
    149.         vIndex = (index+colNumber) / colCount;
    150.  
    151.         //Debug.Log("uIndex:"+uIndex+",vIndex:"+vIndex);
    152.  
    153.         // build offset
    154.         // v coordinate is the bottom of the image in opengl so we need to invert.
    155.         //was buggy in wicki version, fixed it here:
    156.         offset = Vector2 ((uIndex) * size.x, (1.0 - size.y) - (vIndex+rowNumber) * size.y);
    157.    
    158.         renderer.material.SetTextureOffset ("_MainTex", offset);
    159.         renderer.material.SetTextureScale  ("_MainTex", size);     
    160.        
    161.        
    162.     }else {
    163.         index = (Time.time-offsetTime) * fps;
    164.         //Debug.Log(index+","+totalCells);
    165.  
    166.         if(index<=totalCells){
    167.         //index= Mathf.Min(totalCells,index);
    168.    
    169.    
    170.    
    171.    
    172.    
    173.         // split into horizontal and vertical index
    174.         //was buggy in wikie version, fixed these two lines here:
    175.         uIndex = (index+colNumber) % colCount;
    176.         vIndex = (index+colNumber) / colCount;
    177.  
    178.         //Debug.Log("uIndex:"+uIndex+",vIndex:"+vIndex);
    179.  
    180.         // build offset
    181.         // v coordinate is the bottom of the image in opengl so we need to invert.
    182.         //was buggy in wicki version, fixed it here:
    183.         offset = Vector2 ((uIndex) * size.x, (1.0 - size.y) - (vIndex+rowNumber) * size.y);
    184.    
    185.         renderer.material.SetTextureOffset ("_MainTex", offset);
    186.         renderer.material.SetTextureScale  ("_MainTex", size);
    187.         }else{
    188.             playing= false;
    189.         }
    190.     }
    191. }
    192.  
    193.  

    and here´s SpriteAnimationInfoObject.js

    Code (csharp):
    1.  
    2.  
    3. class SpriteAnimationInfoObject {
    4.  
    5.     public var animationName: String;
    6.     public var rowNumber: int= 0;
    7.     public var colNumber: int= 0;
    8.     public var totalCells: int= 1;  
    9.     public var fps: int= 10;  
    10.     public var looping: boolean= false;
    11.  
    12.     function SpriteAnimationInfoObject(incomingAnimationName: String,incomingRowNumber: int,incomingColNumber: int,incomingTotalCells: int,incomingFps: int,incomingLooping: boolean) {
    13.         animationName= incomingAnimationName;
    14.         rowNumber= incomingRowNumber;
    15.         colNumber= incomingColNumber;
    16.         totalCells= incomingTotalCells;
    17.         fps= incomingFps;  
    18.         looping= incomingLooping;
    19.     }
    20.  
    21. }
    22.  
    23.  
     
  40. RBerg

    RBerg

    Joined:
    Aug 14, 2008
    Posts:
    10
    @tommosaur

    This looks great. I'm attaching my version of the animation code to this thread. Maybe there are some concepts from both of ours that can be merged. I took a slightly different approach and created a SpriteAnimation class and added it to the SpriteManager.cs file. I've also added the animation support directly onto the Sprite object. You can add animations and then start / stop them. Please let me know what you think. This is also not yet 100% I fully expect that there are some small bugs yet to be discovered.

    My most recent modification to this code was to add autodestruct functionality. I'm still not sure if it should be part of the core animation system that the community develops, but it was useful for my game. Basically it allows you to instantiate a sprite prefab (such as an explosion) and have it animate the explosion, and then the game object is automatically destroyed when the animation is finished. I borrowed this line of reasoning from particle systems, and their autodestruct functionality.


    Here is the test script that creates an animation and then plays it:

    Code (csharp):
    1.  
    2.  
    3. public class TestSpriteAnimation : MonoBehaviour
    4. {
    5.  
    6.     private SpriteManager sm;
    7.     private Sprite s;
    8.    
    9.     void Awake ()
    10.     {
    11.         sm = (SpriteManager) GameObject.Find("SpriteManager")
    12.                                        .GetComponent("SpriteManager");         
    13.     }
    14.    
    15.     // Use this for initialization
    16.     void Start ()
    17.     {
    18.         // Create the sprite
    19.         s = sm.AddSprite(this.gameObject, 5.0f, 5.0f, 0, -200, 60, 60, false);
    20.        
    21.         // Add the animation
    22.         s.AddAnimation("explode",   // animation name
    23.                         0,          // lower left x coord of first frame
    24.                         -200,       // lower left y coord of first frame
    25.                         10,         // number of frames in the animation
    26.                         30.0f,      // frame rate (30fps)
    27.                         false,      // should loop
    28.                         true);      // should autodestruct when animation finished
    29.                        
    30.         // Start the animation
    31.         s.StartAnimation("explode");
    32.     }
    33.    
    34.     // Update is called once per frame
    35.     void Update ()
    36.     {
    37.         // Call step animation in the update loop
    38.         s.stepAnimation(Time.deltaTime);       
    39.     }
    40. }
    41.  
    42.  
    Let me know what you think! Thanks!!
    -Ryan
     

    Attached Files:

  41. se7en

    se7en

    Joined:
    Dec 3, 2008
    Posts:
    232
    Okay I got it working. For some reason the solution is really weird and makes no sense. but anyway here it is.

    This is how I normally get a dynamic reference in JS:
    Code (csharp):
    1. private var SM : SpriteManager;
    2.  
    3. function Awake() {
    4. var GO : GameObject = GameObject.Find("SpriteManager");
    5. SM = GO.GetComponent("LinkedSpriteManager");
    6. }
    ...because I don't want the var exposed to the Editor. I often try to change it in the script and forget that the Editor overrides it and waste like 2 hours trying to figure out what's wrong. : )

    In this case it is the only way that it works:
    Code (csharp):
    1. var SM : SpriteManager;
    2.  
    3. function Awake() {
    4. var GO : GameObject = GameObject.Find("SpriteManager");
    5. SM = GO.GetComponent("LinkedSpriteManager");
    6. }
    Anyone have any ideas why this would happen? Just curious for future ref.

    Anyway thanks Seon! Finally I can try this out in my game. I do think that the sprite system uses more memory but I guess I can compensate by making the texture atlas a lot smaller.
     
  42. ugur

    ugur

    Joined:
    Jun 3, 2008
    Posts:
    692
    to RBerg: cool, i´ll give it a try :)

    And on destructing objects:
    I think its a good idea if SpriteManager and animation side has an fn/property for handling destructing objects, but yeah, let´s see what Brady and the others say :)
     
  43. seon

    seon

    Joined:
    Jan 10, 2007
    Posts:
    1,441
    Great, glad you are up and running. No idea why private var is not working.. I use that all the time too.
     
  44. mattimus

    mattimus

    Joined:
    Mar 8, 2008
    Posts:
    576
    Anyone know how the sprite manager handles backfacing sprites? I'm working with 2D sprites in a 3D world and was thinking it would be really nice to have a choice of A) backfacing sprites that get flipped and UVs reversed (think Paper Mario) or B) backfacing sprites don't get drawn, saving on poly count for objects that aren't actually drawing anything.
     
  45. seon

    seon

    Joined:
    Jan 10, 2007
    Posts:
    1,441
    You can do (B) with a modified shader, where you can turn off backface draw etc.
     
  46. dock

    dock

    Joined:
    Jan 2, 2008
    Posts:
    605
    I'm having some trouble with the 'offset' feature.

    Code (csharp):
    1. (mySprites[2] as Sprite).offset = Vector3(4,4,4);
    This doesn't seem to offset the sprite at all. I'm using LinkedSpriteManager, and the sprites are definitely updating, but they don't seem to be offset from their parent object at all.
     
  47. ugur

    ugur

    Joined:
    Jun 3, 2008
    Posts:
    692
    Chatting with dock i tried setting the offset of a Sprite after it was created, too and it didn´t work in my game either.
    To have a quick workaround solution to see if the prop works at all i changed the SpriteManager AddSprite fn so one also passes a offset Vector3 param to it and then it works setting the offset of the Sprite.
    Would still be cool if one could also set the offset after the Sprite was created, but yeah, for those who only need to set it in the moment they create the Sprite, here´s the changed SpriteManager file:


    Code (csharp):
    1.  
    2. //-----------------------------------------------------------------
    3. //  SpriteManager v0.622 (1-24-2009)
    4. //  Copyright 2009 Brady Wright and Above and Beyond Software
    5. //  All rights reserved
    6. //-----------------------------------------------------------------
    7. // A class to allow the drawing of multiple "quads" as part of a
    8. // single aggregated mesh so as to achieve multiple, independently
    9. // moving objects using a single draw call.
    10. //-----------------------------------------------------------------
    11.  
    12.  
    13. using UnityEngine;
    14. using System.Collections;
    15.  
    16.  
    17. //-----------------------------------------------------------------
    18. // Describes a sprite
    19. //-----------------------------------------------------------------
    20. public class Sprite
    21. {
    22.     protected float m_width;                    // Width and Height of the sprite in worldspace units
    23.     protected float m_height;
    24.     protected Vector2 m_lowerLeftUV;            // UV coordinate for the upper-left corner of the sprite
    25.     protected Vector2 m_UVDimensions;         // Distance from the upper-left UV to place the other UVs
    26.     protected GameObject m_client;        // Reference to the client GameObject
    27.     protected SpriteManager m_manager;      // Reference to the sprite manager in which this sprite resides
    28.     protected bool m_billboarded = false;      // Is the sprite to be billboarded?
    29.     protected bool m_hidden = false;            // Indicates whether this sprite is currently hidden
    30.  
    31.     protected Vector3[] meshVerts;        // Pointer to the array of vertices in the mesh
    32.     protected Vector2[] UVs;                    // Pointer to the array of UVs in the mesh
    33.  
    34.     public Transform clientTransform;         // Transform of the client GameObject
    35.     public Vector3 offset = new Vector3();    // Offset of sprite from center of client GameObject
    36.     public Color color;       // The color to be used by all four vertices
    37.  
    38.     public int index;                     // Index of this sprite in its SpriteManager's list
    39.  
    40.     public Vector3 v1 = new Vector3();      // The sprite's vertices in local space
    41.     public Vector3 v2 = new Vector3();
    42.     public Vector3 v3 = new Vector3();
    43.     public Vector3 v4 = new Vector3();
    44.  
    45.     public int mv1;       // Indices of the associated vertices in the actual mesh (this just provides a quicker way for the SpriteManager to get straight to the right vertices in the vertex array)
    46.     public int mv2;
    47.     public int mv3;
    48.     public int mv4;
    49.  
    50.     public int uv1;       // Indices of the associated UVs in the mesh
    51.     public int uv2;
    52.     public int uv3;
    53.     public int uv4;
    54.  
    55.     public int cv1;       // Indices of the associated color values in the mesh
    56.     public int cv2;
    57.     public int cv3;
    58.     public int cv4;
    59.  
    60.     public Sprite()
    61.     {
    62.         m_width = 0;
    63.         m_height = 0;
    64.         m_client = null;
    65.         m_manager = null;
    66.         clientTransform = null;
    67.         index = 0;
    68.         color = Color.white;
    69.  
    70.         offset = Vector3.zero;
    71.     }
    72.  
    73.     public SpriteManager manager
    74.     {
    75.         get { return m_manager; }
    76.         set { m_manager = value; }
    77.     }
    78.  
    79.     public GameObject client
    80.     {
    81.         get { return m_client; }
    82.         set
    83.         {
    84.             m_client = value;
    85.             if (m_client != null)
    86.                 clientTransform = m_client.transform;
    87.             else
    88.                 clientTransform = null;
    89.         }
    90.     }
    91.  
    92.     public Vector2 lowerLeftUV
    93.     {
    94.         get { return m_lowerLeftUV; }
    95.         set
    96.         {
    97.             m_lowerLeftUV = value;
    98.             m_manager.UpdateUV(this);
    99.         }
    100.     }
    101.  
    102.     public Vector2 uvDimensions
    103.     {
    104.         get { return m_UVDimensions; }
    105.         set
    106.         {
    107.             m_UVDimensions = value;
    108.             m_manager.UpdateUV(this);
    109.         }
    110.     }
    111.  
    112.     public float width
    113.     {
    114.         get { return m_width; }
    115.     }
    116.  
    117.     public float height
    118.     {
    119.         get { return m_height; }
    120.     }
    121.  
    122.     public bool billboarded
    123.     {
    124.         get { return m_billboarded; }
    125.         set
    126.         {
    127.             m_billboarded = value;
    128.         }
    129.     }
    130.  
    131.     public bool hidden
    132.     {
    133.         get { return m_hidden; }
    134.         set
    135.         {
    136.             // No need to do anything if we're
    137.             // already in this state:
    138.             if (value == m_hidden)
    139.                 return;
    140.  
    141.             m_hidden = value;
    142.  
    143.             if (value)
    144.                 m_manager.HideSprite(this);
    145.             else
    146.                 m_manager.ShowSprite(this);
    147.         }
    148.     }
    149.  
    150.     // Sets the physical dimensions of the sprite in the XY plane:
    151.     public void SetSizeXY(float width, float height)
    152.     {
    153.         m_width = width;
    154.         m_height = height;
    155.         v1 = offset + new Vector3(-m_width / 2, m_height / 2, 0);   // Upper-left
    156.         v2 = offset + new Vector3(-m_width / 2, -m_height / 2, 0);  // Lower-left
    157.         v3 = offset + new Vector3(m_width / 2, -m_height / 2, 0);   // Lower-right
    158.         v4 = offset + new Vector3(m_width / 2, m_height / 2, 0);    // Upper-right
    159.  
    160.         m_manager.UpdatePositions();
    161.     }
    162.  
    163.     // Sets the physical dimensions of the sprite in the XZ plane:
    164.     public void SetSizeXZ(float width, float height)
    165.     {
    166.         m_width = width;
    167.         m_height = height;
    168.         v1 = offset + new Vector3(-m_width / 2, 0, m_height / 2);   // Upper-left
    169.         v2 = offset + new Vector3(-m_width / 2, 0, -m_height / 2);  // Lower-left
    170.         v3 = offset + new Vector3(m_width / 2, 0, -m_height / 2);   // Lower-right
    171.         v4 = offset + new Vector3(m_width / 2, 0, m_height / 2);    // Upper-right
    172.  
    173.         m_manager.UpdatePositions();
    174.     }
    175.  
    176.     // Sets the physical dimensions of the sprite in the YZ plane:
    177.     public void SetSizeYZ(float width, float height)
    178.     {
    179.         m_width = width;
    180.         m_height = height;
    181.         v1 = offset + new Vector3(0, m_height / 2, -m_width / 2);   // Upper-left
    182.         v2 = offset + new Vector3(0, -m_height / 2, -m_width / 2);  // Lower-left
    183.         v3 = offset + new Vector3(0, -m_height / 2, m_width / 2);   // Lower-right
    184.         v4 = offset + new Vector3(0, m_height / 2, m_width / 2);        // Upper-right
    185.  
    186.         m_manager.UpdatePositions();
    187.     }
    188.  
    189.     // Sets the vertex and UV buffers
    190.     public void SetBuffers(Vector3[] v, Vector2[] uv)
    191.     {
    192.         meshVerts = v;
    193.         UVs = uv;
    194.     }
    195.  
    196.     // Applies the transform of the client GameObject and stores
    197.     // the results in the associated vertices of the overall mesh:
    198.     public void Transform()
    199.     {
    200.         meshVerts[mv1] = clientTransform.TransformPoint(v1);
    201.         meshVerts[mv2] = clientTransform.TransformPoint(v2);
    202.         meshVerts[mv3] = clientTransform.TransformPoint(v3);
    203.         meshVerts[mv4] = clientTransform.TransformPoint(v4);
    204.  
    205.         m_manager.UpdatePositions();
    206.     }
    207.  
    208.     // Applies the transform of the client GameObject and stores
    209.     // the results in the associated vertices of the overall mesh:
    210.     public void TransformBillboarded(Transform t)
    211.     {
    212.         Vector3 pos = clientTransform.position;
    213.  
    214.         meshVerts[mv1] = pos + t.InverseTransformDirection(v1);
    215.         meshVerts[mv2] = pos + t.InverseTransformDirection(v2);
    216.         meshVerts[mv3] = pos + t.InverseTransformDirection(v3);
    217.         meshVerts[mv4] = pos + t.InverseTransformDirection(v4);
    218.  
    219.         m_manager.UpdatePositions();
    220.     }
    221.  
    222.     // Sets the specified color and automatically notifies the
    223.     // SpriteManager to update the colors:
    224.     public void SetColor(Color c)
    225.     {
    226.         color = c;
    227.         m_manager.UpdateColors(this);
    228.     }
    229. }
    230.  
    231.  
    232. //-----------------------------------------------------------------
    233. // Holds a single mesh object which is composed of an arbitrary
    234. // number of quads that all use the same material, allowing
    235. // multiple, independently moving objects to be drawn on-screen
    236. // while using only a single draw call.
    237. //-----------------------------------------------------------------
    238. public class SpriteManager : MonoBehaviour
    239. {
    240.     // In which plane should we create the sprites?
    241.     public enum SPRITE_PLANE
    242.     {
    243.         XY,
    244.         XZ,
    245.         YZ
    246.     };
    247.  
    248.     // Which way to wind polygons?
    249.     public enum WINDING_ORDER
    250.     {
    251.         CCW,        // Counter-clockwise
    252.         CW      // Clockwise
    253.     };
    254.  
    255.     public Material material;            // The material to use for the sprites
    256.     public int allocBlockSize;        // How many sprites to allocate space for at a time. ex: if set to 10, 10 new sprite blocks will be allocated at a time. Once all of these are used, 10 more will be allocated, and so on...
    257.     public SPRITE_PLANE plane;        // The plane in which to create the sprites
    258.     public WINDING_ORDER winding=WINDING_ORDER.CCW; // Which way to wind polygons
    259.     public bool autoUpdateBounds = false;   // Automatically recalculate the bounds of the mesh when vertices change?
    260.  
    261.     protected ArrayList availableBlocks = new ArrayList(); // Array of references to sprites which are currently not in use
    262.     protected bool vertsChanged = false;    // Have changes been made to the vertices of the mesh since the last frame?
    263.     protected bool uvsChanged = false;    // Have changes been made to the UVs of the mesh since the last frame?
    264.     protected bool colorsChanged = false;   // Have the colors changed?
    265.     protected bool vertCountChanged = false;// Has the number of vertices changed?
    266.     protected bool updateBounds = false;    // Update the mesh bounds?
    267.     protected Sprite[] sprites;    // Array of all sprites (the offset of the vertices corresponding to each sprite should be found simply by taking the sprite's index * 4 (4 verts per sprite).
    268.     protected ArrayList activeBlocks = new ArrayList(); // Array of references to all the currently active (non-empty) sprites
    269.     protected ArrayList activeBillboards = new ArrayList(); // Array of references to all the *active* sprites which are to be rendered as billboards
    270.     protected float boundUpdateInterval;    // Interval, in seconds, to update the mesh bounds
    271.  
    272.     protected MeshFilter meshFilter;
    273.     protected MeshRenderer meshRenderer;
    274.     protected Mesh mesh;                    // Reference to our mesh (contained in the MeshFilter)
    275.  
    276.     protected Vector3[] vertices;         // The vertices of our mesh
    277.     protected int[] triIndices;    // Indices into the vertex array
    278.     protected Vector2[] UVs;                // UV coordinates
    279.     protected Color[] colors;            // Color values
    280.     //protected Vector3[] normals;      // Normals
    281.  
    282.     //--------------------------------------------------------------
    283.     // Utility functions:
    284.     //--------------------------------------------------------------
    285.  
    286.     // Converts pixel-space values to UV-space scalar values
    287.     // according to the currently assigned material.
    288.     // NOTE: This is for converting widths and heights-not
    289.     // coordinates (which have reversed Y-coordinates).
    290.     // For coordinates, use PixelCoordToUVCoord()!
    291.     public Vector2 PixelSpaceToUVSpace(Vector2 xy)
    292.     {
    293.         Texture t = material.GetTexture("_MainTex");
    294.  
    295.         return new Vector2(xy.x / ((float)t.width), xy.y / ((float)t.height));
    296.     }
    297.  
    298.     // Converts pixel-space values to UV-space scalar values
    299.     // according to the currently assigned material.
    300.     // NOTE: This is for converting widths and heights-not
    301.     // coordinates (which have reversed Y-coordinates).
    302.     // For coordinates, use PixelCoordToUVCoord()!
    303.     public Vector2 PixelSpaceToUVSpace(int x, int y)
    304.     {
    305.         return PixelSpaceToUVSpace(new Vector2((float)x, (float)y));
    306.     }
    307.  
    308.     // Converts pixel coordinates to UV coordinates according to
    309.     // the currently assigned material.
    310.     // NOTE: This is for converting coordinates and will reverse
    311.     // the Y component accordingly.  For converting widths and
    312.     // heights, use PixelSpaceToUVSpace()!
    313.     public Vector2 PixelCoordToUVCoord(Vector2 xy)
    314.     {
    315.         Vector2 p = PixelSpaceToUVSpace(xy);
    316.         p.y = 1.0f - p.y;
    317.         return p;
    318.     }
    319.  
    320.     // Converts pixel coordinates to UV coordinates according to
    321.     // the currently assigned material.
    322.     // NOTE: This is for converting coordinates and will reverse
    323.     // the Y component accordingly.  For converting widths and
    324.     // heights, use PixelSpaceToUVSpace()!
    325.     public Vector2 PixelCoordToUVCoord(int x, int y)
    326.     {
    327.         return PixelCoordToUVCoord(new Vector2((float)x, (float)y));
    328.     }
    329.  
    330.     //--------------------------------------------------------------
    331.     // End utility functions
    332.     //--------------------------------------------------------------
    333.  
    334.     void Awake()
    335.     {
    336.         gameObject.AddComponent("MeshFilter");
    337.         gameObject.AddComponent("MeshRenderer");
    338.  
    339.         meshFilter = (MeshFilter)GetComponent(typeof(MeshFilter));
    340.         meshRenderer = (MeshRenderer)GetComponent(typeof(MeshRenderer));
    341.  
    342.         meshRenderer.renderer.material = material;
    343.         mesh = meshFilter.mesh;
    344.  
    345.         // Create our first batch of sprites:
    346.         EnlargeArrays(allocBlockSize);
    347.  
    348.         // Move the object to the origin so the objects drawn will not
    349.         // be offset from the objects they are intended to represent.
    350.         transform.position = Vector3.zero;
    351.         transform.rotation = Quaternion.identity;
    352.     }
    353.  
    354.     // Allocates initial arrays
    355.     protected void InitArrays()
    356.     {
    357.         sprites = new Sprite[1];
    358.         vertices = new Vector3[4];
    359.         UVs = new Vector2[4];
    360.         colors = new Color[4];
    361.         triIndices = new int[6];
    362.     }
    363.  
    364.     // Enlarges the sprite array by the specified count and also resizes
    365.     // the UV and vertex arrays by the necessary corresponding amount.
    366.     // Returns the index of the first newly allocated element
    367.     // (ex: if the sprite array was already 10 elements long and is
    368.     // enlarged by 10 elements resulting in a total length of 20,
    369.     // EnlargeArrays() will return 10, indicating that element 10 is the
    370.     // first of the newly allocated elements.)
    371.     protected int EnlargeArrays(int count)
    372.     {
    373.         int firstNewElement;
    374.  
    375.         if (sprites == null)
    376.         {
    377.             InitArrays();
    378.             firstNewElement = 0;
    379.             count = count - 1;  // Allocate one less since InitArrays already allocated one sprite for us
    380.         }
    381.         else
    382.             firstNewElement = sprites.Length;
    383.  
    384.         // Resize sprite array:
    385.         Sprite[] tempSprites = sprites;
    386.         sprites = new Sprite[sprites.Length + count];
    387.         tempSprites.CopyTo(sprites, 0);
    388.  
    389.         // Vertices:
    390.         Vector3[] tempVerts = vertices;
    391.         vertices = new Vector3[vertices.Length + count*4];
    392.         tempVerts.CopyTo(vertices, 0);
    393.  
    394.         // UVs:
    395.         Vector2[] tempUVs = UVs;
    396.         UVs = new Vector2[UVs.Length + count*4];
    397.         tempUVs.CopyTo(UVs, 0);
    398.  
    399.         // Colors:
    400.         Color[] tempColors = colors;
    401.         colors = new Color[colors.Length + count * 4];
    402.         tempColors.CopyTo(colors, 0);
    403.  
    404.         // Triangle indices:
    405.         int[] tempTris = triIndices;
    406.         triIndices = new int[triIndices.Length + count*6];
    407.         tempTris.CopyTo(triIndices, 0);
    408.  
    409.         // Inform existing sprites of the new vertex and UV buffers:
    410.         for (int i = 0; i < firstNewElement; ++i)
    411.         {
    412.             sprites[i].SetBuffers(vertices, UVs);
    413.         }
    414.  
    415.         // Setup the newly-added sprites and Add them to the list of available
    416.         // sprite blocks. Also initialize the triangle indices while we're at it:
    417.         for (int i = firstNewElement; i < sprites.Length; ++i)
    418.         {
    419.             // Create and setup sprite:
    420.  
    421.             sprites[i] = new Sprite();
    422.             sprites[i].index = i;
    423.             sprites[i].manager = this;
    424.  
    425.             sprites[i].SetBuffers(vertices, UVs);
    426.  
    427.             // Setup indices of the sprite's vertices in the vertex buffer:
    428.             sprites[i].mv1 = i * 4 + 0;
    429.             sprites[i].mv2 = i * 4 + 1;
    430.             sprites[i].mv3 = i * 4 + 2;
    431.             sprites[i].mv4 = i * 4 + 3;
    432.  
    433.             // Setup the indices of the sprite's UV entries in the UV buffer:
    434.             sprites[i].uv1 = i * 4 + 0;
    435.             sprites[i].uv2 = i * 4 + 1;
    436.             sprites[i].uv3 = i * 4 + 2;
    437.             sprites[i].uv4 = i * 4 + 3;
    438.  
    439.             // Setup the indices to the color values:
    440.             sprites[i].cv1 = i * 4 + 0;
    441.             sprites[i].cv2 = i * 4 + 1;
    442.             sprites[i].cv3 = i * 4 + 2;
    443.             sprites[i].cv4 = i * 4 + 3;
    444.  
    445.             // Setup the default color:
    446.             sprites[i].SetColor(Color.white);
    447.  
    448.             // Add as an available sprite:
    449.             availableBlocks.Add(sprites[i]);
    450.  
    451.             // Init triangle indices:
    452.             if(winding == WINDING_ORDER.CCW)
    453.             {   // Counter-clockwise winding
    454.                 triIndices[i * 6 + 0] = i * 4 + 0;  //    0_ 2            0 ___ 3
    455.                 triIndices[i * 6 + 1] = i * 4 + 1;  //  | /      Verts:  |   /|
    456.                 triIndices[i * 6 + 2] = i * 4 + 3;  // 1|/                1|/__|2
    457.  
    458.                 triIndices[i * 6 + 3] = i * 4 + 3;  //      3
    459.                 triIndices[i * 6 + 4] = i * 4 + 1;  //   /|
    460.                 triIndices[i * 6 + 5] = i * 4 + 2;  // 4/_|5
    461.             }
    462.             else
    463.             {   // Clockwise winding
    464.                 triIndices[i * 6 + 0] = i * 4 + 0;  //    0_ 1            0 ___ 3
    465.                 triIndices[i * 6 + 1] = i * 4 + 3;  //  | /      Verts:  |   /|
    466.                 triIndices[i * 6 + 2] = i * 4 + 1;  // 2|/                1|/__|2
    467.  
    468.                 triIndices[i * 6 + 3] = i * 4 + 3;  //      3
    469.                 triIndices[i * 6 + 4] = i * 4 + 2;  //   /|
    470.                 triIndices[i * 6 + 5] = i * 4 + 1;  // 5/_|4
    471.             }
    472.         }
    473.  
    474.         vertsChanged = true;
    475.         uvsChanged = true;
    476.         colorsChanged = true;
    477.         vertCountChanged = true;
    478.  
    479.         return firstNewElement;
    480.     }
    481.  
    482.     // Adds a sprite to the manager at the location and rotation of the client
    483.     // GameObject and with its transform.  Returns a reference to the new sprite
    484.     // Width and height are in world space units
    485.     // leftPixelX and bottomPixelY- the bottom-left position of the desired portion of the texture, in pixels
    486.     // pixelWidth and pixelHeight - the dimensions of the desired portion of the texture, in pixels
    487.     public Sprite AddSprite(GameObject client, float width, float height, int leftPixelX, int bottomPixelY, int pixelWidth, int pixelHeight, bool billboarded, Vector3 offset)
    488.     {
    489.         return AddSprite(client, width, height, PixelCoordToUVCoord(leftPixelX, bottomPixelY), PixelSpaceToUVSpace(pixelWidth, pixelHeight), billboarded, offset);
    490.     }
    491.  
    492.     // Adds a sprite to the manager at the location and rotation of the client
    493.     // GameObject and with its transform.  Returns a reference to the new sprite
    494.     // Width and height are in world space units
    495.     // lowerLeftUV - the UV coordinate for the upper-left corner
    496.     // UVDimensions - the distance from lowerLeftUV to place the other UV coords
    497.     public Sprite AddSprite(GameObject client, float width, float height, Vector2 lowerLeftUV, Vector2 UVDimensions, bool billboarded, Vector3 offset)
    498.     {
    499.         int spriteIndex;
    500.  
    501.         // Get an available sprite:
    502.         if (availableBlocks.Count < 1)
    503.             EnlargeArrays(allocBlockSize);  // If we're out of available sprites, allocate some more:
    504.  
    505.         // Use a sprite from the list of available blocks:
    506.         spriteIndex = ((Sprite)availableBlocks[0]).index;
    507.         availableBlocks.RemoveAt(0);    // Now that we're using this one, remove it from the available list
    508.  
    509.         // Assign the new sprite:
    510.         Sprite newSprite = sprites[spriteIndex];
    511.         newSprite.client = client;
    512.         newSprite.lowerLeftUV = lowerLeftUV;
    513.         newSprite.uvDimensions = UVDimensions;
    514.  
    515.         newSprite.offset= offset;
    516.  
    517.         switch(plane)
    518.         {
    519.             case SPRITE_PLANE.XY:
    520.                 newSprite.SetSizeXY(width, height);
    521.                 break;
    522.             case SPRITE_PLANE.XZ:
    523.                 newSprite.SetSizeXZ(width, height);
    524.                 break;
    525.             case SPRITE_PLANE.YZ:
    526.                 newSprite.SetSizeYZ(width, height);
    527.                 break;
    528.             default:
    529.                 newSprite.SetSizeXY(width, height);
    530.                 break;
    531.         }
    532.  
    533.         // Save this to an active list now that it is in-use:
    534.         if(billboarded)  
    535.         {
    536.             newSprite.billboarded = true;
    537.             activeBillboards.Add(newSprite);
    538.         }
    539.         else
    540.             activeBlocks.Add(newSprite);
    541.  
    542.         // Transform the sprite:
    543.         newSprite.Transform();
    544.  
    545.         // Setup the UVs:
    546.         UVs[newSprite.uv1] = lowerLeftUV + Vector2.up * UVDimensions.y;  // Upper-left
    547.         UVs[newSprite.uv2] = lowerLeftUV;                         // Lower-left
    548.         UVs[newSprite.uv3] = lowerLeftUV + Vector2.right * UVDimensions.x;// Lower-right
    549.         UVs[newSprite.uv4] = lowerLeftUV + UVDimensions;                     // Upper-right
    550.  
    551.         // Set our flags:
    552.         vertsChanged = true;
    553.         uvsChanged = true;
    554.  
    555.         return newSprite;
    556.     }
    557.  
    558.     public void SetBillboarded(Sprite sprite)
    559.     {
    560.         // Make sure the sprite isn't in the active list
    561.         // or else it'll get handled twice:
    562.         activeBlocks.Remove(sprite);
    563.         activeBillboards.Add(sprite);
    564.     }
    565.  
    566.     public void RemoveSprite(Sprite sprite)
    567.     {
    568.         sprite.SetSizeXY(0,0);
    569.         sprite.v1 = Vector3.zero;
    570.         sprite.v2 = Vector3.zero;
    571.         sprite.v3 = Vector3.zero;
    572.         sprite.v4 = Vector3.zero;
    573.  
    574.         vertices[sprite.mv1] = sprite.v1;
    575.         vertices[sprite.mv2] = sprite.v2;
    576.         vertices[sprite.mv3] = sprite.v3;
    577.         vertices[sprite.mv4] = sprite.v4;
    578.  
    579.         sprite.client = null;
    580.  
    581.         availableBlocks.Add(sprite);
    582.  
    583.         // Remove the sprite from the billboarded list
    584.         // since that list should only contain active
    585.         // sprites:
    586.         if (sprite.billboarded)
    587.             activeBillboards.Remove(sprite);
    588.         else
    589.             activeBlocks.Remove(sprite);
    590.  
    591.         sprite.billboarded = false;
    592.  
    593.         vertsChanged = true;
    594.     }
    595.  
    596.     public void HideSprite(Sprite sprite)
    597.     {
    598.         vertices[sprite.mv1] = Vector3.zero;
    599.         vertices[sprite.mv2] = Vector3.zero;
    600.         vertices[sprite.mv3] = Vector3.zero;
    601.         vertices[sprite.mv4] = Vector3.zero;
    602.  
    603.         // Remove the sprite from the billboarded list
    604.         // since that list should only contain sprites
    605.         // we intend to transform:
    606.         if (sprite.billboarded)
    607.             activeBillboards.Remove(sprite);
    608.         else
    609.             activeBlocks.Remove(sprite);
    610.  
    611.         sprite.hidden = true;
    612.  
    613.         vertsChanged = true;
    614.     }
    615.  
    616.     public void ShowSprite(Sprite sprite)
    617.     {
    618.         // Only show the sprite if it has a client:
    619.         if(sprite.client == null)
    620.             return;
    621.  
    622.         if(sprite.billboarded)
    623.             activeBillboards.Add(sprite);
    624.         else
    625.             activeBlocks.Add(sprite);
    626.  
    627.         sprite.hidden = false;
    628.  
    629.         // Update the vertices:
    630.         sprite.Transform();
    631.  
    632.         vertsChanged = true;
    633.     }
    634.  
    635.     public Sprite GetSprite(int i)
    636.     {
    637.         if (i < sprites.Length)
    638.             return sprites[i];
    639.         else
    640.             return null;
    641.     }
    642.  
    643.     // Updates the vertices of a sprite based on the transform
    644.     // of its client GameObject
    645.     public void Transform(Sprite sprite)
    646.     {
    647.         sprite.Transform();
    648.  
    649.         vertsChanged = true;
    650.     }
    651.  
    652.     // Updates the vertices of a sprite such that it is oriented
    653.     // more or less toward the camera
    654.     public void TransformBillboarded(Sprite sprite)
    655.     {
    656.         Vector3 pos = sprite.clientTransform.position;
    657.         Transform t = Camera.main.transform;
    658.  
    659.         vertices[sprite.mv1] = pos + t.TransformDirection(sprite.v1);
    660.         vertices[sprite.mv2] = pos + t.TransformDirection(sprite.v2);
    661.         vertices[sprite.mv3] = pos + t.TransformDirection(sprite.v3);
    662.         vertices[sprite.mv4] = pos + t.TransformDirection(sprite.v4);
    663.  
    664.         vertsChanged = true;
    665.     }
    666.  
    667.     // Informs the SpriteManager that some vertices have changed position
    668.     // and the mesh needs to be reconstructed accordingly
    669.     public void UpdatePositions()
    670.     {
    671.         vertsChanged = true;
    672.     }
    673.  
    674.     // Updates the UVs of the specified sprite and copies the new values
    675.     // into the mesh object.
    676.     public void UpdateUV(Sprite sprite)
    677.     {
    678.         UVs[sprite.uv1] = sprite.lowerLeftUV + Vector2.up * sprite.uvDimensions.y;  // Upper-left
    679.         UVs[sprite.uv2] = sprite.lowerLeftUV;                              // Lower-left
    680.         UVs[sprite.uv3] = sprite.lowerLeftUV + Vector2.right * sprite.uvDimensions.x;// Lower-right
    681.         UVs[sprite.uv4] = sprite.lowerLeftUV + sprite.uvDimensions;     // Upper-right
    682.  
    683.         uvsChanged = true;
    684.     }
    685.  
    686.     // Updates the color values of the specified sprite and copies the
    687.     // new values into the mesh object.
    688.     public void UpdateColors(Sprite sprite)
    689.     {
    690.         colors[sprite.cv1] = sprite.color;
    691.         colors[sprite.cv2] = sprite.color;
    692.         colors[sprite.cv3] = sprite.color;
    693.         colors[sprite.cv4] = sprite.color;
    694.  
    695.         colorsChanged = true;
    696.     }
    697.  
    698.     // Instructs the manager to recalculate the bounds of the mesh
    699.     public void UpdateBounds()
    700.     {
    701.         updateBounds = true;
    702.     }
    703.  
    704.     // Schedules a recalculation of the mesh bounds to occur at a
    705.     // regular interval (given in seconds):
    706.     public void ScheduleBoundsUpdate(float seconds)
    707.     {
    708.         boundUpdateInterval = seconds;
    709.         InvokeRepeating("UpdateBounds", seconds, seconds);
    710.     }
    711.  
    712.     // Cancels any previously scheduled bounds recalculations:
    713.     public void CancelBoundsUpdate()
    714.     {
    715.         CancelInvoke("UpdateBounds");
    716.     }
    717.  
    718.     // Use this for initialization
    719.     void Start ()
    720.     {
    721.  
    722.     }
    723.  
    724.     // LateUpdate is called once per frame
    725.     virtual public void LateUpdate ()
    726.     {
    727.         // Were changes made to the mesh since last time?
    728.         if (vertCountChanged)
    729.         {
    730.             vertCountChanged = false;
    731.             colorsChanged = false;
    732.             vertsChanged = false;
    733.             uvsChanged = false;
    734.             updateBounds = false;
    735.  
    736.             mesh.Clear();
    737.             mesh.vertices = vertices;
    738.             mesh.uv = UVs;
    739.             mesh.colors = colors;
    740.             //mesh.normals = normals;
    741.             mesh.triangles = triIndices;
    742.         }
    743.         else
    744.         {
    745.             if (vertsChanged)
    746.             {
    747.                 vertsChanged = false;
    748.  
    749.                 if (autoUpdateBounds)
    750.                     updateBounds = true;
    751.  
    752.                 mesh.vertices = vertices;
    753.             }
    754.  
    755.             if (updateBounds)
    756.             {
    757.                 mesh.RecalculateBounds();
    758.                 updateBounds = false;
    759.             }
    760.  
    761.             if (colorsChanged)
    762.             {
    763.                 colorsChanged = false;
    764.  
    765.                 mesh.colors = colors;
    766.             }
    767.  
    768.             if (uvsChanged)
    769.             {
    770.                 uvsChanged = false;
    771.                 mesh.uv = UVs;
    772.             }
    773.         }
    774.     }
    775. }
    776.  
    777.  



    So once you have replaced your old one with that you create a Sprite like this:

    Code (csharp):
    1.    
    2. var offset: Vector3=Vector3(150,5,5);          
    3. var newSprite: Sprite= spriteManager.AddSprite(tile.gameObject, tileSize,tileSize, Vector2(foundObject.uvPosX,foundObject.uvPosY), Vector2(foundObject.uvWidth,foundObject.uvHeight),false,offset);
    4.  
     
  48. dock

    dock

    Joined:
    Jan 2, 2008
    Posts:
    605
    How feasible do you think it would be to force alpha-sorting within the Sprite Manager? Obviously it would be computationally expensive, but it's a fairly limited number of objects to sort in comparison to a per-vertex sorting solution. It would definitely help out in a lot of cases.
     
  49. MrJoy

    MrJoy

    Joined:
    Oct 31, 2007
    Posts:
    60
    Having encountered a need for a number of variations on the SpriteManager, I wound up hacking it up to implement them.

    First one: A sprite that does not require a real GameObject/Transform. This is handled by refactoring the Sprite class into a SpriteBase and SpriteHeavy class and adding a SpriteLite class (with corresponding changes in SpriteManager to facilitate these classes).

    I've found this exceptionally useful for when you have lots of objects getting spawned and destroyed rapidly, like projectiles.

    Second one: An alternative to GUITexture / UnityGUI that doesn't destroy performance for non-trivial GUIs. This is implemented via a sub-class of SpriteManager that catches OnRenderObject and renders a mesh using a pixel projection matrix. This lets you have multiple screen-space images displayed from a single atlas in a single draw call. Very handy stuff.

    Building on the second one, I created a WidgetManager class and VERY rough edit-time UI builder for putting together screens of widgets. Widgets essentially act as buttons, and with a little clever coding of click callback handlers you can implement more or less any type of widget you want without much difficulty.

    Lots of rough corners involved -- if you rotate a widget, the clickable area isn't affected at all, z-ordering is twitchy and requires that widgets be sorted back-to-front (there's a button to sort your widgets for you), and I don't have any plans of even attempting to handle text directly (still using GUIText for that), nine-slicing is annoying because there's some sub-pixel sampling going on that I can't seem to fix (???), I think I broke billboarded sprites outright, and so forth BUT it's generally usable.

    http://www.mrjoy.com/uploads/NewUI.mov

    http://www.mrjoy.com/uploads/GhettoUIBuilder2.png
     

    Attached Files:

  50. Wenceslao

    Wenceslao

    Joined:
    Feb 7, 2009
    Posts:
    142
    Thanks MrJoy (jfrisby)! I know this has taken you a lot of work and I personally appreciate you sharing. I'll be taking a look at it this weekend to see what you did and it will most likely help me on my current project.