Automatic sprite sheet

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.

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?

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.

@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.

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.

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.

@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.

yes, steganography FTW

@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.

@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.

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

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

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

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?

It also compresses better.

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.

@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.

@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.

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

@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.