Postmortem: Adventure Drivers

Recommended Posts


Adventure Drivers is my second HTML5 game after Medieval Defense Z. 

My main work went into engine, tools, code and levels and the art was done by Gudo1.

I can share some of the details of what went into making this game. I also wrote a postmortem about my previous game Medieval Defense Z which you can check it here:


About Adventure Drivers
    - 60fps 2D racing game.
    - Written in Haxe.
    - Used Pixi v4, Howler and Nape Physics Engine.
    - Over 50k lines of code.

Visual Assets

All the visual assets were in flash SWF files and i needed to get sprites into spritesheets. Exporting hundreds of assets to PNG's by hand was too time consuming, so i wrote a command line tool in Adobe Air which takes assets from SWF files, applies transformations such as scale or rotation and exports all movie clips as images to a single graphics folder. The tool also creates XML file which has the animation data and anchor values for every image. Then i used TexturePacker command line tool to pack everything into a single spritesheet. For bitmap fonts i used Littera and packed font PNG's into the main spritesheet. After loading .fnt file i had loop over all chars and add x and y offsets based on the location of the font texture in the main spritesheet.

spritesheet1.png.ca755630c82e4772176039cfe20ecd35.png spritesheet2.png.fb8149621e83f1897cf94de6e44fe0bd.png

Audio Assets

Audacity and ffmpeg worked really well. Audacity for quick sound editing and ffmpeg for converting between various formats. Like in Medieval Defense Z i chose M4A for iOS and OGG for android and PC.

Batch script to convert all WAV files to OGG (64 bit rate):

    for %%i in (*.wav) do (
        ffmpeg -y -i "%%i" -codec:a libvorbis -qscale:a 4 -ac 1 "%%~ni.ogg"

Batch script to convert all WAV files to M4a (64 bit rate):

    for %%i in (*.wav) do (
        fdkaac -b 64k -o "%%~ni.m4a" "%%i"
Editing in Audacity: Fade In/Out, Amplify, Normalize, Change Speed and Equalization were the most useful tools/functions.


Level Editor

Used my own editor written in Action Script 3 which is basically a general purpose non-tile based 2D object placer with prefabs, grouping, undo/redo, etc. In order to create levels faster i also used Perlin Noise algorithm for terrain generation and procedurally placed decorations. After generating the level i still had to cleanup decorations, adjust terrain, add caves, place obstacles and items. 

For saving to files i wrote a simple NodeJS server in Haxe and used POST to send and receive data which then is saved to disk.



Nape Physics Engine was great until i needed to run my game on mobile... Dynamic polygons became a real drag on CPU and i needed to simplify geometry in order to have any gains in terms of performance. Weirdly enough the terrain size was not that influential as much as adding 1 more car.

Also there was this weird issue with inlined code. Inlined code ran SLOWER in chrome than non-inlined... It was especially noticeable then physics engine caused large lag spikes. I noticed the same problem in Medieval Defense Z and large functions. I could not find anything online, so i disabled inline and split some of the large functions into smaller ones.

Frameworks and Rendering

At first i used Phaser framework but the performance issues were never ending. The transform updates were a major bottleneck so i had to pretty much hack around and disable portions of the framework until i was just using basic Pixi sprites and groups. Thankfully i didn't use much of any other stuff from Phaser (no camera, no physics, etc.) which made it easier to transition to Pixi v4.

One of the biggest issues was the terrain drawing. Phaser does not have any equivalent for large mesh rendering with exception of using tiles and masks which are very slow. So i needed to write a complicated set of functions to cut the terrain into thousands of rectangles and add them to the stage during gameplay. After converting to Pixi v4 i simply used pixi Mesh object and EarCut library for cutting polygon into a triangles. This was about 10x simpler to implement and it improved the performance. For Canvas i still needed to hack around and use pattern fill in Phaser and in Pixi.



Different js sound libraries had a major difference in performance. Phaser sound library was quite good, however it did not have .rate property which i needed for the car engine sound, so i used 2 different engine sound loops with different rates and changed volume based on driving speed. I tried Pixi sound library but it had incredibly poor performance on mobile and was basically unusable even though this library had really nice features. Ended up with Howler which has the ability to set .rate and the performance was really good. Also Pixi v4 + Howler did not crash my old android device which was nice. :) 


The AI code can be broken down in 3 steps: data gathering, decisions and actions. 

In data gathering step the bot collects and processes information. For example: how long back and front wheels are on the ground or in air, how the rotation changed in the past second, average speed, etc.

Decisions are made in timed intervals and they depend on the "personality" of the car which i describe in 4 values: "ability", "aggression", "craziness" and "panic". The bot "decides" only if all conditions are satisfied. The decisions include: air rotation, ground rotation, random_rotation, random_jump, flip, wheelie, nitro, jump_over, jump_pickup and use_ability. For some of the decisions the bot also has to check nearby items and other cars.

The last step is "action" which simply means that the bot performs a function for a limited time unless some unexpected thing occurs. So the bot will stop the air flip action if any of the wheels interact with the ground.



I wanted to do something interesting with the backgrounds so i decided to try pseudo 2.5D background scrolling stuff to see how it goes. The waves looked really nice so we went with it.

If you want to do something similar, the function you are looking for is this:

    public static applyProjectionWithCamera(to_projection:Point, eye:Point3, camera:Point3, point:Point3):Point{
        to_projection.x = (eye.z * (point.x + camera.x - eye.x)) / (eye.z + (point.z + camera.z)) + eye.x;
        to_projection.y = (eye.z * (point.y + camera.y - eye.y)) / (eye.z + (point.z + camera.z)) + eye.y;
        return to_projection;
Eye has fixed x = 0, y = 0 and z = 800 values. Camera x, y, z values are tied to the game camera which is tied to the player position. Each sprite has 3 3D points (triangle) associated with it and these points are placed somewhere in the background with high z value (distance), varying x value (horizontal) and high y value (vertical) for the clouds. Then i calculate 2D points (projections) for every 3D point. And set x, y, width and height for the sprite based on the 3 projected points.


The Game

For now we are doing a limited release on few websites and the game should playable very soon.

If you found this post useful let me know. :) 


Share this post

Link to post
Share on other sites
4 hours ago, b10b said:

Great write-up, I enjoyed reading it and wish you every success with "Adventure Drivers".  May I ask how you combined Howler with the Pixi loader - was it straight forward and were Haxe externs readily available for Howler?

I am glad you liked it!

Both Pixi and Holwer do have Haxe externs, however I could not find any integration between these two libraries so i loaded assets separately. 

Share this post

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Recently Browsing   0 members

    No registered users viewing this page.