Day 13: Image Sprites – Part 2

Last day we started implementing an “image sprite”. We wrote down the code for extracting a sprite’s animation frames from an atlas. Today use it and complete our image sprite.

Preparing the texture class

If you remember from day 8, our texture class was designed to always draw the whole image resource. But that’s not really handy when it comes to atlases since we need to be able to choose specific parts of a texture to draw. In the same day we defined a field called tempTextureBuffer and filled it manually. It’s now time to drop this field and everything related to it before we can make this class more flexible. So bring your Texture class into view, and omit the following from it:

  • The field tempTextureBuffer
  • The method buildTextureMapping()
  • The three lines from the end of the load method:

    // If texture mapping buffer has not been initialized yet, do it now.
    if(tempTextureBuffer == null)
    	buildTextureMapping();
    

What we just erased were predefined texture mappings, which we no longer need. Instead, we will be providing mappings as an input to the prepare method. The signature for this method should currently look like this:

public final void prepare(GL10 gl, int wrap)

This is about to change. We are going to add another argument to this function. This is how it should be modified:

public final void prepare(GL10 gl, FloatBuffer textureBuffer, int wrap) {
	// Enable 2D texture
	gl.glEnable(GL10.GL_TEXTURE_2D);

	// Bind our texture name
	gl.glBindTexture(GL10.GL_TEXTURE_2D, textureId);

	// Set texture wrap methods
	gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, wrap);
	gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, wrap);

	// Enable texture coordinate arrays and load (activate) ours
	gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
	gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer);
}

Note that the only changes here are the addition of the argument in the first line, and the use of it in the last. Our texture class is now ready for image sprites.

Basic structure

Image sprites display parts of OpenGL textures as frames. So in order for them to be displayed, those textures need to be loaded first. Fortunately, we have already designed a texture manager that handles texture loading for us. We just need to have an instance of Texture in ImageSprite. Moreover, we need to know what part of the texture (which is possibly an atlas) is used by our image sprite. That’s why we implemented :

private Texture frames = null;
private Cutout cutout = null;

Lazy loading textures

We would also like to interfere with texture management as little as possible. We want to be able to create an image sprite even before the corresponding resource is wrapped by our texture manager. In other words, the field frames stays null after constructor call and only the resource identifier is recorded:

private int resId = -1;

public ImageSprite(int resourceId, Cutout co) {
	super();
	cutout = co;
	resId = resourceId;
}

The actual loading of the frames field will be done later when rendering. We will get to it shortly.

Frame management

As image sprites display multi-frame images, we need to implement the means for that into the class. The first thing is to add a frame indicator:

private int currentFrame;

We also define two methods to work with the frame:

public void gotoFrame(int index) {
	currentFrame = index;
}
	
public int getCurrentFrame() {
	return currentFrame;
}

We could also make the access mode of currentFrame public and thus we wouldn’t need the two methods. But we can sacrifice this much performance for a more beautiful code.

Rendering

Loading the texture

We now get to implement the render method. The first thing we need to make sure is that the texture we are going to draw is not null, because this could cause a crash.

if(frames == null) {
	frames = TextureManager.getTexture(resId);
	return;
}

We also return immediately from the render method in this case, the reason being that although we have tried to retrieve the texture identified by the resource identifier, it might not be declared at all. This means that the result of the getTexture function might be null. So, the simplest thing to do is just to skip this frame, so the texture is validated again on the next frame. This also makes sure that a texture that is not declared, would never be drawn and wouldn’t crash the framework.

Generating the cutout

If you remember, we did lazy loading on the cutout as well. The render method is a perfect place to handle that too:

if(!cutout.isGenerated())
	cutout.generate(frames.getWidth(), frames.getHeight());

This code should be added right after the frame retrieval snippet. Note a few things about this operation:

  • Note that the cutout generation is performed only once.
  • We did this here because we need texture width and height for cutout generation.
  • If for any reason the texture is not ready, we made sure we won’t even get here.

Actual rendering

Now that everything is in place, we just use the draw method to cut out and render the piece of the texture we want. Out cutout object holds texture mappings for all our frames in the textureBuffers array. And at this point we know that array is initialized. So we just pass the right element to the prepare function.

final float width = scale.x * cutout.frameWidth;
final float height = scale.y * cutout.frameHeight;
frames.prepare(gl, cutout.textureBuffers[currentFrame], GL10.GL_CLAMP_TO_EDGE);
frames.draw(gl, pos.x, pos.y, width, height, rotation);

The final render method will look like this:

@Override
public void render(GL10 gl) {
	if(frames == null) {
		frames = TextureManager.getTexture(resId);
		return;
	}

	if(alpha != 0) {
		if(!cutout.isGenerated())
			cutout.generate(frames.getWidth(), frames.getHeight());

		final float width = scale.x * cutout.frameWidth;
		final float height = scale.y * cutout.frameHeight;

		frames.prepare(gl, cutout.textureBuffers[currentFrame], GL10.GL_CLAMP_TO_EDGE);
		frames.draw(gl, pos.x, pos.y, width, height, rotation);
	}
}

You might have noticed an extra check for alpha added in the final version. This is not necessary and the code works without it. But if alpha is zero, the sprite is essentially invisible, so we can save some processing by not drawing it, and that’s what this check is doing.

Next steps

The image sprite we designed here is somehow complete. There are some spices however that we can still add to it. Like always, you may download today’s code below.