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.
With the most recent versions of both frameworks it’s really easy to share the same WebGL context and thus improving performance.
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.
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.
The final color is obtained by multiplying the diffuse texture with the matcap lookup, creating a much more polished look.
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.
As the game engine directly manages the collisions, they could be tested way before the objects were actually rendered
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.
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…
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.
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.
Doing so, we were able to shave off around 70% on the 3D assets and reduce the total vertex count.
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.
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.
This is the final 2048x2048px texture we ended up using in production. We actually still have a lot of available space.
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.
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.
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.
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.
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.
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.
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:
During the QA phase those values were adjusted and fine-tuned to make sure that the game was running smoothly on every device.
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.
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.