How to adapt HTML5 games to be published on Telegram

The Announcement

On October 3rd, Telegram published its gaming platform. Now, the bots are able to launch HTML5 games inside Telegram and share your score with your friends. @gamebot and @gamee were the two first bots announced and Ludei is not going to be the last when it comes to HTML5.

The Rules

When creating a game with @BotFather, he will make you accept a series of rules:

  1. You will not implement any ads or any external links on your custom URL pages.
  2. You will not invite users to pay for any services provided on custom URL pages.
  3. You will not use any data collected over the course of user interaction with your custom URLs for spamming Telegram users.
  4. You will not transfer any Telegram data collected over the course of user interaction with your custom URLs to any third parties.
  5. In order to protect the privacy of Telegram users, your custom URL pages must not set any cookies.

Now, those are some serious rules that made us change some things in our games.

Due to these rules we had to remove every way of promoting the rest of our games and every social media integration like Twitter or Facebook. Thankfully, we didn’t collect any information for third parties neither used cookies in our games -removing all that sounds like a headache.

Game Rules by @BotFather

Game rules by @BotFather

Sharing the games

The first thing you notice when reading the technical post is the script you have to include inside your games. It is a rather short script. This is because launching a game inside Telegram means opening a URL, provided by our bot, in the webview of Telegram. Our game should need hardly any changes.

<script src="https://telegram.org/js/games.js"></script>

This script provides us with the TelegramGameProxy.shareScore() method, which is used to share the game (but not a score, weird) with Telegram chats. It works like the usual share button in Telegram, there is no complexity with this part. We added a button with the shareScore() function at the result screen of each game in case you don’t know what to do with it.

Sending scores

The name “shareScore“ is quite confusing since you can’t even pass it a parameter to share. To actually send a score you should also read our post about a game bot creation. The game must send the score to the bot (with a HTTP POST, in our case) and the bot is responsible of notifying Telegram of our score. But how does the bot know who we are when it receives it? We solved this problem sending along the score a number of identifiers the bot previously added to the URL of the page. These identifiers are always userId (identifies the user) and either inlineId (if the bot was summoned with an inline call), or the pair chatId and messageId. The necessary ones are in the request Telegram sends to our bot when a user taps the play button.

Update scores in Telegram

@ludeiBot updates scores in Telegram

To sum it up: the user activates the play button, Telegram asks our bot for a URL to open and sends the identifications of the user, the bot returns a URL containing those identifiers in a query, and finally, our game can send the score and the identifiers of the user so the bot can update the rankings.

Detecting Telegram

At this point we have a game that can be published to Telegram, but why would we use two versions of the source code of our game? We still want to be able to promote our games if the user plays at ludei.net. If we can know when the game is executing as a Telegram game we can serve it from its original server to Telegram. Any special change we made for Telegram just has to follow a condition.

At first we tried checking window.TelegramWebviewProxy !== undefined  (like Telegram does in its script), but somehow TelegramWebviewProxy is undefined even inside the Telegram webview, so in the end, we just check if the URL contains the identifiers of Telegram our bot adds. In our case the identifiers were query parameters because our games already had a method to parse them. Although they could have been hash parameter too; Telegram exposes an object containing them.

Testing your game

The last thing to do is testing. Most of the testing can be done easily like with any web page development: set up a local server and open the URL in your devices. But we found some resolution problems when Telegram launched the games. Some elements appeared several times larger than when using a typical browser app. Therefore, one last advice from us is to create a minimal test bot, capable of just launching the games, enough to be able to fix any bug that might appear only in Telegram.

Our games

Check out our games (please open the following links on your mobile device):

Mobile GPU setbacks

We received a question in the forum that prompted us to write a detailed explanation of what things you should have in mind when developing games for mobile hardware.

Here’s the question by xero in our forum:

In earlier posts you mentioned to keep the number of canvases tight. However, it is a common technique in browsers to use multiple layered canvases to gain performance boost, since only some part of the content gets redrawn. What are you’re suggestions about that? Are CocoonJS canvases more expensive than browser ones? Will I benefit from having to redraw less stuff or will the overhead (memory, CPU?) of managing additional canvases by CocoonJS overweight all possible performance gains?

The first thing you must understand to develop for mobile platforms is those devices have very low performance compared to what you can expect in a modern PC. On the other hand, while most mobile browsers draw everything using the CPU, CocoonJS uses the GPU to draw shapes and images really fast, so the picture is more complex than it would initially appear. Mobile devices are slower, but we’re squeezing every last bit of performance out of them.

In this case, the performance optimization mentioned by xero is generally useful in run-off-the-mill browsers because blending several images in a canvas is pretty slow there. In CocoonJS however, image blending is done by the GPU, so it’s a lot cheaper.

The optimization does usually work, however, if you intend to draw lots of static images into a canvas, and then reuse that canvas during lots of frames. In effect, you’ll be creating a “cache” with that image to reuse it later. This can be very useful if you want to display, say, 500 objects which won’t change for a long time.

The thing is, drawing from that “cached” canvas to the final screen isn’t free either, so you must make sure the original draws are actually slower than drawing the “cache” canvas will be. For example, using a canvas to cache a simple static background probably won’t get you any performance improvement compared to just drawing the background itself.

More to the point, drawing full-screen canvases can easily exhaust what in GPU jargon is called “fill-rate”. Fill-rate, usually expressed in MPixels/s, is the speed at which a GPU can paint pixels. A mid-range GPU nowadays has about 500 MPixels/s of fill-rate. If you intend to run at 60fps, that means you get ~8.3 MPixels/frame. If your screen has a resolution of 1280 by 720 (pretty standard nowadays), each fullscreen canvas you draw takes 0.92 Mpixels of fill-rate. That means if you have 5 canvases you’re already exhausting more than half the fill-rate your GPU is capable of delivering.

Some devices have such a small fill-rate/screen-size ratio that you can only get about 2-3 fullscreen draws before you hit the GPU’s max drawing capacity. This is unfortunately a hardware limitation you’ll have to think about in your games if you want to deploy to low-power mobile devices.

So, to sum everything up, if you want to optimize your game you’re gonna have to know a bit about the hardware, test your assumptions and measure results. CocoonJS provides you with a powerful time profiling mechanism you can use to track how much CPU each part of your game is using.

In the case of the optimization mentioned by xero, how well it works will depend on how many canvases are used, how much work is saved by drawing to a canvas, how powerful is the hardware, and if fill-rate is actually the bottleneck for you! The only way to be sure is trying the technique in your game and measuring performance. Always measure!