In this tutorial, we'll cover how to create a YouTube Playables game with Phaser using our starter template and the YouTube Playables SDK. Playables are interactive games and experiences published to YouTube and playable through the YouTube app and website.

Getting Started

This tutorial assumes you are familiar with Node, npm, and GitHub.

First, check out the Phaser YouTube Playables Template. You can find this in our repository at:

https://github.com/phaserjs/template-youtube-playables

As this is a 'template' repository, you have the option on GitHub to 'Use this template', which will create a new repository under your own account, using our template as the source.

Or you can get it from Google's official 'web-game-samples' repository at:

https://github.com/google/web-game-samples

Once checked out locally, install the project dependencies by running npm install. If you used the Phaser repo, do this in the root project folder, or do it from the phaser folder if you used the Google repo.

You can start the development server by running npm run dev.

The local development server runs on http://localhost:8080 by default. Please see the Vite documentation if you wish to change this or add SSL support.

Once the server is running, you can edit any of the files in the src folder. Vite will automatically recompile your code and then reload the browser.

To run your game, go to the YouTube SDK Test Suite and enter http://localhost:8080 as the Game URL.

Template Project Structure

Our starter template provides a full sample game for you to get started with. We thought this would be a good way of showing you the most common SDK hooks. The directory structure is as follows:

  • index.html - A basic HTML page to contain the game.
  • src - Contains the game source code.
  • src/main.js - The main entry point. This contains the game configuration and starts the game.
  • src/YouTubePlayables.js - This is our optional YouTube Playables Helper Library.
  • src/scenes/ - The Phaser Scenes are in this folder.
  • src/gameobjects/ - The Phaser Game Objects are in this folder.
  • public/style.css - Some simple CSS rules to help with page layout.
  • public/assets - Contains the static assets used by the game.

YouTube Playables Helper Library

The YouTubePlayables.js file is our helper wrapper for the Playables SDK version 1.

Although the SDK is nice and easy to use, we found some common things we had to repeat doing, such as UTF-16 string checks or parsing JSON after a loadData call. So, we created this open-source wrapper to help get you going quickly.

You'll see it used throughout our sample game, but we have also included all of the native SDK calls as comments so you can swap between them as needed.

To use our helper, import it:

import { YouTubePlayables } from './YouTubePlayables';

The wrapper is not a class, and you don't need to instantiate it. You can import it and then access any of its functions directly.

This wrapper is also not specific to Phaser. It can be used with any game framework. Just copy the single file into your project, import it, and then use all of the functions outlined in the rest of this tutorial.

Game Boot

You need to delay your Phaser game instantiation so that it waits for both the document to load and the YouTube Playables SDK to be ready before starting. Failure to wait will fail in the YouTube Test Suite.

To do this, use the boot wrapper:

YouTubePlayables.boot(() => {

    new Phaser.Game(config);

});

Inside the boot handler, you can put all of your game configuration and then create the Phaser Game instance. For example, here is the boot handler for our template game:

YouTubePlayables.boot(() => {

    new Phaser.Game({
        type: Phaser.AUTO,
        width: 820,
        height: 1180,
        parent: 'gameParent',
        scale: {
            mode: Phaser.Scale.EXPAND
        },
        physics: {
            default: 'matter',
            matter: {
                positionIterations: 12,
                velocityIterations: 8,
                gravity: {
                    y: 1.6
                },
                debug: false
            }
        },
        scene: [
            Boot,
            Preloader,
            Background,
            MainMenu,
            Game,
            GameBackground,
            GameOver,
            UI
        ]
    });

});

You must do this even when testing your game in the YouTube Test Suite, so we recommend setting it up first.

First Frame Ready

YouTube uses a concept known as 'first frame ready'. This is a signal that you send to YouTube, only once, that indicates that the first rendered frame has been displayed within your game. That doesn't mean your game has started to be played, or has even finished loading, it just means it has rendered something meaningful to the player - for example, a 'Loading' screen.

To do this, you simply call the firstFrameReady method:

YouTubePlayables.firstFrameReady();

In our template, you will see that we do this at the start of the Preloader after we have added the loading progress bar on the screen. You can do it at any point, but it must be similar in scope.

Should you need to test if your game has sent this request, you can do the following:

YouTubePlayables.isFirstFrameReady();

Which will return a boolean.

Pause and Resume

Once the game has been created, it's a good idea to define handlers for the 'pause' and 'resume' events that YouTube emits. These can happen at any time and are invoked by the player interacting with the YouTube app or website, which may trigger them.

Phaser has the ability to pause and resume the whole game, which we will use in our callback handlers:

YouTubePlayables.setOnPause(() => {

    console.log('YouTube Playables API has requested game pause');

    this.game.pause();

});

YouTubePlayables.setOnResume(() => {

    console.log('YouTube Playables API has requested game resume');

    this.game.resume();

});

If you need more complex event handling then you can redefine the logic in these handlers as required, by simply setting them again.

Responding to Audio events

In addition to dealing with pause and resume, YouTube can request that your game mute or unmute all audio. Indeed, you should start your game by querying the YouTube SDK to see if you should play audio or not.

This is done via the isAudioEnabled call:

if (YouTubePlayables.isAudioEnabled())
{
    //  Enable audio for your game
}
else
{
    //  Disable audio for your game
}

You must set the audio change callback to respond to an audio event. This allows you to set a handler that is invoked when YouTube requests you change audio muting. The handler is sent a boolean parameter 'enabled' which defines if the audio should be enabled or not:

YouTubePlayables.setAudioChangeCallback((enabled) => {

    if (enabled)
    {
        //  Enable audio for your game
    }
    else
    {
        //  Disable audio for your game
    }

});

Phaser has the ability to globally mute and unmute all in-game audio, which you can use to handle these events, i.e.:

YouTubePlayables.setAudioChangeCallback((enabled) => {

    if (enabled)
    {
        this.sound.setMute(false);
    }
    else
    {
        this.sound.setMute(true);
    }

});

You can call setAudioChangeCallback multiple times. Each call will unset the previous callback before setting the new one.

If you want to unset the callback without setting a new one, call YouTubePlayables.unsetAudioChangeCallback().

Game Ready

While the 'first frame' event signifies that you have rendered something, the gameReady call signifies that your game is ready to be played.

That means that the game is now in a state where it's waiting for user interaction, such as making their first move or clicking a 'start' or level selection button.

YouTubePlayables.gameReady();

You don't have to have loaded all game assets when you call this. That could still happen after this event, but you are telling YouTube that the game is now ready to be interacted with immediately.

You should ensure that you have loaded any game data and queried the state of the audio before doing this.

Should you need to test if your game is in a ready state, you can do:

YouTubePlayables.isGameReady();

You can also check if everything is ready:

YouTubePlayables.isReady();

This call will return true if the YouTube environment is ready, the 'first frame ready' has been sent, and 'game ready' has been sent. If all of those conditions match, then you're truly ready for your game to start.

Testing if within YouTube

Once you have called the boot function and your game instance has been created, you can check to see if you're running within YouTube or not by calling:

YouTubePlayables.isLoaded();

This will return true only if the YouTube SDK is available and the game is running within the Playables environment, as it would be within the Test Suite, or if live on YouTube.

Sometimes, the SDK may be loaded, but you're simply not running in the Test Suite, in which case you can test this boolean:

if (YouTubePlayables.inPlayablesEnv) { ... }

Although most of the functions our library provides don't actually do anything if it isn't properly loaded, this check allows your game to do things like set 'fake data' or a language in the absence of the actual information from YouTube due to testing locally.

Localisation

YouTube provides a call that will tell you which language the user has set in YouTube:

const language = await YouTubePlayables.loadLanguage();

As you can see, we have used await here because this call returns a Promise. You can either await it or handle it when the promise is resolved.

The language comes back in BCP 47 format, i.e. en-US for US English, or ja-JP for Japanese. Using this allows you to localize any text content in your game.

You only need to call loadLanguage once. If you need to get the language at any point after this, you can call:

const language = YouTubePlayables.getLanguage();

If no language has been loaded, this will return the string unavailable.

Loading and Saving Data

The YouTube Playables SDK allows you to save and load game data. However, this is only available if the player is signed into YouTube and the saved data is specific to that player. If the player is not signed in, the Promise will still resolve; it will just do so with empty data.

To load any previously saved data, you can use the loadData function:

try
{
    const data = await YouTubePlayables.withTimeout(YouTubePlayables.loadData(), 1000);

    console.log('loadData() result', data);
}
catch (error)
{
    console.error(error);
}

We wrap it in our withTimeout function because when writing this, the YouTube Test Suite has an issue with games running in development mode using HMR, or Hot Module Replacement, that often triggers this promise to fail if not given enough time to resolve. Through testing, we found that a 1000ms delay was sufficient enough to get past this.

When using our loadData function, the data returns as a standard JavaScript object. If using the SDK directly, it comes back as a string, which you should then JSON parse yourself, assuming it isn't empty.

You can store all kinds of data in this object. For example, completed levels, game progress, timers for an idle game, etc. To save the data, call the saveData function:

const gameData = {
    // your game data goes here
};

//  Save the data to YouTube
YouTubePlayables.saveData(gameData);

If using the SDK directly, you need to format your data using a valid UTF-16 string with no lone surrogates. Failure to do so will result in an error when saving.

Our wrapper function will perform these checks for you, but if you want to do it directly, you can use our hasLoneSurrogates function to check the data first. If it fails the check, you should string replace the lone surrogates out of the string, leaving clean UTF-16.

Saving a Score

YouTube also has the concept of saving a game 'score'.

The score should reflect a single aspect of progress within your game. If your game tracks multiple aspects, you need to choose one to remain consistent and be saved. For example, if your game tracks both distance and coins collected, you must decide which to send as the primary score. Scores will be ranked, with the highest shown in the YouTube UI. Ensure any high score display in-game aligns with the data you send through this API.

YouTubePlayables.sendScore(3400);

In the example above, we send the value '3400' as our score. The score must be an integer, less than or equal to the max safe integer value, which, let's face it, is large enough for any game score.

At the time of writing, the YouTube SDK can't retrieve scores or a leaderboard. The scores are displayed in the YouTube UI, outside your game.

Logging Errors and Warnings

You can log both errors:

YouTubePlayables.logError();

And warnings:

YouTubePlayables.logWarning();

The logging is described on the YouTube site as 'best effort' and 'rate limited'. At present, it doesn't appear you can send any data to either of these calls. It also doesn't surface in the log in the Test Suite. As such, it's of limited use in practice at the moment.

Using the Template with the Test Suite

You can launch the template game in 'dev' mode. This uses Vite locally for serving and launches it on http://localhost:8080. See the Vite documentation and config if you need to change this. Use the command npm run dev to start the dev build.

To run your game in the YouTube SDK Test Suite, go to https://developers.google.com/youtube/gaming/playables/test_suite and enter http://localhost:8080 as the Game URL. If you use our template Basketball Shoot out template game, it should look like this:

Clicking the game starts it, and you can follow the output in the SDK Events window. You can also simulate YouTube app interactions, such as pausing the game, toggling audio, rotating the device, or entering full-screen mode to check how your game reacts.

Once the game is complete, the command npm run build will do a final bundle and save it in the dist folder, which can be uploaded to the YouTube staging site.

Further Reading

After getting our template, the best place to start is with the documentation for the YouTube Playables API v1.

YouTube also lists publishing requirements that span accessibility, design, integration, monetization, privacy, data, stability, and more.

You should ensure your game passes all of these requirements. Some are 'MUST' pass requirements, and some are 'SHOULD', but the more you pass, the better.

Find the full list on the certification pages.

Once your game implements their SDK properly and adheres to all of their requirements, use the Playables Interest Form to submit your game for consideration. At the time of writing, the platform is curated and moderated by YouTube directly.