Animation and other Effects

This section assumes that you are familiar with the nature of State classes and how to use them. It's sufficient to read the Getting Started section to understand the following examples. This section will build up on the previous sections to create more complex effects and arrangements of sprite.

Basic Animations

In this section it is shown how to use the default animation implementations to create some basic effects. Animations available are so far:

  1. AlphaValueAnimation
  2. RotateAnimation
  3. AbsoluteSingleNodeLinearTranslation
  4. RelativeLinearTranslation
  5. LinearTranslation

Follow the same procedure to use one of those animations:

  • Instantiate one type of animation
  • add it to a sprite
  • start the animation by calling the beginAnimation() method of the sprite

For every animation you can decide whether the final value of the animated property should be kept or reset. The default is true for all animations and that means that the property of the sprite will remain in this state if the animation finishes.

Multiple animations can be added to a sprite. Added animations can be started all together or by calling a specific animation to start.

The following two examples show the usage for AlphaValueAnimation and RotateAnimation. The same applies for all other animation classes.

AlphaValueAnimation

The alpha value of the sprite can be changed to make it opaque or transparent. Therefore a value from 0 to 255 is used where 0 means fully transparent.

In this example a sprite will become fully opaque in two seconds, starting from a half see-through sprite. If the animation ends the alpha value will be reset so that the sprite will be half transparent again (the initial value of the animation will be used - in this case 100).

1
2
3
4
AlphaValueTransition alpha = new AlphaValueTransition(100, 255, 2000);
alpha.setDoReset(true); // reset to the initial value of 100
sprite.addAnimation(alpha);
sprite.beginAnimation();

RotateAnimation

In this example a triangle will rotate 405° starting from a 45° angle. After the animation finishes it will stop just there:

1
2
3
4
5
6
7
8
TriangleSprite triangle = new TriangleSprite(Color.BLUE);
triangle.initWithLength(200, new PointF(200, 200));

RotationAnimation alpha = new RotationAnimation(45, 405, 4000);
alpha.setDoReset(false); // this is default
alpha.setLoop(false);
triangle.addAnimation(alpha);
triangle.beginAnimation();

The second argument of the RotationAnimation says how many degrees the sprite should be rotated whereas the first argument depicts the starting angle.

Parallax Background

A parallax background can be used within a State only. Follow the steps below to implement a parallax background for your State.

Create the specific background, and in the init() method of your state type:

1
2
ParallaxLayer bgLayer = new ParallaxLayer(ResManager.BACKGROUND_STAR_2, 1f);
ParallaxLayer bgLayerTwo = new ParallaxLayer(ResManager.PARALAYER_STAR_1, 0.6f);

The second argument of the constructor takes a factor that defines how fast the background is moving in relation to a reference sprite or 2D point in space. With a value of 1.0 you say that it's moving as fast as the reference sprite. The lesser the faster the background is translated. Later on you will see why this reference sprite is needed.

The following code example creates a static background that remains still on the screen and doesn't move, and on top of it a parallax layer is added, which moves relative to the reference point:

1
2
StaticBackgroundLayer bgLayer = new StaticBackgroundLayer(ResManager.BACKGROUND_STAR_2); <br/>
ParallaxLayer bgLayerTwo = new ParallaxLayer(ResManager.PARALAYER_STAR_1, 0.6f);

Once created we have to add it to the state (right after the above code):

1
2
addBackgroundLayer(bgLayer);
addBackgroundLayer(backgroundLayer);

The order does matter. Internally the state has a BackgroundNode class that will manage all added background layers.

Next, we need a reference sprite. For the sake of convenience we create a circle with a translation animation:

1
2
3
4
5
6
7
8
CircleSprite player = new CircleSprite(20, Color.BLUE);
player.init(100, 100);
LinearTranslation translation = new LinearTranslation(100, 400, 5000);
player.addAnimation(translation);
player.beginAnimation();

//Important
setReferenceSprite(player);

The animation helps us to see the moving background in action. Therefore we needed to move the actor of the state.

Within the render() method type in the following:

1
2
3
4
5
    @Override
    public void render(Canvas canvas, Paint paint, long currentTime) {
        drawBackground(canvas);
        drawSprites(canvas, currentTime);
    }

For the method updateLogc():

1
2
3
4
5
    @Override
    public void updateLogic() {
        setReferenceSprite(player); //important when using a parallax background
        updateSprites();
    }

Because the actor (circle) gets translated the background needs the new reference point for its canvas translation. Therefore we used the setter setReferenceSprite in the updateLogic method within the state.

The reference sprite can also be an empty sprite not visible but active in the state. If you want to animate the parallax background or indirectly change the position of this reference sprite to animate the background.

Advanced Effects

This section shows some practical examples on how to create some more complex animations or effects. You can see the full source code in the example app in the Play Store or look into Github.

Random explosions

For this example we want to create animated explosion effects. That means we need some image that contains the whole animation.

First, we're going the load a horizontal sprite sheet which contains each step of the whole explosion animation. This texture comes from Explosion Generator, a tool for creating some explosion sprite sheets.

explosion sprite sheet

All in all there are 12 single frames, and the image has a size of 768x64 (width x height) pixels - we need both information later.

The code for loading a bitmap in Android is:

1
2
3
4
5
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.ARGB_8888;
opt.inScaled = false;

Bitmap bitmapExplosion = BitmapFactory.decodeResource(RetroEngine.Resources, R.drawable.explode_3, opt);

For that we use the android BitmapFactory class. The texture has to be placed in the resource directory of your android app. Here it was saved under the name explode_3.png. Have a look into the section Loading Bitmaps on how to access the Resources object of android.

Next, we have to create an animated sprite with the following code:

1
2
3
PointF position = new PointF(RetroEngine.W / 2, RetroEngine.H / 2);
AnimatedSprite explosion = new AnimatedSprite();
explosion.initAsAnimation(bitmapExplosion, 64, 64, 25, 12, position, true);

Remember the values of the sprite sheet? Those are needed for the init() method: The first argument sets the bitmap we loaded before. The next two arguments are for the size of one single frame in the animation. Our sprite sheet has a width of 768 pixels and we have 12 frames that makes 768 / 12 = 64 pixels. The height is 64 pixels and so for each frame. It's important to use pixel values here. The value of 25 (fourth argument) describes the framerate at which the animation should be played. This value should be set in accordance with the overall frames available to achieve a fluent and realistic animation. In this case the explosion will take roughly a half second. Furthermore, the sprite is positioned in the center of the state when added later on. The last argument determines if the animation should be repeated after it ends.

Decorated sprites with text

This nice feature enables you to display a text onto a sprite. To decorate a sprite we need a sprite first. You can use any sprite even animated ones. Let's use the explosion sprite of the example before. To add a text on top of the explosion:

1
2
3
4
5
6
7
TextElement textElement = new TextElement(explosion);
GameFont font = new GameFont(34);
font.setFontColor(Color.WHITE);
textElement.setFont(font);
textElement.initWithText("This is an explosion");

addSprite(textElement);

All what we have to do was to place the previously created explosion sprite as argument in the constructor of the TextElement class. Additionally we set a bigger font size and the font colour the white. Now that the explosion sprite is decorated with a text sprite we don't need to add this sprite to the root node. Instead just add the textElement object with addSprite(textElement). Because of the decorator pattern used here the TextElement class will call the appropriate parent sprite methods before its own to update the logic and the drawing.

Random colored circles falling down

This example utilises the animation system of the engine. We will generate random numbers to place the circles at an arbitrary position and get variable animation duration for each circle. You will learn how to add multiple animations at once, and how to start each circle animation with a delay.

The first thing to do is to create a State class. The main logic is defined in the updateLogic() method:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
    @Override
    public void updateLogic() {
        updateSprites();

        if (getSpriteCount() < MAX_CIRCLES) {
            // choose a random colour from the array
            int ixColour = MathUtils.getRandomBetween(0, COLOURS.length - 1);
            int color = this.manager.getParentActivity().getResources().getColor(COLOURS[ixColour]);

            final CircleSprite circleSprite = circleBuilder.createAtRandomPosition(color, 10);
            circleSprite.setAlphaValue(0); //make circle transparent first
            addSprite(circleSprite);
            // do not start the animation for each circle at the same time
            int startDelay = MathUtils.getRandomBetween(300, 2000);
            getHandler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    circleSprite.beginAnimation();
                }
            }, startDelay);
        }
    }

To set up the circles we declared an inner class that will act as a builder for the sprites:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
    /**
     * The builder class for all the circles
     */
    private static class CircleBuilder {
        CircleSprite createAtRandomPosition(int color, float radius) {
            int posX = MathUtils.getRandomBetween((int) radius, RetroEngine.W);
            int posY = MathUtils.getRandomBetween((int) radius, RetroEngine.H);
            CircleSprite circleSprite = new CircleSprite(radius, color);
            circleSprite.init(posX, posY);
            addAnimation(circleSprite);
            return circleSprite;
        }

        void addAnimation(final CircleSprite circleSprite) {
            int duration = MathUtils.getRandomBetween(1000, 5000);
            ScaleAnimation scaleAnimation = new ScaleAnimation(1, 6, duration);
            PointF oldPos = circleSprite.getPosition();
            PointF end = new PointF(0, RetroEngine.H - oldPos.y);

            RelativeLinearTranslation linearTranslation = new RelativeLinearTranslation(end, duration);
            linearTranslation.setDoReset(false);
            circleSprite.addAnimation(scaleAnimation);
            circleSprite.addAnimation(linearTranslation);
            circleSprite.addAnimation(new AlphaValueTransition(0, 255, duration));

            scaleAnimation.setListener(new IAnimationSuiteListener() {
                @Override
                public void onAnimationStart(AnimationSuite animationSuite) {

                }

                @Override
                public void onAnimationRepeat(AnimationSuite animationSuite) {

                }

                @Override
                public void onAnimationEnd(AnimationSuite animationSuite) {
                    circleSprite.setActive(false);
                }
            });
        }
    }

The createAtRandomPosition() method shows how to create the circle sprites whereas the addAnimation() method illustrates how to add multiple animations to a sprite to realise the described effect. This builder class takes its action in the updateLogic() method as seen before.

Animated Tiled Background

This more advanced example shows you how to combine several animations and the sprite system to create a animated tiled-based background.

Lets begin with loading the following tile image tile_1.png (64 x 64 pixels) in the init() method of a state:

tile image

1
2
3
4
5
6
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inScaled = false;

Bitmap bitmapTile = BitmapFactory.decodeResource(RetroEngine.Resources,
    R.drawable.tile_1,
    opt);

If we want to add a sprite we do this:

1
2
3
AnimatedSprite tile = new AnimatedSprite();
tile.init(bitmapTile, new PointF(0, 0));
addSprite(tile);

Perfect! The tile sprite will be drawn on the top-left corner of the canvas. Now comes the algorithm. If we want fo fill the whole canvas with the sprites we have to measure the width of the screen first to calculate how many of those sprites can fit in one row. After that we have to calculate how many rows of sprites can be placed onto the canvas. We can obtain the canvas size with the RetroEngine class. The size of the tile image is know and we can acquire this information by the bitmapExplosion variable.

There are some ways how to achieve this effect. I will do it in the following way: Draw a matrix of tile sprites first to fill the complete canvas. Add an additional column "left" from the canvas and one row "over" the top of the visible drawing surface. Add all sprites to a new SpriteGroup, then add the RelativeLinearTranslation to the sprite group.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//create a new group
SpriteGroup background = new SpriteGroup();

int width = bitmapTile.getWidth();
int height = bitmapTile.getHeight();
int rowCount = (int) (RetroEngine.W / width + 1f);
int colCount = (int) (RetroEngine.H / height + 1f);

// begin "behind" the visible drawing surface outside the screen
for (int i = -1; i <= colCount; i++) {
    for (int j = -1; j <= rowCount; j++) {
        AnimatedSprite tile = new AnimatedSprite();
        AnimatedSprite tmp = tile.init(bitmapTile, new PointF(j * width, i * height));
        // (!) important so that the sprite doesn't get deactivated
        tmp.setAutoDestroy(false);
        background.add(tmp);
    }
}

RelativeLinearTranslation linearTranslation = new RelativeLinearTranslation(new PointF(0, 64), 1000);
linearTranslation.setLoop(true);
background.addAnimation(linearTranslation);
background.beginAnimation();
addSprite(background);

This will achieve the desired effect in the horizontal downward movement. Nonetheless the method may have a drawback. After the animation finishes it repeats - for that the position of every sprite is reset to its original position. This may take a while and can results in a sluggish animation if the duration of the translation is to small. Of course it's sufficient for the translation value to be a factor of the tile width and height. In this example 64 pixels. Use negative values to translate in the opposite direction.

Animation Sequences