Tuesday, August 9, 2011

A cutscene framework for Android - part 2

In the last tutorial we've made a framework for basic animations, like translation and text changes. Now it's time to put them together to transform an actor.

The actor
An abstract actor is an object that has variables that can be transformed. In most cases, an actor can be drawn to the screen. For this tutorial, we'll assume that they all do. The actor class should at least contain a set of animations and a transformation matrix that defines how the model is drawn on the screen.

The actor also contains the elements that will be transformed by the animations, and therefore it should also be an AnimationChangedListener.

public abstract class Actor implements AnimationChangedListener {
private final String name;
private boolean visible;
private Matrix transform;
private Set<Animation> animations;

public Actor(String name) {
this.name = name;
visible = false;
animations = new HashSet<Animation>();
transform = new Matrix();
}

public String getName() {
return name;
}

public Matrix getTransform() {
return transform;
}

public boolean isVisible() {
return visible;
}

public void setVisible(boolean visible) {
this.visible = visible;
}

/**
* Adds an animation and links the actor as a listener.
*
* @param animation
* Animation
*/
public void addAnimation(Animation animation) {
animations.add(animation);
animation.addListener(this);
}

public void update() {
for (Animation animation : animations) {
animation.doTimeStep();
}
}

public boolean isIdle() {
for (Animation animation : animations) {
if (!animation.isDone()) {
return false;
}
}

return true;
}

@Override
public void valueChanged(Animation animation) {
float[] old = new float[9];
transform.getValues(old);

switch (animation.getType()) {
case POSITION:
Vector2f trans = (Vector2f) animation.getCurrentValue();
Vector2f diff = trans.sub(new Vector2f(old[Matrix.MTRANS_X],
old[Matrix.MTRANS_Y]));
transform.postTranslate(diff.getX(), diff.getY());
break;
case VISIBILITY:
visible = (Boolean) animation.getCurrentValue();
break;
}
}

public final Vector2f getPosition() {
float[] old = new float[9];
transform.getValues(old);
return new Vector2f(old[Matrix.MTRANS_X], old[Matrix.MTRANS_Y]);
}

public abstract void draw(Canvas canvas);

}

The base class Actor listens to two animations: a visibility animation and a translation animation. The exact implementation of the visibility animation is something I leave up to the reader, but it's just a simple switch at a certain frame. At this time, I use the Matrix class from Android to store the rotations, scaling and translations, but due to the lack of getters like getPosition or getRotation, I may replace this code with my own Matrix class. Alternatively, I could store the rotation, translation and scale in three different variables.

I have given the actor a name, so it can be identified at a later point, for debugging or for setting specifics in the source code (instead of the XML, we'll get to that).

The actor is abstract, because the draw method is not implemented. We'll look at two very useful derived classes now.

The text actor
The text actor is an actor that displays a piece of text on the screen. We'll use the drawText function of the Canvas for that. The class should also be an AnimationChangedListener, because it has to receive a notification when a TextAnimation changes the text.

public class TextActor extends Actor implements AnimationChangedListener {
private String text;
private final Paint paint;

public TextActor(String name, String text, Vector2f pos, Paint paint) {
super(name);
this.text = text;
this.paint = paint;

getTransform().setTranslate(pos.getX(), pos.getY());
}

public Paint getPaint() {
return paint;
}

@Override
public void valueChanged(Animation animation) {
if (animation.getType() == AnimationType.TEXT) {
text = (String) animation.getCurrentValue();
}

super.valueChanged(animation);
}

@Override
public void draw(Canvas canvas) {
Vector2f pos = getPosition();
canvas.drawText(text, pos.getX(), pos.getY(), paint);
}

}

The class also requires a Paint instance to draw the text correctly.

The bitmap actor
Probably the most important actor is the bitmap actor: this class draws a bitmap to the screen using a certain transformation (defined in the base class). The implementation is very simple:


public class BitmapActor extends Actor {
private Bitmap bitmap;
private final Paint paint;

public BitmapActor(String name, Bitmap bitmap, Paint paint) {
super(name);
this.bitmap = bitmap;
this.paint = paint;
setVisible(false);
}

public void setBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
}

public void draw(Canvas canvas) {
if (bitmap != null && isVisible()) {
canvas.drawBitmap(bitmap, getTransform(), paint);
}
}
}

Now that we've seen how to make actors, what's left is to put them together in a cutscene. Additionally, we'll learn how to define these cutscenes in simple XML. See you next time!

No comments:

Post a Comment