punter.js 1.0.0 → 1.1.0
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/README.MD +171 -460
- package/games/pong.html +19 -6
- package/package.json +3 -2
- package/punter.js +17 -17
- package/tests/fixtures/index.html +2 -2
- package/tests/interface-spec.js +1 -1
package/README.MD
CHANGED
|
@@ -2,62 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
<p align="center"><img src="images/punterjs.png" width="290" height="174" /></p>
|
|
4
4
|
|
|
5
|
-
A simple, dependency-free 2D game engine for the browser.
|
|
6
|
-
|
|
7
|
-
**Why?** Back in the Amiga days, AMOS let you build games in BASIC without touching assembly - sprites, sound, and input just worked, so you could get a character moving in minutes. `punter.js` brings that same philosophy to the browser: no build step, no ES6, no dependencies - just a small single file with a readable API.
|
|
5
|
+
A simple, dependency-free 2D game engine for the browser. No build step, no frameworks - just one script tag.
|
|
8
6
|
|
|
9
7
|
**Features**
|
|
10
|
-
* **Auto-scaling** -
|
|
11
|
-
* **Retina support** - renders at up to 2× device pixel ratio
|
|
8
|
+
* **Auto-scaling** - sprites automatically reposition on resize
|
|
9
|
+
* **Retina support** - renders at up to 2× device pixel ratio
|
|
12
10
|
* **Pixel-accurate collision** - bounding boxes ignore transparent pixels
|
|
13
|
-
* **HTML state attributes** - `data-punter-*` attributes on `<html>` let you drive CSS from game state
|
|
14
|
-
* **CSS variables** - `--punter-vpw`/`--punter-vph` on `<html>`, updated on resize
|
|
15
11
|
|
|
16
12
|
## Contents
|
|
17
13
|
|
|
18
|
-
- [
|
|
19
|
-
- [
|
|
20
|
-
- [
|
|
21
|
-
- [
|
|
14
|
+
- [Install](#install)
|
|
15
|
+
- [Run the Example](#run-the-example)
|
|
16
|
+
- [How it Works](#how-it-works)
|
|
17
|
+
- [Setup](#setup)
|
|
22
18
|
- [Scenes](#scenes)
|
|
23
19
|
- [Events](#events)
|
|
24
20
|
- [Sprites](#sprites)
|
|
25
|
-
- [Creating a Sprite](#creating-a-sprite)
|
|
26
|
-
- [Sprite Properties](#sprite-properties)
|
|
27
|
-
- [Sprite Methods](#sprite-methods)
|
|
28
|
-
- [Collision Detection](#collision-detection)
|
|
29
|
-
- [Animation](#animation)
|
|
30
|
-
- [Scrolling & Parallax](#scrolling--parallax)
|
|
31
21
|
- [Sound](#sound)
|
|
32
22
|
- [Input](#input)
|
|
33
|
-
- [Game Loop Control](#game-loop-control)
|
|
34
23
|
- [Engine Properties](#engine-properties)
|
|
24
|
+
- [Game Loop Control](#game-loop-control)
|
|
35
25
|
- [CSS & HTML Hooks](#css--html-hooks)
|
|
36
26
|
- [Debug Mode](#debug-mode)
|
|
37
27
|
- [Browser Support](#browser-support)
|
|
38
28
|
|
|
39
|
-
##
|
|
29
|
+
## Install
|
|
40
30
|
|
|
41
|
-
Download `punter.js` and include it with a script tag
|
|
31
|
+
Download `punter.js` and include it with a script tag before your game code:
|
|
42
32
|
|
|
43
33
|
```html
|
|
44
34
|
<!doctype html>
|
|
45
35
|
<html>
|
|
46
36
|
<head>
|
|
47
37
|
<script src="punter.js"></script>
|
|
48
|
-
<script>
|
|
49
|
-
// your game code here
|
|
50
|
-
</script>
|
|
51
38
|
</head>
|
|
52
39
|
<body>
|
|
53
40
|
<canvas id="game"></canvas>
|
|
41
|
+
<script>
|
|
42
|
+
// your game code here
|
|
43
|
+
</script>
|
|
54
44
|
</body>
|
|
55
45
|
</html>
|
|
56
46
|
```
|
|
57
47
|
|
|
58
|
-
##
|
|
48
|
+
## Run the Example
|
|
59
49
|
|
|
60
|
-
The repo includes a Pong game in `games/pong.html`.
|
|
50
|
+
The repo includes a Pong game in `games/pong.html`. Open it with a local web server - browsers block local file loading without one.
|
|
61
51
|
|
|
62
52
|
```bash
|
|
63
53
|
npm start
|
|
@@ -65,33 +55,29 @@ npm start
|
|
|
65
55
|
|
|
66
56
|
Then open **http://localhost:3000/games/pong.html** in your browser.
|
|
67
57
|
|
|
68
|
-
> `npm start` uses `npx serve`
|
|
58
|
+
> `npm start` uses `npx serve` - no install step needed beyond having Node.js.
|
|
59
|
+
|
|
60
|
+
## How it Works
|
|
69
61
|
|
|
70
|
-
|
|
62
|
+
Every punter.js game follows the same three-step pattern:
|
|
63
|
+
|
|
64
|
+
1. **setup** - specify which canvas to use and which images/sounds to preload
|
|
65
|
+
2. **scene** - define a named scene (level, menu, etc.) with update/draw logic
|
|
66
|
+
3. **ready** - switch to a scene once everything has loaded
|
|
71
67
|
|
|
72
68
|
```js
|
|
73
|
-
|
|
69
|
+
// 1. Load assets
|
|
70
|
+
punter.setup({
|
|
74
71
|
canvas: '#game',
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
},
|
|
78
|
-
sounds: {
|
|
79
|
-
jump: 'sounds/jump.mp3'
|
|
80
|
-
}
|
|
72
|
+
images: { player: 'images/player.png' },
|
|
73
|
+
sounds: { jump: 'sounds/jump.mp3' }
|
|
81
74
|
});
|
|
82
75
|
|
|
83
|
-
//
|
|
76
|
+
// 2. Define a scene
|
|
84
77
|
punter.scene('level1', function () {
|
|
85
78
|
|
|
86
|
-
|
|
87
|
-
var player = punter.createSprite({
|
|
88
|
-
id: 'player',
|
|
89
|
-
key: 'player',
|
|
90
|
-
x: '50%',
|
|
91
|
-
y: '50%'
|
|
92
|
-
});
|
|
79
|
+
var player = punter.createSprite({ id: 'player', key: 'player', x: '50%', y: '50%' });
|
|
93
80
|
|
|
94
|
-
// move it on ever frame if required
|
|
95
81
|
punter.on('update', function () {
|
|
96
82
|
if (punter.keys['ArrowRight']) player.moveX(3);
|
|
97
83
|
if (punter.keys['ArrowLeft']) player.moveX(-3);
|
|
@@ -99,204 +85,127 @@ punter.scene('level1', function () {
|
|
|
99
85
|
if (punter.keys['ArrowDown']) player.moveY(3);
|
|
100
86
|
});
|
|
101
87
|
|
|
102
|
-
// draw it to every frame
|
|
103
88
|
punter.on('draw', function () {
|
|
104
|
-
player.draw(this);
|
|
89
|
+
player.draw(this); // 'this' is the canvas 2D context
|
|
105
90
|
});
|
|
106
91
|
});
|
|
107
92
|
|
|
108
|
-
//
|
|
93
|
+
// 3. Start when ready
|
|
109
94
|
punter.on('ready', function () {
|
|
110
95
|
punter.go('level1');
|
|
111
96
|
});
|
|
112
97
|
```
|
|
113
98
|
|
|
114
|
-
##
|
|
99
|
+
## Setup
|
|
115
100
|
|
|
116
|
-
|
|
101
|
+
Call this once at the start. The `ready` event fires once all assets have loaded.
|
|
117
102
|
|
|
118
103
|
```js
|
|
119
|
-
punter.
|
|
120
|
-
canvas: '#game',
|
|
121
|
-
debug: false,
|
|
122
|
-
|
|
123
|
-
player:
|
|
124
|
-
|
|
125
|
-
coin: 'images/coin.png'
|
|
104
|
+
punter.setup({
|
|
105
|
+
canvas: '#game', // CSS selector or HTMLCanvasElement
|
|
106
|
+
debug: false, // true = show FPS and bounding boxes
|
|
107
|
+
images: {
|
|
108
|
+
player: 'images/player.png',
|
|
109
|
+
coin: 'images/coin.png'
|
|
126
110
|
},
|
|
127
111
|
sounds: {
|
|
128
|
-
jump:
|
|
129
|
-
collect: 'sounds/collect.mp3'
|
|
112
|
+
jump: 'sounds/jump.mp3'
|
|
130
113
|
}
|
|
131
114
|
});
|
|
132
|
-
|
|
133
|
-
punter.on('ready', function () {
|
|
134
|
-
// safe to create sprites and go to a scene here
|
|
135
|
-
punter.go('menu');
|
|
136
|
-
});
|
|
137
115
|
```
|
|
138
116
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
`
|
|
144
|
-
`debug` |
|
|
145
|
-
`sprites` | `object` | No | Key/URL pairs of images to preload: `{ key: 'path/to/image.png' }`
|
|
146
|
-
`sounds` | `object` | No | Key/URL pairs of audio files to preload: `{ key: 'path/to/sound.mp3' }`
|
|
117
|
+
Option | Required | Description
|
|
118
|
+
----------|----------|------------------------------------------------
|
|
119
|
+
`canvas` | Yes | CSS selector (`'#game'`) or `HTMLCanvasElement`
|
|
120
|
+
`images` | No | Key/path pairs of images to preload
|
|
121
|
+
`sounds` | No | Key/path pairs of audio files to preload
|
|
122
|
+
`debug` | No | Enables the debug overlay. Default: `false`
|
|
147
123
|
|
|
148
124
|
## Scenes
|
|
149
125
|
|
|
150
|
-
Scenes are the different
|
|
126
|
+
Scenes are the different screens in your game - a menu, level 1, game over. Define each with `punter.scene()`, then switch between them with `punter.go()`. Switching scenes automatically clears the previous update/draw handlers.
|
|
151
127
|
|
|
152
128
|
```js
|
|
153
|
-
// Define scenes anywhere before calling punter.go()
|
|
154
129
|
punter.scene('menu', function () {
|
|
155
|
-
|
|
156
|
-
var title = punter.createSprite({ id: 'title', key: 'titleImage', x: '50%', y: '30%' });
|
|
157
|
-
|
|
158
|
-
punter.on('draw', function () {
|
|
159
|
-
title.draw(this);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
130
|
punter.on('update', function () {
|
|
163
|
-
|
|
164
|
-
if (punter.mouse.clicked) {
|
|
165
|
-
punter.go('level1');
|
|
166
|
-
}
|
|
131
|
+
if (punter.mouse.clicked) punter.go('level1');
|
|
167
132
|
});
|
|
168
|
-
|
|
169
133
|
});
|
|
170
134
|
|
|
171
135
|
punter.scene('level1', function () {
|
|
172
136
|
// level setup here
|
|
173
137
|
});
|
|
174
|
-
|
|
175
|
-
// Switching scenes clears the previous update/draw handlers automatically
|
|
176
|
-
punter.go('menu');
|
|
177
138
|
```
|
|
178
139
|
|
|
179
|
-
### Methods
|
|
180
|
-
|
|
181
|
-
Method | Description
|
|
182
|
-
-------------------------|-------------------------------------------------------------------------------------------------------
|
|
183
|
-
`punter.scene(name, fn)` | Registers a scene. `name` is a string key; `fn` is the setup function that runs when the scene starts.
|
|
184
|
-
`punter.go(name)` | Switches to the named scene. Starts the game loop if it isn't already running. Clears all input state.
|
|
185
|
-
|
|
186
140
|
## Events
|
|
187
141
|
|
|
188
|
-
|
|
142
|
+
Register handlers with `punter.on()`. Each event can only have one handler at a time - calling `punter.on()` again for the same event replaces the previous one.
|
|
189
143
|
|
|
190
144
|
```js
|
|
191
145
|
punter.on('ready', function () {
|
|
192
|
-
// fires once - all
|
|
146
|
+
// fires once - all assets loaded, safe to create sprites
|
|
193
147
|
punter.go('level1');
|
|
194
148
|
});
|
|
195
149
|
|
|
196
150
|
punter.on('update', function () {
|
|
197
|
-
// fires ~60 times/sec - move things, check
|
|
151
|
+
// fires ~60 times/sec - move things, check input, check collisions
|
|
198
152
|
});
|
|
199
153
|
|
|
200
154
|
punter.on('draw', function () {
|
|
201
|
-
// fires every frame -
|
|
202
|
-
// 'this' inside draw is the canvas 2D context
|
|
155
|
+
// fires every frame - 'this' is the canvas 2D context
|
|
203
156
|
player.draw(this);
|
|
204
157
|
});
|
|
205
158
|
|
|
206
159
|
punter.on('resize', function () {
|
|
207
|
-
// fires when the browser window
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
punter.on('go', function (sceneName) {
|
|
211
|
-
// fires every time punter.go() is called - receives the new scene name
|
|
160
|
+
// fires when the browser window resizes
|
|
212
161
|
});
|
|
213
162
|
```
|
|
214
163
|
|
|
215
|
-
### Event Reference
|
|
216
|
-
|
|
217
|
-
Event | Callback signature | When it fires
|
|
218
|
-
---------|-----------------------|--------------------------------------------------------------
|
|
219
|
-
`ready` | `function()` | Once, after all sprites and sounds have finished loading
|
|
220
|
-
`update` | `function()` | Every logic tick (~60/sec) - put movement and game logic here
|
|
221
|
-
`draw` | `function()` | Every render frame - `this` is the canvas 2D context
|
|
222
|
-
`resize` | `function()` | When the browser window resizes or orientation changes
|
|
223
|
-
`go` | `function(sceneName)` | When `punter.go()` is called; receives the new scene name
|
|
224
|
-
|
|
225
|
-
> Each event can only have one handler at a time. Calling `punter.on('update', fn)` again replaces the previous handler. Switching scenes via `punter.go()` automatically clears the `update` and `draw` handlers.
|
|
226
|
-
|
|
227
164
|
## Sprites
|
|
228
165
|
|
|
229
|
-
A sprite is any image in your game - a character,
|
|
230
|
-
|
|
231
|
-
### Creating a Sprite
|
|
166
|
+
A sprite is any image in your game - a character, enemy, coin, background tile. Create one with `punter.createSprite()`:
|
|
232
167
|
|
|
233
168
|
```js
|
|
234
169
|
var player = punter.createSprite({
|
|
235
|
-
id: 'player',
|
|
236
|
-
key: 'player',
|
|
237
|
-
x: '50%',
|
|
238
|
-
y: '80%'
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
// Sprite with a fixed size
|
|
242
|
-
var background = punter.createSprite({
|
|
243
|
-
id: 'bg',
|
|
244
|
-
key: 'background',
|
|
245
|
-
x: 0,
|
|
246
|
-
y: 0,
|
|
247
|
-
w: '100%', // width as percent of canvas width
|
|
248
|
-
h: '100%' // height as percent of canvas height
|
|
170
|
+
id: 'player', // unique name - no two sprites can share one
|
|
171
|
+
key: 'player', // matches a key from config.sprites
|
|
172
|
+
x: '50%', // position - number (pixels) or percent string
|
|
173
|
+
y: '80%'
|
|
249
174
|
});
|
|
250
175
|
```
|
|
251
176
|
|
|
252
177
|
### Creation Options
|
|
253
178
|
|
|
254
|
-
Option |
|
|
255
|
-
|
|
256
|
-
`id` |
|
|
257
|
-
`key` |
|
|
258
|
-
`x` |
|
|
259
|
-
`y` |
|
|
260
|
-
`w` |
|
|
261
|
-
`h` |
|
|
262
|
-
`preserveAspect` | `
|
|
263
|
-
`collidable` | `
|
|
264
|
-
`
|
|
265
|
-
`
|
|
266
|
-
`
|
|
267
|
-
`
|
|
268
|
-
`clipFrom` | `string` | `'bottom'` | Which end to clip from: `'top'` or `'bottom'`.
|
|
179
|
+
Option | Default | Description
|
|
180
|
+
-----------------|------------|----------------------------------------------------------------------------------------------------
|
|
181
|
+
`id` | - | **Required.** Unique name for this sprite.
|
|
182
|
+
`key` | - | **Required.** Image key from `config.images`. Pass an array (`['frame1','frame2']`) for animation.
|
|
183
|
+
`x` | - | **Required.** Horizontal position in pixels or `'50%'`.
|
|
184
|
+
`y` | - | **Required.** Vertical position in pixels or `'50%'`.
|
|
185
|
+
`w` | auto | Width in pixels or percent. Inferred from the image if omitted.
|
|
186
|
+
`h` | auto | Height in pixels or percent. Inferred from the image if omitted.
|
|
187
|
+
`preserveAspect` | `true` | Maintain aspect ratio when only one dimension is set.
|
|
188
|
+
`collidable` | `true` | Compute a collision bounding box for this sprite.
|
|
189
|
+
`repeatX` | `false` | Tile horizontally across the canvas (e.g. a floor).
|
|
190
|
+
`repeatY` | `false` | Tile vertically across the canvas (e.g. a wall).
|
|
191
|
+
`clipHeight` | `null` | Limit the drawn height in pixels - useful for health/fill bars.
|
|
192
|
+
`clipFrom` | `'bottom'` | Which end to clip from: `'top'` or `'bottom'`.
|
|
269
193
|
|
|
270
194
|
### Sprite Properties
|
|
271
195
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
`
|
|
277
|
-
`
|
|
278
|
-
`
|
|
279
|
-
`
|
|
280
|
-
`actualW` | `number` | No | Rendered width (floored). Read-only.
|
|
281
|
-
`actualH` | `number` | No | Rendered height, respecting `clipHeight`. Read-only.
|
|
282
|
-
`initialX` | `number` | Yes | The x position the sprite started at (used by `bounce()`).
|
|
283
|
-
`initialY` | `number` | Yes | The y position the sprite started at (used by `bounce()`).
|
|
284
|
-
`frame` | `number\ | null` | Yes | Override the current animation frame index. Set to `null` to let `animate()` control it.
|
|
285
|
-
`visible` | `boolean` | No | `true` if any part of the sprite is within the canvas bounds. Read-only.
|
|
286
|
-
`seen` | `boolean` | Yes | Becomes `true` once the sprite has been visible on screen. Can be reset manually.
|
|
287
|
-
`destroyed` | `boolean` | No | `true` after `destroy()` has been called. Read-only.
|
|
288
|
-
`bounds` | `object` | No | Pixel-accurate bounding box: `{ x, y, w, h }`. Updated each frame after `draw()`. Read-only.
|
|
289
|
-
`collidable` | `boolean` | Yes | Whether to compute collision bounds.
|
|
290
|
-
`clipHeight` | `number\ | null` | Yes | Current clip height.
|
|
291
|
-
`key` | `string\ | string[]` | Yes | The image key (or array of keys for animation).
|
|
292
|
-
`id` | `string` | No | The sprite's unique ID.
|
|
293
|
-
`aspectRatio` | `number` | No | Width-to-height ratio of the original image. Read-only.
|
|
196
|
+
Property | Description
|
|
197
|
+
------------|-----------------------------------------------------------------------------------------
|
|
198
|
+
`x`, `y` | Position in pixels. Set directly to move the sprite.
|
|
199
|
+
`w`, `h` | Size in pixels.
|
|
200
|
+
`visible` | `true` if any part of the sprite is within the canvas. Read-only.
|
|
201
|
+
`bounds` | Pixel-accurate bounding box `{ x, y, w, h }`. Updated each frame. Read-only.
|
|
202
|
+
`frame` | Override the current animation frame index. Set to `null` to let `animate()` control it.
|
|
203
|
+
`destroyed` | `true` after `destroy()` has been called. Read-only.
|
|
294
204
|
|
|
295
205
|
### Sprite Methods
|
|
296
206
|
|
|
297
207
|
#### `sprite.draw(ctx)`
|
|
298
|
-
|
|
299
|
-
Draws the sprite to the canvas. Call this inside your `draw` event handler. `ctx` is the canvas 2D context passed as `this` in the `draw` callback.
|
|
208
|
+
Draws the sprite. Call inside your `draw` handler - `ctx` is `this`.
|
|
300
209
|
|
|
301
210
|
```js
|
|
302
211
|
punter.on('draw', function () {
|
|
@@ -306,356 +215,194 @@ punter.on('draw', function () {
|
|
|
306
215
|
```
|
|
307
216
|
|
|
308
217
|
#### `sprite.moveX(dx)` / `sprite.moveY(dy)`
|
|
309
|
-
|
|
310
|
-
Move the sprite by `dx` pixels horizontally or `dy` pixels vertically. Call these in your `update` handler.
|
|
218
|
+
Move by `dx` / `dy` pixels. Call in `update`.
|
|
311
219
|
|
|
312
220
|
```js
|
|
313
221
|
punter.on('update', function () {
|
|
314
222
|
if (punter.keys['ArrowRight']) player.moveX(3);
|
|
315
223
|
if (punter.keys['ArrowLeft']) player.moveX(-3);
|
|
316
|
-
if (punter.keys['Space']) player.moveY(-5); // jump
|
|
317
224
|
});
|
|
318
225
|
```
|
|
319
226
|
|
|
320
227
|
#### `sprite.center(offsetX?, offsetY?)`
|
|
321
|
-
|
|
322
|
-
Centers the sprite on the canvas. Optional pixel offsets shift it from the centre.
|
|
323
|
-
|
|
324
|
-
```js
|
|
325
|
-
player.center(); // perfectly centred
|
|
326
|
-
player.center(0, -50); // centred, but 50px above the middle
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
Also available as `sprite.centerX(offsetX?)` and `sprite.centerY(offsetY?)` to centre on one axis only.
|
|
330
|
-
|
|
331
|
-
#### `sprite.bounce(range?, speed?)`
|
|
332
|
-
|
|
333
|
-
Makes the sprite float up and down in a smooth sine-wave motion. Call every frame in `update`.
|
|
228
|
+
Centre the sprite on the canvas. Optional offsets shift it from the centre.
|
|
334
229
|
|
|
335
230
|
```js
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
});
|
|
231
|
+
player.center(); // perfectly centred
|
|
232
|
+
player.center(0, -50); // centred, 50px above the middle
|
|
339
233
|
```
|
|
340
234
|
|
|
341
|
-
|
|
342
|
-
----------|----------|---------|--------------------------------------------------
|
|
343
|
-
`range` | `number` | `8` | Max pixels to move up/down from `initialY`.
|
|
344
|
-
`speed` | `number` | `10` | Controls how fast the bounce is. Higher = slower.
|
|
235
|
+
Use `sprite.centerX()` or `sprite.centerY()` to centre on one axis only.
|
|
345
236
|
|
|
346
237
|
#### `sprite.animate(delayMs)`
|
|
347
|
-
|
|
348
|
-
Advances to the next animation frame. Call every frame in `update`. Requires `key` to be an array of image keys.
|
|
238
|
+
Advance to the next animation frame. Requires `key` to be an array of image keys.
|
|
349
239
|
|
|
350
240
|
```js
|
|
351
241
|
var run = punter.createSprite({
|
|
352
|
-
id:
|
|
353
|
-
key: ['run1', 'run2', 'run3'
|
|
242
|
+
id: 'runner',
|
|
243
|
+
key: ['run1', 'run2', 'run3'],
|
|
354
244
|
x: 100, y: 200
|
|
355
245
|
});
|
|
356
246
|
|
|
357
247
|
punter.on('update', function () {
|
|
358
|
-
run.animate(120); //
|
|
248
|
+
run.animate(120); // new frame every 120ms
|
|
359
249
|
});
|
|
360
250
|
```
|
|
361
251
|
|
|
362
|
-
|
|
252
|
+
Set `sprite.frame = 2` to jump to a specific frame. Set it to `null` to let `animate()` take over again.
|
|
363
253
|
|
|
364
|
-
#### `sprite.
|
|
365
|
-
|
|
366
|
-
Removes the sprite from the engine. Any subsequent `draw()` calls on it will be ignored. The `sprite.destroyed` property becomes `true`.
|
|
254
|
+
#### `sprite.bounce(range?, speed?)`
|
|
255
|
+
Float up and down in a smooth wave. Call every frame in `update`.
|
|
367
256
|
|
|
368
257
|
```js
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}
|
|
258
|
+
punter.on('update', function () {
|
|
259
|
+
coin.bounce(6, 12); // 6px range, speed 12 (higher = slower)
|
|
260
|
+
});
|
|
373
261
|
```
|
|
374
262
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
Collision detection lets you know when two sprites are touching or overlapping. The engine uses each image's actual pixel content to build a tight bounding box - transparent areas around the image are ignored - so collisions feel accurate even for irregularly shaped sprites.
|
|
263
|
+
#### `sprite.isCollidingWith(other)`
|
|
264
|
+
Returns `true` if the bounding boxes of two sprites overlap. Both must have `collidable: true` (the default). Always call `sprite.draw()` before checking - bounds are updated during draw.
|
|
378
265
|
|
|
379
266
|
```js
|
|
380
267
|
punter.on('update', function () {
|
|
381
268
|
if (player.isCollidingWith(bullet)) {
|
|
382
|
-
punter.playSound('hit');
|
|
383
269
|
bullet.destroy();
|
|
384
|
-
|
|
270
|
+
punter.playSound('hit');
|
|
385
271
|
}
|
|
386
272
|
});
|
|
387
273
|
```
|
|
388
274
|
|
|
389
|
-
#### `sprite.
|
|
390
|
-
|
|
391
|
-
Returns `true` if the bounding boxes of the two sprites overlap, `false` otherwise. Both sprites must have `collidable: true` (which is the default).
|
|
392
|
-
|
|
393
|
-
```js
|
|
394
|
-
var hit = player.isCollidingWith(enemy); // true or false
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
> Bounding boxes are automatically updated each frame when the sprite is drawn. Always call `sprite.draw()` before checking collisions.
|
|
398
|
-
|
|
399
|
-
### Animation
|
|
400
|
-
|
|
401
|
-
Sprites can cycle through multiple images to create animations. Pass an array of keys to `key` when creating the sprite, then call `animate()` each frame:
|
|
402
|
-
|
|
403
|
-
```js
|
|
404
|
-
punter.init({
|
|
405
|
-
sprites: {
|
|
406
|
-
idle1: 'images/idle1.png',
|
|
407
|
-
idle2: 'images/idle2.png',
|
|
408
|
-
idle3: 'images/idle3.png'
|
|
409
|
-
}
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
var character = punter.createSprite({
|
|
413
|
-
id: 'char',
|
|
414
|
-
key: ['idle1', 'idle2', 'idle3'],
|
|
415
|
-
x: 100, y: 100
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
punter.on('update', function () {
|
|
419
|
-
character.animate(150); // new frame every 150ms
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
punter.on('draw', function () {
|
|
423
|
-
character.draw(this);
|
|
424
|
-
});
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
To manually jump to a specific frame, set `character.frame = 2`. To hand control back to `animate()`, set it to `null`.
|
|
428
|
-
|
|
429
|
-
### Scrolling & Parallax
|
|
430
|
-
|
|
431
|
-
Three methods are available for scrolling behaviour on sprites used as backgrounds or moving scenery:
|
|
432
|
-
|
|
433
|
-
#### `sprite.parallaxScrollX(speed, respawnAfterMs, offset?)`
|
|
434
|
-
|
|
435
|
-
Moves the sprite left or right continuously. When it goes fully off-screen, it waits `respawnAfterMs` milliseconds then reappears from the opposite edge. Great for clouds, birds, or background layers.
|
|
436
|
-
|
|
437
|
-
```js
|
|
438
|
-
var cloud = punter.createSprite({ id: 'cloud1', key: 'cloud', x: 0, y: 50, w: 120, h: 60 });
|
|
439
|
-
|
|
440
|
-
punter.on('update', function () {
|
|
441
|
-
cloud.parallaxScrollX(-2, 1000); // move left at 2px/frame, respawn after 1 second
|
|
442
|
-
});
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
Parameter | Type | Description
|
|
446
|
-
-----------------|----------|----------------------------------------------------------------------
|
|
447
|
-
`speed` | `number` | Pixels per frame. Negative = left, positive = right.
|
|
448
|
-
`respawnAfterMs` | `number` | Milliseconds to wait before reappearing from the opposite edge.
|
|
449
|
-
`offset` | `number` | Extra pixels beyond the screen edge before respawning. Default: `50`.
|
|
450
|
-
|
|
451
|
-
#### `sprite.parallaxScrollY(speed, respawnAfterMs, offset?)`
|
|
452
|
-
|
|
453
|
-
Same as `parallaxScrollX` but moves vertically.
|
|
454
|
-
|
|
455
|
-
```js
|
|
456
|
-
punter.on('update', function () {
|
|
457
|
-
rain.parallaxScrollY(4, 500); // fall down at 4px/frame, respawn after 0.5s
|
|
458
|
-
});
|
|
459
|
-
```
|
|
275
|
+
#### `sprite.destroy()`
|
|
276
|
+
Removes the sprite. All subsequent `draw()` calls are silently ignored.
|
|
460
277
|
|
|
461
|
-
####
|
|
278
|
+
#### Scrolling
|
|
462
279
|
|
|
463
|
-
|
|
280
|
+
Method | Description
|
|
281
|
+
---------------------------------------------------------|------------------------------------------------------------------------
|
|
282
|
+
`sprite.parallaxScrollX(speed, respawnAfterMs, offset?)` | Scroll horizontally. Respawns from the opposite edge after a delay.
|
|
283
|
+
`sprite.parallaxScrollY(speed, respawnAfterMs, offset?)` | Scroll vertically. Respawns from the opposite edge after a delay.
|
|
284
|
+
`sprite.loopScrollX(speed)` | Scroll horizontally and wrap immediately. Good for looping backgrounds.
|
|
464
285
|
|
|
465
286
|
```js
|
|
466
|
-
var ground = punter.createSprite({ id: 'ground', key: 'ground', x: 0, y: '90%', w: '100%', h: 64 });
|
|
467
|
-
|
|
468
287
|
punter.on('update', function () {
|
|
469
|
-
|
|
288
|
+
cloud.parallaxScrollX(-2, 1000); // move left, respawn after 1s
|
|
289
|
+
ground.loopScrollX(-3); // loop left seamlessly
|
|
470
290
|
});
|
|
471
291
|
```
|
|
472
292
|
|
|
473
293
|
|
|
474
294
|
## Sound
|
|
475
295
|
|
|
476
|
-
punter.js preloads audio files and lets you play and stop them by name. Works on mobile too - sounds are decoded using the Web Audio API, so they play instantly without delay.
|
|
477
|
-
|
|
478
296
|
```js
|
|
479
|
-
punter.
|
|
480
|
-
sounds: {
|
|
481
|
-
jump: 'sounds/jump.mp3',
|
|
482
|
-
music: 'sounds/theme.mp3'
|
|
483
|
-
}
|
|
297
|
+
punter.setup({
|
|
298
|
+
sounds: { jump: 'sounds/jump.mp3', music: 'sounds/theme.mp3' }
|
|
484
299
|
});
|
|
485
300
|
|
|
486
301
|
punter.on('ready', function () {
|
|
487
|
-
//
|
|
488
|
-
punter.playSound('music', { loop: true, volume: 0.5 });
|
|
489
|
-
|
|
302
|
+
punter.playSound('music', { loop: true, volume: 0.5 }); // background music
|
|
490
303
|
punter.go('level1');
|
|
491
304
|
});
|
|
492
305
|
|
|
493
|
-
//
|
|
494
|
-
punter.
|
|
495
|
-
|
|
496
|
-
// stop a sound that's playing
|
|
497
|
-
punter.stopSound('music');
|
|
306
|
+
punter.playSound('jump'); // one-shot effect
|
|
307
|
+
punter.stopSound('music'); // stop a playing sound
|
|
498
308
|
```
|
|
499
309
|
|
|
500
310
|
### `punter.playSound(name, options?)`
|
|
501
311
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
`
|
|
507
|
-
`
|
|
508
|
-
`once` | `boolean` | `false` | If `true`, the sound is not tracked - `stopSound()` cannot stop it.
|
|
509
|
-
`speed` | `number` | `1` | Playback speed multiplier. `0.5` = half speed, `2` = double speed.
|
|
312
|
+
Option | Default | Description
|
|
313
|
+
---------|---------|--------------------------------------------
|
|
314
|
+
`volume` | `1` | Volume from `0` (silent) to `1` (full).
|
|
315
|
+
`loop` | `false` | Repeat indefinitely.
|
|
316
|
+
`speed` | `1` | Playback speed. `0.5` = half, `2` = double.
|
|
317
|
+
`once` | `false` | If `true`, `stopSound()` cannot stop it.
|
|
510
318
|
|
|
511
319
|
### `punter.stopSound(name)`
|
|
512
|
-
|
|
513
|
-
Stops all currently playing instances of the named sound.
|
|
320
|
+
Stops all playing instances of the named sound.
|
|
514
321
|
|
|
515
322
|
|
|
516
323
|
## Input
|
|
517
324
|
|
|
518
|
-
|
|
325
|
+
Check input state inside your `update` handler each frame. Touch events on mobile are automatically translated into mouse events.
|
|
519
326
|
|
|
520
327
|
### Keyboard
|
|
521
328
|
|
|
522
|
-
`punter.keys` is a plain object where each key name maps to `true` when held down and `false` (or absent) when released.
|
|
523
|
-
|
|
524
329
|
```js
|
|
525
330
|
punter.on('update', function () {
|
|
526
331
|
if (punter.keys['ArrowLeft']) player.moveX(-3);
|
|
527
332
|
if (punter.keys['ArrowRight']) player.moveX(3);
|
|
528
|
-
if (punter.keys['
|
|
529
|
-
if (punter.keys['ArrowDown']) player.moveY(3);
|
|
530
|
-
if (punter.keys[' ']) player.moveY(-8); // spacebar to jump
|
|
333
|
+
if (punter.keys[' ']) player.moveY(-8); // spacebar
|
|
531
334
|
if (punter.keys['Escape']) punter.go('menu');
|
|
532
335
|
});
|
|
533
336
|
```
|
|
534
337
|
|
|
535
|
-
Key names
|
|
338
|
+
Key names follow the browser's [`KeyboardEvent.key`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key) values.
|
|
536
339
|
|
|
537
340
|
### Mouse & Touch
|
|
538
341
|
|
|
539
|
-
`punter.mouse` tracks the current pointer position and whether a click or tap has occurred.
|
|
540
|
-
|
|
541
342
|
```js
|
|
542
343
|
punter.on('update', function () {
|
|
543
344
|
if (punter.mouse.clicked) {
|
|
544
|
-
// player tapped or clicked - punter.mouse.x / .y has the position
|
|
545
345
|
fireAt(punter.mouse.x, punter.mouse.y);
|
|
546
346
|
}
|
|
547
347
|
});
|
|
548
348
|
```
|
|
549
349
|
|
|
550
|
-
Property |
|
|
551
|
-
|
|
552
|
-
`punter.mouse.x` |
|
|
553
|
-
`punter.mouse.y` |
|
|
554
|
-
`punter.mouse.clicked` | `
|
|
555
|
-
|
|
556
|
-
> Coordinates are in canvas pixel space — the same coordinate system as `punter.width` / `punter.height` and sprite `x`/`y` values.
|
|
350
|
+
Property | Description
|
|
351
|
+
-----------------------|---------------------------------------------------
|
|
352
|
+
`punter.mouse.x` | X position of the last click/tap in canvas pixels.
|
|
353
|
+
`punter.mouse.y` | Y position of the last click/tap in canvas pixels.
|
|
354
|
+
`punter.mouse.clicked` | `true` for one update tick after a click or tap.
|
|
557
355
|
|
|
558
356
|
### `punter.clearInput()`
|
|
559
|
-
|
|
560
|
-
Resets all keyboard key states to `false` and clears `mouse.clicked`. Called automatically whenever `punter.go()` switches scenes.
|
|
561
|
-
|
|
562
|
-
```js
|
|
563
|
-
punter.on('update', function () {
|
|
564
|
-
if (punter.mouse.clicked) {
|
|
565
|
-
punter.go('level1');
|
|
566
|
-
// no need to clear - punter.go() does it automatically
|
|
567
|
-
}
|
|
568
|
-
});
|
|
569
|
-
```
|
|
570
|
-
|
|
571
|
-
## Game Loop Control
|
|
572
|
-
|
|
573
|
-
The game loop is what makes `update` and `draw` fire continuously. It starts automatically when you call `punter.go()`. These methods let you pause and resume it manually - for example, when showing a pause menu.
|
|
574
|
-
|
|
575
|
-
```js
|
|
576
|
-
// Pause the loop (stops update and draw from firing)
|
|
577
|
-
punter.pause();
|
|
578
|
-
|
|
579
|
-
// Resume from where it left off
|
|
580
|
-
punter.resume();
|
|
581
|
-
|
|
582
|
-
// Force a single redraw without running update logic
|
|
583
|
-
// Useful after a resize when the loop isn't running
|
|
584
|
-
punter.redraw();
|
|
585
|
-
```
|
|
586
|
-
|
|
587
|
-
Method | Description
|
|
588
|
-
------------------|-------------------------------------------------------
|
|
589
|
-
`punter.pause()` | Stops the game loop. The screen freezes.
|
|
590
|
-
`punter.resume()` | Restarts the game loop.
|
|
591
|
-
`punter.redraw()` | Redraws all sprites once without advancing game logic.
|
|
357
|
+
Resets all key states and clears `mouse.clicked`. Called automatically on `punter.go()`.
|
|
592
358
|
|
|
593
359
|
|
|
594
360
|
## Engine Properties
|
|
595
361
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
`punter.
|
|
601
|
-
`punter.
|
|
602
|
-
`punter.
|
|
603
|
-
`punter.
|
|
604
|
-
`punter.
|
|
605
|
-
`punter.
|
|
606
|
-
`punter.
|
|
607
|
-
`punter.
|
|
608
|
-
`punter.isMobile` | `boolean` | `true` if the engine detected a mobile device.
|
|
609
|
-
`punter.isDesktop` | `boolean` | `true` if the engine detected a desktop device.
|
|
610
|
-
`punter.orientation` | `string` | `'portrait'` or `'landscape'` based on current window dimensions.
|
|
611
|
-
`punter.sceneName` | `string` | The name of the currently active scene, or `''` if none.
|
|
612
|
-
`punter.debug` | `boolean` | Whether debug mode is active. Can be set at runtime: `punter.debug = true`.
|
|
613
|
-
`punter.canvas` | `HTMLCanvasElement` | The raw canvas element.
|
|
614
|
-
`punter.ctx` | `CanvasRenderingContext2D` | The canvas 2D drawing context.
|
|
615
|
-
`punter.sprites` | `Sprite[]` | Array of all live (non-destroyed) sprites.
|
|
362
|
+
Property | Description
|
|
363
|
+
---------------------|-----------------------------------------------------
|
|
364
|
+
`punter.width` | Canvas width in pixels (DPR-adjusted).
|
|
365
|
+
`punter.height` | Canvas height in pixels (DPR-adjusted).
|
|
366
|
+
`punter.dpr` | Device pixel ratio (capped at `2`).
|
|
367
|
+
`punter.frame` | Frame counter, cycling 1–60.
|
|
368
|
+
`punter.running` | `true` if the game loop is running.
|
|
369
|
+
`punter.paused` | `true` if the loop is paused.
|
|
370
|
+
`punter.isMobile` | `true` on mobile devices.
|
|
371
|
+
`punter.orientation` | `'portrait'` or `'landscape'`.
|
|
372
|
+
`punter.sceneName` | Name of the current scene.
|
|
373
|
+
`punter.debug` | Toggle debug mode at runtime: `punter.debug = true`.
|
|
616
374
|
|
|
617
375
|
### `punter.getSprite(id)`
|
|
618
|
-
|
|
619
|
-
Retrieves a sprite by its ID. Returns `null` if not found.
|
|
376
|
+
Returns a sprite by ID, or `null` if not found.
|
|
620
377
|
|
|
621
378
|
```js
|
|
622
379
|
var player = punter.getSprite('player');
|
|
623
|
-
if (player) player.moveX(5);
|
|
624
380
|
```
|
|
625
381
|
|
|
626
382
|
|
|
627
|
-
##
|
|
628
|
-
|
|
629
|
-
As the engine runs, it automatically sets CSS variables and HTML attributes on the `<html>` element. You can use these in your CSS to style the page differently based on the current game state - for example, hiding the loader once assets are ready, or applying a different layout in landscape mode.
|
|
630
|
-
|
|
631
|
-
### HTML Attributes
|
|
383
|
+
## Game Loop Control
|
|
632
384
|
|
|
633
|
-
The
|
|
385
|
+
The loop starts automatically when you call `punter.go()`. Pause and resume it manually - for example, when showing a pause menu.
|
|
634
386
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
`data-punter-orientation` | `"portrait"` / `"landscape"` | Updated on every resize.
|
|
641
|
-
`data-punter-scene` | scene name string | The name of the current scene; updated on `punter.go()`.
|
|
387
|
+
```js
|
|
388
|
+
punter.pause(); // stop update + draw
|
|
389
|
+
punter.resume(); // restart
|
|
390
|
+
punter.redraw(); // single redraw without running update logic
|
|
391
|
+
```
|
|
642
392
|
|
|
643
|
-
**Example - show a loading screen:**
|
|
644
393
|
|
|
645
|
-
|
|
646
|
-
/* show loader until engine is ready */
|
|
647
|
-
#loader { display: block; }
|
|
648
|
-
html[data-punter-loading] #loader { display: block; }
|
|
649
|
-
html:not([data-punter-loading]) #loader { display: none; }
|
|
394
|
+
## CSS & HTML Hooks
|
|
650
395
|
|
|
651
|
-
|
|
652
|
-
html[data-punter-orientation="landscape"] .controls { flex-direction: row; }
|
|
653
|
-
html[data-punter-orientation="portrait"] .controls { flex-direction: column; }
|
|
654
|
-
```
|
|
396
|
+
The engine sets attributes on `<html>` as the game runs. Use them in CSS to react to game state.
|
|
655
397
|
|
|
656
|
-
|
|
398
|
+
Attribute | When set
|
|
399
|
+
--------------------------|-------------------------------------------------------------
|
|
400
|
+
`data-punter-loading` | Present while assets are loading; removed when `ready` fires
|
|
401
|
+
`data-punter-scene` | Updated on every `punter.go()` - value is the scene name
|
|
402
|
+
`data-punter-orientation` | `"portrait"` or `"landscape"`, updated on resize
|
|
403
|
+
`data-punter-device` | `"mobile"` or `"desktop"`, set on init
|
|
657
404
|
|
|
658
|
-
|
|
405
|
+
It also exposes CSS variables on `:root`, updated on resize:
|
|
659
406
|
|
|
660
407
|
Variable | Description
|
|
661
408
|
---------------|------------------------------------------
|
|
@@ -663,68 +410,32 @@ Variable | Description
|
|
|
663
410
|
`--punter-vph` | Viewport height in pixels (e.g. `667px`).
|
|
664
411
|
|
|
665
412
|
```css
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
}
|
|
413
|
+
/* hide loader once assets are ready */
|
|
414
|
+
html[data-punter-loading] #loader { display: block; }
|
|
415
|
+
html:not([data-punter-loading]) #loader { display: none; }
|
|
670
416
|
```
|
|
671
417
|
|
|
672
418
|
|
|
673
419
|
## Debug Mode
|
|
674
420
|
|
|
675
|
-
Turning on debug mode overlays useful information directly onto the game canvas - handy when you're trying to understand why a sprite is in the wrong place or why a collision isn't triggering.
|
|
676
|
-
|
|
677
|
-
Enable it in `punter.init()`:
|
|
678
|
-
|
|
679
421
|
```js
|
|
680
|
-
punter.
|
|
422
|
+
punter.setup({ canvas: '#game', debug: true }); // enable at startup
|
|
423
|
+
punter.debug = true; // or toggle at runtime
|
|
681
424
|
```
|
|
682
425
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
punter.debug = false;
|
|
688
|
-
```
|
|
689
|
-
|
|
690
|
-
When debug mode is active, the engine draws:
|
|
691
|
-
|
|
692
|
-
- **FPS counter** and current canvas resolution in the bottom-right corner
|
|
693
|
-
- **Red bounding boxes** around each collidable sprite's collision area
|
|
694
|
-
- **Position and size labels** (`x=100 y=200 (32x32)`) near each sprite
|
|
695
|
-
|
|
696
|
-
### Customising the Debug Overlay
|
|
697
|
-
|
|
698
|
-
The overlay colours and font are pulled from CSS variables on `:root`. Add these to your stylesheet to customise them:
|
|
699
|
-
|
|
700
|
-
```css
|
|
701
|
-
:root {
|
|
702
|
-
--punter-debug-background: rgba(0, 0, 0, 0.6);
|
|
703
|
-
--punter-debug-text: #00ff00;
|
|
704
|
-
--punter-debug-font: 11px 'Courier New', monospace;
|
|
705
|
-
}
|
|
706
|
-
```
|
|
707
|
-
|
|
708
|
-
Variable | Default | Description
|
|
709
|
-
----------------------------|-------------------------|----------------------------------------
|
|
710
|
-
`--punter-debug-background` | `rgba(255,255,255,0.7)` | Background colour of debug label boxes.
|
|
711
|
-
`--punter-debug-text` | `red` | Text colour of debug labels.
|
|
712
|
-
`--punter-debug-font` | `12px monospace` | Font for debug labels.
|
|
426
|
+
When active, the engine draws:
|
|
427
|
+
- **FPS** and canvas resolution in the bottom-right corner
|
|
428
|
+
- **Red bounding boxes** around each collidable sprite
|
|
429
|
+
- **Position and size labels** near each sprite
|
|
713
430
|
|
|
714
431
|
|
|
715
432
|
## Browser Support
|
|
716
433
|
|
|
717
|
-
Works
|
|
718
|
-
|
|
719
|
-
punter.js requires:
|
|
434
|
+
Works in all modern browsers (Chrome, Firefox, Safari, Edge). For older browsers, polyfills for `fetch` and `Promise` are enough.
|
|
720
435
|
|
|
721
|
-
|
|
722
|
-
- `Promise`
|
|
723
|
-
- `requestAnimationFrame`
|
|
724
|
-
- `AudioContext` / `webkitAudioContext`
|
|
725
|
-
- `canvas` 2D context with `getImageData`
|
|
436
|
+
Requires: `fetch`, `Promise`, `requestAnimationFrame`, `AudioContext`, canvas `getImageData`.
|
|
726
437
|
|
|
727
|
-
|
|
438
|
+
> Don't manually resize the canvas - the engine manages that internally.
|
|
728
439
|
|
|
729
440
|
## License
|
|
730
441
|
|
package/games/pong.html
CHANGED
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
|
+
<title>Pong</title>
|
|
4
5
|
<meta charset="utf-8">
|
|
5
6
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<title>Pong</title>
|
|
7
7
|
<style>
|
|
8
|
-
* {
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
* {
|
|
9
|
+
margin: 0;
|
|
10
|
+
padding: 0;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
html, body {
|
|
15
|
+
width: 100%;
|
|
16
|
+
height: 100%;
|
|
17
|
+
background: #1e2230;
|
|
18
|
+
overflow: hidden;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
canvas {
|
|
22
|
+
display: block;
|
|
23
|
+
}
|
|
11
24
|
</style>
|
|
12
25
|
<script src="../punter.js"></script>
|
|
13
26
|
</head>
|
|
@@ -80,9 +93,9 @@
|
|
|
80
93
|
// SETUP
|
|
81
94
|
// Load the two sprite images before the game starts.
|
|
82
95
|
// =========================================================
|
|
83
|
-
punter.
|
|
96
|
+
punter.setup({
|
|
84
97
|
canvas: '#game',
|
|
85
|
-
|
|
98
|
+
images: {
|
|
86
99
|
paddle: '../images/pong/sprites/paddle.png',
|
|
87
100
|
ball: '../images/pong/sprites/ball.png'
|
|
88
101
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "punter.js",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "A
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "A simple, dependency-free 2D game engine for the browser. No build step, no frameworks - just one script tag.",
|
|
5
5
|
"main": "punter.js",
|
|
6
6
|
"directories": {
|
|
7
7
|
"test": "tests"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "npx serve .",
|
|
11
|
+
"start:pong": "npx serve . -l 4000 & npx open-cli http://localhost:4000/games/pong.html",
|
|
11
12
|
"test": ". \"$NVM_DIR/nvm.sh\" && nvm use && NODE_ENV=test TZ=UTC node_modules/.bin/jasmine --config=tests/jasmine/config.json"
|
|
12
13
|
},
|
|
13
14
|
"repository": {
|
package/punter.js
CHANGED
|
@@ -106,11 +106,11 @@
|
|
|
106
106
|
})();
|
|
107
107
|
|
|
108
108
|
/**
|
|
109
|
-
* Initialises the engine with canvas,
|
|
109
|
+
* Initialises the engine with canvas, images, sounds, and buttons
|
|
110
110
|
* @param {Object} config - configuration object
|
|
111
111
|
* @returns {void}
|
|
112
112
|
*/
|
|
113
|
-
function
|
|
113
|
+
function setup(config) {
|
|
114
114
|
|
|
115
115
|
config = config || {};
|
|
116
116
|
|
|
@@ -151,11 +151,11 @@
|
|
|
151
151
|
_boundsCanvas = document.createElement('canvas');
|
|
152
152
|
_boundsCtx = _boundsCanvas.getContext('2d', { willReadFrequently: true });
|
|
153
153
|
|
|
154
|
-
|
|
154
|
+
loadImages(config.images || {}).then(function() {
|
|
155
155
|
|
|
156
156
|
// precompute bounds for all sprite frames
|
|
157
|
-
if (config.
|
|
158
|
-
for (var key in config.
|
|
157
|
+
if (config.images) {
|
|
158
|
+
for (var key in config.images) {
|
|
159
159
|
var img = images[key];
|
|
160
160
|
if (img && img.complete && img.naturalWidth) {
|
|
161
161
|
// this warms up the boundingCache
|
|
@@ -182,13 +182,13 @@
|
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
/**
|
|
185
|
-
* Loads and decodes all
|
|
186
|
-
* @param {Object}
|
|
185
|
+
* Loads and decodes all images
|
|
186
|
+
* @param {Object} images - Key-value map of image keys to image URLs
|
|
187
187
|
* @returns {Promise} - Resolves when all images are loaded and decoded
|
|
188
188
|
*/
|
|
189
|
-
function
|
|
189
|
+
function loadImages(imageMap) {
|
|
190
190
|
|
|
191
|
-
var keys = Object.keys(
|
|
191
|
+
var keys = Object.keys(imageMap);
|
|
192
192
|
var total = keys.length;
|
|
193
193
|
|
|
194
194
|
if (!total) return Promise.resolve();
|
|
@@ -234,7 +234,7 @@
|
|
|
234
234
|
|
|
235
235
|
for (var i = 0; i < total; i++) {
|
|
236
236
|
var key = keys[i];
|
|
237
|
-
var url =
|
|
237
|
+
var url = imageMap[key];
|
|
238
238
|
var img = new Image();
|
|
239
239
|
img.key = key; // for debugging
|
|
240
240
|
img.onload = handleLoad.bind(img, key);
|
|
@@ -1003,7 +1003,7 @@
|
|
|
1003
1003
|
*/
|
|
1004
1004
|
function startLoop() {
|
|
1005
1005
|
|
|
1006
|
-
if (!_initilised) throw new Error('punter.
|
|
1006
|
+
if (!_initilised) throw new Error('punter.setup must be called first');
|
|
1007
1007
|
|
|
1008
1008
|
_canvasCtx = _canvas.getContext('2d', { alpha: true, desynchronized: true });
|
|
1009
1009
|
_canvasCtx.imageSmoothingEnabled = false;
|
|
@@ -1067,7 +1067,7 @@
|
|
|
1067
1067
|
|
|
1068
1068
|
function pauseLoop() {
|
|
1069
1069
|
|
|
1070
|
-
if (!_initilised) throw new Error('punter.
|
|
1070
|
+
if (!_initilised) throw new Error('punter.setup must be called first');
|
|
1071
1071
|
|
|
1072
1072
|
if (_loopId !== null) {
|
|
1073
1073
|
cancelAnimationFrame(_loopId);
|
|
@@ -1090,7 +1090,7 @@
|
|
|
1090
1090
|
*/
|
|
1091
1091
|
function playSound(name, options) {
|
|
1092
1092
|
|
|
1093
|
-
if (!_initilised) throw new Error('punter.
|
|
1093
|
+
if (!_initilised) throw new Error('punter.setup must be called first');
|
|
1094
1094
|
|
|
1095
1095
|
var buffer = sounds[name];
|
|
1096
1096
|
if (!buffer) return;
|
|
@@ -1131,7 +1131,7 @@
|
|
|
1131
1131
|
|
|
1132
1132
|
function stopSound(name) {
|
|
1133
1133
|
|
|
1134
|
-
if (!_initilised) throw new Error('punter.
|
|
1134
|
+
if (!_initilised) throw new Error('punter.setup must be called first');
|
|
1135
1135
|
|
|
1136
1136
|
var arr = playingSounds[name];
|
|
1137
1137
|
if (!arr || !arr.length) return;
|
|
@@ -1296,7 +1296,7 @@
|
|
|
1296
1296
|
var api = {
|
|
1297
1297
|
|
|
1298
1298
|
// initialization
|
|
1299
|
-
|
|
1299
|
+
setup: setup,
|
|
1300
1300
|
|
|
1301
1301
|
// scene lifecycle
|
|
1302
1302
|
scene: function (name, handler) {
|
|
@@ -1304,7 +1304,7 @@
|
|
|
1304
1304
|
},
|
|
1305
1305
|
go: function (name) {
|
|
1306
1306
|
|
|
1307
|
-
if (!_initilised) throw new Error('punter.
|
|
1307
|
+
if (!_initilised) throw new Error('punter.setup must be called first');
|
|
1308
1308
|
if (!_scenes[name]) throw new Error('punter.go: unknown scene "' + name + '"');
|
|
1309
1309
|
|
|
1310
1310
|
// remove existing game loop handlers
|
|
@@ -1355,7 +1355,7 @@
|
|
|
1355
1355
|
|
|
1356
1356
|
// sprite factory
|
|
1357
1357
|
createSprite: function(opts) {
|
|
1358
|
-
if (!_initilised) throw new Error('createSprite: punter.
|
|
1358
|
+
if (!_initilised) throw new Error('createSprite: punter.setup must be called first');
|
|
1359
1359
|
return new Sprite(opts);
|
|
1360
1360
|
},
|
|
1361
1361
|
getSprite: function(id) {
|
package/tests/interface-spec.js
CHANGED
|
@@ -19,7 +19,7 @@ describe('Interface', function () {
|
|
|
19
19
|
it('exposes the expected methods', async function () {
|
|
20
20
|
var result = await page.evaluate(function () {
|
|
21
21
|
return {
|
|
22
|
-
|
|
22
|
+
setup: typeof punter.setup,
|
|
23
23
|
scene: typeof punter.scene,
|
|
24
24
|
go: typeof punter.go,
|
|
25
25
|
on: typeof punter.on,
|