Showing posts with label animations. Show all posts
Showing posts with label animations. Show all posts

Saturday, August 13, 2011

A cutscene framework for Android - part 3

We've reached the third and final part of the tutorial on how to make cutscenes. Now that we've already defined Actors and Animations, we just have to define the Cutscene. The cutscene should consist of a list of actors, the current frame and the frame at which the cutscene ends. Animations don't have to be included explicitly, because they are part of the Actor class.

You may ask why we need to explicitly define an end frame. We could also look at the frame at which all the animations of each actor have finished. If we set that frame as the end frame, the cutscene is cut off immediately after the last transition. This leaves no time to actually see what's going on. That's why we use an end frame.

public class Cutscene {
 private Set<Actor> actors;
 private int endFrame;
 private int currentFrame;
 
 public Cutscene(Context context, int cutsceneResource) {
  load(context, cutsceneResource);
  currentFrame = 0;
 }
 
 public boolean isActive() {
  return currentFrame <= endFrame;
 }
 
 public Set<Actor> getActors() {
  return actors;
 }
 
 public void update() {
  if (!isActive()) {
   return;
  }
  
  for (Actor actor : actors) {
   actor.update();
  }
  
  currentFrame++;
 }
 
 public void draw(Canvas canvas) {
  for (Actor actor : actors) {
   actor.draw(canvas);
  }
 }
} 

As you can see, the Cutscene class is pretty straightforward. Using the framework so far, we can construct a cutscene from source code, with relative ease. It would be even better if we could just read it in from an XML file. For example, look at the following structure:

<?xml version="1.0" encoding="UTF-8"?>
<cutscenes>
 <cutscene end="140">
  <actor type="text" name="Intro" x="10.0" y="40.0"
   visible="true" text="This is the starting text.">
   <animation type="text" start="50" text="@string/anim_text" />
  </actor>
  <actor type="bitmap" name="Player" visible="true" x="0.0" y="50.0" bitmap="@drawable/player">
            <animation type="translate" start="4" end="60" from_x="0.0" from_y="50.0" to_x="100.0" to_y="50.0" />
  </actor>
 </cutscene>
</cutscenes>

All the elements of a simple scene are there: there is a text actor that displays two lines of text and a player bitmap that is moved across the screen. I've added two different ways to add text: you either hardcode it, or you give a reference to a string. The latter is recommended to support internationalisation.

I've given each actor a name, so that you can easily add additional information from source code. For example, in my project the player bitmap is a subrectangle of the resource @drawable/player. When this cutscene is parsed, I look for the actor with the name Player, and I substitute the bitmap. If you want to have a custom bitmap, you should use bitmap="custom" in the XML.

Now we have to parse the file:

public void load(Context context, int id) {
    actors = new HashSet<Actor>();
    
    XmlResourceParser parser = context.getResources().getXml(id);
    try {
        int eventType = parser.getEventType();
        
        Actor currentActor = null;
        
        while (eventType != XmlPullParser.END_DOCUMENT) {
            if (eventType == XmlPullParser.START_TAG) {
                if (parser.getName().equals("cutscene")) {
                    endFrame = parser.getAttributeIntValue(null, "end", 0);
                }
                
                if (parser.getName().equals("actor")) {
                    String type = parser.getAttributeValue(null, "type");
                    String name = parser.getAttributeValue(null, "name");
                    float x = parser.getAttributeFloatValue(null, "x", 0);
                    float y = parser.getAttributeFloatValue(null, "y", 0);
                    boolean visible = parser.getAttributeBooleanValue(null,
                    "visible", false);
                    
                    /* Read type specifics. */
                    if (type.equals("text")) {
                        String text = parser
                        .getAttributeValue(null, "text");
                        int res = parser.getAttributeResourceValue(null,
                        "text", -1);
                        if (res != -1) {
                            text = context.getResources().getString(res);
                        }
                        
                        // TODO: do something for paint. */
                        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
                        paint.setColor(Color.WHITE);
                        currentActor = new TextActor(name, text,
                        new Vector2f(x, y), paint);
                        currentActor.setVisible(visible);
                    }
                    
                    if (type.equals("bitmap")) {
                        Bitmap bitmap = null;
                        if (!parser.getAttributeValue(null, "bitmap")
                        .equals("custom")) {
                            int bitmapID = parser
                            .getAttributeResourceValue(null,
                            "bitmap", -1);
                            bitmap = BitmapFactory.decodeResource(
                            context.getResources(), bitmapID);
                        }
                        
                        currentActor = new BitmapActor(name, bitmap,
                        new Paint());
                        currentActor.setVisible(visible);
                        currentActor.getTransform().setTranslate(x, y);
                    }
                    
                    if (currentActor != null) {
                        actors.add(currentActor);
                    }
                }
                
                if (parser.getName().equals("animation")) {
                    String type = parser.getAttributeValue(null, "type");
                    int start = parser.getAttributeIntValue(null, "start",
                    0);
                    int end = parser.getAttributeIntValue(null, "end",
                    start);
                    
                    if (type.equals("text")) {
                        String text = parser
                        .getAttributeValue(null, "text");
                        currentActor.addAnimation(new TextAnimation(text,
                        start));
                    }
                    
                    if (type.equals("translate")) {
                        float from_x = parser.getAttributeFloatValue(null,
                        "from_x", 0);
                        float from_y = parser.getAttributeFloatValue(null,
                        "from_y", 0);
                        float to_x = parser.getAttributeFloatValue(null,
                        "to_x", 0);
                        float to_y = parser.getAttributeFloatValue(null,
                        "to_y", 0);
                        currentActor.addAnimation(new TranslationAnimation(
                        new Vector2f(from_x, from_y), new Vector2f(
                        to_x, to_y), start, end));
                    }
                }
            }
            
            eventType = parser.next();
        }
        } catch (XmlPullParserException e) {
        e.printStackTrace();
        } catch (IOException e) {
        e.printStackTrace();
    }
}

This function reads the XML and creates a cutscene. I have left some things out, like defining a Paint in the XML, because I haven't implemented those myself. If you want to do such a thing, I would recommend creating another XML structure just for Paints. Then you can add a reference to the resource in the cutscene structure. As you can see, this framework is easily extendable.

And there you have it. In this tutorial you've seen how to build a framework for cutscenes. We've started with simple building blocks like animations and actors and finally we've seen how to put them together to make cutscenes and read these from XML files. I hope you liked it!

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!

Friday, August 5, 2011

A cutscene framework for Android - part 1

Not many Android games I have played have cutscenes or lots of animations. Perhaps because it is hard to make. A cutscene requires higher quality sprites or meshes which the developers may not have. Secondly, and more importantly, coding all the animations is tedious. However, cutscenes contribute to the immersion of the player, much more than a static wall of text! In this tutorial series I will show you how to create a framework to create animations for multiple objects with relative ease. Over the course of this tutorial we'll look at the building blocks for cutscenes, like animations, actors and finally we'll learn to use an XML-format to quickly describe them all.

The code is not Android-specific and could be used for other platforms as well.

Defining what we want
Before we start coding, we have to get a better sense of what we want to achieve. For a cutscene we want a scene with multiple objects. These objects could be anything, like a bitmap (sprite) or a piece of text. We will call these objects actors. Each actor can be modified while playing the cutscene by an animation. This could be anything as well, for example: a translation, a rotation, a change of text or a visibility toggle. An animation basically mutates a property of the actor. In this part, we'll look at how to define these animations.

Gathering some tools
First we'll check if Android has some handy tools for us. It seems that from Android 3.0 on, the Property Animation is very useful and may do about the same as the framework we're going to build. However, Android 3.0 is too much of a restriction for me. I want my games to run on Android phones from 2.1 on. Their second library called View animations is only applicable to views, so that's not useful for sprites rendered in OpenGL or bitmap rendered on a canvas.

So it seems we have to do some things ourselves. That's ok, it's not a lot of work. First we start with some tools, a class that describes a 2d vector with float values: Vector2f.

import android.os.Parcel;
import android.os.Parcelable;

public class Vector2f implements Parcelable {
 private final float x, y;

 public Vector2f(float x, float y) {
  super();
  this.x = x;
  this.y = y;
 }

 public Vector2f(Vector2f vec) {
  super();
  this.x = vec.x;
  this.y = vec.y;
 }

 public Vector2f(Vector2d vec) {
  super();
  this.x = vec.getX();
  this.y = vec.getY();
 }

 public float getX() {
  return x;
 }

 public float getY() {
  return y;
 }

 public Vector2f add(Vector2f vec) {
  return new Vector2f(x + vec.x, y + vec.y);
 }

 public Vector2f sub(Vector2f vec) {
  return new Vector2f(x - vec.x, y - vec.y);
 }

 public Vector2f scale(float f) {
  return new Vector2f(x * f, y * f);
 }

 public float lengthSquared() {
  return x * x + y * y;
 }

 @Override
 public String toString() {
  return "(" + x + ", " + y + ")";
 }

 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + Float.floatToIntBits(x);
  result = prime * result + Float.floatToIntBits(y);
  return result;
 }

 @Override
 public boolean equals(Object obj) {
  if (this == obj)
   return true;
  if (obj == null)
   return false;
  if (getClass() != obj.getClass())
   return false;
  Vector2f other = (Vector2f) obj;
  if (Float.floatToIntBits(x) != Float.floatToIntBits(other.x))
   return false;
  if (Float.floatToIntBits(y) != Float.floatToIntBits(other.y))
   return false;
  return true;
 }

 public Vector2f(Parcel in) {
  x = in.readInt();
  y = in.readInt();
 }

 @Override
 public int describeContents() {
  return 0;
 }

 public static final Parcelable.Creator<Vector2f> CREATOR = new Parcelable.Creator<Vector2f>() {
  @Override
  public Vector2f createFromParcel(Parcel in) {
   return new Vector2f(in);
  }

  @Override
  public Vector2f[] newArray(int size) {
   return new Vector2f[size];
  }
 };

 @Override
 public void writeToParcel(Parcel dest, int flags) {
  dest.writeFloat(x);
  dest.writeFloat(y);

 }

}

This is a thread-safe 2d vector implementation that's parcelable (useful if you want to store a vector using onSaveInstanceState). You can also see that a class Vector2d is mentioned, which is the same class only with ints instead of floats. I use Vector2d for positions in a grid.

Next in line is defining an Animation. This will be the abstract framework on top of which all the possible animations are built. It should certainly have the start frame, the end frame and the current frame. The framerate itself is determined by the main loop of the application (a nice tutorial can be found here). The Animation classes will all transform a single variable (this can be expanded) between those two keyframes. To identify which type of variable is transformed, I use an Enum. You could also use instanceof. 

public abstract class Animation {
 private final int startFrame;
 private final int endFrame;
 private int currentFrame;
 private AnimationType type;
 private List<AnimationChangedListener> listeners;

 public Animation(int startFrame, int endFrame, AnimationType type) {
  super();
  this.startFrame = startFrame;
  this.endFrame = endFrame;
  this.type = type;

  currentFrame = 0;
  listeners = new ArrayList<AnimationChangedListener>();
 }

 public AnimationType getType() {
  return type;
 }

 public boolean isActive() {
  return currentFrame >= startFrame && currentFrame <= endFrame;
 }

 public void addListener(AnimationChangedListener listener) {
  listeners.add(listener);
 }

 protected void dispatchValueChangedEvent() {
  for (AnimationChangedListener listener : listeners) {
   listener.valueChanged(this);
  }
 }

 /**
  * Can be overridden. This function should be called at the end.
  */
 public void doTimeStep() {
  if (currentFrame <= endFrame) {
   currentFrame++;
  }
 }

 public abstract Object getCurrentValue();
}


As you can see, I use an observer pattern to notify all listeners that a variable has changed. Because the abstract base class cannot see when a variable is changed, it is up to the derived classes to call dispatchValueChangedEvent(). AnimationChangedListener is a simple interface:

public interface AnimationChangedListener {
 void valueChanged(Animation animation);
}

Each time a frame is being updated, doTimeStep is called. Classes that extend Animation should do their logic there. Let's write a simple animation to see how this works in practice.

The translation animation
Image we want to translate a sprite on the screen between frames 10 and 20. The starting position is (0,0) and the final position is (100,100). This means that the velocity has to be:

dir = end.sub(start).scale(1.f / (float) (endFrame - startFrame));
This is simply the distance divided by the time in frames. What's left now is to add dir to the variable that tracks the position if the current frame is between the start frame and end frame, or in other words: when the animation is active.

public class TranslationAnimation extends Animation {
 private final Vector2f dir;
 private Vector2f current;

 public TranslationAnimation(Vector2f start, Vector2f end, int startFrame,
   int endFrame) {
  super(startFrame, endFrame, AnimationType.POSITION);

  current = start;
  dir = end.sub(start).scale(1.f / (float) (endFrame - startFrame));
 }

 @Override
 public void doTimeStep() {
  if (isActive()) {
   current = current.add(dir);
   dispatchValueChangedEvent();
  }

  super.doTimeStep();
 }

 @Override
 public Object getCurrentValue() {
  return current;
 }
}

And there we have it. The animated variable can be retrieved by calling getCurrentValue(). As we can see, that function returns an Object and not a Vector2f. By querying the type with getType() we know that the variable is a Vector2f.

Instead of polling getCurrentValue all the time, it's better to use the callback from dispatchValueChangedEvent().

The text changer
A second example of a useful animation in cutscenes is one that changes text. This is easily built:


public class TextAnimation extends Animation {
private String text;
private String newText;

public TextAnimation(String newText, int startFrame) {
super(startFrame, startFrame, AnimationType.TEXT);
this.newText = newText;
text = null;
}

@Override
public void doTimeStep() {
if (isActive()) {
text = newText;
dispatchValueChangedEvent();
}

super.doTimeStep();
}

@Override
public Object getCurrentValue() {
return text;
}

}

This class doesn't store the original text, because that would complicate the constructor. It's no problem, because the listeners are only called when a real value is set.


We've reached the end of tutorial one. Part 2 will come soon!