Day 5: First OpenGL ES Project

| 2013-12-08

Before we begin designing our stage, it is good to know how stuff works. Today we are going to create a simple OpenGL ES app. I will try to design it in a way that we can easily transform it to our engine’s library. So buckle up and open your Android Studio.

Setting Up the Environment

Creating the Project

First we need to create a project. We target the latest version of Android, while keeping it still compatible with API Level 8 (Android 2.2 Froyo). Thus our app will be compatible with almost 95% of the devices in market. So, in case you have any doubts, make your New Project’s options look like the following (click to enlarge):

New Project Dialog

As you can see, we have selected to create no activity. This is to avoid the excess files that are automatically generated, as we want it just simple. We will create the activity manually. When you create your project in Android Studio, you will probably get an empty environment. Just press Alt+1 to see the project tree.

Creating GLSurfaceView

GLSurfaceView is a special type of View that contains OpenGL ES content. We are going to extend GLSurfaceView and add it to our main activity’s layout. This very first class will play the role of our game engine’s Stage entity later on in this guide. Open the project tree to your project’s package name.

Project Tree

Right-click the package-name and choose New > Java Class. Name the class Stage. We now let it extend GLSurfaceView:

For now we have only coded one constructor. This specific constructor is needed by the layout inflater. When our layout is being built by calling the setContentView on our activity, it is this very constructor of each View that is called.

Creating the Main Layout

Next, we create a layout file that contains our Stage. Right-click the res folder of your project and choose New > Android Resource File.

Project Tree

We choose FrameLayout as our root element, which will cover the whole container with only one element. As this is going to be a game and not an app, that’s exactly what we want. Now open main_layout.xml in the editor. At the bottom of the editor, there are two tabs, reading Design and Text. Go to the Text tab to view the XML code. We now add our Stage to the layout:

Creating the Main Activity

The final step is creating the activity. Again, add a new Java class to your project’s package. I would like to call it MainActivity but you can call it as you please. Extend the class from Activity and load main_layout into it. If you are unsure how to do this, your code should look like the following:

We want this activity to be the first thing that is brought to view when the user taps our app’s icon in the launcher. For that we need to modify the application’s manifest file. So, open your AndroidManifest.xml file and add the following XML snippet within the application tag.

This code simply introduces our MainActivity as the main activity of the app. That’s what the children are intent-filter are doing. We also give it a title and a theme with no title bar. There is not much need for a title bar in a game!

Warning: You cannot run the project yet at this point. More coding needs to be done for GLSurfaceView before you can run the app without errors.

Coding the Surface

Assigning a Renderer

Before we can use an object of type GLSurfaceView, we must assign a renderer to it. A renderer is a Java interface that plays these roles for a GLSurfaceView:

  • Initialize the OpenGL ES context
  • Modify the context when necessary
  • Draw frames (render)

We create a private class inside our Stage, which implements Renderer:

We then create an instance of this class in Stage‘s constructor:

Next we need to implement each of the functions in MyRenderer class.

Initializing OpenGL ES Context

The function onSurfaceCreated of the renderer is used to initialize OpenGL ES. It is called as soon as the View is created and is ready. However, you should have two things in mind:

  • Be careful that at this stage, the View might be not laid out yet and dimensions might not be available.
  • This code is run only once when the object is created. So we should only do one-off tasks here.

We do all the initial tasks as follows. I have put comments in the code so you know what each part is doing:

About vertex and texture coordinate arrays, don’t worry if you don’t know what they are. They will be covered in later days. Alpha blending is also better explained when we get to textures.

Responding to Layout Changes

The OpenGL context needs to know exactly the area it should render to, and it should be adjusted accordingly. In the previous section we mentioned that onSurfaceCreated was not a good place to do this, because the dimensions are essentially unknown. That’s why there is another function called onSurfaceChanged which is exactly for this purpose. Each time a layout is applied to the GLSurfaceView, this method is called. This includes manually handled screen orientation changes.

What we are going to do here is as follows:

  1. Set the clear color of the stage to black (R:0 G:0 B:0 A:1).
  2. Set up a projection matrix based on width and height of the View (to project back to the basic cube).
  3. Load identity matrix. OpenGL’s visible space is a 2x2x1 cube. All the images you see on your HD screen in 3D are results of matrix multiplications that map all those big numbers onto these dimensions. Movement, rotation and scaling are also performed using matrices. So, loading the identity matrix in the beginning makes sure all objects are initially drawn straight and without any transformation.

Our final code for this function will be something like this:

In the code above, we first check the orientation using width and height. We always make sure the smallest dimension always is interpreted to 600 and with larger one scaled accordingly. This makes it easy to handle various screen sizes as we always have to work with the same dimensions regardless. This will not affect quality. If the end user has an HD or higher screen, they will still be able to see crisp graphics. But it does make it easy for us to develop a game.

To understand the last five lines of the function, you should know about matrix modes. We just learned what matrices do in OpenGL. There are two main matrix modes:

  • Projection: When on this matrix mode, we will set how your points are projected onto the screen. You can view it as the matrix that affects the camera.
  • View: When on this matrix mode, we will set how objects are transformed. We usually alter the view matrix once before drawing each object.

The function glOrtho sets the simplest projection matrix. Before we do so, we switch to GL_PROJECTION matrix mode, because we want to adjust the camera. An orthogonal camera is one in which there is no perspective. That’s not a big deal as we are working in a 2-dimensional environment and we don’t need perspective. The parameters of this function are (from left to right): left, right, bottom, top, near, far. You are probably with the first four. The last two are for the depth dimension. The near edge is where the one closest to the camera and the far edge is the one the farthest (after which nothing will be drawn).

Please note that we set the projection matrix to identity right before calling glOrtho. The reason is that glOrtho multiplies the orthogonal projection matrix with the current matrix. So wee must reset our matrix to identity to avoid unwanted behavior.

After setting the projection matrix, we will switch back to view matrix, because once set, we no longer need to change the camera and we will only be working with objects.

Completing Main Activity

Screen Settings

Before we can continue, we need to make some small changes to our activity. The first changes we are going to make is about how a game should appear on a mobile device. Most games have the following in common:

  • They are full-screen.
  • They override normal back-light and screen behaviour and make screen always on.

In order to do this, we should modify some window flags. So in onCreate of MainActivity we add the following lines of code:

OpenGL ES Life-Cycle

A very important modification we should make to our activity is management code for OpenGL ES life-cycle. We don’t want our app to work when we navigate out of the app. Rendering is a heavy task and it should be stopped when activity is not in view, in order to save system resources (memory, processor time) and battery life. So we need to make sure we handle that. Make a variable of type Stage (the class we made on day 5), in your activity and acquire it from the layout, after applying it (under setContentView):

Now, we need to notify stage whenever our app pauses or resumes. GLSurfaceView will then make sure resources are freed when needed.

Next Steps

At this point you will be able to run your project without any error. It will do nothing and will only show you a black screen. You can download today’s files below: