Compare commits

..

No commits in common. "master" and "1.0.0" have entirely different histories.

31 changed files with 379 additions and 2493 deletions

View File

@ -1,2 +0,0 @@
projects
examples

View File

@ -1,33 +0,0 @@
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"
]
}
};

View File

@ -1,48 +1,12 @@
![asdf-games](https://gitea.arnweb.nl/arne/asdf-games/raw/branch/master/res/asdf-logo.png "asdf-games logo")
# asdf-games
My attempt at making the framework featured in the book HTML5 Games: Novice to Ninja.
Me making games using HTML5 Games: Novice to Ninja by Sitepoint.
[You can find the book here.](https://www.sitepoint.com/premium/books/html5-games-novice-to-ninja)
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
});
```
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.
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/)

View File

@ -1,65 +0,0 @@
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;

View File

@ -1,54 +0,0 @@
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;

View File

@ -1,30 +1,30 @@
class Container {
constructor() {
this.pos = { x: 0, y: 0 };
this.children = [];
}
constructor() {
this.pos = { x: 0, y: 0 };
this.children = [];
}
add(child) {
this.children.push(child);
return child;
}
add(child) {
this.children.push(child);
return child;
}
remove(child) {
this.children = this.children.filter(c => c !== child);
return child;
}
remove(child) {
this.children = this.children.filter(c => c !== child);
return child;
}
map(f) {
return this.children.map(f);
}
map(f) {
return this.children.map(f);
}
update(dt, t) {
this.children = this.children.filter(child => {
if (child.update) {
child.update(dt, t, this);
}
return child.dead ? false : true;
});
}
update(dt, t) {
this.children = this.children.filter(child => {
if (child.update) {
child.update(dt, t, this);
}
return child.dead ? false : true;
});
}
}
module.exports = Container;

View File

@ -1,43 +1,40 @@
var Container = require("./Container"),
CanvasRenderer = require("./renderer/CanvasRenderer")
CanvasRenderer = require('./renderer/CanvasRenderer')
;
const STEP = 1 / 60;
const FRAME_MAX = 5 * STEP;
class Game {
constructor(w, h, pixelated, parent = "#board") {
this.w = w;
this.h = h;
this.renderer = new CanvasRenderer(w, h);
document.querySelector(parent).appendChild(this.renderer.view);
constructor(w, h, pixelated, parent = "#board") {
this.w = w;
this.h = h;
this.renderer = new CanvasRenderer(w, h);
document.querySelector(parent).appendChild(this.renderer.view);
if (pixelated) {
this.renderer.setPixelated();
}
if (pixelated) {
this.renderer.setPixelated();
}
this.scene = new Container();
this.paused = false;
}
this.scene = new Container();
}
run(gameUpdate = () => { }) {
let dt = 0;
let last = 0;
const loop = ms => {
requestAnimationFrame(loop);
run(gameUpdate = () => { }) {
let dt = 0;
let last = 0;
const loop = ms => {
requestAnimationFrame(loop);
const t = ms / 1000;
dt = Math.min(t - last, FRAME_MAX);
last = t;
const t = ms / 1000;
dt = Math.min(t - last, FRAME_MAX);
last = t;
if (!this.paused) {
this.scene.update(dt, t);
gameUpdate(dt, t);
this.renderer.render(this.scene);
}
};
requestAnimationFrame(loop);
}
this.scene.update(dt, t);
gameUpdate(dt, t);
this.renderer.render(this.scene);
};
requestAnimationFrame(loop);
}
}
module.exports = Game;

View File

@ -1,9 +0,0 @@
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;

View File

@ -1,10 +0,0 @@
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;

View File

@ -1,11 +1,10 @@
class Sprite {
constructor(texture) {
this.texture = texture;
this.pos = { x: 0, y: 0 };
this.anchor = { x: 0, y: 0 };
this.scale = { x: 1, y: 1 };
this.pivot = { x: 0, y: 0 };
this.rotation = 0;
}
constructor(texture) {
this.texture = texture;
this.pos = { x: 0, y: 0 };
this.anchor = { x: 0, y: 0 };
this.scale = { x: 1, y: 1 };
this.rotation = 0;
}
}
module.exports = Sprite;

View File

@ -1,39 +1,39 @@
class SpriteSheetXML {
constructor(url) {
this.array = [];
this.fetchXMLtoArray(url);
}
constructor(url) {
this.array = [];
this.fetchXMLtoArray(url);
}
fetchXMLtoArray(url) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, false);
xhr.send(null);
fetchXMLtoArray(url) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, false);
xhr.send(null);
if (xhr.status === 200) {
var children = xhr.responseXML.children[0].children;
for (let index = 0; index < children.length; index++) {
const element = children[index];
this.array.push({
name: element.attributes.name.nodeValue,
x: element.attributes.x.nodeValue,
y: element.attributes.y.nodeValue,
width: element.attributes.width.nodeValue,
height: element.attributes.height.nodeValue
});
}
} else {
console.error("XML file cannot be loaded!");
}
}
if (xhr.status === 200) {
var children = xhr.responseXML.children[0].children;
for (let index = 0; index < children.length; index++) {
const element = children[index];
this.array.push({
name: element.attributes.name.nodeValue,
x: element.attributes.x.nodeValue,
y: element.attributes.y.nodeValue,
width: element.attributes.width.nodeValue,
height: element.attributes.height.nodeValue
});
}
} else {
console.error('XML file cannot be loaded!')
}
}
findIndex(attribute, value) {
for (let index = 0; index < this.array.length; index++) {
const element = this.array[index];
if (element[attribute] == value) {
return index;
}
}
}
findIndex(attribute, value) {
for (let index = 0; index < this.array.length; index++) {
const element = this.array[index];
if (element[attribute] == value) {
return index;
}
}
}
}
module.exports = SpriteSheetXML;

View File

@ -1,29 +0,0 @@
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;

View File

@ -1,8 +1,8 @@
class Text {
constructor(text = "", style = {}) {
this.pos = { x: 0, y: 0 };
this.text = text;
this.style = style;
}
constructor(text = "", style = {}) {
this.pos = { x: 0, y: 0 };
this.text = text;
this.style = style;
}
}
module.exports = Text;

View File

@ -1,7 +1,7 @@
class Texture {
constructor(url) {
this.img = new Image();
this.img.src = url;
}
constructor(url) {
this.img = new Image();
this.img.src = url;
}
}
module.exports = Texture;

View File

@ -1,73 +1,25 @@
var Container = require("./Container"),
TileSprite = require("./TileSprite")
TileSprite = require("./TileSprite")
;
class TileMap extends Container {
constructor(tiles, mapW, mapH, tileW, tileH, texture) {
super();
this.mapW = mapW;
this.mapH = mapH;
this.tileW = tileW;
this.tileH = tileH;
this.w = mapW * tileW;
this.h = mapH * tileH;
constructor(tiles, mapW, mapH, tileW, tileH, texture) {
super();
this.mapW = mapW;
this.mapH = mapH;
this.tileW = tileW;
this.tileH = tileH;
this.w = mapW * tileW;
this.h = mapH * tileH;
this.children = tiles.map((frame, i) => {
const s = new TileSprite(texture, tileW, tileH);
s.frame = frame;
s.pos.x = i % mapW * tileW;
s.pos.y = Math.floor(i / mapW) * tileH;
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
})
);
}
this.children = tiles.map((frame, i) => {
const s = new TileSprite(texture, tileW, tileH);
s.frame = frame;
s.pos.x = i % mapW * tileW;
s.pos.y = Math.floor(i / mapW) * tileH;
return s;
});
}
}
module.exports = TileMap;

View File

@ -1,73 +1,25 @@
var Container = require("./Container"),
TileSpriteXML = require("./TileSpriteXML")
TileSpriteXML = require("./TileSpriteXML")
;
class TileMapXML extends Container {
constructor(tiles, mapW, mapH, texture, xml) {
super(texture);
this.mapW = mapW;
this.mapH = mapH;
this.tileW = xml.array[tiles[0]].width;
this.tileH = xml.array[tiles[0]].height;
this.w = mapW * this.tileW;
this.h = mapH * this.tileH;
constructor(tiles, mapW, mapH, texture, xml) {
super(texture);
this.mapW = mapW;
this.mapH = mapH;
this.tileW = xml.array[tiles[0]].width;
this.tileH = xml.array[tiles[0]].height;
this.w = mapW * this.tileW;
this.h = mapH * this.tileH;
this.children = tiles.map((frame, i) => {
const s = new TileSpriteXML(texture, xml, frame);
s.frame = frame;
s.pos.x = i % mapW * this.tileW;
s.pos.y = Math.floor(i / mapW) * this.tileH;
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
})
);
}
this.children = tiles.map((frame, i) => {
const s = new TileSpriteXML(texture, xml, frame);
s.frame = frame;
s.pos.x = i % mapW * this.tileW;
s.pos.y = Math.floor(i / mapW) * this.tileH;
return s;
});
}
}
module.exports = TileMapXML;

View File

@ -1,26 +1,12 @@
var Sprite = require("./Sprite"),
AnimManager = require("./AnimManager");
var Sprite = require("./Sprite");
class TileSprite extends Sprite {
constructor(texture, w, h) {
super(texture);
this.tileW = w;
this.tileH = h;
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);
}
constructor(texture, w, h) {
super(texture);
this.tileW = w;
this.tileH = h;
this.frame = { x: 0, y: 0 };
}
}
module.exports = TileSprite;

View File

@ -1,27 +1,13 @@
var Sprite = require("./Sprite"),
AnimManager = require("./AnimManager");
var Sprite = require("./Sprite");
class TileSpriteXML extends Sprite {
constructor(texture, xml, index) {
super(texture);
var src = xml.array[index];
this.imgPos = { x: src["x"], y: src["y"] };
this.width = src["width"];
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);
}
constructor(texture, xml, index) {
super(texture);
var src = xml.array[index];
this.imgPos = { x: src['x'], y: src['y'] };
this.width = src['width'];
this.height = src['height'];
}
}
module.exports = TileSpriteXML;

View File

@ -1,78 +1,61 @@
class KeyControls {
constructor(listen = true) {
this.keys = {};
if (listen) {
// Bind event handlers
document.addEventListener("keydown", e => {
if ([37, 38, 39, 40].indexOf(e.which) >= 0) {
e.preventDefault();
}
this.keys[e.which] = true;
}, false);
document.addEventListener("keyup", e => {
this.keys[e.which] = false;
}, false);
}
}
constructor() {
this.keys = {};
// Bind event handlers
document.addEventListener("keydown", e => {
if ([37, 38, 39, 40].indexOf(e.which) >= 0) {
e.preventDefault();
}
this.keys[e.which] = true;
}, false);
document.addEventListener('keyup', e => {
this.keys[e.which] = false;
}, false);
}
get action() {
// Spacebar
return this.keys[32];
}
get action() {
// Spacebar
return this.keys[32];
}
get ctrl() {
// Control
return this.keys[17];
}
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 shift() {
// Shift
return this.keys[16];
}
get escape() {
// Escape
return this.keys[27];
}
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;
}
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) {
if (value !== undefined) {
this.keys[key] = value;
}
return this.keys[key];
}
key(key, value) {
if (value !== undefined) {
this.keys[key] = value;
}
return this.keys[key];
}
reset() {
for (let key in this.keys) {
this.keys[key] = false;
}
}
reset() {
for (let key in this.keys) {
this.keys[key] = false;
}
}
}

View File

@ -1,49 +1,45 @@
class MouseControls {
constructor(container, listen = true) {
this.el = container || document.body;
// State
this.pos = { x: 0, y: 0 };
this.isDown = false;
this.pressed = false;
this.released = false;
constructor(container) {
this.el = container || document.body;
// State
this.pos = { x: 0, y: 0 };
this.isDown = false;
this.pressed = 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);
}
if (listen) {
// 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 }) {
const { el, pos } = this;
const rect = el.getBoundingClientRect();
const xr = el.width / el.clientWidth;
const yr = el.height / el.clientHeight;
pos.x = (clientX - rect.left) * xr;
pos.y = (clientY - rect.top) * yr;
}
}
move(e) {
this.mousePosFromEvent(e);
}
mousePosFromEvent({ clientX, clientY }) {
const { el, pos } = this;
const rect = el.getBoundingClientRect();
const xr = el.width / el.clientWidth;
const yr = el.height / el.clientHeight;
pos.x = (clientX - rect.left) * xr;
pos.y = (clientY - rect.top) * yr;
}
down(e) {
this.isDown = true;
this.pressed = true;
this.mousePosFromEvent(e);
}
move(e) {
this.mousePosFromEvent(e);
}
up() {
this.isDown = false;
this.released = true;
}
down(e) {
this.isDown = true;
this.pressed = true;
this.mousePosFromEvent(e);
}
up() {
this.isDown = false;
this.released = true;
}
update() {
this.released = false;
this.pressed = false;
}
update() {
this.released = false;
this.pressed = false;
}
}
module.exports = MouseControls;

370
lib/index.d.ts vendored
View File

@ -2,11 +2,6 @@ export as namespace asdf;
type Coordinates = {x: number, y: number};
class Renderable {
visible?: boolean;
dead?: boolean
}
/**
* TileSpriteXML class
*/
@ -92,52 +87,6 @@ export class TileMap extends Container<TileSprite> {
* @param texture Texture instance of source image file
*/
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[]
}
/**
@ -154,29 +103,27 @@ export class Texture {
constructor(url: string);
}
interface TextStyleOptions {
font?: string,
fill?: string | CanvasGradient | CanvasPattern,
align?: CanvasTextAlign
}
/**
* Text class
*/
export class Text extends Renderable {
export class Text {
pos: Coordinates;
text: string;
visible: boolean;
update?: (dt?: number, t?: number) => void;
style: TextStyleOptions;
style: {
font?: string,
fill?: string | CanvasGradient | CanvasPattern,
align?: CanvasTextAlign
};
/**
* Prints styled text on canvas
* @param text Text to print
* @param style Styles to apply to text
*/
constructor(text: string, style: TextStyleOptions);
constructor(text: string, style: {});
}
/**
@ -224,13 +171,15 @@ export class SpriteSheetXML {
/**
* Sprite class
*/
export class Sprite extends Renderable {
export class Sprite {
texture: Texture;
pos: Coordinates;
anchor: Coordinates;
scale: Coordinates;
pivot: Coordinates;
visible: boolean;
rotation: number;
dead: boolean;
update?: (dt?: number, t?: number) => void;
@ -249,7 +198,6 @@ export class Game {
h: number;
renderer: CanvasRenderer;
scene: Container<unknown>;
paused: boolean;
/**
* Set the games parameters
@ -264,14 +212,14 @@ export class Game {
* Start game loop
* @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
*/
export class Container<T> {
pos: Coordinates;
pos: {x: number, y: number};
children: T[];
constructor();
@ -301,9 +249,9 @@ export class Container<T> {
* Updates all children when called
* @param dt Delta time
* @param t Total time
* @returns Returns anything
* @returns Returns if the child is dead or not
*/
update(dt: number, t: number): any;
update(dt: number, t: number): boolean;
}
/**
@ -373,24 +321,6 @@ export class KeyControls {
*/
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
*
@ -413,10 +343,10 @@ export class KeyControls {
* @param value Value to set to key
* @return Value of key
*/
key(key: number, value?: boolean): boolean;
key(key: number, value: boolean): boolean;
/**
* Resets default value (false) to all keys
* Resets default value to all keys
*/
reset(): void;
}
@ -451,275 +381,7 @@ export class CanvasRenderer {
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 {
/**
* 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
* @param min Minimum value

View File

@ -1,50 +1,32 @@
var AnimManager = require("./AnimManager.js"),
Camera = require("./Camera.js"),
Container = require("./Container.js"),
CanvasRenderer = require("./renderer/CanvasRenderer.js"),
Game = require("./Game.js"),
math = require("./utilities/math.js"),
entity = require("./utilities/entity.js"),
wallslide = require("./movement/wallslide.js"),
deadInTracks = require("./movement/deadInTracks.js"),
State = require("./State.js"),
Sound = require("./sound/Sound.js"),
Rect = require("./Rect.js"),
Line = require("./Line.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")
var Container = require("./Container.js"),
CanvasRenderer = require("./renderer/CanvasRenderer.js"),
Game = require("./Game.js"),
math = require("./utilities/math.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 = {
AnimManager,
CanvasRenderer,
Camera,
Container,
Game,
math,
entity,
wallslide,
deadInTracks,
State,
Sound,
Rect,
Line,
KeyControls,
MouseControls,
Sprite,
TileMap,
TileMapXML,
TileSprite,
SpriteSheetXML,
TileSpriteXML,
Text,
Texture
CanvasRenderer,
Container,
Game,
math,
KeyControls,
MouseControls,
Sprite,
TileMap,
TileMapXML,
TileSprite,
SpriteSheetXML,
TileSpriteXML,
Text,
Texture
};

View File

@ -1,16 +0,0 @@
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
};

View File

@ -1,51 +0,0 @@
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
};

View File

@ -1,115 +1,97 @@
class CanvasRenderer {
constructor(w, h) {
const canvas = document.createElement("canvas");
this.w = canvas.width = w;
this.h = canvas.height = h;
this.view = canvas;
this.ctx = canvas.getContext("2d");
this.ctx.textBaseLine = "top";
}
constructor(w, h) {
const canvas = document.createElement("canvas");
this.w = canvas.width = w;
this.h = canvas.height = h;
this.view = canvas;
this.ctx = canvas.getContext("2d");
this.ctx.textBaseLine = "top";
}
setPixelated() {
this.ctx["imageSmoothingEnabled"] = false; /* standard */
this.ctx["mozImageSmoothingEnabled"] = false; /* Firefox */
this.ctx["oImageSmoothingEnabled"] = false; /* Opera */
this.ctx["webkitImageSmoothingEnabled"] = false; /* Safari */
this.ctx["msImageSmoothingEnabled"] = false; /* IE */
}
setPixelated() {
this.ctx['imageSmoothingEnabled'] = false; /* standard */
this.ctx['mozImageSmoothingEnabled'] = false; /* Firefox */
this.ctx['oImageSmoothingEnabled'] = false; /* Opera */
this.ctx['webkitImageSmoothingEnabled'] = false; /* Safari */
this.ctx['msImageSmoothingEnabled'] = false; /* IE */
}
render(container, clear = true) {
const { ctx } = this;
function renderRec(container) {
// Render container children
container.children.forEach(child => {
if (child.visible == false) {
return;
}
render(container, clear = true) {
const { ctx } = this;
function renderRec(container) {
// Render container children
container.children.forEach(child => {
if (child.visible == false) {
return;
}
ctx.save();
ctx.save();
if (child.pos) {
ctx.translate(Math.round(child.pos.x), Math.round(child.pos.y));
}
if (child.pos) {
ctx.translate(Math.round(child.pos.x), Math.round(child.pos.y));
}
if (child.anchor) {
ctx.translate(child.anchor.x, child.anchor.y);
}
if (child.anchor) {
ctx.translate(child.anchor.x, child.anchor.y);
}
if (child.scale) {
ctx.scale(child.scale.x, child.scale.y);
}
if (child.scale) {
ctx.scale(child.scale.x, child.scale.y);
}
if (child.rotation) {
const px = child.pivot ? child.pivot.x : 0;
const py = child.pivot ? child.pivot.y : 0;
ctx.translate(px, py);
ctx.rotate(child.rotation);
ctx.translate(-px, -py);
}
if (child.rotation) {
const px = child.pivot ? child.pivot.x : 0;
const py = child.pivot ? child.pivot.y : 0;
ctx.translate(px, py);
ctx.rotate(child.rotation);
ctx.translate(-px, -py);
}
if (child.text) {
const { font, fill, align } = child.style;
if (font) ctx.font = font;
if (fill) ctx.fillStyle = fill;
if (align) ctx.textAlign = align;
ctx.fillText(child.text, 0, 0);
}
if (child.text) {
const { font, fill, align } = child.style;
if (font) ctx.font = font;
if (fill) ctx.fillStyle = fill;
if (align) ctx.textAlign = align;
ctx.fillText(child.text, 0, 0);
}
else if (child.texture) {
const img = child.texture.img;
if (child.tileW && child.tileH) {
ctx.drawImage(
img,
child.frame.x * child.tileW,
child.frame.y * child.tileH,
child.tileW, child.tileH,
0, 0,
child.tileW, child.tileH
);
} else if (child.imgPos && child.width && child.height) {
ctx.drawImage(
img,
child.imgPos.x,
child.imgPos.y,
child.width, child.height,
0, 0,
child.width, child.height
);
} else {
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);
else if (child.texture) {
const img = child.texture.img;
if (child.tileW && child.tileH) {
ctx.drawImage(
img,
child.frame.x * child.tileW,
child.frame.y * child.tileH,
child.tileW, child.tileH,
0, 0,
child.tileW, child.tileH
);
} else if (child.imgPos && child.width && child.height) {
ctx.drawImage(
img,
child.imgPos.x,
child.imgPos.y,
child.width, child.height,
0, 0,
child.width, child.height
);
} else {
ctx.drawImage(img, 0, 0);
}
}
ctx.arc(0, 0, child.radius, 0, 2 * Math.PI);
ctx.fillStyle = gradient;
ctx.fill();
} else if (child.style && child.target) {
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(child.target.x, child.target.y);
ctx.strokeStyle = child.style;
ctx.stroke();
}
// Handle children with children
if (child.children) {
renderRec(child);
}
ctx.restore();
});
}
if (clear) {
ctx.clearRect(0, 0, this.w, this.h);
}
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;

View File

@ -1,53 +0,0 @@
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;

View File

@ -1,96 +0,0 @@
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
};

View File

@ -1,48 +1,26 @@
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) {
return Math.floor(randf(min, max));
return Math.floor(randf(min, max));
}
function randf(min, max) {
if (max == null) {
max = min || 1;
min = 0;
}
return Math.random() * (max - min) + min;
if (max == null) {
max = min || 1;
min = 0;
}
return Math.random() * (max - min) + min;
}
function randOneFrom(items) {
return items[rand(items.length)];
return items[rand(items.length)];
}
function randOneIn(max = 2) {
return rand(0, max) === 0;
return rand(0, max) === 0;
}
module.exports = {
angle,
clamp,
distance,
rand,
randf,
randOneFrom,
randOneIn
rand,
randf,
randOneFrom,
randOneIn
};

1109
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "asdf-games",
"version": "1.1.6",
"version": "1.0.0",
"description": "Javascript gaming framework, based upon the framework made in the book HTML5 Games: Novice to Ninja by Sitepoint.",
"main": "lib/index.js",
"repository": {
@ -17,11 +17,5 @@
"name": "Arne van Iterson",
"url": "https://gitea.arnweb.nl/arne/"
},
"license": "ISC",
"devDependencies": {
"eslint": "^6.8.0"
},
"dependencies": {
"easystarjs": "^0.4.3"
}
"license": "ISC"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB