How it works
Your game is the guest; the PlayVlay app is the host. The host runs the lifecycle (init → start → pause/resume → restart) and owns all the chrome. The guest only:
- calls
PlayVlay.ready()once it's loaded, - reacts to
onStart/onPause/onResume/onRestart, - calls
PlayVlay.reportScore(n)as the score changes, and - calls
PlayVlay.gameOver(n)when the run ends.
You do not add a <script src> for the SDK — the app
injects window.PlayVlay automatically when your game is uploaded. Just
call the methods. The starter guards every call with if (window.PlayVlay)
so it also runs when opened directly in a browser.
The contract
Host → your game
| Method | When |
|---|---|
PlayVlay.onInit(fn) | Once, before start. fn(ctx) gets { level, difficultyParams, muted, seed }. |
PlayVlay.onStart(fn) | Begin the run. |
PlayVlay.onPause(fn) / onResume(fn) | Freeze / unfreeze. |
PlayVlay.onRestart(fn) | Reset and start again. |
PlayVlay.onSetMuted(fn) | Mute / unmute audio. fn(muted). |
Your game → host
| Method | When |
|---|---|
PlayVlay.ready() | Required. After registering handlers — tells the host it can start you. |
PlayVlay.reportScore(n) | Required. Whenever the score changes (plain integer). |
PlayVlay.gameOver(n) | Required. Exactly once when the run ends. |
PlayVlay.reportHighScore(n) | Optional. Submit a high-score attempt WITHOUT ending the run. Host records it against the user's best and the game keeps playing. Safe to call repeatedly (endless / continuous games). |
PlayVlay.reachedLevel(n) | Optional. Tell the host the player unlocked Level n without ending the run. Advances the user's per-game Rank (drives "Play Next Level" and the Level badge). |
PlayVlay.haptic('success') | Optional juice — the host owns the device. |
PlayVlay.track(name, props) | Optional analytics event. |
Rules for a self-contained game
One .html file
Everything inline — markup, CSS, JS. Max 2 MB.
No network
Submitted games run sandboxed (strict CSP). Remote <script src>, fetch, and CDNs are blocked — inline your engine/assets.
Inline assets
Use data URLs / base64 for images & audio, or draw with canvas.
Report score & end
Call reportScore and gameOver — the app can't read your variables.
Tilt / gyroscope
Tilt-based games work out of the box. Listen for the standard browser events — the app grants permission automatically and pipes device motion samples in from native sensors (WebViews on iOS otherwise block motion access for third-party apps, so this bridge is required).
- Listen for
deviceorientation—event.alpha / beta / gammain degrees. - Listen for
devicemotion—event.acceleration,accelerationIncludingGravity,rotationRate. - Do not gate on
DeviceOrientationEvent.requestPermission(); it resolves to"granted"automatically. - Samples arrive at ~30 Hz while the run is active and stop when the user pauses.
Player & rival photos
Every uploaded game gets a ⚙ Settings gear in the feed. Players can upload their own photo plus up to 5 rival photos and rival names — your game reads them from these globals and paints them onto its characters:
| Global | Payload |
|---|---|
window.__PV_AVATAR(uri) | Data-URL for the current player's face. |
window.__PV_ENEMY(index, uri) | Data-URL for rival index (0..N-1). |
window.__PV_ENEMY_NAMES(names) | Array of strings — rival display names. |
The host calls these whenever the values change (initial load and after the
user saves in Settings). Implement them as functions on window;
the last value received is the current one — cache it and redraw on your
next frame. If your game doesn't use these, leave them undefined and the
settings rows still work.
Safe layout
Feed chrome floats over your canvas: a level badge and plays counter across the top, an action rail (share / play / more) on the right, and a settings gear at the bottom-left. Keep interactive gameplay UI inside a safe rectangle:
- ~44 px reserved at the top (level badge + plays chip).
- ~64 px reserved on the right (action rail).
- ~72 px reserved at the bottom (gear + home indicator).
Read window.innerWidth / window.innerHeight and
size your playfield inside that box — the host already clips your canvas
to a safe region, but centering your logic on the safe rect prevents
buttons from overlapping our overlays.
Starter template
A minimal tap-to-score game wired to the contract. Copy it, open it in a browser to test, then upload it from the Create tab in the app. (The app's “Download starter template” button gives you this same file.)
Submitting
- Open the Create tab in the PlayVlay app (curated creators only).
- Tap Upload and choose your
.htmlfile. - We validate it and auto-add the SDK. Preview it full-screen to test.
- Tap Submit for review. An admin approves it before it goes live in the feed.