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.

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.

4 Responses to “A simple Movie Class”

  1. nathan May 14, 2010 at 16:44 #

    This is brilliant, looking forward to trying it out.

  2. mike May 14, 2010 at 23:01 #

    Great stuff! I’m using nstimers now ..this will make the game much cleaner!

  3. zeeyang May 20, 2010 at 21:38 #

    Thanks for the post.

    One thing I noticed was that in the first code block, this line “[myJuggler addChild:mMovie];” should have been “[myJuggler addObject…”

    Another interesting problem I ran into was when I tried to attach the SimpleMovie to the stage juggler during Sprite init. It failed silently because the “stage” property is nil until after the Sprite is attached to the parent display object.

  4. Daniel May 21, 2010 at 08:03 #

    Thanks for reporting the error! I corrected the line in the code sample above.

    The other point is perfectly valid. When I wrote the juggler, I considered creating singleton access to the juggler instead of creating a stage juggler ([Juggler instance] …), since that would work in the init-method, too.

    But then I realized that it’s not a good practice to add something to a global juggler in the init method, anyway. Think about it: just that an object is created does not mean that it will be used right away. It might added to the stage only after some time! So I recommend the following approach:

    - (id)init
    {
      if (self = [super init])
      {
        mMyMovie = [[SimpleMovie alloc] init ...];
        [self addEventListener:@selector(onAddedToStage:)
             atObject:self
             forType:SP_EVENT_TYPE_ADDED_TO_STAGE];
      }
      return self;
    }
    
    - (void)onAddedToStage:(SPEvent *)event
    {
      [self.stage.juggler addObject:mMyMovie];
    }
    

    That way, it will work fine, and the movie will only be animated when the object is part of the display list.

    You’ll want to remove it from the juggler in the REMOVED_FROM_STAGE method. Unfortunately, you will run into the next problem here: you have no access to the stage in that method, because you are already disconnected from it. Bummer. This will be corrected in the next Sparrow release — sorry for that. In the meantime, you can either create your own juggler or save a reference to the stage juggler.

    I hope that helps =)
    Daniel