A simple movie class

Daniel Sperl on May 14, 2010

Update: Since version 0.8, Sparrow contains a movie clip class, so you don't need to create one yourself, as it is shown in this tutorial. However, reading this post is worthwhile nevertheless, because you can use this technique for your custom classes, too.

One thing that many Flash developers will miss in Sparrow is the MovieClip class. In Flash, you normally use the Flash authoring tool to create animations (like the walk cycle of your main character), and then display and manipulate these movie clips in the game.

"Walking Cycle"

In Sparrow, you have to use textures to create animations. If you want, you can still create your movie clips in Flash, or in any other animation tool you like. To display these animations in Sparrow, you need to export each frame of your movie as an image. I recommend that you add all your frames to a texture atlas - or several texture atlases, if necessary.

The next Sparrow release will most probably contain a class called "SimpleMovie", which makes it easy to display those animations. But as such a class is really easy to create in Sparrow, I think it might be interesting to see how you could create it yourself. That's what this post is all about.

Overview of "SimpleMovie"

The idea is simple. The class will be a subclass of "SPSprite" and will contain one "SPImage" object. It will also contain all "SPTexture" instances that are part of the animation. When the animation is running, the "SPImage" will change its texture at a specified interval.

Let's first see how we will use the class:

SPTextureAtlas *atlas = [SPTextureAtlas 
    atlasWithContentsOfFile:@"atlas.xml"];        
        
mMovie = [[SimpleMovie alloc] initWithFps:5];
[mMovie addFrame:[atlas textureByName:@"frame_0"]];
[mMovie addFrame:[atlas textureByName:@"frame_1"]];
[mMovie addFrame:[atlas textureByName:@"frame_2"]];

[self addChild:mMovie];
[myJuggler addObject:mMovie];

[mMovie release]; 

I think that should be quite self explanatory. The atlas contains the frames for the animation, and all relevant frames are added to the movie object. As you can see, we also add the movie object to a juggler - just like you do with tweens. As soon as the movie clip is added to the juggler, it will execute the animation.

The implementation

Now to the implementation. Here is the header of our class:

@interface SimpleMovie : SPSprite <SPAnimatable>
{
    SPImage *mFrameDisplay;
    NSMutableArray *mFrames;
    float mFrameDuration;
    double mElapsedTime;
}

- (id)initWithFps:(float)fps;
- (void)addFrame:(SPTexture *)texture;

@property (nonatomic, readonly) int numFrames;
@property (nonatomic, readonly) double duration;

@end

As you can see, SimpleMovie implements the "SPAnimatable" protocol. This is required when we want to add it to a juggler. "mFrameDisplay" is the image that will always display each texture that is active at a given moment. I think all other members and methods should speak for themselves.

Thus, we can continue to the implementation file:

// private interface
@interface SimpleMovie ()
- (void)setActiveFrame:(SPTexture *)texture;
@end

// class implementation
@implementation SimpleMovie

- (id)initWithFps:(float)fps
{
    if (self = [super init])
    {
        mFrameDuration = 1.0f / fps;
        mFrames = [[NSMutableArray alloc] init];        
    }
    return self;
}

- (void)addFrame:(SPTexture *)texture
{
    [mFrames addObject:texture];
    
    if (!mFrameDisplay)
    {
        mFrameDisplay = [[SPImage alloc] initWithTexture:texture];
        [self addChild:mFrameDisplay];
        [mFrameDisplay release];
        [self setActiveFrame:texture];
    }
}

- (void)setActiveFrame:(SPTexture *)texture
{
    mFrameDisplay.texture = texture;
    mFrameDisplay.width = texture.width;
    mFrameDisplay.height = texture.height;
    mFrameDisplay.x = -texture.width/2.0f;
    mFrameDisplay.y = -texture.height/2.0f;            
}

- (void)advanceTime:(double)seconds
{        
    mElapsedTime += seconds;    
    int frameID = (int)(mElapsedTime / mFrameDuration) % 
                  self.numFrames;

    SPTexture *texture = [mFrames objectAtIndex:frameID];
    if (texture != mFrameDisplay.texture)
        [self setActiveFrame:texture];
}

- (BOOL)isComplete
{
    return NO;
}

- (int)numFrames
{        
    return mFrames.count;
}

- (double)duration
{
    return self.numFrames * mFrameDuration;
}

- (void)dealloc
{
    [mFrames release];
    [super dealloc];
}

@end

The part right at the beginning is a private interface. If you haven't seen this before: that's the Objective C way of marking methods as private. A user of the class will not see the methods that are declared here.

The "addFrame"-method just adds the texture to our frame array, and it creates the frame display image if that hasn't been done before.

The interesting part happens in "advanceTime". This method is part of the "SPAnimatable"-protocol, and will be called by the juggler in every frame. Here, we find out which frame needs to be displayed at the current moment. The correct frame (texture) is then displayed by the "mFrameDisplay" object. (In this implementation, I center the image horizontally and vertically, but if all your frame textures have the same size, you can omit that step.)

The "isComplete" method is the other method that is required by the "SPAnimatable"-protocol. As soon as this method returns "YES", the juggler will throw that object out, and "advanceTime" will not be called any longer. Since we want our movie to loop forever, we just return "NO" here.

Conclusion

That's all there is to it! As you can see, thanks to the "SPAnimatable"-protocol and Sparrow's "Juggler", creating the "SimpleMovie" class is really ... simple!

If you understand the steps that are done in this class, you can extend it as you like. You could e.g. add a "pause" method or define if and how the animation should loop.

But more than that: you should now be able to use the "SPAnimatable" protocol in different parts of your game. It's a very simple yet powerful protocol, and can help you every time something needs to be moved or animated in your game.