A "reactive" event-driven web game engine.
.-> video
state -> view -|-> audio
^ |-> interactions -.
| '-> timers ------.|
'-----------------------------'
All game state is stored in a JSON-serializable value called state.
This is then converted into a scene graph by a function called a view which is interpreted to produce video, audio and points of interaction.
.----------------.
.-------------------. |â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘|
|â–‘â–‘O X+â–‘â–‘| |O X+|
|â–‘â–‘ â–‘â–‘| | |
|â–‘â–‘Y+ â–‘â–‘| |Y+ |
'-------------------' |â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘|
'----------------'
A "safe zone" (shown unshaded) is centered in the page and scaled to be as large as possible while keeping within its boundaries. The remaining space (shown shaded) is not cropped or trimmed; in wider viewers, additional background may be visible to the left and right of the "safe zone", while in taller viewers, it may be visible above and below.
The coordinate space is a number of "units" wide (X) and tall (Y) (defined in the game's "metadata.json"), where 0, 0 is the top left corner.
## Variables
| Name | Initialization | Views | Callbacks |
|---------|----------------|-----------|------------|
| state | Read/write | Read-only | Read/write |
| content | Read-only | Read-only | Read-only |
| borders | No | Read-only | No |
### state
The JSON-serializable game state.
Initially an empty anonymous object.
### content
Any files considered content are stored inside an object. Filenames are split
by slashes to form hierarchy:
- `a/b/c/d`
- `e/f/g/h`
- `a/b/i/j`
- `a/b/c/k`
```json
{
a: {
b: {
c: {
d: "(varies by content type)",
k: "(varies by content type)"
},
i: {
j: "(varies by content type)"
}
}
},
e: {
f: {
g: {
h: "(varies by content type)"
}
}
}
}
If the path ends with two slashes, this is taken to mean a file with a name of a slash inside a folder:
Input | 1 | 2 | 3 |
---|---|---|---|
fonts/default// |
fonts | default | / |
fonts/default/\ |
fonts | default | \ |
fonts\default\/ |
fonts | default | / |
fonts\default\\ |
fonts | default | \ |
Contains the borders of the page, in scene graph coordinates.
/* Assuming that the page is wider than the configured 320x180 "safe zone". */
console.log(borders.left) /* -4.78 */
console.log(borders.right) /* 324.78 */
console.log(borders.top) /* 0 */
console.log(borders.bottom) /* 180 */
Name | Initialization | Views | Callbacks |
---|---|---|---|
view | Yes | No | No |
sprite | No | Yes | No |
move | No | Yes | No |
moveBetween | No | Yes | No |
moveAt | No | Yes | No |
scale | No | Yes | No |
scaleBetween | No | Yes | No |
fade | No | Yes | No |
fadeBetween | No | Yes | No |
click | No | Yes | No |
at | No | Yes | No |
batch | Yes | No | Yes |
pause | Yes | No | Yes |
resume | Yes | No | Yes |
Defines a new view "pass"/"layer".
var viewReference = view("View Name", function() {
return aSceneGraph
})
Draws a specified SVG.
var sceneGraph = sprite("Object Name", stringContainingSvgWhichWasLikelyImported)
Translates a child by a given number of units.
var sceneGraph = move(
14, // 14 units right.
7, // 7 units down.
childSceneGraph
)
var sceneGraph = moveBetween(
14, // Linearly interpolate from 14 units right.
7, // Linearly interpolate from 7 units down.
11, // Linearly interpolate to 11 units right.
29, // Linearly interpolate to 29 units down.
46, // Start moving at 46 seconds of elapsed time.
72, // Stop moving at 72 seconds of elapsed time.
childSceneGraph
)
var sceneGraph = moveBetween(
14, // Linearly interpolate from 14 units right.
7, // Linearly interpolate from 7 units down.
11, // Linearly interpolate to 11 units right.
29, // Linearly interpolate to 29 units down.
46, // Start moving at 46 seconds of elapsed time.
72, // Stop moving at 72 seconds of elapsed time.
childSceneGraph,
function() { /* Executed at 72 seconds of elapsed time. */ }
)
var sceneGraph = moveBetween(
14, // Linearly interpolate from 14 units right.
7, // Linearly interpolate from 7 units down.
11, // Linearly interpolate to 11 units right.
29, // Linearly interpolate to 29 units down.
46, // Start moving at 46 seconds of elapsed time.
2, // Moves at 2 units per second.
childSceneGraph
)
var sceneGraph = moveBetween(
14, // Linearly interpolate from 14 units right.
7, // Linearly interpolate from 7 units down.
11, // Linearly interpolate to 11 units right.
29, // Linearly interpolate to 29 units down.
46, // Start moving at 46 seconds of elapsed time.
2, // Move at 2 units per second.
childSceneGraph,
function() { /* Executed on reaching the destination. */ }
)
Note: if wrapped in a scaling function, the move itself will be scaled:
// Actually moves 7, 12 units.
var sceneGraph = scale(0.5, 2, move(14, 6, childSceneGraph))
Multiplies the size of a child scene graph by a given factor.
var sceneGraph = scale(
2, // Double in size horizontally.
0.5, // Halve in size vertically.
childSceneGraph
)
var sceneGraph = scale(
2, // Linearly interpolate from doubling in size horizontally.
0.5, // Linearly interpolate from halving in size vertically.
3, // Linearly interpolate to tripling in size horizontally.
0.25, // Linearly interpolate to dividing its size by 4 vertically.
46, // Start scaling at 46 seconds of elapsed time.
72, // Stop scaling at 72 seconds of elapsed time.
childSceneGraph
)
var sceneGraph = scale(
2, // Linearly interpolate from doubling in size horizontally.
0.5, // Linearly interpolate from halving in size vertically.
3, // Linearly interpolate to tripling in size horizontally.
0.25, // Linearly interpolate to dividing its size by 4 vertically.
46, // Start scaling at 46 seconds of elapsed time.
72, // Stop scaling at 72 seconds of elapsed time.
childSceneGraph,
function() { /* Executed at the end of the scaling. */ }
)
Multiplies the opacity of a child scene graph by a given factor. Clamped to 0 and 1 at the time of emitting objects. This additionally adjusts the volume of sounds.
var sceneGraph = fade(
0.3, // 30% opacity.
childSceneGraph
)
var sceneGraph = fadeBetween(
0.25, // Linearly interpolate from 25% opacity.
0.75, // Linearly interpolate to 75% opacity.
46, // Start fading at 46 seconds of elapsed time.
72, // Stop fading at 72 seconds of elapsed time.
childSceneGraph
)
var sceneGraph = fadeBetween(
0.25, // Linearly interpolate from 25% opacity.
0.75, // Linearly interpolate to 75% opacity.
46, // Start fading at 46 seconds of elapsed time.
72, // Stop fading at 72 seconds of elapsed time.
childSceneGraph,
function() { /* Executed at the end of the fading. */ }
)
Executes a callback (and subsequently re-renders) on clicking on any object emitted by a child scene graph.
var sceneGraph = click(
childSceneGraph,
function () { /* Executed on clicking on the below child scene graph. */ }
)
Note: if multiple calls are nested, only the inner-most handler will be used on clicking on its children:
click([
childSceneGraphA,
click(
childSceneGraphB,
function() {
/* Called only on clicking on childSceneGraphB. */
}
),
childSceneGraphC
], function() {
/* Called only on clicking on childSceneGraphA or childSceneGraphC. */
})
Executes a callback (and subsequently re-renders) at a specified time.
var sceneGraph = at(46, function () {
/* Called at 46 seconds of elapsed time. */
})
Caches a complex child scene graph, meaning it is only generated once.
var cached = batch(childSceneGraph)
var sceneGraph = sprite("Object Name", cached)
Pauses a view's timer. Has no effect if already paused.
pause(viewReference)
Unpauses a view's timer. Has no effect if not paused.
resume(viewReference)
|'- dist
| |'- (game name).zip
| '- (game name)
| |'- index.html
| '- index.js
'- src
|'- bootloader
| '- (**/*.js)
|'- engine
| '- (**/*.js)
'- (game name)
|'- metadata.json
|'- icon.svg
|'- (**/*.js)
'- (**/*.svg)
A zip file containing every part of the corresponding game, minfied, but not including debugging tools. Only produced by "one-off" builds.
A directory containing every part of the corresponding game, including debugging tools, without much in the way of minification. Only produced by "watch" builds.
All JavaScript in the "src/bootloader" directory (and its subdirectories) is loaded at startup, and is responsible for handling errors and loading all other content.
All JavaScript in the "src/engine" directory (and its subdirectories) is included in every game. This is the runtime engine. It is responsible for:
- Timing.
- DOM manipulation.
- Input.
- Save/load.
- Debugging tools.
- State management.
A JSON file describing that particular game.
{
"name": "Game Name",
"description": "Game Description",
"developer": {
"name": "Developer Name",
"url": "https://example.com"
},
"width": 320,
"height": 180
}
Shown as the page title, and when "pinned" to the home screen of a mobile device.
A short description of the game, included in metadata.
The name of the developer of the game, included in metadata.
The URL of the developer of the game, included in metadata.
The number of units the "safe zone" is "wide" (see Coordinate Space).
The number of units the "safe zone" is "tall" (see Coordinate Space).
An icon to use; shown as a favicon, splash screen and when "pinned" to the home screen of a mobile devce. Expected to be square.
Included as game code. The order in which it is included is non-deterministic.
Included in the content
object. If an Inkscape SVG with multiple layers is
found, it is split into one piece of content per layer and their names are
included in their file names; a file with a name of:
a/b/c.svg
Containing layers:
d/e/f
g/h/i
j/k/l
Is equivalent to separate files of:
a/b/c/d/e/f
a/b/c/g/h/i
a/b/c/j/k/l
The final build product is a set of files which can be hosted on a HTTP/S server to play the game.
"Watch" builds will automatically host such a server on HTTP port 5000. Navigate to http://localhost:5000/game-name to test your game.
Fork this repository to develop your own games.
Build tasks exist for many common tasks. Generally, you would want to run "Install dependencies", then "Watch for changes" for local development tasks.
The default Travis CI configuration will build all games and publish them as separate zip files as GitHub release assets. However, the "deploy" section of the .travis.yml file will need to be regenerated as the access token will not be applicable to your fork's GitHub repository or Travis CI instance; see the Travis CI documentation for details on how to do this.