How to make games
A great idea, but a disappointing result
You woke up one morning with a great idea for a game. You rushed to your phone to convince your best friend to do the artwork, your uncle to create sounds and music, and your sister to build a web site to promote your future success. Unfortunately, three weeks later, and after a few sleepless nights, you have to face reality: your game is crap. It has a nice concept, it is pretty, it has cool music and the screenshots are very engaging on the web site, BUT it is slow, it frequently has memory problems, it does not adapt well to different screen resolutions, the animations are choppy, the gameplay is poor, the special effects have been reduced to... none, the AI is dumb as a bag of hammers, the collision detection system is far from perfect (and maybe you do not even know what a collision detection system is), the sounds do not mix with the music and it is necessary to know a whole bunch of rules before playing. In short, it is probably time for you to read this tutorial.
A few words about me
I’m a professional developer and I made a few games in the 80’s, but I never worked in the game industry and my experience in creating games for Android is rather limited, so I don’t ask you to blindly trust my words in what follows. But I am passionate since long by games, how they are designed, the algorithms they use, how they solicit your imagination, what skills and tools they require, so I’m not without some knowledge in that domain. And my goal is less to explain you how to make a hit game (that I’ve never created myself) than to help you avoid the most common pitfalls and to give you some directions.
What’s a game?
“A game is structured playing, usually undertaken for enjoyment and sometimes used as an educational tool. Games are distinct from work, which is usually carried out for remuneration, and from art, which is more often an expression of aesthetic or ideological elements.
Key components of games are goals, rules, challenge, and interaction. Games generally involve mental or physical stimulation, and often both.”
As said above, your game must have goals and rules, and your player needs to know them when he/she’s playing. The goals have to be obvious or clearly explained at the beginning of the game: collect all gems, drive fast to be first, eat all food dots, kill all monsters, survive until dawn, etc. The rules can be explained along the way, with tutorials, panels between levels or help balloons, but they have to be explained and easy to remind. If your game has a steep learning curve or too many rules, it has a great chance to end in the depths of the Google Play Store. Having to read a book for playing a game is something to avoid in the Android market.
Your game must propose progressive challenges, and that’s probably one of the most difficult tasks when you create a game. The game should be easy at the beginning to allow the player to become familiar with the controls, to fully understand the basic rules, to be in the mood... and the difficulty should increase regularly. Make it too difficult at start and you will discourage casual players. Make it too easy and it will look more like a demo than a game. Nowadays, this task is devoted to a specialist (the game designer, or level designer) in gaming companies and is considered as one of the keys to success, so don’t overlook it. Put your game in many different hands and observe how people play it. Make changes and test again. Take note of what people say. Don’t think you know better than everyone what is fun or cool in your game, and what’s challenging or not.
Your game must be playable: the controls have to be easy to use, the controlled object has to react precisely and without delay, the secondary screens (e.g. the inventory or map screen) have to be accessed quickly, the menus have to be clear... It’s very frustrating when you lose because you were unable to move precisely your character or because you didn’t open the right screen on time to select a spell or a weapon. Any problem with the controls will increase the difficulty of your game. And keep in mind that your players are not as skilled as you are. You played many times to your game, they did not. So don’t expect they will master the controls very quickly, especially in a driving game where a small change of direction can make all the difference between success and failure.
A game is a complex combination of goals, rules, challenges, graphics, audio, special effects, gameplay, controls, atmosphere, etc. and mixing all this to create something great is very difficult. Whatever your ambition is, you will see that creating a game is not the easiest work and takes a lot of time. You will face many technical and artistic problems, and maybe you’ll abandon more than one project before reaching your objective. So I hope that the following advices will speed up your development and prevent any bad choices.
Prepare your work
Coding a game is not really different from coding another type of application. You have to know what’s the result to obtain, what are the tools you need and how you are going to use them. So no surprise here:
- Limit your dreams to the reality of your skills. Don’t imagine that your first game will compete with Riptide GP, Angry Birds or Ingress. It’s impossible. Start by a simple game, with simple rules, and if you’re successful, you will add complexity, special effects, new levels, etc.
- Write the rules and the logic of your game, then translate them into pseudocode, and finally use this pseudocode to comment your classes and functions. That will make things clear and ease any future modification. Note that it’s mandatory if you want to work with a teammate.
- Draw your game screens on paper. Things are usually very different in your mind and on the paper, with the right size. You will realize, for example, that a touchpad takes a lot of place or that it’s not a good idea to put the action button at the top because it is too far to be used quickly.
- Collect tools, libraries, snippets of code and tutorials. If you don’t know how to do some parts of your game, learn by reading tutorials and books. If you don’t have the right tools, search them or write them. Become familiar with the numerous libraries of Basic4Android. It’s too bad when, in the middle of a development, you realize that you need a library which does not exist yet.
Tools and libraries
You could use only the standard libraries of Basic4Android to make a game but this game would be very limited. It is a lot easier and faster to create a game with the right tools. Here’s a selection of libraries at your disposal in February 2014:
- Game engines: LibGDX, RSAndEngine.
- Rendering: OpenGL, OpenGL2, jPCT-AE, GameView, Accelerated Surface, Game Sprite.
- Animation: Animation, AnimationPlus, NineOldAndroids, TweenEngine.
- Audio: MediaPlayer, SoundPool, AudioTrack, OpenSL.
- Input: Gesture Detector, PhoneAccelerometer.
- Artificial intelligence: SteeringBehaviors.
- Physics: JBox2D, ABPhysics.
- Files/data: JSON, SQL, XML, RandomAccessFile.
- Networking/multiplayer: Network, HTTP, GamePlayServices.
The libraries of the “game engine” category provide a framework to create various kinds of games. They include a rendering engine for 2D, a physics engine (Box2D), an audio engine, a tiled map renderer, a scene graph and a lot of helper classes to ease your burden. They should be your first choice.
I’d recommend LibGDX because it has plenty of features and a very fast rendering engine, but it is a big fat baby (about 2.8 MB for the full version and 1.7 MB for the lite version), so you could prefer a combination of Accelerated Surface, Game Sprite or OpenGL with MediaPlayer, SoundPool and ABPhysics for a lightweight application.
For 3D games, I’d recommend a combination of jPCT-AE + MediaPlayer + SoundPool.
The LibGDX, RSAndEngine and jPCT-AE libraries belong to the OpenGL ES family. Their rendering engine makes an optimized use of the OpenGL ES library and fully benefits from the hardware acceleration.
The GameView and Accelerated Surface libraries belong to the accelerated Canvas family. They call also the OpenGL ES methods, but without any optimization. They cannot be as fast as the libraries of the OpenGL ES family, and it’s worse with the Android versions prior to Honeycomb because they cannot benefit from the hardware acceleration.
The Canvas class of Basic4Android belongs to the Skia Canvas family, which has many drawbacks and should not be used in a game.
For a 2D game with two dozens of graphic objects on screen, it doesn’t matter if you choose a library of the OpenGL ES family or a library of the accelerated Canvas family. A few games have been created with the Accelerated Surface library and they didn’t exhibit a limited performance on most devices. But for games more demanding, for 3D or just for peace of mind, you should prefer a library of the OpenGL ES family.
Beside the above libraries, you may find useful PreferenceActivity or AHPreferenceActivity to save the game options, ABExtDrawing to create or transform your bitmap images, AHLocale to translate your application to different languages, Threading to run subs on separate threads, and Reflection of course which gives a full access to the Android API.
You will need also tools to create your images, your sounds, your animations, your 3D models... You have probably your preferences in each category. For Box2D, it exists two scene editors: the C2D scene editor, which is free, and R.U.B.E, which is well worth its price. If you have no artistic skill and no artist in your team, you may find graphic resources on deviantart.org or opengameart.org, and use Bfxr to generate sounds. For music, alas, you have to buy royalty-free songs or take music lessons.
As I explained, OpenGL ES (Open Graphics Library for Embedded Systems) is at the core of the best rendering engines so I’d like to give you a short introduction to this library.
This library has been wrapped for Basic4Android and is named OpenGL for the version 1 and OpenGL2 for the version 2. The version 2 eliminates most of the fixed-function rendering pipeline in favor of a programmable one. Almost all rendering features of the transform and lighting stage, such as the specification of materials and light parameters formerly specified by the fixed-function API, are replaced by shaders written in GLSL, the shading language. As a result, OpenGL ES 2.0 is not backward compatible with OpenGL ES 1.1.
Nowadays, almost all Android devices support OpenGL ES 1.1 and OpenGL ES 2.0, but the version 2 is more complex to learn than the version 1.x and doesn’t offer a significant increase in performance, so if you’re new to OpenGL, you should start with the version 1.1.
Jelly Bean 4.3 introduced support for OpenGL ES 3 but this version is not yet available to Basic4Android users and needs devices with appropriate drivers.
If you intend to use directly OpenGL ES, and not a library with higher level functions, be prepared for a serious headache because it’s a somewhat complex library. If you’re scared by geometry or by words like “frustum”, then OpenGL is maybe not for you.
There are a lot of tutorials on Internet devoted to OpenGL, so I won’t add one. Here’s just a list of the main features and things to know:
- OpenGL is basically a triangle renderer. Thus, a rectangle drawn by OpenGL is made of two triangles. Each triangle is made of three points called vertices, which have a position in space: x, y, z.
- Each rendered object has a geometry (a shape made of triangles), a set of colors, a texture (an image applied to its surface) and a material defining how this object interacts with light.
- Objects are seen by a camera. The camera can move, rotate, and zoom in or out.
- OpenGL constructs a 2D view of a 3D scene from the camera position. It uses one of the two available projections: parallel (the objects size is always the same regardless of the distance of objects from the camera) or perspective (the objects are smaller when they are far).
- The size and aspect ratio of the 2D view on screen are defined by the viewport.
- The Y-axis is pointing upwards.
- Color values range from 0 to 1.
- OpenGL stores its data in native heap memory, not in the Java heap memory of your application, and thus is not limited by the VM heap size and is not affected by garbage collection. It’s a big advantage over the Canvas-based solutions.
Your first steps with OpenGL will probably be disappointing because it needs a lot of code to do simple things, and the result is often not faster than an accelerated Canvas library. In fact, you have to use a batching technique and advanced features such as the Vertex Buffer Objects to make the most of OpenGL. So, is it worth to spend time learning OpenGL since the API provided by LibGDX and jPCT-AE are already optimized and a lot easier to use? IMHO no, except for very particular needs, e.g. if you intend to add a realistic water ripple or wave effect to your game and you didn’t find the appropriate code on Internet.
Now, you are able to make reasoned choices and select the right tools. It’s time to write your code, but what to do first? And what to do next?
From the core to the shell
A game is usually written from the core (the functions that draw, compute or move your objects) to the shell (the artwork, the user interface, the music, the bells & whistles, etc.). In a gaming company, it’s a bit different because people work in parallel, but when you’re alone or a very small team, you should follow this order because:
1) You will be able to try out the core of the game in the smallest amount of time. You will see quickly what’s really lacking, what does not work, what is technically too difficult, etc.
2) It is less discouraging to abandon a project at an early stage for a very technical reason, than to abandon it near the end for the same reason.
So your first task is to produce a playable prototype with the core mechanics of the game. When this prototype will be fully satisfying, and it might not be before long, then it will be time to render your characters with their final texture, add sounds and music, save the game state on disk, make nice menus, etc.
Use your imagination
Even experienced game makers face difficulties because each game is unique and challenge your skills. Sometimes there’s no easy solution and internet and books are of no help. You have to invent a new way of doing things or mix existing approaches in an unusual manner. It’s not uncommon with A.I. or special effects. Example: a laser FX. That’s why I’m used to say that your imagination will be as much requested as your skills.
The main functions of your game
To build your prototype, you have to create a set of specialized routines. They depend on the type of your game, but usually you have:
- a loader: it loads the resources and initializes data
- a screen manager: it selects the appropriate screen (menu, map, options, etc.) according to the game state and player actions;
- an input handler: it processes the keyboard and touch events
- a “brain”: it selects the actions of enemies
- a position calculator: it computes all moves and calls the collision detector
- a collision detector: it detects collisions between objects
- an updater: it defines the new state of objects (wounded, dead, paralyzed, hasted, etc.) and computes the score and lives
- a renderer: it draws the objects on screen
- a resetter: it resets all data for a new game
For the clarity of your code and to make changes easier, create a class for each object type (a class for the hero, a class for his horse, a class for his enemies, a class for the map, a class for the furnitures, a class for the weapons, etc.). Then, create a specialized routine (loader, updater, renderer, etc.) in each class. You can choose another model, where the routines are in their own code module. In this case, each class makes a call to the common routine with its custom parameters.
In the main class, keep track of the game state (“loading”, “ready”, “running”, “paused”, “game over”, etc.).
If you don’t use a game engine, add the classes that will handle graphics, sounds, animation, files, input, physics, threading and networking at a lower level. These classes will be separated from the logic of the game and can be reused in other games.
Adapt your game to the screen
The first stage of your game is the loading of assets, and quickly a problem will arise: your background image has a size of 800x600 which does not fit the current screen resolution in landscape mode (854x480). What can you do?
- Adjust the image size, but keep the same aspect ratio. Reduce the image dimension which is larger than the screen dimension and compute accordingly the other dimension. In my example, the image height is superior to the screen height so the image height is set to 480. The width becomes 480 * 800 / 600 = 640. The aspect ratio remains the same: 640/480 = 800/600 = 1.3333. By doing this, I get black stripes on the sides, but the image is entirely on screen whatever the resolution may be.
- Stretch the image. The width and height are set to the screen size. I recommend using a nine-patch (a drawable with stretchable areas) in this case or your image will look distorted.
- Draw your image as is. The width and height are not changed (or they are just scaled to the device density so that your image gets the same physical size on all devices). It’s too bad for the guys with small screens!
This problem with resolutions brings a new question: what is the ideal size for my images? The answer is simple: there’s none. Create three versions of the same image with different resolutions and use the one that needs the smallest scaling. If you don’t want to bother with that, create large images. The scaling down of a large image will produce a better result than a small image scaled up.
In one of my applications, I decided to set all background images to 1040x640. The image is large enough to scale up nicely on large screens and small enough for small screens. If I lose a bit of details, it’s not important because it’s just a background. This size is exactly in the middle of 800x480 (WVGA) and 1280x800 (WXGA). For the other graphic objects, I chose to size them properly for a 1280x800 screen. They don’t suffer too much from the scaling down.
To test different resolutions or densities, you can use the emulator.
Keep an eye on memory
Don’t forget that an image takes up space in memory and that some devices allocate a very limited amount of memory to your application. So, if you want to load a bitmap, use the LoadScaledBitmap function of the Accelerated Surface library to resample and resize the image to the right dimensions.
This problem will be less annoying with libGDX because this library transfers the loaded textures in native heap memory.
In all cases, Out of Memory is an error to avoid at all costs. This error crashes the game, and even when you trap it, it’s not easy to recover from it. Note that LoadBitmapSample tries to sample down your images when this error occurs, but I cannot recommend relying upon this feature. First because that means that you don’t manage well your resources, and second because nobody likes to play a game with ugly images.
To track the memory usage of your application, you can use the Monitor tool provided with the Android SDK and/or log the FreeMemory property of the StrictMode library.
To reduce the memory usage, there’s not a lot of solutions: try to use smaller images, change their color format to RGB565 if you don’t need transparency (see the ReduceColors function of the Accelerated Surface library), set to Null the bitmaps or canvas instances that you no longer use, and/or store your images in native heap memory with either a library of the OpenGL ES family or the Native DirectBuffer library (reserved to experts).
All that you may read about recycling bitmaps is mostly useless for the new versions of Android. This was only useful in some cases with Gingerbread and older versions. It is now deprecated.
If you still have problems with memory, you can add this line in the manifest to get more memory with Android versions >= 3:
From the world to the screen
Using the screen coordinates to position your graphic objects is sometimes a bad idea. Think to a chess board where the pawns can only move from a tile to another. It’s more convenient to position them by indicating a tile than computing their coordinates in pixels. Think also to a big world that cannot fit entirely on your screen. It’s more convenient to position your characters with world coordinates than with the limited screen coordinates. And sometimes you don’t have the choice. The physics engine Box2D, for example, doesn’t use pixels, only meters.
When you use a library based on OpenGL, you can define with the viewport a different unit scale to position and size your graphic objects. With the other libraries, you have to create functions that make the conversion. Examples:
Public Sub FromTileXToPixelX(TileX As Int) As Int Return TileX * TileSize End Sub Public Sub FromWorldXToScreenX(WorldX As Int) As Int If WorldX < ScreenLeft Or WorldX > ScreenRight Then Return –1 Return WorldX – ScreenLeft End Sub
Draw only what’s needed
Assets are loaded, data are initialized, and all actors of the battle are ready to fight. It’s time to draw the battlefield and the sprites of the warriors at their initial position. Once rendered, the first computations will take place to know who strikes who, who is wounded, who is dead, who moves and where, etc. And the renderer will be called again to show the new situation. How to sequence these different phases properly?
In libGDX, for example, the Render event is called continuously. You can put all your game logic in this event and sequence the phases as you wish (input handling, computation, rendering, then input handling, computation, rendering, and so on). In the Accelerated Surface library, a Timer raises regularly the Update event, then the Draw event, if you enabled that sequence with StartRegularUpdateAndDraw. It’s advised to put only the rendering functions in Draw, and everything else in Update. If the processing of one of the events takes more time than the Timer interval, the Timer waits before firing the next event so you cannot be in a situation where you start to draw with an incomplete calculation. In the GameView library, you have to set a Timer and use its Tick event. It’s like the Render event of libGDX; you can put everything here and order your phases as you wish.
Regardless of the number of things to do between two renderings of a scene, everything has to be done in less than 16ms, rendering included. You could set this Timer interval to a longer value with some libraries but I advise against it. A span of 16 ms will display about 60 frames per second, which is an ideal rate for your game. First because the brain won’t have to create intermediate images to give you the feeling of a smooth and continuous motion, and second because your hardware runs at its best with this rate. Some devices are capped to 40fps, but it is still excellent.
If one of the phases takes too long, but only from time to time, it will introduce an irregularity in the sequence and your animations will be choppy. If a phase takes too long, but always with the same duration, your game will be slowed. Usually the phase involved is the rendering phase. What can you do when that happens?
There are two main reasons for a slow rendering: your graphic objects are too numerous or too complex. The first thing to do is to avoid rendering objects that are not visible, either because they are hidden by something else or because they are off screen. The second thing to do is to limit the number of textures because OpenGL binds only one texture at a time and doesn’t switch quickly to another. Take the habit to use the texture regions of a texture atlas (see the documentation of your library). A third thing to do is to minimize the complexity of your rendering. Far objects do not need to be rendered with a detailed texture. Fast moving objects do not need to be anti-aliased. Composite backgrounds do not need to be recreated each frame (create a snapshot of the background and draw this snapshot). Disable blending for opaque images (if possible) and minimize the transparent areas. Allow your players to disable some special effects (e.g. fog, water reflection, shadows, etc.). There are other optimizations but, if you apply these first three, you should gain many fps.
I talked about the common case where the rendering has to be regular, but in a lot of games, like puzzle games, it’s not necessary and you should avoid soliciting constantly the CPU or the GPU for nothing. With libGDX, for example, you can stop the continuous rendering and raises the Render event only when needed. Your device battery will love it!
Mind the step
On a device, your application is not alone. There are services running, e.g. to check your mailbox regularly or to scan programs for viruses, and they can alter the flow of your game during short periods of time. You cannot do anything to prevent this, but you can adapt your game to these sudden changes by altering the time step. What’s this?
We saw in the previous chapter that you have only 16ms to do all your computations and render a scene. This span is not the time step. It’s just a timer interval and it will be impacted by a slowdown of your device. The time step is in fact a float value that defines the speed of your game. An example will be better than a long explanation:
Each time that you move an object in your game world, you increase or decrease its coordinates. If you do X = X + 2, two units are added whatever the elapsed time between two frames. If you introduce a time step, the formula becomes: X = X + (2 * TimeStep). With this time step, you can accelerate or decelerate your game at will, just by changing this value. Set it to the delta of time between two frames and your objects will move faster when the device is slowed. The player will see a constant move.
A real example:
Actor.X = Actor.X + Speed * lGdx.Graphics.DeltaTime
Sometimes the interval between two frames is very long (it’s the case when the antivirus of my phone checks the launched application) and the time step causes an issue called the tunnel effect. The step is so great that the move is really big, and your object can cross walls (it is moved directly on the other side). To avoid this, you should limit the value of your time step. Example:
DeltaTime = Min(lGdx.Graphics.DeltaTime, 0.05) 'max=50ms i.e. 20 fps