Repository navigation
CrazyGames
CrazyGames is a website of browser games, played on desktop and mobile and in the CrazyGames app. Developers submit their games through the Developer Portal. A new game first gets a Basic Launch: it is shown to a small share of players for 7 to 21 days, with ads off and the SDK optional. Games that do well then get a Full Launch, which needs the SDK and turns ads and the revenue share on.
This guide covers melonJS 20.15 or later, for save.setStorage(), with CrazyGames' HTML5 SDK v3. See Platforms for the pattern shared with the other portals.
Load the SDK in index.html, before the game:
<script src="https://sdk.crazygames.com/crazygames-sdk-v3.js"></script>It defines window.CrazyGames.SDK, which can't be used until init() resolves. What it does then depends on where the game runs, and sdk.environment says which:
-
"crazygames": on CrazyGames. -
"local": onlocalhostand127.0.0.1. No ads (a text stands in for each one, for 5 seconds), and every call is logged in the console. Add?useLocalSdk=trueto the address to get it elsewhere. -
"disabled": anywhere else.init()resolves after about 7 seconds, then the SDK's calls throwsdkDisabled("CrazySDK is disabled on this domain"). A build that also runs on other sites checkssdk.environmentbefore using it.
import { Application, audio, loader, save, state, video } from "melonjs";
import { PlayScreen } from "./screens/play.js";
import { resources } from "./resources.js";
const sdk = window.CrazyGames.SDK;
await sdk.init();
const app = new Application(1280, 720, { parent: "screen", scaleMethod: "fit", renderer: video.AUTO });
await app.init();
// melonJS's save, kept by CrazyGames
save.setStorage(sdk.data);
// CrazyGames' mute wins over the game's own sound
const followMute = ({ muteAudio }) => (muteAudio ? audio.muteAll() : audio.unmuteAll());
followMute(sdk.game.settings);
sdk.game.addSettingsChangeListener(followMute);
sdk.game.loadingStart();
await loader.preload(resources);
sdk.game.loadingStop();
state.set(state.PLAY, new PlayScreen());
state.change(state.PLAY, true);The sections below explain each part.
sdk.game.gameplayStart() when the player starts playing, or plays again after a break. sdk.game.gameplayStop() at every break: a menu, the end of a level, the game's own pause. A Full Launch requires them.
// in the play screen's onResetEvent()
sdk.game.gameplayStart();
// at a break, such as the end of a level
sdk.game.gameplayStop();Don't call gameplayStop() when the player switches to another tab: CrazyGames handles that itself. melonJS's own pause on blur stays as it is: there's no need for device.setFocusEvents(false) here.
loadingStart() and loadingStop(), around the loader as above, let CrazyGames measure how long the game takes to load.
While an ad plays, the game must be paused and silent, and its buttons must not answer. All three must be undone when the ad finishes, or when it fails. One function does it for both kinds of ad:
import { audio, state, UIBaseElement } from "melonjs";
// play an ad with the game paused and silent: resolves true once it played
function showAd(type) {
return new Promise((resolve) => {
let mutedBefore; // set once the ad starts
let pausedBefore;
let focused;
const end = (played) => {
if (mutedBefore !== undefined) {
// a game already paused, by its pause menu say, stays paused
if (!pausedBefore) {
state.resume(true);
}
// unless CrazyGames' mute was turned on meanwhile
if (!mutedBefore && !sdk.game.settings.muteAudio) {
audio.unmuteAll();
}
focused?.focus();
}
resolve(played);
};
sdk.ad.requestAd(type, {
adStarted: () => {
mutedBefore = audio.muted();
pausedBefore = state.isPaused();
audio.muteAll();
state.pause(true);
// a paused game still takes key presses: Enter would press the focused button
focused = UIBaseElement.focusOwner;
focused?.blur();
},
adFinished: () => end(true),
adError: () => end(false),
});
});
}// between two levels
sdk.game.gameplayStop();
await showAd("midgame");
sdk.game.gameplayStart();
// a rewarded ad, from a button that says it shows one
if (await showAd("rewarded")) {
progress.stars += 1;
}-
state.pause(true)stops the game and its music,audio.muteAll()the rest. The game is muted when the ad starts, not when it is requested, as CrazyGames asks. -
adErroroften comes without an ad having started: none available (unfilled), one requested too soon (adCooldown), an ad blocker (adblock), or ads off (adsDisabled, as during a Basic Launch). Nothing was paused then, so nothing is undone, and the game carries on. - A tab switch during the ad doesn't resume the game under it: on focus, melonJS only resumes a pause it made itself.
- Show a midgame ad at a natural break, never during play or on a navigation button. Too soon after the last one, the SDK just skips it.
- Give a reward only on
adFinished, never onadError. The game must stay playable with an ad blocker, so don't leave a rewarded ad button there with no effect.
CrazyGames' Data module keeps the player's data: in their CrazyGames account when they are logged in, on every device they play on; in the browser otherwise, copied to their account when they log in. It has the same calls as localStorage, so melonJS's save takes it as it is:
save.setStorage(sdk.data); // once init() has resolvedThe game then just uses save (save.add(), then plain assignments).
- Turn on "Progress Save" when submitting the game, or the Data module is disabled.
- The data is limited to 1 MB.
- Without the Data module, CrazyGames backs up
localStorageby itself (its "automatic progress save"), except for games with in-game purchases. The Data module is the one CrazyGames recommends.
A Full Launch requires following the muteAudio setting, which wins over the game's own sound settings. The code in Starting the game does it: sdk.game.settings holds the setting, and the listener is called with the new settings when it changes. In the local environment it is always off.
The game must have English, and should use the player's language: sdk.user.systemInfo.locale gives it as a tag, such as "en-US".
The game runs in a frame on a page that scrolls, and the arrow keys and Space must not scroll that page. melonJS stops the browser's own action for the keys bound with input.bindKey() and, from melonJS 20.15.1, for the keys the UI's keyboard focus takes and for a button's bindKey key. On 20.15.0 the focus reads the arrow keys and Space without binding them, so bind them when the game uses it:
input.bindKey(input.KEY.UP, "up");
input.bindKey(input.KEY.DOWN, "down");
input.bindKey(input.KEY.LEFT, "left");
input.bindKey(input.KEY.RIGHT, "right");
input.bindKey(input.KEY.SPACE, "accept");Leave input.setPreventDefault() at its default, true: a key bound after input.setPreventDefault(false) scrolls the page.
The mouse wheel over the canvas doesn't scroll the page either, once melonJS listens to the pointer (UI widgets and pointer events do).
Also:
- Support the mouse and the keyboard, and touch if the game is offered on mobile.
- Don't add a fullscreen button: CrazyGames has its own. Escape leaves fullscreen, so don't use it in the game.
- On a French keyboard, W, A, S and D are Z, Q, S and D.
- On mobile,
user-select: noneon the page'sbodystops a long press from selecting text. - The game must be playable in landscape on desktop, and readable on a screen with a pixel ratio of 1. Supported orientations are set when submitting it.
- Up to 50 MB downloaded before the game can be played (20 MB to appear on the mobile home page), 250 MB in total, and 1500 files.
- Relative paths only:
base: "./"with Vite.
On localhost, the SDK runs in its local environment: build the game and serve the build (vite build then vite preview), and open it. Every call is logged in the console, such as Requesting gameplay start (local) or Set "me.save.level" = 2. Each ad is a text over the game for 5 seconds, then adFinished. The Data module keeps the save in localStorage.
The Developer Portal's preview, under "Submit a game", runs the game as CrazyGames will and checks the SDK integration, before you submit it.
- In the Developer Portal, submit the production build.
- Turn on "Progress Save" if the game saves through the Data module, and set the orientations it supports.
- The game goes through CrazyGames' review, then its Basic Launch.
The full list of requirements: Requirements.
| Symptom | Cause |
|---|---|
| the game's buttons answer Enter during an ad | a paused game still takes key presses: blur the focus while the ad plays, as showAd() above does |
| the arrow keys scroll the page around the game while they move the UI focus | melonJS 20.15.0 stopped the browser's action only for bound keys: update to 20.15.1, or bind them with input.bindKey(). On any version, a key bound after input.setPreventDefault(false) scrolls the page: leave it on |
sdkDisabled: "CrazySDK is disabled on this domain" |
the game runs somewhere other than CrazyGames or localhost: add ?useLocalSdk=true to test there |
dataModuleDisabled |
"Progress Save" is not turned on in the submission |
adError with "An ad request is already in progress" |
an ad was requested while another was playing |
| "Game data when converted to JSON cannot exceed 1048576 bytes. Data was not saved" | the save is over 1 MB |
- CrazyGames developer documentation, the HTML5 SDK and the requirements
- Platforms