Andy Hadlington is a freelance interactive developer based in the United Kingdom. Once Andy took a dive into mobile development, he created his first 2D top down racer game – Turbo Sprint – with Corona SDK. In his guest piece, Andy breaks down several commonly asked questions about Turbo Sprint.
I’m Andy Hadlington, a freelance interactive developer based in Bristol, UK. My main ‘bread and butter’ work involves developing content for the online gaming/casino sector using Flash and ActionScript but in my (very rare!) spare time, I like to develop small mobile apps.
At first, I thought I’d leverage my existing experience/tools (i.e. Flash/AS3) to develop an app, but I soon became disappointed with the performance of the iOS packager in Flash, so I started to look for other alternatives and I discovered Corona SDK.
After some experimenting, I realized that it was a powerful and relatively easy to develop apps that would rival native apps for mobile, so I decided to start work on the type of game that I’d like to play myself, so I started to develop a 2D top down racer called Turbo Sprint.
This is a quick overview of the main components of the game which over the last few months I’ve been asked about. It’s by no means exhaustive but I did learn quite a lot during the project so would like to share some snippets of information and techniques I learnt with the community. If you have any more questions, feel free to ask!
Track And Level Design
Obviously, a racing game needs tracks and the way I approached this was to design track tiles and place them in a level using the fantastic Level Helper and Sprite Helper tools. This allowed me to create a tile-set and arrange them in a grid to create the basic track layout. This did limit the track directions to a simple N, E, S and W appearance (i.e. no diagonals) but I felt that it was ok and fitted with the style of game I wanted to create. This literally saved me HOURS of development work as Level Helper provides a very simple API that allows you to load a level in just one line of code. If I wasn’t happy, I switched back to Level Helper to adjust the track, save it off, and then try it again. Fantastic!
We need cars right? Of course we do. Again, I used Sprite Helper to create a sprite sheet containing the car sprites (as well as other trackside furniture that racing games have). It was a simple matter of adding a physics hull to each object via Sprite Helper and then placing them on the track via the Level Editor. I could also add other objects (true stacks, trees etc.) to the level, save it off, and was good to go.
Although it was relatively straightforward to get a track and physics objects on the screen, it was necessary to write custom code for the cars, track logic and more. To do this was a matter of grabbing the objects from the level after it was loaded and then injecting the necessary code into them.
Objects within the level can be referenced by name of course, but the nice feature was that they can be tagged by type. For example, the landscape tiles were tagged ‘LANDSCAPE’, car object ‘CARS’, trees ‘TREES’ … you get the picture I’m sure. This made it simple to grab these objects from the level and skip through them injecting the necessary code. For example, it would be wasteful to draw every landscape tile if it wasn’t on screen, so a simple ‘am I on-screen’ check was injected into each object tagged ‘LANDSCAPE’ which was called every frame to make the tile visible/invisible depending on the current player car position. At first I thought this may be a performance killer as some of the tracks had upwards of 40 tiles, and although I threw away tiles I knew couldn’t possibly be on screen, I was doing this every frame. I was wrong; Corona SDK ate it up and demanded more. I had plenty of power available for the other stuff.
Similarly, an ‘enter frame’ method was created for each car. All cars shared the same code (both player and CPU) and this handled the cards behaviour under acceleration, steering and processed the maths behind the skidding and general car handling (which aren’t as difficult was they sound!). I will endeavour to post a more in-depth explanation of this at a later date – that is, if anyone is interested!
Collisions were handled using the in-built physics with event listeners added (via the Level Helper API) to collisions between certain types of objects. For example, car to car would play a crashing sound, car to power-up would process the power up and so forth. All very simple.
Controls and AI
The player car was controlled by on screen input (left/right/accelerate), which were passed to the ‘enter frame’ function of the players car, but obviously, the CPU cars needed to be able to navigate around the track somehow. This is where Level Helper came to the rescue as it allows the creation of Bezier curves on the level, so it was a simple process of drawing the ‘racing line’ on top of each track and then via code, grabbing the line from the level and querying the line points. Every few seconds, the CPU car was told to head towards a line point and when it got close enough, move to the next, etc. This proved to be very effective.
Also, each car had a different max speed and acceleration parameter that ensured that they were always sufficiently spread out along the track during the race.
Who’s in the lead?
By using this Bezier line data, it was also possible to create a list of invisible polygons, all numbered from 1 to 20, each encapsulating the track into ‘segments’ (around 20 segments per track was optimal).
Before each race, this data was pre-processed to create each segment polygon. These were then tagged with its distance around the track (in pixels) e.g.:
poly.number = 5 â segment index
poly.tl = x = -100, y = 100 ;
poly.tr = x = 100, y = 100 ;
poly.bl = x = -100, y = -100 ;
poly.br = x = 100, y = 100 ;
poly.distance = 280; â distance around track (pixels)
Then around 5 times per second (it wasn’t necessary to perform this check every frame), each car checks to see what segment it’s in by checking each polygon in this list. After calculating this segment number, we then query its pre-calculated distance to get a rough value of where the car is around the circuit.
However, this value is a little coarse, as positional information needs to be fairly exact, especially when cars are overtaking each other constantly, so we then calculate how far the car is along the current segment and then add that to the original segment distance. By storing the total distance travelled values in each car, and dividing these values by length of the track, we can them simply compare them to decide what position each car is in. For example, a car distance of 0.5 means that it’s travelled halfway around the first lap. A car distance of 1.5 means it’s half way through its 2nd lap and a car distance of 3.9 means it’s on its 4th lap, closing in on its 5th lap.
Example car positions using a track length of 500:
|Car number||Distance Travelled||Position||Lap Value|
|Car 1||1578.3||#2||3.1566 – Lap 4|
|Car 2||1976.2||#1||3.1566 – Lap 4|
|Car 3||1456.7||#3||2.9134 – Lap 3|
|Car 4||1256.9||#4||2.5138 – Lap 3|
|Car 5||1000||#5||2.0 – Just started Lap 3|
Aw, you cheat.
This method was also useful for controlling any non-standard driving on the players part (going the wrong way, detecting short cuts etc.). During each segment check, the segment number had increased, then the car is moving the correct way around the track. However, if the car had moved to a segment with a lower value, then the car was obviously going the wrong way so a message was flashed to the player. However, if the car had moved from the last to the first segment, then the car has completed a lap.
To detect the player attempting short cuts, it was necessary to keep track of how many segments the player has driven though during each lap. When the player completes a lap, if it was found that he was still due to pass through segments (e.g. segments_left > 0) then we can safely assume he’s taken a short cut and flash the appropriate message. This could be achieved in a similar way by using checking the total distance the car has travelled in the current lap against the lap length, but I went for the segment check (as it seemed easier).
Facebook and Game Center integration was a lot simpler than I imagined too (so much so, that I left it until late in the game’s development as I really didn’t want to face it!). However I was pleasantly surprised and after a few questions posted on the forums, I was soon pointed in the right direction. Indeed, one of the great things about the platform is Corona SDK’s community (thanks guys and gals!).
There are probably numerous other ways of creating a racing game and its associated logic. The way I describe here is purely a personal preference but I thought I’d share it anyway. The important thing I want to put across is speed of development that can be attained using Corona SDK and Lua. I managed to get a player controlled car and CPU cars on screen within the first few hours of development. Also, bear in mind that although I’m an experienced coder, I was completely new to Corona SDK and Lua, so I imagine that I’ll develop my future Corona SDK projects even more quickly.
Coming from a Flash/AS3 background, I found Corona SDK and Lua to be extremely simple yet remarkably powerful. The techniques I’ve described above are relatively easy to implement due to the fantastic performance and dynamic, flexible nature of the SDK. I’m also a great fan of the speed in which you can edit and test code. All changes are viewable virtually instantly using the simulator, which I found incredibly useful, especially when fine-tuning the player car physics towards the end of the project. The built-in physics is great plus too.
All in all. Corona SDK is a major weapon in my arsenal of game making tools these days. A veritable WMD!