@hopple/sdk 0.0.0-stage → 0.1.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +729 -2
- package/config.schema.json +784 -0
- package/hopple-sdk.min.js +155 -0
- package/index.d.ts +3664 -0
- package/index.js +3585 -0
- package/manifest.md +281 -0
- package/package.json +30 -4
package/manifest.md
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# The game manifest
|
|
2
|
+
|
|
3
|
+
Every game has a manifest. It says what the game is and declares anything a
|
|
4
|
+
host needs to know **before** the game runs: its score boards, achievements,
|
|
5
|
+
analytics events, input actions, audio channels and so on.
|
|
6
|
+
|
|
7
|
+
The manifest is written in the game's project, as the `manifest` in its hopple
|
|
8
|
+
config (see [the CLI](https://docs.hopple.app/cli.md#the-projects-config)), and uploaded
|
|
9
|
+
beside the game's HTML file as `game.manifest.json`.
|
|
10
|
+
|
|
11
|
+
The host reads the manifest itself. The game never sends it over the bridge,
|
|
12
|
+
and the host never asks the game what it is allowed to do.
|
|
13
|
+
|
|
14
|
+
## A minimal manifest
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"schema": 1,
|
|
19
|
+
"id": "ping",
|
|
20
|
+
"version": "1.0.0",
|
|
21
|
+
"title": "Ping",
|
|
22
|
+
"orientation": "portrait",
|
|
23
|
+
"sdk": "^0.1.0",
|
|
24
|
+
"audio": null
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
These seven keys are required. Everything else is optional.
|
|
29
|
+
|
|
30
|
+
| Key | Rules | Meaning |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| `schema` | `1` | The manifest format version. |
|
|
33
|
+
| `id` | lowercase letters, digits and single hyphens; at most 64 characters | The game's name. On the server, a game is `<owner>/<id>`. |
|
|
34
|
+
| `version` | `MAJOR.MINOR.PATCH`, optional `-prerelease` | The game's own version. Shown to the game as `sdk.info.game.version`. |
|
|
35
|
+
| `title` | at most 60 characters | What players see. |
|
|
36
|
+
| `description` | optional; at most 500 characters | A sentence or two shown beside the title. |
|
|
37
|
+
| `orientation` | `portrait`, `landscape` or `any` | The shape the game is designed for. Hosts lay the game out to match. |
|
|
38
|
+
| `sdk` | a version, optionally with `^` or `~` | The SDK the game was built against. Recorded, not enforced; the protocol handshake is what checks compatibility. |
|
|
39
|
+
| `audio` | `null`, or an object | Whether the game makes sound, and its volume channels. See [`audio`](#audio). |
|
|
40
|
+
|
|
41
|
+
## Validating
|
|
42
|
+
|
|
43
|
+
`hopple check` validates the manifest and names every problem with its field.
|
|
44
|
+
The CLI, the dev shell, the server and the clients all use the same function,
|
|
45
|
+
so they always agree on whether a manifest is valid.
|
|
46
|
+
|
|
47
|
+
- **Unknown keys are allowed.** A game built with a newer SDK may declare
|
|
48
|
+
things an older host doesn't know about, and must still run there.
|
|
49
|
+
- **Known keys are checked strictly.** A misspelt value (such as
|
|
50
|
+
`"draw_under"`) is an error, not silently ignored.
|
|
51
|
+
|
|
52
|
+
### In an editor
|
|
53
|
+
|
|
54
|
+
`@hopple/sdk` ships the hopple config's JSON Schema, the manifest's included,
|
|
55
|
+
as `config.schema.json`, for completion, hover docs and errors as you type. A
|
|
56
|
+
JSON config can point at it:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{ "$schema": "./node_modules/@hopple/sdk/config.schema.json", "page": "dist/index.html", "manifest": { ... } }
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
A TypeScript config gets the same checks from its type, `HoppleConfig`.
|
|
63
|
+
|
|
64
|
+
A few rules relate one part of a manifest to another, which JSON Schema can't
|
|
65
|
+
express: buttons must name declared actions, a control's `as` must name its
|
|
66
|
+
own buttons, and tiers must be in order and short of the target. An editor
|
|
67
|
+
won't flag them, but `hopple check` will.
|
|
68
|
+
|
|
69
|
+
**Tip:** write the config in TypeScript, as `hopple create` sets up. Then a
|
|
70
|
+
mistake in the manifest is a type error, and your code and manifest can share
|
|
71
|
+
constants.
|
|
72
|
+
|
|
73
|
+
## Optional keys
|
|
74
|
+
|
|
75
|
+
Each SDK feature that needs the host to know something in advance has its own
|
|
76
|
+
top-level key. The sections below show each one; see
|
|
77
|
+
[the SDK's guide](https://docs.hopple.app/sdk.md) for how to use the feature from code.
|
|
78
|
+
|
|
79
|
+
### layout
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
"layout": { "insets": "draw-under" }
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
| `insets` | Meaning |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `reserve` (default) | The host shrinks the game's frame so none of the host's own buttons, or the device's notch, cover it. |
|
|
88
|
+
| `draw-under` | The frame fills the whole screen. The game reads `sdk.layout` and keeps its own important content clear of what covers it. |
|
|
89
|
+
|
|
90
|
+
This has to be in the manifest because the host sizes the frame before the
|
|
91
|
+
game has started.
|
|
92
|
+
|
|
93
|
+
### analytics
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
"analytics": {
|
|
97
|
+
"events": {
|
|
98
|
+
"level.complete": { "level": "number", "seconds": "number" },
|
|
99
|
+
"shop.open": {}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Declares every event name the game may send, and the type of each property:
|
|
105
|
+
`string`, `number` or `boolean`.
|
|
106
|
+
|
|
107
|
+
- An event whose name isn't declared is refused (and the game is told).
|
|
108
|
+
- A property of the wrong type is dropped; the event is kept.
|
|
109
|
+
- Every property is optional.
|
|
110
|
+
- Names use lowercase letters, digits, `.`, `_` and `-`. Names starting with
|
|
111
|
+
`hopple.` are reserved for the host's own records.
|
|
112
|
+
- Limits: 64 events, 8 properties per event, names up to 48 characters,
|
|
113
|
+
property names up to 24.
|
|
114
|
+
|
|
115
|
+
### save
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
"save": { "bytes": 2048 }
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Says the game keeps a save and roughly how big it will be (at most 1 MiB).
|
|
122
|
+
Hosts that load saves from a server use this to know which games to prefetch.
|
|
123
|
+
A game without this key can still use `sdk.save`.
|
|
124
|
+
|
|
125
|
+
### score
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
"score": {
|
|
129
|
+
"boards": {
|
|
130
|
+
"points": { "title": "High score", "direction": "desc" },
|
|
131
|
+
"speed": { "title": "Best time", "direction": "asc", "format": "duration-ms" }
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
| Field | Required | Meaning |
|
|
137
|
+
|---|---|---|
|
|
138
|
+
| `title` | yes | What players see. At most 40 characters. |
|
|
139
|
+
| `direction` | yes | `desc`: higher is better. `asc`: lower is better (times, strokes). |
|
|
140
|
+
| `format` | no | `integer` (default), `decimal`, or `duration-ms` (so `224310` shows as `3:44.31`). |
|
|
141
|
+
|
|
142
|
+
`boards` is a map even for one board, so a game can add a second one later
|
|
143
|
+
without changing the meaning of the first. At most 16 boards.
|
|
144
|
+
|
|
145
|
+
### achievements
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
"achievements": {
|
|
149
|
+
"list": {
|
|
150
|
+
"first-ping": { "title": "Hello there", "detail": "Press unlock, once", "icon": "👋" },
|
|
151
|
+
"centurion": {
|
|
152
|
+
"title": "Centurion",
|
|
153
|
+
"progress": {
|
|
154
|
+
"target": 100,
|
|
155
|
+
"tiers": [{ "at": 10, "title": "Bronze" }, { "at": 50, "title": "Silver" }]
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
"speedrun": {
|
|
159
|
+
"title": "Under two minutes",
|
|
160
|
+
"progress": { "target": 120000, "direction": "asc", "format": "duration-ms" }
|
|
161
|
+
},
|
|
162
|
+
"the-ending": { "title": "You found it", "secret": true }
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
There are two kinds:
|
|
168
|
+
|
|
169
|
+
- **Flags** (no `progress`): earned once, by `sdk.achievements.unlock(id)`.
|
|
170
|
+
- **Measured** (with `progress`): the game reports a value and the achievement
|
|
171
|
+
is earned when it reaches `target`. `direction` is `desc` (more is better,
|
|
172
|
+
the default) or `asc` (less is better). `format` works as for score boards.
|
|
173
|
+
`tiers` are named milestones on the way to the target; they are labels, not
|
|
174
|
+
separate achievements.
|
|
175
|
+
|
|
176
|
+
`secret: true` hides the title and detail from players until it is earned.
|
|
177
|
+
The manifest is public, so this only stops spoilers.
|
|
178
|
+
|
|
179
|
+
Limits: 128 achievements, titles up to 40 characters, details up to 120, icons
|
|
180
|
+
up to 8 characters, 8 tiers.
|
|
181
|
+
|
|
182
|
+
### input
|
|
183
|
+
|
|
184
|
+
```json
|
|
185
|
+
"input": {
|
|
186
|
+
"supported": [["touch"], ["gamepad"], ["keyboard", "mouse"]],
|
|
187
|
+
"actions": {
|
|
188
|
+
"shoot": { "bind": ["key.KeyZ", "pad.south"], "title": "Shoot", "label": "A" },
|
|
189
|
+
"special": { "bind": ["key.KeyC"], "title": "Special", "label": "C" },
|
|
190
|
+
"pause": { "bind": ["key.KeyP", "pad.start"], "title": "Pause" }
|
|
191
|
+
},
|
|
192
|
+
"controls": {
|
|
193
|
+
"play": {
|
|
194
|
+
"left": { "move": { "type": "thumbstick", "as": "pad.left.stick" } },
|
|
195
|
+
"right": { "acts": { "type": "buttons", "shape": "column", "buttons": ["shoot", "special"] } }
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
- **`supported`**: which input devices the game can be played with. Each entry
|
|
202
|
+
is one set of devices that together are enough. The example means: touch
|
|
203
|
+
alone, or a gamepad alone, or keyboard and mouse together. Devices are
|
|
204
|
+
`keyboard`, `mouse`, `touch` and `gamepad`. This is the author's claim and
|
|
205
|
+
is not checked against the bindings (a game may read input directly). A
|
|
206
|
+
host can use it, through `playableWith`, to decide whether to offer a game
|
|
207
|
+
to a player at all.
|
|
208
|
+
- **`actions`**: named buttons the game reacts to, and their default bindings.
|
|
209
|
+
`title` is shown on the host's key-binding screen; `label` is printed on the
|
|
210
|
+
on-screen button, and is what `sdk.input.mapping` calls the action on a touch
|
|
211
|
+
screen. Both are optional; `bind` is not.
|
|
212
|
+
- **`controls`**: named layouts of on-screen controls for touch screens. Each
|
|
213
|
+
layout places controls in zones (`left`, `right`, `full`). A control is
|
|
214
|
+
either a `thumbstick` (with `as` naming which stick it acts as, and an
|
|
215
|
+
optional `axis` of `x` or `y` holding it to one direction) or a set of
|
|
216
|
+
`buttons` (naming actions) arranged as a `row`, `column`, `diamond` or
|
|
217
|
+
`cluster`. An unknown shape is drawn as a column. A stick held to an axis is
|
|
218
|
+
drawn as a track and always reads 0 on the other axis; a real pad's stick
|
|
219
|
+
still reports both.
|
|
220
|
+
|
|
221
|
+
Binding names:
|
|
222
|
+
|
|
223
|
+
| Binding | Means |
|
|
224
|
+
|---|---|
|
|
225
|
+
| `key.KeyZ` | A physical key, by `KeyboardEvent.code`. `KeyA` is the same physical key on every keyboard layout. |
|
|
226
|
+
| `pointer.primary` | A left click or a tap. (`pointer.secondary`: a right click.) |
|
|
227
|
+
| `mouse.left` | A left click only, not a tap. Also `mouse.middle`, `mouse.right`. |
|
|
228
|
+
| `touch.tap` | A tap only, not a click. |
|
|
229
|
+
| `pad.south` | The bottom face button of either cluster, on any gamepad or on-screen pad. |
|
|
230
|
+
| `pad.right.south` | The bottom right face button (A on Xbox, cross on PlayStation). |
|
|
231
|
+
| `pad.left.south` | D-pad down. |
|
|
232
|
+
| `pad.left.bumper`, `pad.right.trigger` | Shoulder buttons. |
|
|
233
|
+
| `pad.start`, `pad.select` | The menu buttons. |
|
|
234
|
+
| `pad.left.stick` | The left thumbstick. Sticks are read with `sdk.input.onStick`, not bound to actions. |
|
|
235
|
+
| `controls.<name>` | An on-screen control with no physical equivalent. |
|
|
236
|
+
|
|
237
|
+
Positions use compass words (`north`, `south`, `east`, `west`) and clusters are
|
|
238
|
+
`left` and `right`, because controller brands disagree about which button is
|
|
239
|
+
"A". `Escape` can't be bound: hosts use it to leave fullscreen and pointer
|
|
240
|
+
lock. The controller's home button can't either, and holding `pad.start` opens
|
|
241
|
+
the host's sheet; a tap of Start is still the game's.
|
|
242
|
+
|
|
243
|
+
Limits: 64 actions, 8 bindings per action, 8 control layouts.
|
|
244
|
+
|
|
245
|
+
### audio
|
|
246
|
+
|
|
247
|
+
```json
|
|
248
|
+
"audio": {
|
|
249
|
+
"channels": {
|
|
250
|
+
"music": { "title": "Music" },
|
|
251
|
+
"effects": { "title": "Effects" }
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
`audio` is required. Set it to `null` for a game that makes no sound, and to an
|
|
257
|
+
object for one that does. `channels` is optional and may be empty: a game with
|
|
258
|
+
sound but no channels (`"audio": {}`) gets a mute button and a master volume,
|
|
259
|
+
and must still respect them.
|
|
260
|
+
|
|
261
|
+
Each channel gets its own labelled volume slider in the host's sound controls.
|
|
262
|
+
`title` is required: it is the slider's label. `master` is reserved, since
|
|
263
|
+
every host already has a master volume. At most 8 channels.
|
|
264
|
+
|
|
265
|
+
## Changing the manifest in a new version
|
|
266
|
+
|
|
267
|
+
The server refuses a new version whose manifest would corrupt data players
|
|
268
|
+
already have:
|
|
269
|
+
|
|
270
|
+
- **A score board that has scores can't be removed or change direction.**
|
|
271
|
+
Removing it would orphan the scores (and renaming a board looks like removing
|
|
272
|
+
it); flipping `direction` would turn the whole board upside down. An admin
|
|
273
|
+
can clear the board first if that is really what you want.
|
|
274
|
+
- **A published achievement can't be changed.** Its title, detail, icon, secret
|
|
275
|
+
flag, kind, target, direction, format and tiers are fixed, because players already
|
|
276
|
+
hold it. You can add new achievements, and you can stop declaring old ones
|
|
277
|
+
(what players earned is kept).
|
|
278
|
+
|
|
279
|
+
Input action ids and audio channel ids may change freely. A player's remapped
|
|
280
|
+
keys or volume settings for an id that no longer exists are simply dropped.
|
|
281
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hopple/sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0-rc.2",
|
|
4
|
+
"description": "The Hopple SDK: what a game imports to talk to the host that runs it.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://hopple.app",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"hopple",
|
|
9
|
+
"game",
|
|
10
|
+
"games",
|
|
11
|
+
"sdk",
|
|
12
|
+
"html5"
|
|
13
|
+
],
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"type": "module",
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./index.d.ts",
|
|
22
|
+
"default": "./index.js"
|
|
23
|
+
},
|
|
24
|
+
"./hopple-sdk.min.js": "./hopple-sdk.min.js",
|
|
25
|
+
"./config.schema.json": "./config.schema.json",
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"main": "./index.js",
|
|
29
|
+
"types": "./index.d.ts",
|
|
30
|
+
"unpkg": "./hopple-sdk.min.js",
|
|
31
|
+
"jsdelivr": "./hopple-sdk.min.js"
|
|
32
|
+
}
|