Compare commits
62 Commits
Author | SHA1 | Date | |
---|---|---|---|
9f43f63ddf | |||
439f5080de | |||
3465bdad68 | |||
b9a357130b | |||
d2ded2599c | |||
bbe0923523 | |||
c7a5df7923 | |||
10af6cf109 | |||
782f2f6eff | |||
495856ee53 | |||
03fa6c32b0 | |||
c2d9a0a846 | |||
c4baa0ce15 | |||
56ed8b369e | |||
b84c53b111 | |||
8c5ab18f70 | |||
2c28b9205c | |||
7c480c7a2c | |||
64a3dcd0cc | |||
cc78984783 | |||
0c042160ab | |||
825c02532b | |||
9da289ea62 | |||
e93c7fe6f1 | |||
e8cf095cec | |||
401c59d172 | |||
2ee388f7eb | |||
9da6f3990f | |||
eb02467c40 | |||
c52cb48a70 | |||
5e398094f0 | |||
b049787caa | |||
27a30fd0ba | |||
424ce603c1 | |||
1452b28732 | |||
57da262827 | |||
6d735bdfca | |||
8aa3215f5f | |||
8ea643b1bb | |||
612317d752 | |||
0e1f31eaba | |||
17a13eb0c0 | |||
fb755bec7e | |||
aff23993c5 | |||
9b5e993062 | |||
0dc789a168 | |||
f7d6ba99dd | |||
22f4cb851e | |||
7002b5f782 | |||
0cbcfda28b | |||
fae7b38912 | |||
c31cd2b30a | |||
fb3aa86e1d | |||
6db704f2c8 | |||
5bc38b8e65 | |||
924af6745e | |||
cb46be0088 | |||
3a210d1b60 | |||
72633d8c0f | |||
3490dd07ae | |||
e878626d54 | |||
1f096d1ca4 |
2
.eslintignore
Normal file
2
.eslintignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
projects
|
||||||
|
examples
|
33
.eslintrc.js
Normal file
33
.eslintrc.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
module.exports = {
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"commonjs": true,
|
||||||
|
"es6": true
|
||||||
|
},
|
||||||
|
"extends": "eslint:recommended",
|
||||||
|
"globals": {
|
||||||
|
"Atomics": "readonly",
|
||||||
|
"SharedArrayBuffer": "readonly"
|
||||||
|
},
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2018
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"indent": [
|
||||||
|
"error",
|
||||||
|
"tab"
|
||||||
|
],
|
||||||
|
"linebreak-style": [
|
||||||
|
"error",
|
||||||
|
"unix"
|
||||||
|
],
|
||||||
|
"quotes": [
|
||||||
|
"error",
|
||||||
|
"double"
|
||||||
|
],
|
||||||
|
"semi": [
|
||||||
|
"error",
|
||||||
|
"always"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
48
README.md
48
README.md
@ -1,12 +1,48 @@
|
|||||||
|
![asdf-games](https://gitea.arnweb.nl/arne/asdf-games/raw/branch/master/res/asdf-logo.png "asdf-games logo")
|
||||||
# asdf-games
|
# asdf-games
|
||||||
|
|
||||||
Me making games using HTML5 Games: Novice to Ninja by Sitepoint.
|
My attempt at making the framework featured in the book HTML5 Games: Novice to Ninja.
|
||||||
[You can find the book here.](https://www.sitepoint.com/premium/books/html5-games-novice-to-ninja)
|
[You can find the book here.](https://www.sitepoint.com/premium/books/html5-games-novice-to-ninja)
|
||||||
|
|
||||||
The projects here are very similar to those featured in the book, and it will take some time before I get to make my own game.
|
I turned the framework featured in the book into an npm package for use with browserify or Electron.
|
||||||
|
If you are on Github or Gitea now, you can find the npm package [here](https://www.npmjs.com/package/asdf-games).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
To use asdf framework in your projects, you need to:
|
||||||
|
* Install npm on your device (if you don't have it already)
|
||||||
|
* Run ```npm install asdf-games```
|
||||||
|
* Use the snippet below to start off or check out one of the examples, keep in mind that the examples are not comepletely up to date.
|
||||||
|
* Thanks to my friend [Job](https://jobbel.nl/), this project supports TypeScript typings. Make sure your editor supports them to make your life easier.
|
||||||
|
|
||||||
|
|
||||||
|
## Example usage
|
||||||
|
```javascript
|
||||||
|
// Require asdf
|
||||||
|
const asdf = require("asdf-games");
|
||||||
|
|
||||||
|
// Add whatever classes you need here
|
||||||
|
const [ Game, Sprite, Texture, KeyControls ] = asdf;
|
||||||
|
|
||||||
|
// Game(width, height, disable pixel smoothening)
|
||||||
|
var game = new Game(720, 480, true);
|
||||||
|
|
||||||
|
// Any picture URL used in new Texture() must be relative to the location of the HTML file
|
||||||
|
const playerTexture = new Texture('player.png');
|
||||||
|
|
||||||
|
var player = new Sprite(texture);
|
||||||
|
player.pos = {
|
||||||
|
x: (game.w / 2) - (player.w / 2),
|
||||||
|
y: (game.h / 2) - (player.h / 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add your entities to the game's scene
|
||||||
|
game.scene.add(player);
|
||||||
|
|
||||||
|
game.run(() => {
|
||||||
|
// Game loop
|
||||||
|
});
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
To try out asdf library and the projects for yourself, you need to:
|
|
||||||
* TODO
|
|
||||||
|
|
||||||
Releases will be featured on
|
|
||||||
[ARNweb Games](https://arnweb.nl/games/)
|
|
65
lib/AnimManager.js
Normal file
65
lib/AnimManager.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
class Anim {
|
||||||
|
constructor(frames, rate) {
|
||||||
|
this.frames = frames;
|
||||||
|
this.rate = rate;
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.frame = this.frames[0];
|
||||||
|
this.curFrame = 0;
|
||||||
|
this.curTime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(dt) {
|
||||||
|
const { rate, frames } = this;
|
||||||
|
if ((this.curTime += dt) > rate) {
|
||||||
|
this.curFrame++;
|
||||||
|
this.frame = frames[this.curFrame % frames.length];
|
||||||
|
this.curTime -= rate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AnimManager {
|
||||||
|
constructor(e = { x: 0, y: 0 }) {
|
||||||
|
this.anims = {};
|
||||||
|
this.running = false;
|
||||||
|
this.frameSource = e.frame || e;
|
||||||
|
this.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
add(name, frames, speed) {
|
||||||
|
this.anims[name] = new Anim(frames, speed);
|
||||||
|
return this.anims[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
update(dt) {
|
||||||
|
const { current, anims, frameSource } = this;
|
||||||
|
if (!current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const anim = anims[current];
|
||||||
|
anim.update(dt);
|
||||||
|
|
||||||
|
// Sync the tileSprite frame
|
||||||
|
frameSource.x = anim.frame.x;
|
||||||
|
frameSource.y = anim.frame.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
play(anim) {
|
||||||
|
const { current, anims } = this;
|
||||||
|
if (anim === current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.current = anim;
|
||||||
|
anims[anim].reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
this.current = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = AnimManager;
|
||||||
|
|
54
lib/Camera.js
Normal file
54
lib/Camera.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
const Container = require("./Container");
|
||||||
|
const math = require("./utilities/math");
|
||||||
|
|
||||||
|
class Camera extends Container {
|
||||||
|
constructor(subject, viewport, worldSize = viewport) {
|
||||||
|
super();
|
||||||
|
this.w = viewport.w;
|
||||||
|
this.h = viewport.h;
|
||||||
|
this.worldSize = worldSize;
|
||||||
|
this.setSubject(subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSubject(e) {
|
||||||
|
this.subject = e ? e.pos || e : this.pos;
|
||||||
|
this.offset = { x: 0, y: 0 };
|
||||||
|
|
||||||
|
// Center on the entity
|
||||||
|
if (e && e.w) {
|
||||||
|
this.offset.x += e.w / 2;
|
||||||
|
this.offset.y += e.h / 2;
|
||||||
|
}
|
||||||
|
if (e && e.anchor) {
|
||||||
|
this.offset.x -= e.anchor.x;
|
||||||
|
this.offset.y -= e.anchor.y;
|
||||||
|
}
|
||||||
|
this.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
const { pos, w, h, worldSize, subject, offset } = this;
|
||||||
|
|
||||||
|
const centeredX = subject.x + offset.x - w / 2;
|
||||||
|
const maxX = worldSize.w - w;
|
||||||
|
const x = -math.clamp(centeredX, 0, maxX);
|
||||||
|
|
||||||
|
const centeredY = subject.y + offset.y - h / 2;
|
||||||
|
const maxY = worldSize.h - h;
|
||||||
|
const y = -math.clamp(centeredY, 0, maxY);
|
||||||
|
|
||||||
|
pos.x = x;
|
||||||
|
pos.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(dt, t) {
|
||||||
|
super.update(dt, t);
|
||||||
|
|
||||||
|
if (this.subject) {
|
||||||
|
this.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = Camera;
|
@ -1,30 +1,30 @@
|
|||||||
class Container {
|
class Container {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.pos = { x: 0, y: 0 };
|
this.pos = { x: 0, y: 0 };
|
||||||
this.children = [];
|
this.children = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
add(child) {
|
add(child) {
|
||||||
this.children.push(child);
|
this.children.push(child);
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(child) {
|
remove(child) {
|
||||||
this.children = this.children.filter(c => c !== child);
|
this.children = this.children.filter(c => c !== child);
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
map(f) {
|
map(f) {
|
||||||
return this.children.map(f);
|
return this.children.map(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
update(dt, t) {
|
update(dt, t) {
|
||||||
this.children = this.children.filter(child => {
|
this.children = this.children.filter(child => {
|
||||||
if (child.update) {
|
if (child.update) {
|
||||||
child.update(dt, t, this);
|
child.update(dt, t, this);
|
||||||
}
|
}
|
||||||
return child.dead ? false : true;
|
return child.dead ? false : true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
module.exports = Container;
|
module.exports = Container;
|
||||||
|
53
lib/Game.js
53
lib/Game.js
@ -1,40 +1,43 @@
|
|||||||
var Container = require("./Container"),
|
var Container = require("./Container"),
|
||||||
CanvasRenderer = require('./renderer/CanvasRenderer')
|
CanvasRenderer = require("./renderer/CanvasRenderer")
|
||||||
;
|
;
|
||||||
|
|
||||||
const STEP = 1 / 60;
|
const STEP = 1 / 60;
|
||||||
const FRAME_MAX = 5 * STEP;
|
const FRAME_MAX = 5 * STEP;
|
||||||
|
|
||||||
class Game {
|
class Game {
|
||||||
constructor(w, h, pixelated, parent = "#board") {
|
constructor(w, h, pixelated, parent = "#board") {
|
||||||
this.w = w;
|
this.w = w;
|
||||||
this.h = h;
|
this.h = h;
|
||||||
this.renderer = new CanvasRenderer(w, h);
|
this.renderer = new CanvasRenderer(w, h);
|
||||||
document.querySelector(parent).appendChild(this.renderer.view);
|
document.querySelector(parent).appendChild(this.renderer.view);
|
||||||
|
|
||||||
if (pixelated) {
|
if (pixelated) {
|
||||||
this.renderer.setPixelated();
|
this.renderer.setPixelated();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.scene = new Container();
|
this.scene = new Container();
|
||||||
}
|
this.paused = false;
|
||||||
|
}
|
||||||
|
|
||||||
run(gameUpdate = () => { }) {
|
run(gameUpdate = () => { }) {
|
||||||
let dt = 0;
|
let dt = 0;
|
||||||
let last = 0;
|
let last = 0;
|
||||||
const loop = ms => {
|
const loop = ms => {
|
||||||
requestAnimationFrame(loop);
|
requestAnimationFrame(loop);
|
||||||
|
|
||||||
const t = ms / 1000;
|
const t = ms / 1000;
|
||||||
dt = Math.min(t - last, FRAME_MAX);
|
dt = Math.min(t - last, FRAME_MAX);
|
||||||
last = t;
|
last = t;
|
||||||
|
|
||||||
this.scene.update(dt, t);
|
if (!this.paused) {
|
||||||
gameUpdate(dt, t);
|
this.scene.update(dt, t);
|
||||||
this.renderer.render(this.scene);
|
gameUpdate(dt, t);
|
||||||
};
|
this.renderer.render(this.scene);
|
||||||
requestAnimationFrame(loop);
|
}
|
||||||
}
|
};
|
||||||
|
requestAnimationFrame(loop);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Game;
|
module.exports = Game;
|
9
lib/Line.js
Normal file
9
lib/Line.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
class Line {
|
||||||
|
constructor(x1, y1, x2, y2, style = "#000000") {
|
||||||
|
this.pos = { x: x1, y: y1 };
|
||||||
|
this.target = { x: x2, y: y2 };
|
||||||
|
this.style = style;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Line;
|
10
lib/Rect.js
Normal file
10
lib/Rect.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
class Rect {
|
||||||
|
constructor(w, h, style = { fill: "#333" }) {
|
||||||
|
this.pos = { x: 0, y: 0 };
|
||||||
|
this.w = w;
|
||||||
|
this.h = h;
|
||||||
|
this.style = style;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Rect;
|
@ -1,10 +1,11 @@
|
|||||||
class Sprite {
|
class Sprite {
|
||||||
constructor(texture) {
|
constructor(texture) {
|
||||||
this.texture = texture;
|
this.texture = texture;
|
||||||
this.pos = { x: 0, y: 0 };
|
this.pos = { x: 0, y: 0 };
|
||||||
this.anchor = { x: 0, y: 0 };
|
this.anchor = { x: 0, y: 0 };
|
||||||
this.scale = { x: 1, y: 1 };
|
this.scale = { x: 1, y: 1 };
|
||||||
this.rotation = 0;
|
this.pivot = { x: 0, y: 0 };
|
||||||
}
|
this.rotation = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
module.exports = Sprite;
|
module.exports = Sprite;
|
||||||
|
@ -1,39 +1,39 @@
|
|||||||
class SpriteSheetXML {
|
class SpriteSheetXML {
|
||||||
constructor(url) {
|
constructor(url) {
|
||||||
this.array = [];
|
this.array = [];
|
||||||
this.fetchXMLtoArray(url);
|
this.fetchXMLtoArray(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchXMLtoArray(url) {
|
fetchXMLtoArray(url) {
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.open('GET', url, false);
|
xhr.open("GET", url, false);
|
||||||
xhr.send(null);
|
xhr.send(null);
|
||||||
|
|
||||||
if (xhr.status === 200) {
|
if (xhr.status === 200) {
|
||||||
var children = xhr.responseXML.children[0].children;
|
var children = xhr.responseXML.children[0].children;
|
||||||
for (let index = 0; index < children.length; index++) {
|
for (let index = 0; index < children.length; index++) {
|
||||||
const element = children[index];
|
const element = children[index];
|
||||||
this.array.push({
|
this.array.push({
|
||||||
name: element.attributes.name.nodeValue,
|
name: element.attributes.name.nodeValue,
|
||||||
x: element.attributes.x.nodeValue,
|
x: element.attributes.x.nodeValue,
|
||||||
y: element.attributes.y.nodeValue,
|
y: element.attributes.y.nodeValue,
|
||||||
width: element.attributes.width.nodeValue,
|
width: element.attributes.width.nodeValue,
|
||||||
height: element.attributes.height.nodeValue
|
height: element.attributes.height.nodeValue
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error('XML file cannot be loaded!')
|
console.error("XML file cannot be loaded!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
findIndex(attribute, value) {
|
findIndex(attribute, value) {
|
||||||
for (let index = 0; index < this.array.length; index++) {
|
for (let index = 0; index < this.array.length; index++) {
|
||||||
const element = this.array[index];
|
const element = this.array[index];
|
||||||
if (element[attribute] == value) {
|
if (element[attribute] == value) {
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = SpriteSheetXML;
|
module.exports = SpriteSheetXML;
|
29
lib/State.js
Normal file
29
lib/State.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
class State {
|
||||||
|
constructor(state) {
|
||||||
|
this.set(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
set(state) {
|
||||||
|
this.last = this.state;
|
||||||
|
this.state = state;
|
||||||
|
this.time = 0;
|
||||||
|
this.justSetState = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(dt) {
|
||||||
|
this.first = this.justSetState;
|
||||||
|
this.justSetStrate = false;
|
||||||
|
|
||||||
|
this.time += this.first ? 0 : dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
is(state) {
|
||||||
|
return this.state === state;
|
||||||
|
}
|
||||||
|
|
||||||
|
isIn(...states) {
|
||||||
|
return states.some(s => this.is(s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = State;
|
10
lib/Text.js
10
lib/Text.js
@ -1,8 +1,8 @@
|
|||||||
class Text {
|
class Text {
|
||||||
constructor(text = "", style = {}) {
|
constructor(text = "", style = {}) {
|
||||||
this.pos = { x: 0, y: 0 };
|
this.pos = { x: 0, y: 0 };
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.style = style;
|
this.style = style;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
module.exports = Text;
|
module.exports = Text;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
class Texture {
|
class Texture {
|
||||||
constructor(url) {
|
constructor(url) {
|
||||||
this.img = new Image();
|
this.img = new Image();
|
||||||
this.img.src = url;
|
this.img.src = url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
module.exports = Texture;
|
module.exports = Texture;
|
||||||
|
@ -1,25 +1,73 @@
|
|||||||
var Container = require("./Container"),
|
var Container = require("./Container"),
|
||||||
TileSprite = require("./TileSprite")
|
TileSprite = require("./TileSprite")
|
||||||
;
|
;
|
||||||
|
|
||||||
class TileMap extends Container {
|
class TileMap extends Container {
|
||||||
constructor(tiles, mapW, mapH, tileW, tileH, texture) {
|
constructor(tiles, mapW, mapH, tileW, tileH, texture) {
|
||||||
super();
|
super();
|
||||||
this.mapW = mapW;
|
this.mapW = mapW;
|
||||||
this.mapH = mapH;
|
this.mapH = mapH;
|
||||||
this.tileW = tileW;
|
this.tileW = tileW;
|
||||||
this.tileH = tileH;
|
this.tileH = tileH;
|
||||||
this.w = mapW * tileW;
|
this.w = mapW * tileW;
|
||||||
this.h = mapH * tileH;
|
this.h = mapH * tileH;
|
||||||
|
|
||||||
this.children = tiles.map((frame, i) => {
|
this.children = tiles.map((frame, i) => {
|
||||||
const s = new TileSprite(texture, tileW, tileH);
|
const s = new TileSprite(texture, tileW, tileH);
|
||||||
s.frame = frame;
|
s.frame = frame;
|
||||||
s.pos.x = i % mapW * tileW;
|
s.pos.x = i % mapW * tileW;
|
||||||
s.pos.y = Math.floor(i / mapW) * tileH;
|
s.pos.y = Math.floor(i / mapW) * tileH;
|
||||||
return s;
|
return s;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pixelToMapPos(pos) {
|
||||||
|
const { tileW, tileH } = this;
|
||||||
|
return {
|
||||||
|
x: Math.floor(pos.x / tileW),
|
||||||
|
y: Math.floor(pos.y / tileH)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
mapToPixelPos(mapPos) {
|
||||||
|
const { tileW, tileH } = this;
|
||||||
|
return {
|
||||||
|
x: mapPos.x * tileW,
|
||||||
|
y: mapPos.y * tileH
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
tileAtMapPos(mapPos) {
|
||||||
|
return this.children[mapPos.y * this.mapW + mapPos.x];
|
||||||
|
}
|
||||||
|
|
||||||
|
tileAtPixelPos(pos) {
|
||||||
|
return this.tileAtMapPos(this.pixelToMapPos(pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
setFrameAtMapPos(mapPos, frame) {
|
||||||
|
const tile = this.tileAtMapPos(mapPos);
|
||||||
|
tile.frame = frame;
|
||||||
|
return tile;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFrameAtPixelPos(pos, frame) {
|
||||||
|
return this.setFrameAtMapPos(this.pixelToMapPos(pos), frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
tilesAtCorners(bounds, xo = 0, yo = 0) {
|
||||||
|
return [
|
||||||
|
[bounds.x, bounds.y], // Top-left
|
||||||
|
[bounds.x + bounds.w, bounds.y], // Top-right
|
||||||
|
[bounds.x, bounds.y + bounds.h], // Bottom-left
|
||||||
|
[bounds.x + bounds.w, bounds.y + bounds.h] // Bottom-right
|
||||||
|
].map(([x, y]) =>
|
||||||
|
this.tileAtPixelPos({
|
||||||
|
x: x + xo,
|
||||||
|
y: y + yo
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = TileMap;
|
module.exports = TileMap;
|
@ -1,25 +1,73 @@
|
|||||||
var Container = require("./Container"),
|
var Container = require("./Container"),
|
||||||
TileSpriteXML = require("./TileSpriteXML")
|
TileSpriteXML = require("./TileSpriteXML")
|
||||||
;
|
;
|
||||||
|
|
||||||
class TileMapXML extends Container {
|
class TileMapXML extends Container {
|
||||||
constructor(tiles, mapW, mapH, texture, xml) {
|
constructor(tiles, mapW, mapH, texture, xml) {
|
||||||
super(texture);
|
super(texture);
|
||||||
this.mapW = mapW;
|
this.mapW = mapW;
|
||||||
this.mapH = mapH;
|
this.mapH = mapH;
|
||||||
this.tileW = xml.array[tiles[0]].width;
|
this.tileW = xml.array[tiles[0]].width;
|
||||||
this.tileH = xml.array[tiles[0]].height;
|
this.tileH = xml.array[tiles[0]].height;
|
||||||
this.w = mapW * this.tileW;
|
this.w = mapW * this.tileW;
|
||||||
this.h = mapH * this.tileH;
|
this.h = mapH * this.tileH;
|
||||||
|
|
||||||
this.children = tiles.map((frame, i) => {
|
this.children = tiles.map((frame, i) => {
|
||||||
const s = new TileSpriteXML(texture, xml, frame);
|
const s = new TileSpriteXML(texture, xml, frame);
|
||||||
s.frame = frame;
|
s.frame = frame;
|
||||||
s.pos.x = i % mapW * this.tileW;
|
s.pos.x = i % mapW * this.tileW;
|
||||||
s.pos.y = Math.floor(i / mapW) * this.tileH;
|
s.pos.y = Math.floor(i / mapW) * this.tileH;
|
||||||
return s;
|
return s;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pixelToMapPos(pos) {
|
||||||
|
const { tileW, tileH } = this;
|
||||||
|
return {
|
||||||
|
x: Math.floor(pos.x / tileW),
|
||||||
|
y: Math.floor(pos.y / tileH)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
mapToPixelPos(mapPos) {
|
||||||
|
const { tileW, tileH } = this;
|
||||||
|
return {
|
||||||
|
x: mapPos.x * tileW,
|
||||||
|
y: mapPos.y * tileH
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
tileAtMapPos(mapPos) {
|
||||||
|
return this.children[mapPos.y * this.mapW + mapPos.x];
|
||||||
|
}
|
||||||
|
|
||||||
|
tileAtPixelPos(pos) {
|
||||||
|
return this.tileAtMapPos(this.pixelToMapPos(pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
setFrameAtMapPos(mapPos, frame) {
|
||||||
|
const tile = this.tileAtMapPos(mapPos);
|
||||||
|
tile.frame = frame;
|
||||||
|
return tile;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFrameAtPixelPos(pos, frame) {
|
||||||
|
return this.setFrameAtMapPos(this.pixelToMapPos(pos), frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
tilesAtCorners(bounds, xo = 0, yo = 0) {
|
||||||
|
return [
|
||||||
|
[bounds.x, bounds.y], // Top-left
|
||||||
|
[bounds.x + bounds.w, bounds.y], // Top-right
|
||||||
|
[bounds.x, bounds.y + bounds.h], // Bottom-left
|
||||||
|
[bounds.x + bounds.w, bounds.y + bounds.h] // Bottom-right
|
||||||
|
].map(([x, y]) =>
|
||||||
|
this.tileAtPixelPos({
|
||||||
|
x: x + xo,
|
||||||
|
y: y + yo
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = TileMapXML;
|
module.exports = TileMapXML;
|
@ -1,12 +1,26 @@
|
|||||||
var Sprite = require("./Sprite");
|
var Sprite = require("./Sprite"),
|
||||||
|
AnimManager = require("./AnimManager");
|
||||||
|
|
||||||
class TileSprite extends Sprite {
|
class TileSprite extends Sprite {
|
||||||
constructor(texture, w, h) {
|
constructor(texture, w, h) {
|
||||||
super(texture);
|
super(texture);
|
||||||
this.tileW = w;
|
this.tileW = w;
|
||||||
this.tileH = h;
|
this.tileH = h;
|
||||||
this.frame = { x: 0, y: 0 };
|
this.frame = { x: 0, y: 0 };
|
||||||
}
|
this.anims = new AnimManager(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(dt) {
|
||||||
|
this.anims.update(dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
get w() {
|
||||||
|
return this.tileW * Math.abs(this.scale.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
get h() {
|
||||||
|
return this.tileH * Math.abs(this.scale.y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = TileSprite;
|
module.exports = TileSprite;
|
@ -1,13 +1,27 @@
|
|||||||
var Sprite = require("./Sprite");
|
var Sprite = require("./Sprite"),
|
||||||
|
AnimManager = require("./AnimManager");
|
||||||
|
|
||||||
class TileSpriteXML extends Sprite {
|
class TileSpriteXML extends Sprite {
|
||||||
constructor(texture, xml, index) {
|
constructor(texture, xml, index) {
|
||||||
super(texture);
|
super(texture);
|
||||||
var src = xml.array[index];
|
var src = xml.array[index];
|
||||||
this.imgPos = { x: src['x'], y: src['y'] };
|
this.imgPos = { x: src["x"], y: src["y"] };
|
||||||
this.width = src['width'];
|
this.width = src["width"];
|
||||||
this.height = src['height'];
|
this.height = src["height"];
|
||||||
}
|
this.anims = new AnimManager(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(dt) {
|
||||||
|
this.anims.update(dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
get w() {
|
||||||
|
return this.width * Math.abs(this.scale.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
get h() {
|
||||||
|
return this.height * Math.abs(this.scale.y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = TileSpriteXML;
|
module.exports = TileSpriteXML;
|
@ -1,61 +1,78 @@
|
|||||||
class KeyControls {
|
class KeyControls {
|
||||||
|
|
||||||
constructor() {
|
constructor(listen = true) {
|
||||||
this.keys = {};
|
this.keys = {};
|
||||||
// Bind event handlers
|
if (listen) {
|
||||||
document.addEventListener("keydown", e => {
|
// Bind event handlers
|
||||||
if ([37, 38, 39, 40].indexOf(e.which) >= 0) {
|
document.addEventListener("keydown", e => {
|
||||||
e.preventDefault();
|
if ([37, 38, 39, 40].indexOf(e.which) >= 0) {
|
||||||
}
|
e.preventDefault();
|
||||||
this.keys[e.which] = true;
|
}
|
||||||
}, false);
|
this.keys[e.which] = true;
|
||||||
document.addEventListener('keyup', e => {
|
}, false);
|
||||||
this.keys[e.which] = false;
|
document.addEventListener("keyup", e => {
|
||||||
}, false);
|
this.keys[e.which] = false;
|
||||||
}
|
}, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get action() {
|
get action() {
|
||||||
// Spacebar
|
// Spacebar
|
||||||
return this.keys[32];
|
return this.keys[32];
|
||||||
}
|
}
|
||||||
|
|
||||||
get x() {
|
get ctrl() {
|
||||||
// Arrow Left or A (WASD)
|
// Control
|
||||||
if (this.keys[37] || this.keys[65]) {
|
return this.keys[17];
|
||||||
return -1;
|
}
|
||||||
}
|
|
||||||
// Arrow Right or D (WASD)
|
|
||||||
if (this.keys[39] || this.keys[68]) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
get y() {
|
get shift() {
|
||||||
// Arrow Up or W (WASD)
|
// Shift
|
||||||
if (this.keys[38] || this.keys[87]) {
|
return this.keys[16];
|
||||||
return -1;
|
}
|
||||||
}
|
|
||||||
// Arrow Down or S (WASD)
|
get escape() {
|
||||||
if (this.keys[40] || this.keys[83]) {
|
// Escape
|
||||||
return 1;
|
return this.keys[27];
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
}
|
get x() {
|
||||||
|
// Arrow Left or A (WASD)
|
||||||
|
if (this.keys[37] || this.keys[65]) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// Arrow Right or D (WASD)
|
||||||
|
if (this.keys[39] || this.keys[68]) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get y() {
|
||||||
|
// Arrow Up or W (WASD)
|
||||||
|
if (this.keys[38] || this.keys[87]) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// Arrow Down or S (WASD)
|
||||||
|
if (this.keys[40] || this.keys[83]) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
key(key, value) {
|
key(key, value) {
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
this.keys[key] = value;
|
this.keys[key] = value;
|
||||||
}
|
}
|
||||||
return this.keys[key];
|
return this.keys[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
for (let key in this.keys) {
|
for (let key in this.keys) {
|
||||||
this.keys[key] = false;
|
this.keys[key] = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,45 +1,49 @@
|
|||||||
class MouseControls {
|
class MouseControls {
|
||||||
constructor(container) {
|
constructor(container, listen = true) {
|
||||||
this.el = container || document.body;
|
this.el = container || document.body;
|
||||||
// State
|
// State
|
||||||
this.pos = { x: 0, y: 0 };
|
this.pos = { x: 0, y: 0 };
|
||||||
this.isDown = false;
|
this.isDown = false;
|
||||||
this.pressed = false;
|
this.pressed = false;
|
||||||
this.released = false;
|
this.released = false;
|
||||||
// Handlers
|
|
||||||
document.addEventListener('mousemove', this.move.bind(this), false);
|
|
||||||
document.addEventListener('mousedown', this.down.bind(this), false);
|
|
||||||
document.addEventListener('mouseup', this.up.bind(this), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
mousePosFromEvent({ clientX, clientY }) {
|
if (listen) {
|
||||||
const { el, pos } = this;
|
// Handlers
|
||||||
const rect = el.getBoundingClientRect();
|
document.addEventListener("mousemove", this.move.bind(this), false);
|
||||||
const xr = el.width / el.clientWidth;
|
document.addEventListener("mousedown", this.down.bind(this), false);
|
||||||
const yr = el.height / el.clientHeight;
|
document.addEventListener("mouseup", this.up.bind(this), false);
|
||||||
pos.x = (clientX - rect.left) * xr;
|
}
|
||||||
pos.y = (clientY - rect.top) * yr;
|
|
||||||
}
|
|
||||||
|
|
||||||
move(e) {
|
}
|
||||||
this.mousePosFromEvent(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
down(e) {
|
mousePosFromEvent({ clientX, clientY }) {
|
||||||
this.isDown = true;
|
const { el, pos } = this;
|
||||||
this.pressed = true;
|
const rect = el.getBoundingClientRect();
|
||||||
this.mousePosFromEvent(e);
|
const xr = el.width / el.clientWidth;
|
||||||
}
|
const yr = el.height / el.clientHeight;
|
||||||
|
pos.x = (clientX - rect.left) * xr;
|
||||||
|
pos.y = (clientY - rect.top) * yr;
|
||||||
|
}
|
||||||
|
|
||||||
up() {
|
move(e) {
|
||||||
this.isDown = false;
|
this.mousePosFromEvent(e);
|
||||||
this.released = true;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
update() {
|
down(e) {
|
||||||
this.released = false;
|
this.isDown = true;
|
||||||
this.pressed = false;
|
this.pressed = true;
|
||||||
}
|
this.mousePosFromEvent(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
up() {
|
||||||
|
this.isDown = false;
|
||||||
|
this.released = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
this.released = false;
|
||||||
|
this.pressed = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = MouseControls;
|
module.exports = MouseControls;
|
||||||
|
372
lib/index.d.ts
vendored
372
lib/index.d.ts
vendored
@ -2,6 +2,11 @@ export as namespace asdf;
|
|||||||
|
|
||||||
type Coordinates = {x: number, y: number};
|
type Coordinates = {x: number, y: number};
|
||||||
|
|
||||||
|
class Renderable {
|
||||||
|
visible?: boolean;
|
||||||
|
dead?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TileSpriteXML class
|
* TileSpriteXML class
|
||||||
*/
|
*/
|
||||||
@ -87,6 +92,52 @@ export class TileMap extends Container<TileSprite> {
|
|||||||
* @param texture Texture instance of source image file
|
* @param texture Texture instance of source image file
|
||||||
*/
|
*/
|
||||||
constructor(tiles: Coordinates[], mapW: number, mapH: number, tileW: number, tileH: number, texture: Texture);
|
constructor(tiles: Coordinates[], mapW: number, mapH: number, tileW: number, tileH: number, texture: Texture);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates in which tile a pixel is located.
|
||||||
|
* @param pos The position of the pixel.
|
||||||
|
*/
|
||||||
|
pixelToMapPos(pos: Coordinates): Coordinates
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the pixel position of a tile in a map.
|
||||||
|
* @param pos The position of the tile in the map.
|
||||||
|
*/
|
||||||
|
mapToPixelPos(pos: Coordinates): Coordinates
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the TileSprite at a given map position.
|
||||||
|
* @param mapPos the tile of a given position.
|
||||||
|
*/
|
||||||
|
tileAtMapPos(mapPos: Coordinates): TileSprite
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the TileSprite at a given pixel position.
|
||||||
|
* @param pos The pixel position of the tile
|
||||||
|
*/
|
||||||
|
tileAtPixelPos(pos: Coordinates): TileSprite
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the frame attribute of the TileSprite at the given mapPos.
|
||||||
|
* @param mapPos the map position of the tile.
|
||||||
|
* @param frame the new value for the frame attribute.
|
||||||
|
*/
|
||||||
|
setFrameAtMapPos(mapPos: Coordinates, frame: Coordinates): TileSprite
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the frame attribute of the TileSprite at the given pixel position.
|
||||||
|
* @param mapPos the pixel position of the tile.
|
||||||
|
* @param frame the new value for the frame attribute.
|
||||||
|
*/
|
||||||
|
setFrameAtPixelPos(pos: Coordinates, frame: Coordinates): TileSprite
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the tiles at the corner of the bounds.
|
||||||
|
* @param bounds a rectangle which defines what the corner are.
|
||||||
|
* @param xo offset to the x values.
|
||||||
|
* @param yo offset to the y values.
|
||||||
|
*/
|
||||||
|
tilesAtCorners(bounds: {x: number, y: number, w: number, h: number}, xo?: number, yo?: number): TileSprite[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -103,27 +154,29 @@ export class Texture {
|
|||||||
constructor(url: string);
|
constructor(url: string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface TextStyleOptions {
|
||||||
|
font?: string,
|
||||||
|
fill?: string | CanvasGradient | CanvasPattern,
|
||||||
|
align?: CanvasTextAlign
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Text class
|
* Text class
|
||||||
*/
|
*/
|
||||||
export class Text {
|
export class Text extends Renderable {
|
||||||
|
|
||||||
pos: Coordinates;
|
pos: Coordinates;
|
||||||
text: string;
|
text: string;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
update?: (dt?: number, t?: number) => void;
|
update?: (dt?: number, t?: number) => void;
|
||||||
style: {
|
style: TextStyleOptions;
|
||||||
font?: string,
|
|
||||||
fill?: string | CanvasGradient | CanvasPattern,
|
|
||||||
align?: CanvasTextAlign
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prints styled text on canvas
|
* Prints styled text on canvas
|
||||||
* @param text Text to print
|
* @param text Text to print
|
||||||
* @param style Styles to apply to text
|
* @param style Styles to apply to text
|
||||||
*/
|
*/
|
||||||
constructor(text: string, style: {});
|
constructor(text: string, style: TextStyleOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -171,15 +224,13 @@ export class SpriteSheetXML {
|
|||||||
/**
|
/**
|
||||||
* Sprite class
|
* Sprite class
|
||||||
*/
|
*/
|
||||||
export class Sprite {
|
export class Sprite extends Renderable {
|
||||||
texture: Texture;
|
texture: Texture;
|
||||||
pos: Coordinates;
|
pos: Coordinates;
|
||||||
anchor: Coordinates;
|
anchor: Coordinates;
|
||||||
scale: Coordinates;
|
scale: Coordinates;
|
||||||
pivot: Coordinates;
|
pivot: Coordinates;
|
||||||
visible: boolean;
|
|
||||||
rotation: number;
|
rotation: number;
|
||||||
dead: boolean;
|
|
||||||
|
|
||||||
update?: (dt?: number, t?: number) => void;
|
update?: (dt?: number, t?: number) => void;
|
||||||
|
|
||||||
@ -198,7 +249,8 @@ export class Game {
|
|||||||
h: number;
|
h: number;
|
||||||
renderer: CanvasRenderer;
|
renderer: CanvasRenderer;
|
||||||
scene: Container<unknown>;
|
scene: Container<unknown>;
|
||||||
|
paused: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the games parameters
|
* Set the games parameters
|
||||||
* @param w Width of canvas
|
* @param w Width of canvas
|
||||||
@ -212,14 +264,14 @@ export class Game {
|
|||||||
* Start game loop
|
* Start game loop
|
||||||
* @param gameUpdate Function to run next to scene updates such as debug logging, etc.
|
* @param gameUpdate Function to run next to scene updates such as debug logging, etc.
|
||||||
*/
|
*/
|
||||||
run(gameUpdate: (dt?: number, t?: number) => void): void;
|
run(gameUpdate?: (dt?: number, t?: number) => void): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Container class
|
* Container class
|
||||||
*/
|
*/
|
||||||
export class Container<T> {
|
export class Container<T> {
|
||||||
pos: {x: number, y: number};
|
pos: Coordinates;
|
||||||
children: T[];
|
children: T[];
|
||||||
|
|
||||||
constructor();
|
constructor();
|
||||||
@ -249,9 +301,9 @@ export class Container<T> {
|
|||||||
* Updates all children when called
|
* Updates all children when called
|
||||||
* @param dt Delta time
|
* @param dt Delta time
|
||||||
* @param t Total time
|
* @param t Total time
|
||||||
* @returns Returns if the child is dead or not
|
* @returns Returns anything
|
||||||
*/
|
*/
|
||||||
update(dt: number, t: number): boolean;
|
update(dt: number, t: number): any;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -321,6 +373,24 @@ export class KeyControls {
|
|||||||
*/
|
*/
|
||||||
get action(): boolean;
|
get action(): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns value of ctrl key.
|
||||||
|
* @returns Key value
|
||||||
|
*/
|
||||||
|
get ctrl(): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns value of shift key.
|
||||||
|
* @returns Key value
|
||||||
|
*/
|
||||||
|
get shift(): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns value of escape key.
|
||||||
|
* @returns Key value
|
||||||
|
*/
|
||||||
|
get escape(): boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns -1 on Arrow Left or A
|
* Returns -1 on Arrow Left or A
|
||||||
*
|
*
|
||||||
@ -343,10 +413,10 @@ export class KeyControls {
|
|||||||
* @param value Value to set to key
|
* @param value Value to set to key
|
||||||
* @return Value of key
|
* @return Value of key
|
||||||
*/
|
*/
|
||||||
key(key: number, value: boolean): boolean;
|
key(key: number, value?: boolean): boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets default value to all keys
|
* Resets default value (false) to all keys
|
||||||
*/
|
*/
|
||||||
reset(): void;
|
reset(): void;
|
||||||
}
|
}
|
||||||
@ -381,7 +451,275 @@ export class CanvasRenderer {
|
|||||||
render(container: Container<unknown>, clear?: boolean): void
|
render(container: Container<unknown>, clear?: boolean): void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Anim represents a single animation.
|
||||||
|
*/
|
||||||
|
declare class Anim {
|
||||||
|
|
||||||
|
curTime: number;
|
||||||
|
frames: Coordinates[];
|
||||||
|
rate: number;
|
||||||
|
curFrame: number;
|
||||||
|
frame: Coordinates;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for an animation.
|
||||||
|
* @param frames A collection of coordinates for each frame in the texture file.
|
||||||
|
* @param rate The rate at which the animation plays.
|
||||||
|
*/
|
||||||
|
constructor(frames: Coordinates[], rate: number);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the animation to the first frame.
|
||||||
|
*/
|
||||||
|
reset(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Causes the animation to update based on dt.
|
||||||
|
* @param dt Delta time
|
||||||
|
*/
|
||||||
|
update(dt: number): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The AnimManager class
|
||||||
|
*/
|
||||||
|
export class AnimManager {
|
||||||
|
|
||||||
|
anims: {
|
||||||
|
[name: string]: Anim
|
||||||
|
}
|
||||||
|
running: boolean;
|
||||||
|
frameSource: Coordinates;
|
||||||
|
current: string | null;
|
||||||
|
|
||||||
|
constructor(e: Coordinates);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an animation to the AnimManager
|
||||||
|
* @param name The name for the animation
|
||||||
|
* @param frames Where each frame is located in the texture
|
||||||
|
* @param speed The speed at which the animation plays.
|
||||||
|
*/
|
||||||
|
add(name: string, frames: Coordinates[], speed: number): Anim;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current animation.
|
||||||
|
* @param dt delta time
|
||||||
|
*/
|
||||||
|
update(dt: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts playing an animation.
|
||||||
|
* @param anim The name of the animation
|
||||||
|
*/
|
||||||
|
play(anim: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops playing any animation
|
||||||
|
*/
|
||||||
|
stop(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Rect class
|
||||||
|
*/
|
||||||
|
export class Rect {
|
||||||
|
|
||||||
|
pos: Coordinates
|
||||||
|
w: number;
|
||||||
|
h: number;
|
||||||
|
style: {
|
||||||
|
fill: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a rectangle.
|
||||||
|
* @param w The width of the rectangle
|
||||||
|
* @param h The height of the rectangle
|
||||||
|
* @param style The styles of the rectangle
|
||||||
|
*/
|
||||||
|
constructor(w: number, h: number, style: { fill: string })
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Camera extends Container<unknown> {
|
||||||
|
|
||||||
|
w: number;
|
||||||
|
h: number;
|
||||||
|
worldSize: {w: number, h: number};
|
||||||
|
offset: Coordinates;
|
||||||
|
subject: Coordinates;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a camera object. This can be fed into the main scene: `scene.add(camera);`
|
||||||
|
* @param subject The entity on which should be focused
|
||||||
|
* @param viewport the size of the viewport - how much should be visible.
|
||||||
|
* @param worldSize the size of the whole map.
|
||||||
|
*/
|
||||||
|
constructor(subject: NumericalEntity | Coordinates, viewport: {w: number, h: number}, worldSize: {w: number, h: number})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the subject of the camera.
|
||||||
|
* @param e The entity that needs to be set as subject.
|
||||||
|
*/
|
||||||
|
setSubject(e: NumericalEntity | Coordinates): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the camera to the subject
|
||||||
|
*/
|
||||||
|
focus(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* update function for the container.
|
||||||
|
* @param t the elapsed time
|
||||||
|
* @param dt delta time
|
||||||
|
*/
|
||||||
|
update(t: number, dt: number): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SoundOptions {
|
||||||
|
loop?: boolean,
|
||||||
|
volume?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Sound {
|
||||||
|
|
||||||
|
src: string;
|
||||||
|
options: SoundOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiates HTML5 audio element for source audio file with control methods
|
||||||
|
* @param src Source audio file
|
||||||
|
* @param options Play settings
|
||||||
|
*/
|
||||||
|
constructor(src: string, options?: SoundOptions);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts playing the audio file
|
||||||
|
* @param overrides sets options for playing the sound using different setting as defined in `constructor()`
|
||||||
|
*/
|
||||||
|
play(overrides?: SoundOptions): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops playing the audio file
|
||||||
|
*/
|
||||||
|
stop(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NumericalEntityBase {pos: Coordinates}
|
||||||
|
interface NumericalSprite {w: number, h: number}
|
||||||
|
interface NumericalTileSprite {tileW: number, tileH: number}
|
||||||
|
|
||||||
|
type NumericalEntity = NumericalSprite | NumericalTileSprite
|
||||||
|
|
||||||
|
type NumericalEntityWithHitbox = {hitBox: NumericalEntity} & NumericalEntity
|
||||||
|
|
||||||
|
export namespace deadInTracks {
|
||||||
|
/**
|
||||||
|
* This functions checks whether ent walks against a non-walkable object and whether it should move in the x and y position and how much.
|
||||||
|
* The difference with wallslide is that deadInTracks stops the entity entirely when it touches a non-walkable surface.
|
||||||
|
* wallslide will move the entity in x or y if possible.
|
||||||
|
* @param ent The entity that is moving.
|
||||||
|
* @param map The TileMap the entity moves on.
|
||||||
|
* @param x The maximal movement on the x. default is 0
|
||||||
|
* @param y The maximal movement on the y. default is 0
|
||||||
|
* @returns Coordinates of how much the entity walks in x and y.
|
||||||
|
*/
|
||||||
|
export function deadInTracks(ent: NumericalEntity | NumericalEntityWithHitbox, map: TileMap, x?: number, y?: number): Coordinates;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace wallslide {
|
||||||
|
/**
|
||||||
|
* This functions checks whether ent walks against a non-walkable object and whether it should move in the x and y position and how much.
|
||||||
|
* The difference with wallslide is that deadInTracks stops the entity entirely when it touches a non-walkable surface.
|
||||||
|
* wallslide will move the entity in x or y if possible.
|
||||||
|
* @param ent The entity that is moving.
|
||||||
|
* @param map The TileMap the entity moves on.
|
||||||
|
* @param x The maximal movement on the x. default is 0
|
||||||
|
* @param y The maximal movement on the y. default is 0
|
||||||
|
* @returns Coordinates of how much the entity walks in x and y.
|
||||||
|
*/
|
||||||
|
export function wallslide(ent: NumericalEntity | NumericalEntityWithHitbox, map: TileMap, x?: number, y?: number): Coordinates;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace entity {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* addDebug adds a red border around the hitboxes of an entity.
|
||||||
|
* @param e The entity.
|
||||||
|
*/
|
||||||
|
export function addDebug<T extends Container<unknown> | NumericalEntityWithHitbox>(e: T): T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function checks if an entity hits anything in a container.
|
||||||
|
* @param entity The entity.
|
||||||
|
* @param container The container.
|
||||||
|
* @param hitCallback The callback that is executed when an entity hits something in the container.
|
||||||
|
*/
|
||||||
|
export function hits<T extends NumericalEntityWithHitbox | NumericalEntity>(entity: NumericalEntityWithHitbox | NumericalEntity, container: Container<T>, hitCallback: (e2: T) => any): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This functions calculates whether two entities hit each other.
|
||||||
|
* @param e1 The first entity.
|
||||||
|
* @param e2 The second entity.
|
||||||
|
*/
|
||||||
|
export function hit(e1: NumericalEntityWithHitbox | NumericalEntity, e2: NumericalEntityWithHitbox | NumericalEntity): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function calculates the angle relative to the x-axis between the centers of two entities.
|
||||||
|
* @param a The first entity.
|
||||||
|
* @param b The second entity.
|
||||||
|
* @returns the angle in radians.
|
||||||
|
*/
|
||||||
|
export function angle(a: NumericalEntity, b: NumericalEntity): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function calculates the full hitbox of an entity.
|
||||||
|
* @param entity The enitity
|
||||||
|
*/
|
||||||
|
export function bounds(entity: NumericalEntityWithHitbox | NumericalEntity): {x: number, y: number, w: number, h: number};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function calculates the distance between the centers of two entities.
|
||||||
|
* @param a The first entity.
|
||||||
|
* @param b The second entity.
|
||||||
|
*/
|
||||||
|
export function distance(a: NumericalEntity, b: NumericalEntity): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function calculates the center of an entity.
|
||||||
|
* @param entity The entity to calculate the center of.
|
||||||
|
*/
|
||||||
|
export function center(entity: NumericalEntity): number;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
export namespace math {
|
export namespace math {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function calculates the angle relative from the x-axis between two points.
|
||||||
|
* @param a The first point.
|
||||||
|
* @param b The second point.
|
||||||
|
* @returns The angle in radians.
|
||||||
|
*/
|
||||||
|
export function angle(a: Coordinates, b: Coordinates): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function calculates if x is between min and max.
|
||||||
|
* @param x A numerical value
|
||||||
|
* @param min A numerical value
|
||||||
|
* @param max A numerical value
|
||||||
|
* @returns x if x is between min and max.
|
||||||
|
*/
|
||||||
|
export function clamp(x: number, min: number, max: number): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the distance between two points
|
||||||
|
* @param a The first point.
|
||||||
|
* @param b The second point.
|
||||||
|
*/
|
||||||
|
export function distance(a: Coordinates, b: Coordinates): number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns random integer between min and max
|
* Returns random integer between min and max
|
||||||
* @param min Minimum value
|
* @param min Minimum value
|
||||||
|
74
lib/index.js
74
lib/index.js
@ -1,32 +1,50 @@
|
|||||||
var Container = require("./Container.js"),
|
var AnimManager = require("./AnimManager.js"),
|
||||||
CanvasRenderer = require("./renderer/CanvasRenderer.js"),
|
Camera = require("./Camera.js"),
|
||||||
Game = require("./Game.js"),
|
Container = require("./Container.js"),
|
||||||
math = require("./utilities/math.js"),
|
CanvasRenderer = require("./renderer/CanvasRenderer.js"),
|
||||||
KeyControls = require("./controls/KeyControls.js"),
|
Game = require("./Game.js"),
|
||||||
MouseControls = require("./controls/MouseControls.js"),
|
math = require("./utilities/math.js"),
|
||||||
Sprite = require("./Sprite.js"),
|
entity = require("./utilities/entity.js"),
|
||||||
TileMap = require("./TileMap.js"),
|
wallslide = require("./movement/wallslide.js"),
|
||||||
TileMapXML = require("./TileMapXML.js"),
|
deadInTracks = require("./movement/deadInTracks.js"),
|
||||||
TileSprite = require("./TileSprite.js"),
|
State = require("./State.js"),
|
||||||
TileSpriteXML = require("./TileSpriteXML.js"),
|
Sound = require("./sound/Sound.js"),
|
||||||
Text = require("./Text.js"),
|
Rect = require("./Rect.js"),
|
||||||
Texture = require("./Texture.js"),
|
Line = require("./Line.js"),
|
||||||
SpriteSheetXML = require("./SpriteSheetXML.js")
|
KeyControls = require("./controls/KeyControls.js"),
|
||||||
|
MouseControls = require("./controls/MouseControls.js"),
|
||||||
|
Sprite = require("./Sprite.js"),
|
||||||
|
TileMap = require("./TileMap.js"),
|
||||||
|
TileMapXML = require("./TileMapXML.js"),
|
||||||
|
TileSprite = require("./TileSprite.js"),
|
||||||
|
TileSpriteXML = require("./TileSpriteXML.js"),
|
||||||
|
Text = require("./Text.js"),
|
||||||
|
Texture = require("./Texture.js"),
|
||||||
|
SpriteSheetXML = require("./SpriteSheetXML.js")
|
||||||
;
|
;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
CanvasRenderer,
|
AnimManager,
|
||||||
Container,
|
CanvasRenderer,
|
||||||
Game,
|
Camera,
|
||||||
math,
|
Container,
|
||||||
KeyControls,
|
Game,
|
||||||
MouseControls,
|
math,
|
||||||
Sprite,
|
entity,
|
||||||
TileMap,
|
wallslide,
|
||||||
TileMapXML,
|
deadInTracks,
|
||||||
TileSprite,
|
State,
|
||||||
SpriteSheetXML,
|
Sound,
|
||||||
TileSpriteXML,
|
Rect,
|
||||||
Text,
|
Line,
|
||||||
Texture
|
KeyControls,
|
||||||
|
MouseControls,
|
||||||
|
Sprite,
|
||||||
|
TileMap,
|
||||||
|
TileMapXML,
|
||||||
|
TileSprite,
|
||||||
|
SpriteSheetXML,
|
||||||
|
TileSpriteXML,
|
||||||
|
Text,
|
||||||
|
Texture
|
||||||
};
|
};
|
||||||
|
16
lib/movement/deadInTracks.js
Normal file
16
lib/movement/deadInTracks.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
const entity = require("../utilities/entity");
|
||||||
|
|
||||||
|
function deadInTracks(ent, map, x = 0, y = 0) {
|
||||||
|
const bounds = entity.bounds(ent);
|
||||||
|
const tiles = map.tilesAtCorners(bounds, x, y);
|
||||||
|
const walks = tiles.map(t => t && t.frame.walkable);
|
||||||
|
const blocked = walks.some(w => !w);
|
||||||
|
if (blocked) {
|
||||||
|
x = 0,
|
||||||
|
y = 0;
|
||||||
|
}
|
||||||
|
return { x, y };
|
||||||
|
}
|
||||||
|
module.exports = {
|
||||||
|
deadInTracks
|
||||||
|
};
|
51
lib/movement/wallslide.js
Normal file
51
lib/movement/wallslide.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
const entity = require("../utilities/entity.js");
|
||||||
|
|
||||||
|
function wallslide(ent, map, x = 0, y = 0) {
|
||||||
|
let tiles;
|
||||||
|
let tileEdge;
|
||||||
|
const bounds = entity.bounds(ent);
|
||||||
|
|
||||||
|
// Final amounts of movement to allow
|
||||||
|
let xo = x;
|
||||||
|
let yo = y;
|
||||||
|
|
||||||
|
// Check vertical movement
|
||||||
|
if (y !== 0) {
|
||||||
|
tiles = map.tilesAtCorners(bounds, 0, yo);
|
||||||
|
const [tl, tr, bl, br] = tiles.map(t => t && t.frame.walkable);
|
||||||
|
|
||||||
|
// Hit your head
|
||||||
|
if (y < 0 && !(tl && tr)) {
|
||||||
|
tileEdge = tiles[0].pos.y + tiles[0].h;
|
||||||
|
yo = tileEdge - bounds.y;
|
||||||
|
}
|
||||||
|
// Hit your feet
|
||||||
|
if (y > 0 && !(bl && br)) {
|
||||||
|
tileEdge = tiles[2].pos.y - 1;
|
||||||
|
yo = tileEdge - (bounds.y + bounds.h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check horizontal movement
|
||||||
|
if (x !== 0) {
|
||||||
|
tiles = map.tilesAtCorners(bounds, xo, yo);
|
||||||
|
const [tl, tr, bl, br] = tiles.map(t => t && t.frame.walkable);
|
||||||
|
|
||||||
|
// Hit left edge
|
||||||
|
if (x < 0 && !(tl && bl)) {
|
||||||
|
tileEdge = tiles[0].pos.x + tiles[0].w;
|
||||||
|
xo = tileEdge - bounds.x;
|
||||||
|
}
|
||||||
|
// Hit right edge
|
||||||
|
if (x > 0 && !(tr && br)) {
|
||||||
|
tileEdge = tiles[1].pos.x - 1;
|
||||||
|
xo = tileEdge - (bounds.x + bounds.w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// xo & yo contain the amount we're allowed to move by.
|
||||||
|
return { x: xo, y: yo };
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
wallslide
|
||||||
|
};
|
@ -1,97 +1,115 @@
|
|||||||
class CanvasRenderer {
|
class CanvasRenderer {
|
||||||
|
|
||||||
constructor(w, h) {
|
constructor(w, h) {
|
||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
this.w = canvas.width = w;
|
this.w = canvas.width = w;
|
||||||
this.h = canvas.height = h;
|
this.h = canvas.height = h;
|
||||||
this.view = canvas;
|
this.view = canvas;
|
||||||
this.ctx = canvas.getContext("2d");
|
this.ctx = canvas.getContext("2d");
|
||||||
this.ctx.textBaseLine = "top";
|
this.ctx.textBaseLine = "top";
|
||||||
}
|
}
|
||||||
|
|
||||||
setPixelated() {
|
setPixelated() {
|
||||||
this.ctx['imageSmoothingEnabled'] = false; /* standard */
|
this.ctx["imageSmoothingEnabled"] = false; /* standard */
|
||||||
this.ctx['mozImageSmoothingEnabled'] = false; /* Firefox */
|
this.ctx["mozImageSmoothingEnabled"] = false; /* Firefox */
|
||||||
this.ctx['oImageSmoothingEnabled'] = false; /* Opera */
|
this.ctx["oImageSmoothingEnabled"] = false; /* Opera */
|
||||||
this.ctx['webkitImageSmoothingEnabled'] = false; /* Safari */
|
this.ctx["webkitImageSmoothingEnabled"] = false; /* Safari */
|
||||||
this.ctx['msImageSmoothingEnabled'] = false; /* IE */
|
this.ctx["msImageSmoothingEnabled"] = false; /* IE */
|
||||||
}
|
}
|
||||||
|
|
||||||
render(container, clear = true) {
|
render(container, clear = true) {
|
||||||
const { ctx } = this;
|
const { ctx } = this;
|
||||||
function renderRec(container) {
|
function renderRec(container) {
|
||||||
// Render container children
|
// Render container children
|
||||||
container.children.forEach(child => {
|
container.children.forEach(child => {
|
||||||
if (child.visible == false) {
|
if (child.visible == false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.save();
|
ctx.save();
|
||||||
|
|
||||||
if (child.pos) {
|
if (child.pos) {
|
||||||
ctx.translate(Math.round(child.pos.x), Math.round(child.pos.y));
|
ctx.translate(Math.round(child.pos.x), Math.round(child.pos.y));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (child.anchor) {
|
if (child.anchor) {
|
||||||
ctx.translate(child.anchor.x, child.anchor.y);
|
ctx.translate(child.anchor.x, child.anchor.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (child.scale) {
|
if (child.scale) {
|
||||||
ctx.scale(child.scale.x, child.scale.y);
|
ctx.scale(child.scale.x, child.scale.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (child.rotation) {
|
if (child.rotation) {
|
||||||
const px = child.pivot ? child.pivot.x : 0;
|
const px = child.pivot ? child.pivot.x : 0;
|
||||||
const py = child.pivot ? child.pivot.y : 0;
|
const py = child.pivot ? child.pivot.y : 0;
|
||||||
ctx.translate(px, py);
|
ctx.translate(px, py);
|
||||||
ctx.rotate(child.rotation);
|
ctx.rotate(child.rotation);
|
||||||
ctx.translate(-px, -py);
|
ctx.translate(-px, -py);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (child.text) {
|
if (child.text) {
|
||||||
const { font, fill, align } = child.style;
|
const { font, fill, align } = child.style;
|
||||||
if (font) ctx.font = font;
|
if (font) ctx.font = font;
|
||||||
if (fill) ctx.fillStyle = fill;
|
if (fill) ctx.fillStyle = fill;
|
||||||
if (align) ctx.textAlign = align;
|
if (align) ctx.textAlign = align;
|
||||||
ctx.fillText(child.text, 0, 0);
|
ctx.fillText(child.text, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (child.texture) {
|
else if (child.texture) {
|
||||||
const img = child.texture.img;
|
const img = child.texture.img;
|
||||||
if (child.tileW && child.tileH) {
|
if (child.tileW && child.tileH) {
|
||||||
ctx.drawImage(
|
ctx.drawImage(
|
||||||
img,
|
img,
|
||||||
child.frame.x * child.tileW,
|
child.frame.x * child.tileW,
|
||||||
child.frame.y * child.tileH,
|
child.frame.y * child.tileH,
|
||||||
child.tileW, child.tileH,
|
child.tileW, child.tileH,
|
||||||
0, 0,
|
0, 0,
|
||||||
child.tileW, child.tileH
|
child.tileW, child.tileH
|
||||||
);
|
);
|
||||||
} else if (child.imgPos && child.width && child.height) {
|
} else if (child.imgPos && child.width && child.height) {
|
||||||
ctx.drawImage(
|
ctx.drawImage(
|
||||||
img,
|
img,
|
||||||
child.imgPos.x,
|
child.imgPos.x,
|
||||||
child.imgPos.y,
|
child.imgPos.y,
|
||||||
child.width, child.height,
|
child.width, child.height,
|
||||||
0, 0,
|
0, 0,
|
||||||
child.width, child.height
|
child.width, child.height
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
ctx.drawImage(img, 0, 0);
|
ctx.drawImage(img, 0, 0);
|
||||||
}
|
}
|
||||||
}
|
} else if (child.style && child.w && child.h) {
|
||||||
|
ctx.fillStyle = child.style.fill;
|
||||||
|
ctx.fillRect(0, 0, child.w, child.h);
|
||||||
|
} else if (child.style && child.radius) {
|
||||||
|
var gradient = ctx.createRadialGradient(0, 0, 50, 0, 0, 50);
|
||||||
|
gradient.addColorStop(0, child.style.start);
|
||||||
|
gradient.addColorStop(1, child.style.stop);
|
||||||
|
|
||||||
// Handle children with children
|
ctx.arc(0, 0, child.radius, 0, 2 * Math.PI);
|
||||||
if (child.children) {
|
ctx.fillStyle = gradient;
|
||||||
renderRec(child);
|
|
||||||
}
|
ctx.fill();
|
||||||
ctx.restore();
|
} else if (child.style && child.target) {
|
||||||
})
|
ctx.beginPath();
|
||||||
}
|
ctx.moveTo(0,0);
|
||||||
if (clear) {
|
ctx.lineTo(child.target.x, child.target.y);
|
||||||
ctx.clearRect(0, 0, this.w, this.h);
|
ctx.strokeStyle = child.style;
|
||||||
}
|
ctx.stroke();
|
||||||
renderRec(container);
|
}
|
||||||
}
|
|
||||||
|
// Handle children with children
|
||||||
|
if (child.children) {
|
||||||
|
renderRec(child);
|
||||||
|
}
|
||||||
|
ctx.restore();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (clear) {
|
||||||
|
ctx.clearRect(0, 0, this.w, this.h);
|
||||||
|
}
|
||||||
|
renderRec(container);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
module.exports = CanvasRenderer;
|
module.exports = CanvasRenderer;
|
||||||
|
53
lib/sound/Sound.js
Normal file
53
lib/sound/Sound.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
class Sound {
|
||||||
|
constructor(src, options = {}) {
|
||||||
|
this.playing = false;
|
||||||
|
this.src = src;
|
||||||
|
this.options = Object.assign({ volume: 1 }, options);
|
||||||
|
|
||||||
|
// Configure audio element
|
||||||
|
const audio = new Audio();
|
||||||
|
audio.src = src;
|
||||||
|
if (options.loop) {
|
||||||
|
audio.loop = true;
|
||||||
|
}
|
||||||
|
audio.addEventListener(
|
||||||
|
"error",
|
||||||
|
() => {
|
||||||
|
throw Error(`Error loading audio: ${src}`);
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
audio.addEventListener(
|
||||||
|
"ended",
|
||||||
|
() => {
|
||||||
|
this.playing = false;
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
this.audio = audio;
|
||||||
|
}
|
||||||
|
|
||||||
|
play(overrides) {
|
||||||
|
const { audio, options } = this;
|
||||||
|
const opts = Object.assign({ time: 0 }, options, overrides);
|
||||||
|
audio.volume = opts.volume;
|
||||||
|
audio.currentTime = opts.time;
|
||||||
|
audio.play();
|
||||||
|
this.playing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
this.audio.pause();
|
||||||
|
this.playing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get volume() {
|
||||||
|
return this.audio.volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
set volume(volume) {
|
||||||
|
this.options.volume = this.audio.volume = volume;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Sound;
|
96
lib/utilities/entity.js
Normal file
96
lib/utilities/entity.js
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
const math = require("./math");
|
||||||
|
const Rect = require("../Rect");
|
||||||
|
|
||||||
|
function addDebug(e) {
|
||||||
|
e.children = e.children || [];
|
||||||
|
const bb = new Rect(e.w, e.h, { fill: "rgba(255, 0, 0, 0.3)" });
|
||||||
|
e.children.push(bb);
|
||||||
|
if (e.hitBox) {
|
||||||
|
const { x, y, w, h } = e.hitBox;
|
||||||
|
const hb = new Rect(w, h, { fill: "rgba(255, 0, 0, 0.5)" });
|
||||||
|
hb.pos.x = x;
|
||||||
|
hb.pos.y = y;
|
||||||
|
e.children.push(hb);
|
||||||
|
}
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
function angle(a, b) {
|
||||||
|
return math.angle(center(a), center(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
function bounds(entity) {
|
||||||
|
let pos, w, h, hitBox;
|
||||||
|
// Object.prototype.hasOwnProperty.call is needed because of eslint
|
||||||
|
if (Object.prototype.hasOwnProperty.call(entity, "tileW") && Object.prototype.hasOwnProperty.call(entity, "tileH")) {
|
||||||
|
({pos, w, h} = {pos: entity.pos, w: entity.tileW, h: entity.tileH});
|
||||||
|
} else {
|
||||||
|
({pos, w, h} = entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entity.hitBox) hitBox = entity.hitBox;
|
||||||
|
|
||||||
|
const hit = hitBox || { x: 0, y: 0, w, h };
|
||||||
|
return {
|
||||||
|
x: hit.x + pos.x,
|
||||||
|
y: hit.y + pos.y,
|
||||||
|
w: hit.w - 1,
|
||||||
|
h: hit.h - 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function center(entity) {
|
||||||
|
let pos, w, h;
|
||||||
|
|
||||||
|
// Object.prototype.hasOwnProperty.call is needed because of eslint
|
||||||
|
if (Object.prototype.hasOwnProperty.call(entity, "tileW") && Object.prototype.hasOwnProperty.call(entity, "tileH")) {
|
||||||
|
({pos, w, h} = {pos: entity.pos, w: entity.tileW, h: entity.tileH});
|
||||||
|
} else {
|
||||||
|
({pos, w, h} = entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: pos.x + w / 2,
|
||||||
|
y: pos.y + h / 2
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function distance(a, b) {
|
||||||
|
return math.distance(center(a), center(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
function hit(e1, e2) {
|
||||||
|
const a = bounds(e1);
|
||||||
|
const b = bounds(e2);
|
||||||
|
return (
|
||||||
|
a.x + a.w >= b.x &&
|
||||||
|
a.x <= b.x + b.w &&
|
||||||
|
a.y + a.h >= b.y &&
|
||||||
|
a.y <= b.y + b.h
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hits(entity, container, hitCallback) {
|
||||||
|
const a = bounds(entity);
|
||||||
|
container.map(e2 => {
|
||||||
|
const b = bounds(e2);
|
||||||
|
if (
|
||||||
|
a.x + a.w >= b.x &&
|
||||||
|
a.x <= b.x + b.w &&
|
||||||
|
a.y + a.h >= b.y &&
|
||||||
|
a.y <= b.y + b.h
|
||||||
|
) {
|
||||||
|
hitCallback(e2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
addDebug,
|
||||||
|
angle,
|
||||||
|
bounds,
|
||||||
|
center,
|
||||||
|
distance,
|
||||||
|
hit,
|
||||||
|
hits
|
||||||
|
};
|
@ -1,26 +1,48 @@
|
|||||||
|
function angle(a, b) {
|
||||||
|
const dx = a.x - b.x;
|
||||||
|
const dy = a.y - b.y;
|
||||||
|
const angle = Math.atan2(dy, dx);
|
||||||
|
|
||||||
|
return angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clamp(x, min, max) {
|
||||||
|
return Math.max(min, Math.min(x, max));
|
||||||
|
}
|
||||||
|
|
||||||
|
function distance (a, b) {
|
||||||
|
const dx = a.x - b.x;
|
||||||
|
const dy = a.y - b.y;
|
||||||
|
|
||||||
|
return Math.sqrt(dx * dx + dy * dy);
|
||||||
|
}
|
||||||
|
|
||||||
function rand(min, max) {
|
function rand(min, max) {
|
||||||
return Math.floor(randf(min, max));
|
return Math.floor(randf(min, max));
|
||||||
}
|
}
|
||||||
|
|
||||||
function randf(min, max) {
|
function randf(min, max) {
|
||||||
if (max == null) {
|
if (max == null) {
|
||||||
max = min || 1;
|
max = min || 1;
|
||||||
min = 0;
|
min = 0;
|
||||||
}
|
}
|
||||||
return Math.random() * (max - min) + min;
|
return Math.random() * (max - min) + min;
|
||||||
}
|
}
|
||||||
|
|
||||||
function randOneFrom(items) {
|
function randOneFrom(items) {
|
||||||
return items[rand(items.length)];
|
return items[rand(items.length)];
|
||||||
}
|
}
|
||||||
|
|
||||||
function randOneIn(max = 2) {
|
function randOneIn(max = 2) {
|
||||||
return rand(0, max) === 0;
|
return rand(0, max) === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
rand,
|
angle,
|
||||||
randf,
|
clamp,
|
||||||
randOneFrom,
|
distance,
|
||||||
randOneIn
|
rand,
|
||||||
|
randf,
|
||||||
|
randOneFrom,
|
||||||
|
randOneIn
|
||||||
};
|
};
|
1109
package-lock.json
generated
Normal file
1109
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "asdf-games",
|
"name": "asdf-games",
|
||||||
"version": "1.0.0",
|
"version": "1.1.6",
|
||||||
"description": "Javascript gaming framework, based upon the framework made in the book HTML5 Games: Novice to Ninja by Sitepoint.",
|
"description": "Javascript gaming framework, based upon the framework made in the book HTML5 Games: Novice to Ninja by Sitepoint.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
@ -17,5 +17,11 @@
|
|||||||
"name": "Arne van Iterson",
|
"name": "Arne van Iterson",
|
||||||
"url": "https://gitea.arnweb.nl/arne/"
|
"url": "https://gitea.arnweb.nl/arne/"
|
||||||
},
|
},
|
||||||
"license": "ISC"
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"eslint": "^6.8.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"easystarjs": "^0.4.3"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
BIN
res/asdf-logo.png
Normal file
BIN
res/asdf-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.8 KiB |
Loading…
Reference in New Issue
Block a user