Compare commits

..

62 Commits

Author SHA1 Message Date
9f43f63ddf 1.1.6 2020-08-11 18:07:36 +02:00
439f5080de Fixed eslint issue 2020-08-11 18:07:09 +02:00
3465bdad68 Merge pull request '[bug]: Fixed sub-paths for lines not being reset' (#7) from corner/asdf-games:master into master 2020-08-11 17:50:05 +02:00
b9a357130b [bug]: Fixed sub-paths for lines not being reset
See: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/stroke#Re-stroking_paths
2020-08-11 12:43:50 +02:00
d2ded2599c 1.1.5 2020-08-04 15:08:08 +02:00
bbe0923523 Added Line 2020-08-04 15:07:48 +02:00
c7a5df7923 1.1.4 2020-05-26 15:38:49 +02:00
10af6cf109 Added Gamestates (wip) and EasyStarJS 2020-05-26 15:37:24 +02:00
782f2f6eff Merge pull request 'Fixed typings' (#6) from corner-typings-patch into master 2020-05-26 15:33:40 +02:00
495856ee53 Fixed syntax errors 2020-05-25 13:10:17 +02:00
03fa6c32b0 Patched some typings mistakes 2020-05-18 18:28:19 +02:00
c2d9a0a846 Undid last two commits 2020-04-29 14:22:04 +02:00
c4baa0ce15 1.1.3 2020-04-29 14:18:49 +02:00
56ed8b369e 1.1.2 2020-04-29 13:49:59 +02:00
b84c53b111 Fixed class variable naming error in TileSpriteXML 2020-04-29 13:49:05 +02:00
8c5ab18f70 Merge branch 'master' of https://gitea.arnweb.nl/arne/asdf-games 2020-04-29 13:46:46 +02:00
2c28b9205c Merge pull request 'Merge branch entity-center-fix' (#4) from entity-center-fix into master 2020-04-29 13:46:29 +02:00
7c480c7a2c Merge branch 'master' of https://gitea.arnweb.nl/arne/asdf-games 2020-04-29 13:45:34 +02:00
64a3dcd0cc Merge pull request 'Added TextStyleOptions interface' (#3) from corner-text-options into master 2020-04-29 13:45:18 +02:00
cc78984783 Added TextStyleOptions interface 2020-04-29 13:40:41 +02:00
0c042160ab Added support for tileSprites in entity.bounds() 2020-04-24 09:12:47 +02:00
825c02532b 1.1.1 2020-04-23 22:30:22 +02:00
9da289ea62 Added listen variable to controls
This makes it possible to send controls over socket.io
2020-04-23 22:30:06 +02:00
e93c7fe6f1 Added support for tileSprites in entity.center() 2020-04-23 14:54:46 +02:00
e8cf095cec Fixed typo in README 2020-04-19 19:53:22 +02:00
401c59d172 Merge branch 'master' of https://gitea.arnweb.nl/arne/asdf-games 2020-04-19 19:45:21 +02:00
2ee388f7eb 1.1.0 2020-04-19 19:45:04 +02:00
9da6f3990f Updated README and XML related classes 2020-04-19 19:44:52 +02:00
eb02467c40 1.0.15 2020-04-17 00:16:25 +02:00
c52cb48a70 Added pause function to Game.js 2020-04-17 00:15:42 +02:00
5e398094f0 Merge branch 'typings-patch' of arne/asdf-games into master
I am bad at typescript
2020-04-14 19:13:25 +02:00
b049787caa [typings]: Restored deadInTracks 2020-04-12 16:30:34 +02:00
27a30fd0ba [typings]: Removed deadInTracks and patched Sound 2020-04-12 16:23:00 +02:00
424ce603c1 1.0.14 2020-04-12 14:03:17 +02:00
1452b28732 Removed module that should not have been deleted 2020-04-12 14:03:06 +02:00
57da262827 1.0.13 2020-04-12 12:51:16 +02:00
6d735bdfca Added sound class, removed defect classes (Light) 2020-04-12 12:50:57 +02:00
8aa3215f5f 1.0.12 2020-04-06 23:00:16 +02:00
8ea643b1bb Merge branch 'master' of https://gitea.arnweb.nl/arne/asdf-games 2020-04-06 22:57:58 +02:00
612317d752 [typings]: Updated typescript typings. 2020-04-06 12:09:41 +02:00
0e1f31eaba 1.0.11 2020-03-29 18:39:44 +02:00
17a13eb0c0 Added some more keys to KeyControls.js 2020-03-29 18:39:23 +02:00
fb755bec7e 1.0.10 2020-03-26 20:11:10 +01:00
aff23993c5 Lighting is now even more broken 2020-03-26 20:10:53 +01:00
9b5e993062 1.0.9 2020-03-24 20:29:56 +01:00
0dc789a168 Added lightsource 2020-03-24 20:29:33 +01:00
f7d6ba99dd 1.0.8 2020-03-23 20:05:17 +01:00
22f4cb851e Fixed issue in wallslide.js 2020-03-23 20:04:36 +01:00
7002b5f782 1.0.7 2020-03-23 19:40:20 +01:00
0cbcfda28b Forgot to update index.js, whoops 2020-03-23 19:39:48 +01:00
fae7b38912 1.0.6 2020-03-23 19:18:48 +01:00
c31cd2b30a Added some utilites for collision detection 2020-03-23 19:18:30 +01:00
fb3aa86e1d Fixed issue in CanvasRenderer 2020-03-23 18:46:34 +01:00
6db704f2c8 1.0.5 2020-03-22 14:22:57 +01:00
5bc38b8e65 Fixed some issues with Sprite and TileSprite 2020-03-22 14:22:52 +01:00
924af6745e 1.0.4 2020-03-22 13:59:10 +01:00
cb46be0088 Fixed issues in AnimManager.js 2020-03-22 13:57:15 +01:00
3a210d1b60 1.0.3 2020-03-21 17:22:49 +01:00
72633d8c0f Added everything from chapter 4 and set up ESlint 2020-03-21 17:22:28 +01:00
3490dd07ae 1.0.2 2020-03-11 12:03:17 +01:00
e878626d54 Added some logic for collision detection 2020-03-11 12:02:53 +01:00
1f096d1ca4 1.0.1 2020-03-02 12:49:06 +01:00
31 changed files with 2493 additions and 379 deletions

2
.eslintignore Normal file
View File

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

33
.eslintrc.js Normal file
View 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"
]
}
};

View File

@ -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
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)
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
View 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
View 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;

View File

@ -1,5 +1,5 @@
var Container = require("./Container"),
CanvasRenderer = require('./renderer/CanvasRenderer')
CanvasRenderer = require("./renderer/CanvasRenderer")
;
const STEP = 1 / 60;
@ -17,6 +17,7 @@ class Game {
}
this.scene = new Container();
this.paused = false;
}
run(gameUpdate = () => { }) {
@ -29,9 +30,11 @@ class Game {
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);
}

9
lib/Line.js Normal file
View 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
View 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;

View File

@ -4,6 +4,7 @@ class Sprite {
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;
}
}

View File

@ -6,7 +6,7 @@ class SpriteSheetXML {
fetchXMLtoArray(url) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, false);
xhr.open("GET", url, false);
xhr.send(null);
if (xhr.status === 200) {
@ -22,7 +22,7 @@ class SpriteSheetXML {
});
}
} else {
console.error('XML file cannot be loaded!')
console.error("XML file cannot be loaded!");
}
}

29
lib/State.js Normal file
View 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;

View File

@ -20,6 +20,54 @@ class TileMap extends Container {
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;

View File

@ -20,6 +20,54 @@ class TileMapXML extends Container {
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;

View File

@ -1,4 +1,5 @@
var Sprite = require("./Sprite");
var Sprite = require("./Sprite"),
AnimManager = require("./AnimManager");
class TileSprite extends Sprite {
constructor(texture, w, h) {
@ -6,6 +7,19 @@ class TileSprite extends Sprite {
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);
}
}

View File

@ -1,12 +1,26 @@
var Sprite = require("./Sprite");
var Sprite = require("./Sprite"),
AnimManager = require("./AnimManager");
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.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);
}
}

View File

@ -1,7 +1,8 @@
class KeyControls {
constructor() {
constructor(listen = true) {
this.keys = {};
if (listen) {
// Bind event handlers
document.addEventListener("keydown", e => {
if ([37, 38, 39, 40].indexOf(e.which) >= 0) {
@ -9,16 +10,32 @@ class KeyControls {
}
this.keys[e.which] = true;
}, false);
document.addEventListener('keyup', e => {
document.addEventListener("keyup", e => {
this.keys[e.which] = false;
}, false);
}
}
get action() {
// Spacebar
return this.keys[32];
}
get ctrl() {
// Control
return this.keys[17];
}
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]) {

View File

@ -1,15 +1,19 @@
class MouseControls {
constructor(container) {
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;
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);
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 }) {

370
lib/index.d.ts vendored
View File

@ -2,6 +2,11 @@ export as namespace asdf;
type Coordinates = {x: number, y: number};
class Renderable {
visible?: boolean;
dead?: boolean
}
/**
* TileSpriteXML class
*/
@ -87,6 +92,52 @@ 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[]
}
/**
@ -103,27 +154,29 @@ export class Texture {
constructor(url: string);
}
interface TextStyleOptions {
font?: string,
fill?: string | CanvasGradient | CanvasPattern,
align?: CanvasTextAlign
}
/**
* Text class
*/
export class Text {
export class Text extends Renderable {
pos: Coordinates;
text: string;
visible: boolean;
update?: (dt?: number, t?: number) => void;
style: {
font?: string,
fill?: string | CanvasGradient | CanvasPattern,
align?: CanvasTextAlign
};
style: TextStyleOptions;
/**
* Prints styled text on canvas
* @param text Text to print
* @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
*/
export class Sprite {
export class Sprite extends Renderable {
texture: Texture;
pos: Coordinates;
anchor: Coordinates;
scale: Coordinates;
pivot: Coordinates;
visible: boolean;
rotation: number;
dead: boolean;
update?: (dt?: number, t?: number) => void;
@ -198,6 +249,7 @@ export class Game {
h: number;
renderer: CanvasRenderer;
scene: Container<unknown>;
paused: boolean;
/**
* Set the games parameters
@ -212,14 +264,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: {x: number, y: number};
pos: Coordinates;
children: T[];
constructor();
@ -249,9 +301,9 @@ export class Container<T> {
* Updates all children when called
* @param dt Delta 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;
/**
* 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
*
@ -343,10 +413,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 to all keys
* Resets default value (false) to all keys
*/
reset(): void;
}
@ -381,7 +451,275 @@ 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,7 +1,16 @@
var Container = require("./Container.js"),
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"),
@ -15,10 +24,19 @@ var Container = require("./Container.js"),
;
module.exports = {
AnimManager,
CanvasRenderer,
Camera,
Container,
Game,
math,
entity,
wallslide,
deadInTracks,
State,
Sound,
Rect,
Line,
KeyControls,
MouseControls,
Sprite,

View 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
View 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
};

View File

@ -10,11 +10,11 @@ class CanvasRenderer {
}
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 */
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) {
@ -79,6 +79,24 @@ class CanvasRenderer {
} 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);
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
@ -86,7 +104,7 @@ class CanvasRenderer {
renderRec(child);
}
ctx.restore();
})
});
}
if (clear) {
ctx.clearRect(0, 0, this.w, this.h);

53
lib/sound/Sound.js Normal file
View 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
View 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
};

View File

@ -1,3 +1,22 @@
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));
}
@ -19,6 +38,9 @@ function randOneIn(max = 2) {
}
module.exports = {
angle,
clamp,
distance,
rand,
randf,
randOneFrom,

1109
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"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.",
"main": "lib/index.js",
"repository": {
@ -17,5 +17,11 @@
"name": "Arne van Iterson",
"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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB