Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Automatic sprite sheet

dave1707dave1707 Mod
edited May 9 in Code Sharing Posts: 9,430

Here’s something I thru together because there’s been some discussions about using sprite sheets. I’ve posted code long ago showing sprite sheets, but sprites had to be the same size or organized in a certain way to create the sheets. I’m still working on all of this but I thought I’d post something to see if it might be worthwhile. Sprites of any size can be placed in any position on the sprite sheet. You don’t have to worry about how many sprites are across or high or have to line them up in any kind of order. This program just demonstrates getting the sprites out of the sprite sheet as a whole sprite and displaying them one at a time. I have 2 different sprite sheets to demo, just comment/uncomment the sprite sheet to use at the beginning of the code. Tap the screen to cycle thru the 6 sprites. Right now I’m only using 6 sprites, but any amount of sprites could be used. As you can see, there are just a few lines of code to extract the sprites from the sheet, the rest is just the demo to display them. The sprite sheet is at the top of the screen, the individual sprites at the bottom.

PS. Zip file removed. New code below.

Comments

  • Posts: 1,284

    Holy gamoley shizzoli this is cool and for glob’s sake you did it in 41 lines!

    Actually, if you only count the original code in setup() you did it in 18 flesking lines!

    And even though it’s really just 16 lines I don’t understand how it works.

    How did you do this?

  • edited May 8 Posts: 1,284

    Can’t get it working with my own sprite sheets.

    I initially got some errors until I wrote in a couple checks for nil, but even with no errors it didn’t seem to detect anything.

    Attached project includes two sheets I tried: one is the actual spritesheet I made for @West ‘s Foggy Bummer, the other is way smaller and just has six sprites from that sheet.

  • dave1707dave1707 Mod
    Posts: 9,430

    @UberGoober Once it’s complete I’ll post both parts and explain what’s happening. The code that creates the sprite sheet is what’s doing all the work which makes the extract code very simple. Maybe later today or tomorrow I’ll post everything. Since I code while I’m watching TV, it depends on what I’m watching.

  • Posts: 194

    Hey guys,

    I created a program a while back in Codea that provided a UI to select sprites and it would automatically pack them into a sprite sheet of a desired size for you and save it out along with a json file with the texture coordinates of all of the sprites.

    I can dig it out and post it if still works.

  • edited May 8 Posts: 194

    Here you go :smile:

    • It only grabs textures from the Codea documents folder by default.
    • as with above it only saves to the Codea documents folder
    • You can change the spritesheet dimensions in the sidebar

    There’s a comment at the top of PackedTexture explaining how to load a spritesheet and extract sprites. Edit: scratch that, I messed up that bit. The values need to be in pixels but I'm passing 0-1 but you get the idea.

  • Posts: 194
    @dave1707
    Am I right in thinking you're storing the sprite coords in pixels at the bottom left? If so that's very neat, I've never seen that technique before.
  • Posts: 1,089

    yes, steganography FTW

  • dave1707dave1707 Mod
    Posts: 9,430

    @Steppers That's correct. I’m storing all the sprite information in the sprite sheet and reading it back to know how to process each sprite.

  • dave1707dave1707 Mod
    edited May 9 Posts: 9,430

    @Steppers Way back when, I created a 1025x1025 image (I think that was the size) that contained 300,249 words from a dictionary file. I was able to pack 3 letters per pixel.

  • dave1707dave1707 Mod
    Posts: 9,430

    Here’s the code to put sprites in a sprite sheet and get them back out. For now, the sprites to pack are put in the table. When the program is run, all the sprites are at the lower left of the screen. Just touch a sprite and drag it wherever you want. When you have all the sprites arranged how you want, use the sliders to enclose the sprites in the red lines. That will be the size of the sprite sheet. Start with the top parameter and work your way down. Leave a little space around the sprites. You can change the save image name if you want. When ready, tap the pack parameter. The x,y corner position, width, and height of each sprite is embedded in the lower left of the sprite sheet.

    -- pack sprites in a sprite sheet
    
    viewer.mode=STANDARD
    
    function setup()
        parameter.integer("xCorner",1,WIDTH//1,0)
        parameter.integer("yCorner",1,HEIGHT//1,0)
        parameter.integer("xWidth",20,WIDTH//1*2,WIDTH//1*2)
        parameter.integer("yHeight",20,HEIGHT//1*2,HEIGHT//1*2)
        parameter.text("imageName","temp")
        parameter.action("pack",packSprites)
        rectMode(CORNER)
        spriteMode(CORNER)
        dx,dy,sel=0,0,0
    
        -- table of sprites to put in sprite sheet        
        tabPath={
            asset.builtin.Tyrian_Remastered.Blimp_Boss,
            asset.builtin.SpaceCute.Star,
            asset.builtin.SpaceCute.Beetle_Ship,
            asset.builtin.Space_Art.Red_Explosion,
            asset.builtin.Cargo_Bot.Codea_Logo,
            asset.builtin.Small_World.Beam,
            asset.builtin.Space_Art.Red_Ship,
            asset.builtin.Planet_Cute.Character_Horn_Girl,
            } 
        tab={}
        for z=1,#tabPath do
            img=readImage(tabPath[z])
            table.insert(tab,{w=img.width,h=img.height,x=0,y=0,path=tabPath[z]})        
        end   
    end
    
    function draw()
        background(255, 165, 0)
        stroke(255)
        strokeWidth(2)
        noFill()
        for a,b in pairs(tab) do
            sprite(b.path,b.x+dx,b.y+dy)
            rect(b.x-2+dx,b.y-2+dy,b.w+4,b.h+4)
        end
        stroke(255,0,0)
        rect(xCorner+dx,yCorner+dy,xWidth,yHeight)    
    end
    
    function touched(t)
        if t.state==BEGAN then
            for a,b in pairs(tab) do
                if t.x>b.x+dx and t.x<b.x+b.w+dx and t.y>b.y+dy and t.y<b.y+b.h+dy then
                    sel=a
                    break
                end
            end
        end
        if t.state==CHANGED then
            if sel>0 then
                tab[sel].x=(tab[sel].x+t.deltaX)//1
                tab[sel].y=(tab[sel].y+t.deltaY)//1
            else
                dx=dx+t.deltaX
                dy=dy+t.deltaY
            end 
        end 
        if t.state==ENDED then
            sel=0
        end
    end
    
    function packSprites()
        tab1={}
        img=image(xWidth,yHeight)
        setContext(img)
        for a,b in pairs(tab) do
            temp=readImage(b.path)
            table.insert(tab1,b.x-xCorner)
            table.insert(tab1,b.y-yCorner)
            table.insert(tab1,b.w)
            table.insert(tab1,b.h)
            for x=1,b.w do
                for y=1,b.h do
                    r,g,bl,a=temp:get(x,y)
                    img:set(b.x-xCorner+x,b.y-yCorner+y,r,g,bl,a)
                end
            end        
        end
        packInfo()
        setContext()
        saveImage(asset.documents.Dropbox..imageName,img)
        print("Image saved")
        print("Number of sprites  "..#tab)
    end
    
    function packInfo()
        xLoc,yLoc=1,1
        r=#tab  -- number of sprites
        g,b,a=255,255,255
        img:set(xLoc,yLoc,r,g,b,a)
        xLoc=xLoc+1
        for z=1,#tab1 do    -- put sprite info into sprite sheet
            r=tab1[z]//100
            g=tab1[z]%100
            img:set(xLoc,yLoc,r,g,b,a)
            xLoc=xLoc+1
        end
    end
    

    The code to extract the sprites.

    -- extract sprite from sprite sheet
    
    viewer.mode=FULLSCREEN
    
    function setup()
        img=readImage(asset.documents.Dropbox.temp)
        tab={}
        yy,pos,c=1,1,1
        nbr,g,b,a=img:get(1,1)  -- number of sprites
        for z=1,nbr*4,4 do  -- sprites are put in table tab
            r,g,b,a=img:get(z+1,yy)
            x=r*100+g
            r,g,b,a=img:get(z+2,yy)
            y=r*100+g
            r,g,b,a=img:get(z+3,yy)
            w=r*100+g
            r,g,b,a=img:get(z+4,yy)
            h=r*100+g
            tab[pos]=img:copy(x,y,w,h)
            pos=pos+1        
        end   
        fill(255)
    end
    
    function draw()
        background(255, 150, 0)
        sprite(img,WIDTH/2,HEIGHT-300) 
        sprite(tab[c],WIDTH/2,150) 
        text("tap screen to cycle thru sprites",WIDTH/2,HEIGHT-50)
    end
    
    function touched(t)
        if t.state==BEGAN then
            c=c+1
            if c>6 then
                c=1
            end
        end    
    end
    
  • edited May 9 Posts: 1,284

    @dave1707 lol I think just yesterday I imported a project by @Mark that used your word sheet.

  • dave1707dave1707 Mod
    Posts: 9,430

    @UberGoober Not mine. My image was 1025x1025. I tried looking for it but I couldn’t find it.

  • Posts: 1,089

    this got me thinking: what advantages / disadvantages has a packed sheet over separate images? one advantage for packed is that it keeps the folder from getting quite so messy. others?

  • Posts: 1,284

    It also compresses better.

  • Posts: 1,284

    Also there’s a purpose sprite sheets serve in animation that I think @dave1707 ’s process doesn’t seem able to serve.

    By implementing a constant box size in a sprite sheet, you can separate only the moving parts of a sprite animation into their own boxes and still be sure they’ll overlap correctly with the parts that aren’t moving.

  • Posts: 194
    @RonJeffries If you utilise a Mesh with texture coordinates indexing into the entire sprite sheet rather than extracting individual images then it's much more efficient for the GPU.

    For older APIs like OpenGL swapping a texture would usually involve a GPU context switch and a separate draw call for each texture used which can be expensive. If your application uses a single sprite sheet then you would be able to batch every single rendered object into a single draw call as there would be no need to change texture.

    Newer APIs like Metal & Vulkan are a little more forgiving but it's still advantageous to reduce the number of draw calls.
  • dave1707dave1707 Mod
    Posts: 9,430

    @UberGoober Where a sprite is in the sprite sheet has nothing to do with where it’s placed on the screen or relative to the other sprites. You mentioned a constant box size. If for instance a sprite sheet is made up of a bunch of 1” square sprites, those 1” square sprites would be the same sprites whether they’re in a constant squared sprite sheet or in a random non overlapping position on a sprite sheet. Here’s an example. Think of this as multiple same sized sprites (not real sprites) placed anywhere on a sprite sheet (not a real sprite sheet). Tap the screen to start the move of the sprites to where they go. If the sprites are created correctly, it doesn’t matter where they start out, but where they end up.

  • Posts: 1,089

    interesting, @Steppers ... i guess this would apply like to a galaxians game with lots of copies of the same sprite?

  • edited May 9 Posts: 1,284
    @dave1707 please take a look at the attached file—you can ignore the numbering but this might help make clear the animation issue. This is an actual spritesheet guide I included in the Foggy Bummer With Sprites project, and it’s extremely scaled down, but I think it shows enough detail to be used to illustrate the situation

    I made a mistake saying “boxes” because none of the sprite sheets we’re talking about actually use actual boxes drawn right on the image, whereas back in my day working on PC adventure games the sprite sheets actually had pink borders around all the sprites.

    The attached image does show such boxes, but that was just a convenient overlay in the art program I used, they weren’t actually drawn on the real sheet.

    So yeah, I misspoke. Mis-wrote. I wasn’t referring to sprites that were themselves boxes, I was referring to the bounding box that the sprites are drawn inside, the box that used to be a literal drawn box but is now just the box defined by the coordinates used to import the sprites.

    If you look closely at the attached image you can see that sprites 1-6 are different parts of a bee. All of those parts are inside identical-sized bounding boxes, but none of them fill the boxes. This is for placement. What this allows a programmer to do is to to draw all those sprites at the exact same x,y coordinate but have them all in the right position relative to each other, so the head appears in the right relation to the body and wings, etc. In other words the box with the bee has not just the bee art, but the exact right amount of empty space for the bee’s body to be drawn in. And the body box has the right amount of empty space for the head to be drawn in, etc.

    This also lets a programmer or an animator move each part individually, and draw them in different positions and shapes, without having to worry each time about figuring out the right x,y coordinate to place the art at. As long as all of the animation “cells” are the same size, the animation will work.

    So I think what I maybe failed to make clear is that in some cases having the right amount of empty space around a sprite is just as important as the sprite itself, and I don’t know if there’s an easy way to do that using the technique you’ve created.
  • dave1707dave1707 Mod
    edited May 9 Posts: 9,430

    @UberGoober Maybe I don’t fully understand. In your image, the lower left corner of boxes 1 thru 6 have specific x,y values in the sprite sheet. They also have specific width and height values. If I used my program and placed those boxes 1 thru 6 someplace, the lower left corner would have a specific x,y value and the same width and height values. The sprites would be the same size as yours, just in different places. So whatever sprite you get based on it’s x,y values, I would too. I would be using the exact same sprites, just not placed in a specific order.

    PS. Where’s the original sprite sheet. I can’t find it.

  • Posts: 1,284

    Here’s the one actually used.

    @West might be able to do a better job explaining. West, want to try?

  • dave1707dave1707 Mod
    Posts: 9,430

    @UberGoober Thanks, I’ll look at it.

  • Posts: 1,284

    @dave1707 Oops that one actually was the wrong one. Here’s the actual one but with the boundary lines and the contents more clearly visible.

  • dave1707dave1707 Mod
    Posts: 9,430

    @UberGoober Thanks, but the first one you sent was good enough. Here’s an example where I pulled 3 sprites from the sheet. I put the 3 of them in a simulated sprite sheet, but I’m really just pulling them from the Project folder. I drew all three of them overlapping on the screen to product the one image of the bee. So it doesn’t matter where the sprites come from, just where they’re going.

    001h.zip 18.1K
  • edited May 10 Posts: 1,284
    @dave1707 okay, but if I understand correctly what you’re trying to do, you’re only showing the part of the process after the orientation work has been done. You’re working from images that someone else has already adjusted to have the blank spaces in the right places for the overlapping to work correctly. When you say “it doesn’t matter where the sprites come from,” you’re technically correct, but I think you’re missing the point that someone’s got to arrange those bee parts and the blank spaces correctly beforehand. In the source files I worked from, @West did that work. When I made my spritesheet, I preserved that work. And in the files you’ve included in this project, the three image files also preserve that work. But the work had to be done by hand in the first place. In this demo you’re not automatically assembling the images in the right place, you’re automatically assembling image files containing parts that someone else already put in the right place.

    What you first showed us, at the top of this thread, was an image file that had four smaller images on it. And you demonstrated your program automatically turning those images into separate sprites. Now, if all you started with was an image that had four bee parts on it, you could certainly do the same thing. You could automatically detect the precise bounds of each part of the bee. You could automatically turn each part into a sprite that was the exact size of those bounds. But what you could not do, I think, without some prior information, is automatically generate bounding boxes that were the necessary size, and in the necessary position relative to the sprites, for the overlapping of those pieces to work correctly. If the only information you started with was four images of bee parts arranged randomly on a single source page, you simply can’t generate the same information you get from a hand-assembled sprite sheet, as far as I know.

    I mean, even a human has to have some reference to a real bee to get the correct orientation of the bee head to the thorax. A computer also needs some reference to get it right, it can’t do it on its own. So somewhere in the process of creating the bee sprite the human has to somehow give the computer that reference information, whether by hand-programming the parts to be in the right places, or by hand-positioning the parts on a spritesheet. Unless I’m wrong and I always could be, what you have not done, and what there’s no way for anyone to do, is skip the need for a human to manually position those sprites, whether into a single image or inside their own separate image files.
  • dave1707dave1707 Mod
    Posts: 9,430

    @UberGoober I think your idea of a sprite sheet and my idea of a sprite sheet are different. To me, a sprite sheet is used to hold all the sprites a program might use in a single image instead of having the multiple images individually stored someplace. The sprites are already created in the size and position that are required for the program that uses them. The bee example above is what I’m saying. Someone created those 3 sprites so they could be combined to created the image of the bee. So when they’re put in a sprite sheet, it doesn’t matter where in the sheet they are. When they’re pulled out and displayed on the screen, they form a bee.

    My program at the top would take individual sprites already created and allow you to put those sprites at any position in a sprite sheet and pull them back out. It doesn’t require the sprites to be the same size or to be put into a box type area. It’s not meant to take whole images and cut them up into precise pieces. It just holds sprites that are already created.

    For instance, you created the above sprite sheet in a 10 x 9 grid with each square being 120x120 pixels. Some of those images are multiple squares next to each other. For instance, the tree trunk is made up of 15 squares. If I created a sprite sheet, the tree trunk would be 1 sprite, not 15. The snail would be 1 sprite, not 3. Same with some of the other images. The sprite sheet is just a place holder for all the already created sprites, not to take an image and cut it up to create multiple sprites. My sprite sheet doesn’t require the bee to be cut up into pieces, but it will hold them if they are.

  • Posts: 887

    @dave1707 @UberGoober I think you are both right and have different expectations of sprite sheets and their utility. For me sprite sheets are good for collecting together the image assets used in a game in a single file (and I think this can ultimately lead to improved performance by reducing the number of calls on the graphics card). In my opinion the game assets are part of the design process - decisions made in the way to format and present the graphical assets can lead to savings/simplifications on the coding side. This can be particularly true of 2D animations - by choosing a convention of a standard sprite size and relative position within that standard size, the relative position frame to frame is implicit. This requires the person generating the assets to know and stick to the convention. Re-using sprite images from the web, without this prior knowledge, means that adjustments may be required inside the program. So saving time by reusing assets maybe leads to more time expended customizing the coding.

    In the bee case, I made decisions to chop up the bug assets into sup-parts to allow me to add a bit of pseudo animation by moving parts independently but also at an early stage my intention was to have bug parts flying off in different directions as it was shot, but this was superseded when I read the explosion tutorial.

  • edited May 10 Posts: 1,284
    @dave1707 I don’t think we have different ideas of what a sprite sheet is.

    They have many uses, and your process here is awesome because it can radically shorten the work needed to create them.

    It simply can’t eliminate some of the work needed to position sprites correctly relative to each other, that’s all—it can eliminate it in many if not most cases, just not all of them.
  • Posts: 172

    @dave1707 @UberGoober
    there’s been a lot of discussion and code editing here, can someone post the most updated version in a zip? i’d like to dig in to this myself

  • dave1707dave1707 Mod
    edited May 12 Posts: 9,430

    @skar The 2 pieces of code near the top are the only versions I’ve posted. Nothing has changed with them yet.

    PS. I’m making changes that I haven’t posted yet.

  • Posts: 1,284

    Okay I’m thinking out how I would use this if I wanted to try to re-do my Foggy Bummer spritesheet using this process. Please let me know if I’m not understanding something.

    • I’m starting with a bunch of separate files for all the sprites, which is how the original is.
    • Since my goal is to get the file size down, I need to shrink most of the files to fit inside 120x120 pixels.
    • To avoid doing every file by hand, what I did before was just drag the full file stack into a single ProCreate file and shrank them all at once that way.
    • This is kind of where I’m stuck on how I’d use @dave1707’s process. To shrink them all, I had to bring them into one file, but I can’t just send that one file over for Dave’s code to chop up, because once the files are pasted into the larger document they lose their boundary information, which is crucial to the animation. I think the only way to preserve the boundary information is to start from the individual files, and send each file to dave’s process, but if I do that I lose the benefit of shrinking them down. I need a way to both shrink the art all at once and use them with dave’s process in a way that keeps each sprites’ orientation inside its file boundary.
    • So here’s a question for dave: if I used your code to make a single spritesheet directly out of all the original (large) image files, which would then preserve the sprites’ relative positioning, could I manually scale down that spritesheet to a smaller size and somehow preserve their boundary information at the smaller scale? In other words, could I scale the sheet down and have the sheet still work with your process? That would have solved many of the problems I had.
  • dave1707dave1707 Mod
    Posts: 9,430

    @UberGoober I wouldn’t waste your time. I don’t think scaling down the sprites would work because you could end up with fractional values which might throw the images off. Plus my program was just an idea and still needs more work. Not sure how well it would work with so many sprites or how much larger the sprite sheet would be compared to the original.

  • edited May 13 Posts: 1,284

    @dave1707 hm ok I guess it would only work if the scaling was strictly by multiples of 4?

    Edit: brain fart, of course that wouldn’t make a difference.

Sign In or Register to comment.