Another Texture Atlas Loader

Monkey Forums/Monkey Code/Another Texture Atlas Loader

AdamRedwoods(Posted 2011) [#1]
Here's another texture atlas or sprite sheet loader, as with Android programming you can't have enough options for these atlas loaders.
It is compatible with this texture packer (windows):
http://www.texturepacker.com/

Export the atlas as "libGDX", which exports a plain text file. Also it's recommended that you uncheck TRIM and ROTATION, since offsets and rotations cannot be used.


EXAMPLE OF USAGE:
Class Game extends App
   Field unitAtlas:CTextureAtlas
   Field imgunit:Image[10]

   Method OnCreate()
      unitAtlas = LoadAtlas("unit_atlas.png")
      imgunit[0] = unitatlas.Grab("tank_bottom.png",1,Image.MidHandle)
      imgunit[1] = unitatlas.Grab("tank_top.png",1,Image.MidHandle)
      ''..or..
      imgunit[2] = unitatlas.Grab("tank_bullet")
      ''....etc...

   Method OnRender()
      DrawImage imgunit[0],x,y


Create a field for the atlas texture, load it, then Grab() the files.

With my version, it's almost interchangeable with LoadImage(). This way you can develop with LoadImage() and swap it out when you're ready. It could load animations, if you use GrabSet() and return an array.

- You don't need the ".png" at the end of each filename if you don't want to.
- Don't use more than one "." in the filename or it will truncate it.
- The index and flags are optional.
- The index is used for multiple images when the filenames end with numbers (tree01, tree02, etc.). Then you would need to use the index=1, index=2, etc. (TexturePacker truncates numbers at the end of the file name.)


UPDATE:
- added GrabSet:Images[] which returns an array of images based on a width, height, and number of individual images you want returned from a specified file in the atlas. Example: say I added an already-made sprite sheet of an explosion, then I would use this to extract the explosion frames so I could animate it in the array.
- could also be used for a font array
- page fix by Raph
- load atlas array into one atlas

SOURCE:



AdamRedwoods(Posted 2011) [#2]
Updated to pseudo-handle animations, fonts.


GC-Martijn(Posted 2011) [#3]
Oke, thanks for sharing.

Could someone give an example how to use it with moving a player ?
I don't understand the idea with multiply images, so it look that the player is moving.


What I have is for example:
this image map:

then

unitAtlas = LoadAtlas("char1.txt")
imgunit[0] = unitAtlas.Grab("char__0",1,Image.MidHandle)
imgunit[1] = unitAtlas.Grab("char__1",1,Image.MidHandle)
imgunit[2] = unitAtlas.Grab("char__2",1,Image.MidHandle)
imgunit[3] = unitAtlas.Grab("char__3",1,Image.MidHandle)
...

If KeyDown( KEY_LEFT )
 DrawImage imgunit[0],DrawAtX,DrawAtY
End



but where i do the animation , so the player image changing ?


Jesse(Posted 2011) [#4]
to make an animation you need to make use of variables for the array index that change as time goes by.

the steps required in pseudocode

load all of the images in the OnCreate method.
variables are needed for the first and last image for each direction.
a variable that keep track which frame is being displayed
all of the keyboard input and calculations for the animation should be performed on OnUpdate() method.
finally the On Render will just be used to display the image.
    Method OnCreate:int()
        'set frame rate
        'loadImages
    End

    Method OnUpdate:int()
        'check for key presses and select correct animation frame
    End

    Method OnRender()
        'draw the correct image based on calculations done on the OnUpdate() Method
    End



GC-Martijn(Posted 2011) [#5]
thank Jesse,

but I already have that, I don't understand the animation frame key.

the thing I have to do in the OnUpdate() and Onrender

 Method OnCreate:int()
       
        unitAtlas = LoadAtlas("char1.txt")
imgunit[0] = unitAtlas.Grab("char__0",1,Image.MidHandle) ' walk left step 1
imgunit[1] = unitAtlas.Grab("char__1",1,Image.MidHandle) ' walk left step 2
imgunit[2] = unitAtlas.Grab("char__2",1,Image.MidHandle) ' walk left step 3
imgunit[3] = unitAtlas.Grab("char__3",1,Image.MidHandle)
    End

    Method OnUpdate:int()
        'check for key presses and select correct animation frame
If KeyDown( KEY_LEFT )
 DrawImage imgunit[0],DrawAtX,DrawAtY
End

    End

    Method OnRender()
        'draw the correct image based on calculations done on the OnUpdate() Method
    End


Let keep it simple that moving left is 4 pictures, like in this example.
What to do next ?


Jesse(Posted 2011) [#6]
you say you understand but your example illustrates otherwise.
the OnRender is where all of the drawing go. the OnUpdate will give you an error if you try to use
any of the draw commands on.

something like this at it's simplest form:

	Field index:Int


	Method OnCreate:Int()
               SetFrameRate(60)
         	unitAtlas = LoadAtlas("char1.txt")
		imgunit[0] = unitAtlas.Grab("char__0",1,Image.MidHandle) ' walk left step 1
		imgunit[1] = unitAtlas.Grab("char__1",1,Image.MidHandle) ' walk left step 2
		imgunit[2] = unitAtlas.Grab("char__2",1,Image.MidHandle) ' walk left step 3
		imgunit[3] = unitAtlas.Grab("char__3",1,Image.MidHandle)
    		index = 0
	End

    Method OnUpdate:Int()
        'check for key presses and select correct animation frame
		If KeyDown( KEY_LEFT )
		 	index += 1
			If index > 3
				index = 0
			Endif
		End

    End

    Method OnRender()
        'draw the correct image based on calculations done on the OnUpdate() Method
 		DrawImage imgunit[index],DrawAtX,DrawAtY
	End

note that the code doesn't take into consideration desired speed, it changes frames at game rate speed.

have you ever programmed animations in any other programming language and have you ever done any type of object oriented programming?


Jesse(Posted 2011) [#7]
a bit more advance:

Strict
Import Mojo

Function Main:Int()
	New Game
End Function

Class Game Extends App
	Field walkingLeft:Animation
	Field walkingRight:Animation
	Field standingRight:Animation
	Field standingLeft:Animation
	Field currentAnimation:Animation
	Field pinguin:Image
	
	Method OnCreate:Int()
		SetUpdateRate(60)
       	pinguin = LoadImage("walker.png",32,32,16)
    		walkingLeft = New Animation(0,7,5,pinguin)'first frame, last frame ,duration, animated image set
		walkingRight = New Animation(8,15,5,pinguin)
		standingLeft = New Animation(6,6,5,pinguin)
		standingRight = New Animation(9,9,5,pinguin)
		currentAnimation = standingRight
	End

    Method OnUpdate:Int()
        'check for key presses and select correct animation frame
		If KeyDown( KEY_LEFT )
			currentAnimation = walkingLeft
		Elseif KeyDown( KEY_RIGHT)
			currentAnimation = walkingRight
		Else
			Select currentAnimation
				Case walkingLeft
					currentAnimation = standingLeft
				Case walkingRight
					currentAnimation = standingRight
			End Select
		Endif
		currentAnimation.update()
    End

    Method OnRender:Int()
		Cls()
        'draw the correct image based on calculations done on the OnUpdate() Method
 		currentAnimation.display(100,100)
	End
	
End Class	

Class Animation

	Field firstFrame:Int
	Field lastFrame:Int
	Field duration:Int
	Field delay:Int
	Field index:Int
	Field images:Image

	Method New(first:Int,last:Int,dur:Int,img:Image)
		firstFrame = first
		lastFrame = last
		duration = dur
		images = img
		index = first
		delay = 0
	End Method

	Method update:Int()
		delay = delay + 1
		If delay > duration
			index  = index + 1
			If index > lastFrame
				index = firstFrame
			Endif
			delay = 0
		Endif
	End Method
	
	Method display:Int(x:Int,y:Int)
		DrawImage images,x,y,index
	End Method
End Class



therevills(Posted 2011) [#8]
Heres a quick example using Adam's Another Texture Atlas Loader:



BTW Adam I think there is a slight issue with your code, it doesnt like it if there is only one image in the data file ;)


GC-Martijn(Posted 2011) [#9]
@Jesse, sorry I copy/past to quick.
But you saved my life with that advanced code ! thanks very much its realy works very good.

I had to change the CLS() to SetColor(255,255,255) because everything was black except for a red penguin.

@therevills, I will going to combine the code with @Jesse thanks !


Jesse(Posted 2011) [#10]
it's better if you use the millisecs setup that therevills has by replacing the delay and duration on my example.


AdamRedwoods(Posted 2011) [#11]

BTW Adam I think there is a slight issue with your code, it doesnt like it if there is only one image in the data file ;)


Whooops!
Updated, fixed! Thx! (was one small edit) :D


Raph(Posted 2013) [#12]
I'm getting a crash using this...

Monkey Runtime Error : Array index out of range
C:/Monkey/MonkeyModules/modules/textureatlas/textureatlas.monkey<85>
C:/Monkey/MonkeyModules/modules/textureatlas/textureatlas.monkey<12>


It is specifically happening with atlases that are multiple pages, and seems to always be on the last image on the last page.

A single page atlas works fine in a different project.


Raph(Posted 2013) [#13]
So, handling of multiple texture pages is definitely broken. Each page is adding a new image index, which results in the array getting blown. It's doing a Continue when it hits the blank lines. Also means that even if you expand the array, the indices are wrong from that point forward:



A brief sample:


This gives the following output across a page break...



I can take a whack at fixing it later, unless you happen to have a version that works already, Adam...


Raph(Posted 2013) [#14]
Here's a version that seems to work. I haven't tested it with single page atlases, but it should be fine. I added a page field to CSubTextureAtlas and changed CTextureAtlas's field "texture" into an image array. A blank line triggers loading a new file at the end of the array, and Grab and GrabSet now just pick from the right texture in the array.




AdamRedwoods(Posted 2013) [#15]
Ok, cool, thanks for updating that!
I also updated the code to check for a "." in the next line (indication of an actual filename and not just an inadvertent blank line)

So with that in mind, I've added the capability to load an array of atlas txt files, so they can be combined into one atlas.
unitAtlas = LoadAtlas(["cards0.txt","cards1.txt"])
imgunit[0] = unitAtlas.Grab("queen_of_spades") ''in cards0.txt/cards0.png
imgunit[1] = unitAtlas.Grab("red_joker")
imgunit[2] = unitAtlas.Grab("queen_of_diamonds") ''in cards1.txt/cards1.png
imgunit[3] = unitAtlas.Grab("queen_of_hearts")



Raph(Posted 2013) [#16]
Nice! I like this loader because it has virtually no assumptions about the images after, you really can just drop it in and find-and-replace LoadImage, except for animations.

I dug into a GrabAnim that you could load animation frames without the GrabSet workaround, but it looks like there's some limitations in monkey.graphics which prevent it. It isn't that hard to hack it to work, but I tossed a feature request in the bug forum and left it at that.


Raph(Posted 2013) [#17]
I think there is a bug in GrabSet as well. newx and newy were being incremented by w+1 and h+1 respectively. That created a growing offset that corrupted the frames, and could go off the surface altogether.

I put this in instead and it seems to work fine:




Grey Alien(Posted 2014) [#18]
Thanks for the code. However, there's a bug (of sorts) if you have the png and txt file in the same folder (but not in the exe folder)

For example: If you call LoadAtlas() with a filepath e.g. "graphics/myAtlast.png" it fails because this line:

texture[textureCount] = LoadImage(all[0].Trim(), 1)


Uses the texture filename from the top of the .txt file which doesn't have path info if it's in the same file as the texture.
I made a workaround by passing in the original png filepath to ProcessAtlas (in LoadText()):

Return ProcessAtlas(str, fn)


And modified the Method declaration as follows:
ProcessAtlas:Int(str:String, texturePath:String)


Then changed the texture loading as follows:
texture[textureCount] = LoadImage(texturePath, 1)


However it should be noted that I had to comment out this line in the version of LoadAtlas that takes an array of texture filenames:
If Not atlas.ProcessAtlas(fconcat) Then Return Null


Really the code needs to get the path for each png (which may not be the same) and add it to the top of each text file as it joins them together and then use that to load the png.

Alternatively you can click Advanced in the Data section of TexturePacker and fill out the Texture Path and it will add it to the filename at the top of the .txt file. But this is kinda lame really...


Grey Alien(Posted 2014) [#19]
Quick heads up that I tried using the offset values but I've discovered that they are sometimes slightly wrong by a pixel or two! If I output the same data as xml the offset values are correct, so I don't know what is going on there...