Airdrop — Technical casestudy.

This article was written by Luigi De Rosa and Thierry Michel.


We recently developed a Facebook Instant Game for Red Bull, to promote their AirDrop media campaign for the worldwide launch of a new range of soft drinks.

This was an intensive and challenging project for which we had to get technically creative…so we thought we’d share our experience

The game we decided to build is an infinite “sky chaser” type of game. Basically, you are the pilot of a hot-air balloon that has to grab as many Organics(tm) cans as possible and drop them at specific spots to earn points. The gameplay is enhanced by adding several bonuses, special game mechanics and social features.

Following up are some of the techniques we’ve used for the development of this project.


Rendering

We used Three.js for all the 3D scene and pixi.js for all the 2D, UI, game HUD and particles (made with PIXI particles).

With the most recent versions of both frameworks it’s really easy to share the same WebGL context and thus improving performance.

Three.js and pixi.js sharing the same WebGL context

In many cases, overlaying HTML/CSS on top of canvas will result in performance drops due how the browser handles the compositing phase. We couldn’t afford anything of the sort, hence the decision to only use WebGL rendering while playing.

Steps to achieve the final looking

It’s important to note that there are no lights in the scene. All the fake light is brought by a spherical reflection map using a matcap texture to improve the performance of the game even more.

256x256px matcap used for the lightning
Using vertex position and normal, calculate the color using the matcap

The final color is obtained by multiplying the diffuse texture with the matcap lookup, creating a much more polished look.


Game engine

The game / rendering engine decoupling had worked rather well for our last development…and you know what they say about never changing a winning team.

So we applied to the same pattern for this game. This allowed us to test/validate the logic without worrying about the rendering (and vice versa).

The game play being a bit simpler this time (see UPS Delivery Day), we took some time to optimise the collision detection system a little.

The idea was to avoid using a library (collision, physics, …) and still end up with a light code in the end. As each object maps to a disc evolving on a 2D plan, the game didn’t require a huge amount of precision. A simple formula was therefore sufficient to detect collisions.

The simplest of the collision detections!

As the game engine directly manages the collisions, they could be tested way before the objects were actually rendered

Game collision testing.

Nevertheless, if we didn’t want it to quickly become a living hell in terms of debugging, an exact correlation between the 2D and 3D universes (game/rendering engine) was necessary. We handled that aspect of the project by creating a specific orthographic camera.

From 2D to 3D with an orthographic camera.

The game engine thus manages the movements according to the same principle (this is possible only because there is no difference of altitude between the hot air balloon and the other objects).

With the help of two small functions (worldToGame and gameToWorld), we could easily go back and forth between both worlds…

Our portal between 2D and 3D worlds…

3D assets

We wanted to go for a low-poly look and feel for this game; playful but modern looking enough for the game’s audience and RedBull’s tone of voice.

Blender allows to work with Flat or Smooth shading mode

If you work with flat looking shadings in Blender, it automatically adds extra vertices to your mesh when exporting. Blender does that so that the normals are not “smoothed” when passed to the fragment shader.

To keep the file size to a minimum, we exported the assets with the settings set to “Smooth”, without normals. Doing so, our final assets consists in just two buffers: positions and UVs.

We then approximated the normals in our fragment shader using the WebGL extension OES_standard_derivatives.

Normal approximation on fragment shader

Doing so, we were able to shave off around 70% on the 3D assets and reduce the total vertex count.


Texture

Facebook requires Instant games to load within 5 seconds, so it was crucial to keep the whole build size to a strict minimum. The main issue were the textures for the terrain.

Painting each terrain tile was not a viable option for us as it would have resulted in huge texture files. So we decided to use gradient squares instead, and to map the UVs on top of them.

The uvs are mapped to gradients

This did not only allowed us to save in terms of texture size, but also some interesting scenarios.

For example, if the UV coordinates are between a given range, they can be identified as water and we can animate them as such.

Cheap water/ripple effect

This is the final 2048x2048px texture we ended up using in production. We actually still have a lot of available space.

The final texture (2048x2048)

Infinite World

To achieve the infinite globe looking effect we split our terrain into nine different tiles. We first tried modelling our tiles curved around a sphere, with the origin point at the center.

First test modelling the tiles as curved elements.

The idea was to have them rotating around the sphere. We soon realised the limitations of such method: first of all, it would have been quite complicated to model the tiles as such; and we would have ended up with a limited number of tiles.

We scratched that idea and decided to go for a different approach.

We modelled our tiles as flat items, then we “bent” them in the vertex shader. This simplified the 3D modelling, but also gave us the opportunity to fine-tune and adjust the bending of the tiles during the whole development process.

We used glslify to split our GLSL code into different files and functions. This allowed us to reuse the bending not only for the terrain, but also for the “flying objects”. That’s how they are able to follow the same curve perfectly.

As soon as a tile goes behind the camera, it’s marked as free, and it’s eligible for being used again. In the final version we applied a slight fog, so that the user doesn’t see the tile immediately. To avoid too much “repetition”, we used a little trick: when a new tile is being added, there is a 50% chance that it will be flipped.

Tile based terrain

Mist effect

During the game, if you run into a “cloud”, your screen starts filling up with mist. You can either wait for it to disappear or clean it off by swiping furiously on your screen.

To achieve this feature, we generated a trail effect using an offscreen scene rendered into a frame buffer object. We then processed it the post processing pass together with some brightness adjustments, blur, a little noise and refraction using a normal map.

We built a custom post processing module using a triangle instead of the classic fullscreen-quad, which seems to bring some performance benefits.

Trail FBO (128x128px) as UnsignedByteType

The trail FBO is created as UnsignedByteType due the lack of OES_texture_float on Samsung devices.

Since we don’t need much precision the texture is relatively small (128px) with LinearFilter, so that is “smoothed out” when scaled and applied in our post processing.

Trail FBO + Scene FBO + Normal map drops = final result

Adaptative quality

The minimum requirements for a Facebook Instant Game are:

  • iOS 8 and above
  • Android 5.0 and above

Which means a very broad range of devices, from low to high end. It was crucial to have a smooth frame rate on all those devices while preserving good quality on high end devices.

We found some help to achieve that thanks to a very interesting WebGL extension called WEBGL_debug_renderer_info which gives debug information about the video card being used.

Fingerprint the video card

The extension is widely supported, so we could use it to do GPU sniffing. All we needed to do was to find some online mobile GPU benchmarks.

Mobile GPU benchmarks from notebookcheck.net

Looking at those benchmarks, it looks clear that in most cases, between the same series/models, bigger the number, the better the GPU. Applying a series of RegExp on the chipset name we were able to compute a “score” of the user’s GPU.

In this case we applied the quality adaptation on mobile only, and our settings look something like this:

Adaptive settings on startup, depending on the device

During the QA phase those values were adjusted and fine-tuned to make sure that the game was running smoothly on every device.

QA performed by our friends at Testology in London

Social + Facebook

This was part of the initial briefing: creating a game for Facebook’s Instant Game platform.

Great, something new!

We quickly understood that it’s all about using web technologies (HTML / JS / WebGL) and, even if we didn’t have access to the full documentation (early access), we are quite confident about the feasibility of it all.

We eventually got access to 100% of the documentation and to the “Instant Game Developer Community” private group (by far the one that makes the most noise in my timeline 😉), and we started digging in: a little tic-tac-toe demo, interesting “guides” and a well documented SDK… we were ready to roll!

The onboarding was quite simple.. It’s all about asynchronous and wild promises! 😍

The biggest benefit for us was the easy management of the leaderboards. The possibility to add the score of the “next friend to shoot” directly in the interface via `getConnectedPlayerEntriesAsync()`, seemed like a very stimulating feature..

The management of data/stats by user also greatly simplified the implementation of “bonuses” and “gifts”.

Finally, the `payload` system via `getEntryPointData()` allowed us to easily integrate the behaviors related to the bot messages.

Bot messages are key in terms of reengagement!

The fact that all sharing features are fully integrated and that the analytics dashboard is pretty complete is also noteworthy (but we didn’t expect anything less from Facebook) 🤔

Even if we ran across some limitations, it was a very good experience overall. Especially since we could count on the creative, strategic and technical support of a dedicated Facebook team. This gave rise to some epic conference calls between Austria, United Kingdom, Germany, Italy and… Belgium.


Consideration

“Holy here we go again!”

For the younger crowd (or not …), I invite you to discover this nifty little `npm` module: exclamations.

Or “how to spice up your logs with Robin (from Batman) and his legendary expressions!”

Total build size: 4.7mb

Play here