Yalantis: iOS, Android And Web App Development Company

Star Wars: The Force Awakens Or How to Crumble View Into Tiny Pieces on Android

We released our epic Star Wars animation for iOS last month, and you probably had no doubt that we were going to repeat the same story but for the Android audience. The highly anticipated version of Star Wars is now available for Android developers and we’re happy to share our development secrets with you. As always.

To start with, there are two challenging parts in our Star Wars animation: view crumbling into tiny pieces and flying star field. I had a lot of fun implementing those.

star wars Android animation

How to break up the view into tiny pieces

After the view in our Star Wars animation is hit by the Force, it breaks up into 4,000 tiles. This means two things: 1) the Force is indeed quite powerful, and 2) using Canvas to create such complex graphics on Android would’ve been too slow, because Canvas lacks performance.

OpenGL, on the other hand, is far more powerful. What’s more, we used it for implementing our Star Wars animation for iOS when UIDynamics and UIKit (Core Animation) couldn’t cope with the necessary load.

Given the complexity of the animation, I decided to go with OpenGL.

To break up an Android view into tiny pieces, we need to take a screenshot of the entire view, move a texture to OpenGL memory, and only then render the tile effect. Let’s see how we can do this:

1. Take a screenshot of a view. Nothing fancy here:

Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888)
Canvas canvas = new Canvas(bitmap);
super.draw(canvas);

2. Move a texture to OpenGL memory.

3. Break the bitmap into tiles.

Android Extension Pack, a superset of OpenGL ES 3.1, has a tessellation shader which does exactly what we need: it breaks a single plane into lots of triangles. What’s more, unlike OpenGL ES 2.0 where vertex data can only be generated on CPU, with the AEP we could generate these data on GPU.

But unfortunately, OpenGL ES 3.1 + AEP is not supported by the majority of Android devices yet. Because OpenGL ES 2.0 is used by 56% of Android devices, we decided to take the hard way.

8bd08757_1449426208.png

How can we break the view into 4,000 pieces? We can slice an image for every tile! Of course, I’m joking. If we created a few thousands textures a device would probably just melt in our hands. Instead, we are going to use a single large bitmap texture and assign UV (texture coordinates) to each vertex:

final float stepX = 1f / mStarWarsRenderer.sizeX;
final float stepY = 1f / mStarWarsRenderer.sizeY;
  • SizeX - the number of tiles by width
  • SizeY - the number of tiles by height
for (int x = 0; x < mStarWarsRenderer.sizeX; x++) {

        for (int y = 0; y < mStarWarsRenderer.sizeY; y++) {

            final float u0 = x * stepX;

            final float v0 = y * stepY;

            final float u1 = u0 + stepX;

            final float v1 = v0 + stepY;

            // push values to buffer

        }

}

We want to move as many calculations as we can to GPU because GPU is good at calculating many things simultaneously. I did the calculations for all positions in the vertex shader. It takes only one variable that is animated with the help of Android interpolator:

// from 0 to plane height in OpenGL coordinates
animator = ValueAnimator.ofFloat(0, -Const.PLANE_HEIGHT * 2);
animator.setDuration(mAnimationDuration);
animator.setInterpolator(new DecelerateInterpolator(1.3f));
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
 @Override
 public void onAnimationUpdate(ValueAnimator animation) {
   float value = (float) animation.getAnimatedValue();
   mDeltaPosX = value;
   mGlSurfaceView.requestRender();
 }
};
animator.start();

Finally, in the vertex shader we should add this value to the tile positions:

vec4 pos = a_Position;
pos.y += u_DeltaPos;
gl_Position = u_MVPMatrix * calcPos;

How to make stars fly

The other part of the Star Wars animation is a flying star field. For drawing stars, I considered using the Leonids library. It utilizes standard Android Canvas which is very easy to use. However, animating lots of star objects inevitably leads to performance problems, so our animation would’ve become unresponsive, especially on older smartphones. Here is what I’ve got when I tested the animation using the Leonids library:

[The green line corresponds to 60 FPS (16ms). To avoid stuttering animation, we shouldn’t cross the line.]

Given the performance issues, I decided to take a similar approach I used with tiles and animate star movement in the vertex shader.

I’ve noticed that a star texture is rather simple and can be replaced with a little fragment shader trick – by using a formula for rendering star objects:

// Render a star

float color = smoothstep(1.0, 0.0, length(v_TexCoordinate - vec2(0.5)) / v_Radius);

gl_FragColor = vec4(color);

As you can see, we got the same result that we would’ve gotten had we used an image. This also gave us 30% more frames per second. This solution might not always be faster than texture lookup (i.e. using an image) but it often is. The only way to find this out is to measure.

spark_dark.png   

png texture on the left

generated image on the right

When I tested this implementation on my three year old Nexus 4, I was able to render 100 000 stars with 60 FPS.

How to use the Star Wars library

1. Wrap your fragment or activity main view in TilesFrameLayout:

<com.yalantis.starwars.TilesFrameLayout

    android:id="@+id/tiles_frame_layout"

    android:layout_height="match_parent"

    android:layout_width="match_parent"

    app:sw_animationDuration="1500"

    app:sw_numberOfTilesX="35">

               <!-- Your views go here → -->

</com.yalantis.starwars.TilesFrameLayout>


2. Adjust animation with these parameters:

  • sw_animationDuration – duration in milliseconds
  • sw_numberOfTilesX –  the number of square tiles the plane is tessellated into broadwise
mTilesFrameLayout = (TilesFrameLayout) findViewById(R.id.tiles_frame);

mTilesFrameLayout.setOnAnimationFinishedListener(this);

3. In your activity or fragment’s onPause() and onResume() it’s important to call the corresponding methods:

@Override

public void onResume() {

    super.onResume();

    mTilesFrameLayout.onResume();

}



@Override

public void onPause() {

    super.onPause();

    mTilesFrameLayout.onPause();

}

4. To start the animation simply call:

mTilesFrameLayout.startAnimation();

5. Your callback will be called when the animation ends:

@Override

public void onAnimationFinished() {

  // Hide or remove your view/fragment/activity here

}

That's all!

Future plans

It would be fun to rewrite the plane breaking logic utilizing the power of tessellation shaders when Android Extension Pack is more widespread. Stay tuned!

Check out the animation on:

Read also:

Tech

Convert Your iOS App to Android: Get Money, Power and Fame

Tech

Customizable Constructor for Designers of Android Wear

Design

UIDynamics, UIKit or OpenGL? 3 Types of iOS Animations for the Star Wars

Excited to create something outstanding?

We share the same interests.

Let's team up!