Uniplay

Achievements#

Every game on play.unikia.es can award achievements. Achievements live in the player's browser: a game needs no account and no server for them. The Hub reads them to show a Steam-like overview across all games.

This page is the contract. The SDK, @unikia/upe-sdk, is the reference implementation; a game may copy it, port it, or write its own as long as it follows the rules below.

Why this works without a backend#

All games are served from one origin (https://play.unikia.es/<path>/), so they share one localStorage. A game writes its unlocks there and the Hub page at / reads them. Running a game on its own (for example vite dev on localhost:5173) still works; the unlocks just stay on that origin.

Game id#

Each game has a stable game id: id in its unihub.manifest.json, for example heated-heist. It never changes once published, even if the game is renamed or moves to another path.

Game Game id Path
Torture Chamber torture-chamber /torture-game/
Embers of Hollowmaw embers-of-hollowmaw /rpg-game/
Level Dice level-dice /dice-game/
Signal Fire signal-fire /metroidvania-game/
Heated Heist heated-heist /heist-game/
Re-Elect Me! re-elect-me /city-builder-game/
Demonic Maze demonic-maze /maze-game/
Orbion orbion /orbion/

Definitions: achievements.json#

Each game ships achievements.json at the root of its build, next to its index.html (in a Vite project: public/achievements.json), and names it as achievements in the manifest. The Hub embeds the definitions in its catalogue when the game is published, so it can list locked achievements of games the player has never opened. The JSON Schema achievements.v1.json holds these rules.

{
  "v": 1,
  "game": "heated-heist",
  "achievements": [
    {
      "id": "first-score",
      "title": "First Score",
      "description": "Escape with the loot from any house.",
      "icon": "💰",
      "points": 10
    },
    {
      "id": "career-criminal",
      "title": "Career Criminal",
      "description": "Steal $500,000 in total.",
      "icon": "🏦",
      "points": 25,
      "goal": 500000,
      "format": "money"
    }
  ]
}
Field Required Meaning
v yes Spec version, 1.
game yes The game id.
achievements[].id yes Stable id, a-z, 0-9 and -, unique within the game. Never reuse or rename a published id: players' unlocks are keyed by it.
title yes Short name, at most 40 characters.
description yes What to do, one sentence.
icon no One emoji. The Hub falls back to a trophy.
points no 5, 10, 25 or 50. Defaults to 10. Roughly: 5 = happens naturally, 10 = a normal goal, 25 = takes skill or time, 50 = mastery.
hidden no true hides the title and description as "???" until unlocked. Use it for spoilers and jokes, not for normal goals.
goal no Makes it a progress achievement that unlocks when the progress reaches this number.
format no How to print progress: "count" (default), "money" ($12,500) or "time" (seconds as m:ss). Only with a goal.

Order in the file is the display order. Removing an achievement is allowed (old unlocks are ignored); adding new ones at any time is expected.

With the SDK:

import { Uniplay } from '@unikia/upe-sdk';

const uniplay = new Uniplay({ game: 'heated-heist' });
uniplay.achievements.unlock('first-score');
uniplay.achievements.addProgress('career-criminal', 12500);
uniplay.achievements.setProgress('career-criminal', 182000);

Platinum#

A player who unlocks every achievement of a game earns its platinum: the game's badge on their profile, in colour instead of greyed out, and 100 more points in every points total the Hub shows. Give the game a badge with "badge" in its manifest; without one the Hub cuts a round badge from the cover. Adding achievements later takes platinum away again until they're unlocked too.

Unlocks: localStorage#

Key: uniplay:achievements:<game id>. Value: JSON.

{
  "v": 1,
  "unlocked": { "first-score": 1790000000000 },
  "progress": { "career-criminal": 182000 }
}

Each game writes only its own key. The Hub page only reads, and copies unlocks to the player's account and back when they're signed in, merging them the same way.

Unlock notification#

When an achievement unlocks, the game shows a short, non-blocking notification ("Achievement unlocked" + icon + title) for about 5 seconds. The SDK draws one in the top-right corner on top of the game, or in the Hub's alert corner when the game runs inside the Hub. A game may draw its own instead, in its own style, as long as it shows the title.

Live updates#

The Hub listens to the storage event, so unlocks made in another tab show up without a reload. Games do not need to do anything for this.