Rooms, items and some player stuff
parent
36bb9264b3
commit
a745ff299e
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
||||||
|
<html><head><title>Assassin bug</title><script defer="defer" src="game.js"></script></head><body><h1>Assassin bug</h1><div aria-live="polite" id="output-area"></div><input id="input-area" placeholder="Type command"/></body></html>
|
|
@ -9,9 +9,12 @@
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"copy-webpack-plugin": "^9.0.1",
|
"copy-webpack-plugin": "^9.0.1",
|
||||||
|
"eventemitter3": "^4.0.7",
|
||||||
"html-webpack-plugin": "^5.5.0",
|
"html-webpack-plugin": "^5.5.0",
|
||||||
|
"terser-webpack-plugin": "^5.2.4",
|
||||||
"webpack": "^5.61.0",
|
"webpack": "^5.61.0",
|
||||||
"webpack-dev-server": "^4.4.0"
|
"webpack-dev-server": "^4.4.0",
|
||||||
|
"yaml": "^1.10.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"webpack-cli": "^4.9.1"
|
"webpack-cli": "^4.9.1"
|
||||||
|
@ -3655,6 +3658,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/yaml": {
|
||||||
|
"version": "1.10.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||||
|
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yocto-queue": {
|
"node_modules/yocto-queue": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||||
|
@ -6374,6 +6385,11 @@
|
||||||
"integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==",
|
"integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
|
"yaml": {
|
||||||
|
"version": "1.10.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||||
|
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
|
||||||
|
},
|
||||||
"yocto-queue": {
|
"yocto-queue": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||||
|
|
|
@ -16,9 +16,12 @@
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"copy-webpack-plugin": "^9.0.1",
|
"copy-webpack-plugin": "^9.0.1",
|
||||||
|
"eventemitter3": "^4.0.7",
|
||||||
"html-webpack-plugin": "^5.5.0",
|
"html-webpack-plugin": "^5.5.0",
|
||||||
|
"terser-webpack-plugin": "^5.2.4",
|
||||||
"webpack": "^5.61.0",
|
"webpack": "^5.61.0",
|
||||||
"webpack-dev-server": "^4.4.0"
|
"webpack-dev-server": "^4.4.0",
|
||||||
|
"yaml": "^1.10.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"webpack-cli": "^4.9.1"
|
"webpack-cli": "^4.9.1"
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
import Item from '../item';
|
||||||
|
|
||||||
|
export default class ItemBuilder {
|
||||||
|
constructor() {
|
||||||
|
this.item = new Item();
|
||||||
|
}
|
||||||
|
|
||||||
|
withID(ID) {
|
||||||
|
this.item.id = ID;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withName(name) {
|
||||||
|
this.item.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withDescription(description) {
|
||||||
|
this.item.description = description;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
isUsable(value) {
|
||||||
|
this.item.usable = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
isTakeable(value) {
|
||||||
|
this.item.takeable = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withUseCallback(callback) {
|
||||||
|
this.item.useCallback = callback;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withTakeCallback(callback) {
|
||||||
|
this.item.takeCallback = callback;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
create() {
|
||||||
|
return this.item;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
import Room from '../room';
|
||||||
|
|
||||||
|
export default class RoomBuilder {
|
||||||
|
constructor() {
|
||||||
|
this.room = new Room();
|
||||||
|
}
|
||||||
|
|
||||||
|
withID(ID) {
|
||||||
|
this.room.id = ID;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withTitle(title) {
|
||||||
|
this.room.title = title;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withFirstDescription(description) {
|
||||||
|
this.room.firstDescription = description;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withDescription(description) {
|
||||||
|
this.room.description = description;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withExit(direction, roomID) {
|
||||||
|
this.room.addExit(direction, roomID);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withItem(itemID) {
|
||||||
|
this.room.addItem(itemID);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withEnterCallback(callback) {
|
||||||
|
this.room.addEnterCallback(callback);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withExitCallback(callback) {
|
||||||
|
this.room.addExitCallback(callback);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withEnterLogic(func) {
|
||||||
|
this.room.addEnterLogic(func);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withExitLogic(func) {
|
||||||
|
this.room.addExitLogic(func);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withTick(func) {
|
||||||
|
this.room.addTickCallback(func);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
create() {
|
||||||
|
return this.room;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
import LookCommand from "./commands/look";
|
||||||
|
import UseCommand from "./commands/use";
|
||||||
|
const defaultCommands = [
|
||||||
|
[["look", "l"], LookCommand],
|
||||||
|
[["use", "interact"], UseCommand]
|
||||||
|
];
|
||||||
|
|
||||||
|
export default class Commands {
|
||||||
|
constructor(context, commands) {
|
||||||
|
this.context = context;
|
||||||
|
this.commands = commands || new Map();
|
||||||
|
this.addDefaultCommands();
|
||||||
|
}
|
||||||
|
|
||||||
|
doCommand(str) {
|
||||||
|
const room = this.context.getRoom(this.context.player.currentRoom);
|
||||||
|
const split = str.split(" ");
|
||||||
|
if (this.commands.get(split[0])) {
|
||||||
|
this.commands.get(split[0])(split, this.context);
|
||||||
|
}
|
||||||
|
if (room.getExit(split[0])) {
|
||||||
|
this.context.move(room.getExit(split[0]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addCommand(name, func) {
|
||||||
|
if (Array.isArray(name)) {
|
||||||
|
name.forEach((command) => this.commands.set(command, func));
|
||||||
|
} else {
|
||||||
|
this.commands.set(name, func);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addCommands(commands) {
|
||||||
|
commands.forEach((command) => {
|
||||||
|
this.addCommand(command[0], command[1]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addDefaultCommands() {
|
||||||
|
this.addCommands(defaultCommands);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
export default function LookCommand(args, context) {
|
||||||
|
if (args.length == 0) {
|
||||||
|
context.examineRoom();
|
||||||
|
} else {
|
||||||
|
const room = context.getRoom(context.player.currentRoom);
|
||||||
|
const items = room.getItems();
|
||||||
|
let item = null;
|
||||||
|
for (let i of items) {
|
||||||
|
if (i.name.includes(args[1])) {
|
||||||
|
item = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!item) {
|
||||||
|
context.output.say(`I could not find a ${args[1]}`);
|
||||||
|
} else {
|
||||||
|
context.output.say(item.name);
|
||||||
|
context.output.say(item.description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
export default async function UseCommand(args, context) {
|
||||||
|
const room = context.getRoom(context.player.currentRoom);
|
||||||
|
const items = room.getItems();
|
||||||
|
let item = null;
|
||||||
|
for (let i of items) {
|
||||||
|
if (i.name.includes(args[1])) {
|
||||||
|
item = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!item) {
|
||||||
|
context.output.say(`I could not find a ${args[1]}`);
|
||||||
|
} else {
|
||||||
|
await item.onUse();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
import State from './state';
|
||||||
|
import Room from './room';
|
||||||
|
import Player from './player';
|
||||||
|
import Output from './output';
|
||||||
|
import Input from './input';
|
||||||
|
import Commands from './commands';
|
||||||
|
|
||||||
|
export default class Game {
|
||||||
|
constructor() {
|
||||||
|
this.player = new Player();
|
||||||
|
this.state = State;
|
||||||
|
this.rooms = [];
|
||||||
|
this.items = [];
|
||||||
|
this.output = new Output();
|
||||||
|
this.commandHandler = new Commands(this);
|
||||||
|
this.input = new Input(this.commandHandler);
|
||||||
|
this.visitedRooms = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
print(string) {
|
||||||
|
this.output.say(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
init(data) {
|
||||||
|
console.log(data);
|
||||||
|
this.rooms = data.rooms.map((room) => {
|
||||||
|
room.context = this;
|
||||||
|
return room;
|
||||||
|
});
|
||||||
|
this.items = data.items.map((item) => {
|
||||||
|
item.context = this;
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
this.state = data.state;
|
||||||
|
this.commandHandler.addCommands(data.commands);
|
||||||
|
this.player = new Player();
|
||||||
|
this.move(this.player.currentRoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
examineRoom() {
|
||||||
|
const room = this.getRoom(this.player.currentRoom);
|
||||||
|
this.output.say(room.title);
|
||||||
|
if (!this.visitedRooms.get(this.player.currentRoom) && room.firstDescription != "") {
|
||||||
|
this.output.say(room.firstDescription);
|
||||||
|
} else {
|
||||||
|
this.output.say(room.description);
|
||||||
|
}
|
||||||
|
this.examineItems();
|
||||||
|
this.examineExits();
|
||||||
|
}
|
||||||
|
|
||||||
|
examineItems() {
|
||||||
|
const room = this.getRoom(this.player.currentRoom);
|
||||||
|
const items = room.getItems();
|
||||||
|
items.forEach((item) => this.output.say(item.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
examineExits() {
|
||||||
|
const room = this.getRoom(this.player.currentRoom);
|
||||||
|
let exitDescription = "You can go ";
|
||||||
|
for (let exit of room.exits.keys()) {
|
||||||
|
exitDescription += " " + exit;
|
||||||
|
}
|
||||||
|
this.output.say(exitDescription);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRoom(id) {
|
||||||
|
return this.rooms.find((room) => room.id == id);
|
||||||
|
}
|
||||||
|
|
||||||
|
getItem(id) {
|
||||||
|
return this.items.find((item) => item.id == id);
|
||||||
|
}
|
||||||
|
|
||||||
|
wait(ms) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
setTimeout(resolve, ms);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async move(roomID) {
|
||||||
|
const currentRoom = this.getRoom(this.player.currentRoom);
|
||||||
|
const newRoom = this.getRoom(roomID);
|
||||||
|
if (currentRoom.canExit() && newRoom.canEnter()) {
|
||||||
|
await currentRoom.onExit();
|
||||||
|
await newRoom.onEnter();
|
||||||
|
this.player.currentRoom = roomID;
|
||||||
|
this.examineRoom();
|
||||||
|
this.visitedRooms.set(roomID, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
export default class Input {
|
||||||
|
constructor(commandHandler) {
|
||||||
|
this.handler = commandHandler;
|
||||||
|
this.inputField = document.getElementById("input-area");
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.inputField.addEventListener("keydown", (e) => {
|
||||||
|
if (e.which == 13) {
|
||||||
|
const val = this.inputField.value;
|
||||||
|
this.inputField.value = "";
|
||||||
|
this.handler.doCommand(val);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
export default class Item {
|
||||||
|
constructor() {
|
||||||
|
this.id = "item";
|
||||||
|
this.name = "An item";
|
||||||
|
this.description = "You see nothing special about this item";
|
||||||
|
this.usable = true;
|
||||||
|
this.takeable = true;
|
||||||
|
this.useCallback = null;
|
||||||
|
this.takeCallback = null;
|
||||||
|
this.context = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async onUse() {
|
||||||
|
if (this.useCallback) return this.useCallback(this.context);
|
||||||
|
}
|
||||||
|
|
||||||
|
async onTake() {
|
||||||
|
if (this.takeCallback) return this.takeCallback();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { TTS } from '../framework/tts';
|
||||||
|
import { AriaOutput } from '../framework/tts/outputs/aria';
|
||||||
|
|
||||||
|
export default class Output {
|
||||||
|
constructor() {
|
||||||
|
this.tts = new TTS(new AriaOutput());
|
||||||
|
this.history = document.getElementById("output-area");
|
||||||
|
}
|
||||||
|
|
||||||
|
say(string) {
|
||||||
|
const node = document.createElement("p");
|
||||||
|
node.appendChild(document.createTextNode(string));
|
||||||
|
this.history.appendChild(node);
|
||||||
|
// this.tts.speak(string);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
export default class Player {
|
||||||
|
constructor() {
|
||||||
|
this.inventory = [];
|
||||||
|
this.currentRoom = "start";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
export default class Room {
|
||||||
|
constructor() {
|
||||||
|
this.id = "room";
|
||||||
|
this.title = "A room";
|
||||||
|
this.description = "You see nothing special";
|
||||||
|
this.firstDescription = "As you walk into the room, you notice nothing special";
|
||||||
|
this.objects = [];
|
||||||
|
this.exits = new Map();
|
||||||
|
this.enterCallback = null;
|
||||||
|
this.exitCallback = null;
|
||||||
|
this.canEnterLogic = null;
|
||||||
|
this.canExitLogic = null;
|
||||||
|
this.tickCallback = null;
|
||||||
|
this.context = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async onEnter() {
|
||||||
|
if (this.enterCallback) return this.enterCallback(this.context);
|
||||||
|
}
|
||||||
|
|
||||||
|
async onExit() {
|
||||||
|
if (this.exitCallback) return this.exitCallback(this.context);
|
||||||
|
}
|
||||||
|
|
||||||
|
canEnter() {
|
||||||
|
if (this.canEnterLogic) {
|
||||||
|
return this.canEnterLogic(this.context);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
canExit() {
|
||||||
|
if (this.canExitLogic) {
|
||||||
|
return this.canExitLogic(this.context);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
addExit(direction, roomID) {
|
||||||
|
this.exits.set(direction, roomID);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
getExit(direction) {
|
||||||
|
return this.exits.get(direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
addItem(item) {
|
||||||
|
this.objects.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
addEnterCallback(callback) {
|
||||||
|
this.enterCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
addExitCallback(callback) {
|
||||||
|
this.exitCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
addEnterLogic(func) {
|
||||||
|
this.canEnterLogic = func;
|
||||||
|
}
|
||||||
|
|
||||||
|
addExitLogic(func) {
|
||||||
|
this.canExitLogic = func;
|
||||||
|
}
|
||||||
|
|
||||||
|
addTickCallback(callback) {
|
||||||
|
this.tickCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
getItems() {
|
||||||
|
return this.objects.map((item) => this.context.getItem(item));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
class State {
|
||||||
|
constructor() {
|
||||||
|
this.states = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key) {
|
||||||
|
return this.states.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
set(key, value) {
|
||||||
|
return this.states.set(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new State();
|
|
@ -0,0 +1,12 @@
|
||||||
|
import EventEmitter from 'eventemitter3';
|
||||||
|
import { Queue } from './queue';
|
||||||
|
import { AssetStorage } from './storage';
|
||||||
|
export declare class Downloader extends EventEmitter {
|
||||||
|
private storage;
|
||||||
|
private queue;
|
||||||
|
private basePath;
|
||||||
|
constructor(storage: AssetStorage, queue: Queue, basePath?: string);
|
||||||
|
setBasePath(path: string): void;
|
||||||
|
download(): Promise<any>;
|
||||||
|
downloadItem(path: string): Promise<any>;
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import EventEmitter from 'eventemitter3';
|
||||||
|
import { buildPath } from './utils';
|
||||||
|
export class Downloader extends EventEmitter {
|
||||||
|
constructor(storage, queue, basePath = '') {
|
||||||
|
super();
|
||||||
|
this.storage = storage;
|
||||||
|
this.queue = queue;
|
||||||
|
this.basePath = basePath;
|
||||||
|
}
|
||||||
|
setBasePath(path) {
|
||||||
|
this.basePath = this.basePath;
|
||||||
|
}
|
||||||
|
download() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const downloaded = new Map();
|
||||||
|
let numDownloaded = 0;
|
||||||
|
while (this.queue.length() > 0) {
|
||||||
|
const path = this.queue.pop();
|
||||||
|
const item = yield this.downloadItem(buildPath(this.basePath, path));
|
||||||
|
downloaded.set(path, item);
|
||||||
|
numDownloaded++;
|
||||||
|
this.emit('download.progress', {
|
||||||
|
downloaded: numDownloaded,
|
||||||
|
remaining: this.queue.length()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return downloaded;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
downloadItem(path) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const inCache = yield this.storage.get(path);
|
||||||
|
if (inCache) {
|
||||||
|
return inCache;
|
||||||
|
}
|
||||||
|
const response = yield fetch(path);
|
||||||
|
this.storage.add(path, response);
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import EventEmitter from 'eventemitter3';
|
||||||
|
export declare class AssetManager extends EventEmitter {
|
||||||
|
private name;
|
||||||
|
private basePath;
|
||||||
|
private downloader;
|
||||||
|
private queue;
|
||||||
|
private storage;
|
||||||
|
private manifest;
|
||||||
|
constructor(name: string, basePath: string);
|
||||||
|
init(): Promise<boolean>;
|
||||||
|
setManifest(path: string): Promise<any>;
|
||||||
|
enqueue(path: string): void;
|
||||||
|
download(): Promise<any>;
|
||||||
|
downloadFromManifest(key: string): Promise<any>;
|
||||||
|
downloadFile(path: string): Promise<any>;
|
||||||
|
setBasePath(path: string): void;
|
||||||
|
clearCache(): void;
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import { Downloader } from './downloader';
|
||||||
|
import { Queue } from './queue';
|
||||||
|
import { Manifest } from './manifest';
|
||||||
|
import { AssetStorage } from './storage';
|
||||||
|
import EventEmitter from 'eventemitter3';
|
||||||
|
import { buildPath } from './utils';
|
||||||
|
export class AssetManager extends EventEmitter {
|
||||||
|
constructor(name, basePath) {
|
||||||
|
super();
|
||||||
|
this.name = name;
|
||||||
|
this.basePath = basePath;
|
||||||
|
this.queue = new Queue();
|
||||||
|
this.storage = new AssetStorage(name);
|
||||||
|
this.downloader = new Downloader(this.storage, this.queue, this.basePath);
|
||||||
|
console.log(`Asset manager initialized`);
|
||||||
|
}
|
||||||
|
init() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
yield this.storage.init();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setManifest(path) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
this.manifest = yield Manifest(`${this.basePath}/${path}`);
|
||||||
|
this.storage.setManifest(this.manifest);
|
||||||
|
return this.manifest;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
enqueue(path) {
|
||||||
|
this.queue.add(path);
|
||||||
|
}
|
||||||
|
download() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const result = yield this.downloader.download();
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
downloadFromManifest(key) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const paths = this.manifest[key];
|
||||||
|
paths.forEach((path) => this.enqueue(path));
|
||||||
|
this.downloader.on('download.progress', (info) => this.emit('progress', info));
|
||||||
|
const files = yield this.downloader.download();
|
||||||
|
this.downloader.off('download.progress');
|
||||||
|
return files;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
downloadFile(path) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const result = yield this.downloader.downloadItem(buildPath(this.basePath, path));
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setBasePath(path) {
|
||||||
|
this.basePath = this.basePath;
|
||||||
|
this.downloader.setBasePath(this.basePath);
|
||||||
|
}
|
||||||
|
clearCache() {
|
||||||
|
this.storage.clear();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
export declare function Manifest(manifestPath: string): Promise<any>;
|
||||||
|
export declare function CheckManifest(manifest: any): boolean;
|
|
@ -0,0 +1,40 @@
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import * as yaml from 'yaml';
|
||||||
|
export function Manifest(manifestPath) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
try {
|
||||||
|
const response = yield fetch(manifestPath);
|
||||||
|
console.log(response);
|
||||||
|
const data = yield response.text();
|
||||||
|
console.log(`Parsing: `, data);
|
||||||
|
const manifest = yaml.parse(data);
|
||||||
|
return manifest;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
alert(`Error occured: ${error.toString()}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export function CheckManifest(manifest) {
|
||||||
|
const prevManifestStr = localStorage.getItem('manifest');
|
||||||
|
if (!prevManifestStr) {
|
||||||
|
localStorage.setItem('manifest', JSON.stringify(manifest));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const prevManifest = JSON.parse(prevManifestStr);
|
||||||
|
if (prevManifest.version === manifest.version) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
localStorage.setItem('manifest', manifest);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
export declare class Queue {
|
||||||
|
private items;
|
||||||
|
constructor();
|
||||||
|
add(file: string): string[];
|
||||||
|
remove(file: string): string[];
|
||||||
|
pop(): string;
|
||||||
|
length(): number;
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
export class Queue {
|
||||||
|
constructor() {
|
||||||
|
this.items = [];
|
||||||
|
}
|
||||||
|
add(file) {
|
||||||
|
this.items.push(file);
|
||||||
|
return this.items;
|
||||||
|
}
|
||||||
|
remove(file) {
|
||||||
|
this.items = this.items.filter((item) => item !== file);
|
||||||
|
return this.items;
|
||||||
|
}
|
||||||
|
pop() {
|
||||||
|
return this.items.pop();
|
||||||
|
}
|
||||||
|
length() {
|
||||||
|
return this.items.length;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
export declare class AssetStorage {
|
||||||
|
private id;
|
||||||
|
private cache;
|
||||||
|
private manifest;
|
||||||
|
constructor(id: string);
|
||||||
|
init(): Promise<void>;
|
||||||
|
add(request: RequestInfo, response: Response): Promise<Boolean>;
|
||||||
|
get(request: RequestInfo): Promise<Response>;
|
||||||
|
setManifest(manifest: any): Promise<void>;
|
||||||
|
clear(): Promise<void>;
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import { CheckManifest } from './manifest';
|
||||||
|
export class AssetStorage {
|
||||||
|
constructor(id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
init() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
this.cache = yield caches.open(this.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
add(request, response) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const result = yield this.cache.put(request, response);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
get(request) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const result = yield this.cache.match(request);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setManifest(manifest) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
this.manifest = manifest;
|
||||||
|
if (!CheckManifest(this.manifest)) {
|
||||||
|
yield this.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
clear() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const keys = yield this.cache.keys();
|
||||||
|
keys.forEach((key) => __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const result = yield this.cache.delete(key);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export {};
|
|
@ -0,0 +1,5 @@
|
||||||
|
const yaml = require('yaml');
|
||||||
|
const fs = require('fs');
|
||||||
|
const data = fs.readFileSync('manifest.yaml');
|
||||||
|
const parsed = yaml.parse(data.toString());
|
||||||
|
console.log(parsed);
|
|
@ -0,0 +1 @@
|
||||||
|
export declare function buildPath(basePath: string, path: string): string;
|
|
@ -0,0 +1,8 @@
|
||||||
|
export function buildPath(basePath, path) {
|
||||||
|
if (!basePath) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return `${basePath}/${path}`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
export declare class BaseComponent {
|
||||||
|
id: number;
|
||||||
|
properties: any;
|
||||||
|
constructor();
|
||||||
|
clone(): BaseComponent;
|
||||||
|
}
|
||||||
|
export interface Component {
|
||||||
|
id: number;
|
||||||
|
properties: any;
|
||||||
|
clone(): BaseComponent;
|
||||||
|
new (): BaseComponent;
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
export class BaseComponent {
|
||||||
|
constructor() {
|
||||||
|
this.id = 0;
|
||||||
|
this.properties = {};
|
||||||
|
}
|
||||||
|
clone() {
|
||||||
|
const comp = new BaseComponent();
|
||||||
|
comp.properties = this.properties;
|
||||||
|
return comp;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { BaseComponent, Component } from './component';
|
||||||
|
export declare class BaseEntity {
|
||||||
|
id: number;
|
||||||
|
components: Map<number, BaseComponent>;
|
||||||
|
constructor();
|
||||||
|
addComponent(component: Component): void;
|
||||||
|
removeComponent(component: BaseComponent): void;
|
||||||
|
getComponentIDs(): number[];
|
||||||
|
getComponent(component: BaseComponent): BaseComponent;
|
||||||
|
getComponentByID(id: number): BaseComponent;
|
||||||
|
}
|
||||||
|
export interface Entity {
|
||||||
|
new (): BaseComponent;
|
||||||
|
addComponent(component: Component): any;
|
||||||
|
removeComponent(component: BaseComponent): any;
|
||||||
|
getComponentIDs(): number[];
|
||||||
|
getComponent(component: BaseComponent): any;
|
||||||
|
getComponentByID(id: number): BaseComponent;
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
export class BaseEntity {
|
||||||
|
constructor() {
|
||||||
|
this.components = new Map();
|
||||||
|
this.id = 0;
|
||||||
|
}
|
||||||
|
addComponent(component) {
|
||||||
|
let comp = new component();
|
||||||
|
comp.id = component.id;
|
||||||
|
this.components.set(component.id, comp);
|
||||||
|
}
|
||||||
|
removeComponent(component) {
|
||||||
|
this.components.delete(component.id);
|
||||||
|
}
|
||||||
|
getComponentIDs() {
|
||||||
|
return [...this.components.keys()];
|
||||||
|
}
|
||||||
|
getComponent(component) {
|
||||||
|
return this.components.get(component.id);
|
||||||
|
}
|
||||||
|
getComponentByID(id) {
|
||||||
|
return this.components.get(id);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { Component } from './component';
|
||||||
|
import { BaseEntity, Entity } from './entity';
|
||||||
|
import { EventBus } from '../event-bus';
|
||||||
|
import { Query } from './query';
|
||||||
|
import { System } from './system';
|
||||||
|
export declare class World {
|
||||||
|
entities: Array<BaseEntity>;
|
||||||
|
components: Map<number, Component>;
|
||||||
|
componentNamesToIDs: Map<string, number>;
|
||||||
|
systems: Set<System>;
|
||||||
|
nextEntityID: number;
|
||||||
|
nextComponentID: number;
|
||||||
|
nextQueryID: number;
|
||||||
|
queryCache: Array<Query>;
|
||||||
|
eventBus: EventBus;
|
||||||
|
constructor();
|
||||||
|
run(): void;
|
||||||
|
createSystem(systemExecutor: Function): void;
|
||||||
|
addSystem(system: System): void;
|
||||||
|
addEntity(entity: BaseEntity): void;
|
||||||
|
removeEntity(entityToRemove: BaseEntity): void;
|
||||||
|
createEntity(components: Array<Component>): BaseEntity;
|
||||||
|
extendEntity(entity: Entity, components: Array<Component>): BaseEntity;
|
||||||
|
createComponent(component: Component): Component;
|
||||||
|
query(include: Array<Component>, exclude: Array<Component>): Array<BaseEntity>;
|
||||||
|
createQuery(include: Array<Component>, exclude: Array<Component>): Query;
|
||||||
|
markQueriesDirty(): void;
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
import { BaseEntity } from './entity';
|
||||||
|
import { EventBus } from '../event-bus';
|
||||||
|
import { Query } from './query';
|
||||||
|
import { System } from './system';
|
||||||
|
export class World {
|
||||||
|
constructor() {
|
||||||
|
this.nextEntityID = 0;
|
||||||
|
this.nextComponentID = 0;
|
||||||
|
this.nextQueryID = 0;
|
||||||
|
this.entities = new Array();
|
||||||
|
this.systems = new Set();
|
||||||
|
this.components = new Map();
|
||||||
|
this.componentNamesToIDs = new Map();
|
||||||
|
this.queryCache = new Array();
|
||||||
|
this.eventBus = new EventBus();
|
||||||
|
}
|
||||||
|
run() {
|
||||||
|
this.systems.forEach((system) => {
|
||||||
|
system.execute(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
createSystem(systemExecutor) {
|
||||||
|
const newSystem = new System(systemExecutor);
|
||||||
|
this.systems.add(newSystem);
|
||||||
|
}
|
||||||
|
addSystem(system) {
|
||||||
|
this.systems.add(system);
|
||||||
|
}
|
||||||
|
addEntity(entity) {
|
||||||
|
this.entities.push(entity);
|
||||||
|
this.markQueriesDirty();
|
||||||
|
}
|
||||||
|
removeEntity(entityToRemove) {
|
||||||
|
this.entities = this.entities.filter((entity) => entity !== entityToRemove);
|
||||||
|
this.markQueriesDirty();
|
||||||
|
}
|
||||||
|
createEntity(components) {
|
||||||
|
const newEntity = new BaseEntity();
|
||||||
|
newEntity.id = this.nextEntityID;
|
||||||
|
this.nextEntityID++;
|
||||||
|
components.forEach((component) => {
|
||||||
|
if (this.componentNamesToIDs.has(component.name)) {
|
||||||
|
component.id = this.componentNamesToIDs.get(component.name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.componentNamesToIDs.set(component.name, this.nextComponentID);
|
||||||
|
component.id = this.nextComponentID;
|
||||||
|
this.nextComponentID++;
|
||||||
|
}
|
||||||
|
newEntity.addComponent(component);
|
||||||
|
});
|
||||||
|
this.entities.push(newEntity);
|
||||||
|
this.markQueriesDirty();
|
||||||
|
return newEntity;
|
||||||
|
}
|
||||||
|
extendEntity(entity, components) {
|
||||||
|
const toClone = this.entities.find((found) => entity.name === found.constructor.name);
|
||||||
|
const cloned = new BaseEntity();
|
||||||
|
cloned.id = this.nextEntityID;
|
||||||
|
this.nextEntityID++;
|
||||||
|
toClone.components.forEach((component) => {
|
||||||
|
cloned.addComponent(this.components.get(component.id));
|
||||||
|
});
|
||||||
|
components.forEach((component) => {
|
||||||
|
if (this.componentNamesToIDs.has(component.name)) {
|
||||||
|
component.id = this.componentNamesToIDs.get(component.name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.componentNamesToIDs.set(component.name, this.nextComponentID);
|
||||||
|
component.id = this.nextComponentID;
|
||||||
|
this.nextComponentID++;
|
||||||
|
}
|
||||||
|
cloned.addComponent(component);
|
||||||
|
});
|
||||||
|
this.entities.push(cloned);
|
||||||
|
return cloned;
|
||||||
|
}
|
||||||
|
createComponent(component) {
|
||||||
|
const newComponent = component;
|
||||||
|
newComponent.id = this.nextComponentID;
|
||||||
|
this.nextComponentID++;
|
||||||
|
this.components.set(newComponent.id, newComponent);
|
||||||
|
this.componentNamesToIDs.set(component.name, component.id);
|
||||||
|
return newComponent;
|
||||||
|
}
|
||||||
|
query(include, exclude) {
|
||||||
|
const query = new Query(include, exclude, this);
|
||||||
|
const cache = this.queryCache.find((item) => item.include == include && item.exclude == exclude);
|
||||||
|
if (cache) {
|
||||||
|
return cache.execute();
|
||||||
|
}
|
||||||
|
this.queryCache.push(query);
|
||||||
|
return query.execute();
|
||||||
|
}
|
||||||
|
createQuery(include, exclude) {
|
||||||
|
const newQuery = new Query(include, exclude, this);
|
||||||
|
newQuery.id = this.nextQueryID;
|
||||||
|
this.nextQueryID++;
|
||||||
|
this.queryCache.push(newQuery);
|
||||||
|
return newQuery;
|
||||||
|
}
|
||||||
|
markQueriesDirty() {
|
||||||
|
this.queryCache.forEach((query) => (query.isDirty = true));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { World } from '.';
|
||||||
|
import { Component } from './component';
|
||||||
|
import { BaseEntity } from './entity';
|
||||||
|
export declare class Query {
|
||||||
|
include: Array<Component>;
|
||||||
|
exclude: Array<Component>;
|
||||||
|
world: World;
|
||||||
|
id: number;
|
||||||
|
private results;
|
||||||
|
isDirty: boolean;
|
||||||
|
includeComponentIds: number[];
|
||||||
|
excludeComponentIds: number[];
|
||||||
|
constructor(include: Array<Component>, exclude: Array<Component>, world: World);
|
||||||
|
execute(): Array<BaseEntity>;
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
export class Query {
|
||||||
|
constructor(include, exclude, world) {
|
||||||
|
this.include = include;
|
||||||
|
this.exclude = exclude;
|
||||||
|
this.world = world;
|
||||||
|
this.isDirty = true;
|
||||||
|
this.results = new Array();
|
||||||
|
this.includeComponentIds = include.map((component) => component.id);
|
||||||
|
this.excludeComponentIds = exclude.map((component) => component.id);
|
||||||
|
this.id = 0;
|
||||||
|
}
|
||||||
|
execute() {
|
||||||
|
if (!this.isDirty && this.results) {
|
||||||
|
return this.results;
|
||||||
|
}
|
||||||
|
let filtered;
|
||||||
|
this.includeComponentIds = this.include.map((component) => this.world.componentNamesToIDs.get(component.name));
|
||||||
|
this.excludeComponentIds = this.exclude.map((component) => this.world.componentNamesToIDs.get(component.name));
|
||||||
|
const entities = this.world.entities.filter((entity) => {
|
||||||
|
let ids = entity.getComponentIDs();
|
||||||
|
// let includes = ids.map(id => this.includeComponentIds.includes(id)).includes(true);
|
||||||
|
let excludes = ids
|
||||||
|
.map((id) => this.excludeComponentIds.includes(id))
|
||||||
|
.includes(true);
|
||||||
|
let includes = ids.filter((id) => this.includeComponentIds.includes(id));
|
||||||
|
return includes.length === this.includeComponentIds.length && !excludes;
|
||||||
|
});
|
||||||
|
if (entities.length > 0) {
|
||||||
|
this.isDirty = false;
|
||||||
|
this.results = entities;
|
||||||
|
}
|
||||||
|
return entities;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { World } from '.';
|
||||||
|
export declare class System {
|
||||||
|
executor: Function;
|
||||||
|
constructor(executor: Function);
|
||||||
|
execute(world: World): void;
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
export class System {
|
||||||
|
constructor(executor) {
|
||||||
|
this.executor = executor;
|
||||||
|
}
|
||||||
|
execute(world) {
|
||||||
|
if (this.executor) {
|
||||||
|
this.executor(world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,11 @@
|
||||||
|
export declare class EventBus {
|
||||||
|
private events;
|
||||||
|
constructor();
|
||||||
|
emit(id: string, data: any): void;
|
||||||
|
subscribe(id: string, subscriber: Function): void;
|
||||||
|
}
|
||||||
|
export declare class EventItem {
|
||||||
|
id: string;
|
||||||
|
subscribers: Function[];
|
||||||
|
constructor(id: string);
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
export class EventBus {
|
||||||
|
constructor() {
|
||||||
|
this.events = new Map();
|
||||||
|
}
|
||||||
|
emit(id, data) {
|
||||||
|
let ev = this.events.get(id);
|
||||||
|
if (!ev) {
|
||||||
|
let ev = new EventItem(id);
|
||||||
|
this.events.set(id, ev);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ev.subscribers.forEach((subscriber) => {
|
||||||
|
subscriber(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
subscribe(id, subscriber) {
|
||||||
|
let ev = this.events.get(id);
|
||||||
|
if (!ev) {
|
||||||
|
ev = new EventItem(id);
|
||||||
|
this.events.set(id, ev);
|
||||||
|
}
|
||||||
|
ev.subscribers.push(subscriber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class EventItem {
|
||||||
|
constructor(id) {
|
||||||
|
this.id = id;
|
||||||
|
this.subscribers = [];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
export declare class EventBus {
|
||||||
|
private events;
|
||||||
|
constructor();
|
||||||
|
emit(id: string, data?: any): void;
|
||||||
|
subscribe(id: string, subscriber: Function): void;
|
||||||
|
unsubscribe(id: string, subscriber: Function): void;
|
||||||
|
unsubscribeAll(id: string): void;
|
||||||
|
}
|
||||||
|
export declare class EventItem {
|
||||||
|
id: string;
|
||||||
|
subscribers: Function[];
|
||||||
|
constructor(id: string);
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
export class EventBus {
|
||||||
|
constructor() {
|
||||||
|
this.events = new Map();
|
||||||
|
}
|
||||||
|
emit(id, data = {}) {
|
||||||
|
let ev = this.events.get(id);
|
||||||
|
if (!ev) {
|
||||||
|
let ev = new EventItem(id);
|
||||||
|
this.events.set(id, ev);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ev.subscribers.forEach((subscriber) => {
|
||||||
|
subscriber(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
subscribe(id, subscriber) {
|
||||||
|
let ev = this.events.get(id);
|
||||||
|
if (!ev) {
|
||||||
|
ev = new EventItem(id);
|
||||||
|
this.events.set(id, ev);
|
||||||
|
}
|
||||||
|
ev.subscribers.push(subscriber);
|
||||||
|
}
|
||||||
|
unsubscribe(id, subscriber) {
|
||||||
|
if (this.events.has(id)) {
|
||||||
|
let ev = this.events.get(id);
|
||||||
|
ev.subscribers = ev.subscribers.filter((fn) => fn !== subscriber);
|
||||||
|
if (ev.subscribers.length < 1) {
|
||||||
|
this.events.delete(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsubscribeAll(id) {
|
||||||
|
if (this.events.has(id)) {
|
||||||
|
this.events.delete(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class EventItem {
|
||||||
|
constructor(id) {
|
||||||
|
this.id = id;
|
||||||
|
this.subscribers = [];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { AssetManager } from '../asset-manager';
|
||||||
|
import { Input } from '../input';
|
||||||
|
import Resonator from '../resonator';
|
||||||
|
import { Scene } from '../scene/scene';
|
||||||
|
import { SceneManager } from '../scene/manager';
|
||||||
|
import { Scheduler } from '../scheduler';
|
||||||
|
import { TTS } from '../tts';
|
||||||
|
import { HTTPLoader } from '../resonator/loaders/http-loader';
|
||||||
|
import { EventBus } from '../event-bus';
|
||||||
|
import { World } from '../world';
|
||||||
|
export declare class Game extends EventBus {
|
||||||
|
assetLoader: HTTPLoader;
|
||||||
|
assetManager: AssetManager;
|
||||||
|
resonator: Resonator;
|
||||||
|
input: Input;
|
||||||
|
tts: TTS;
|
||||||
|
sceneManager: SceneManager;
|
||||||
|
scheduler: Scheduler;
|
||||||
|
world: World;
|
||||||
|
constructor();
|
||||||
|
init(): void;
|
||||||
|
start(): void;
|
||||||
|
addScene(scene: Scene): void;
|
||||||
|
addDefaultScene(scene: Scene): void;
|
||||||
|
setWorld(world: World): void;
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { AssetManager } from '../asset-manager';
|
||||||
|
import { Input } from '../input';
|
||||||
|
import Resonator from '../resonator';
|
||||||
|
import { SceneManager } from '../scene/manager';
|
||||||
|
import { Scheduler } from '../scheduler';
|
||||||
|
import { TTS } from '../tts';
|
||||||
|
import { AriaOutput } from '../tts/outputs/aria';
|
||||||
|
import { HTTPLoader } from '../resonator/loaders/http-loader';
|
||||||
|
import { EventBus } from '../event-bus';
|
||||||
|
export class Game extends EventBus {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
init() {
|
||||||
|
this.assetManager = new AssetManager('game', '');
|
||||||
|
this.assetLoader = new HTTPLoader();
|
||||||
|
this.resonator = new Resonator(this.assetLoader);
|
||||||
|
this.input = new Input(['keyboard'], document.body);
|
||||||
|
this.tts = new TTS(new AriaOutput());
|
||||||
|
this.sceneManager = new SceneManager();
|
||||||
|
this.scheduler = new Scheduler(60);
|
||||||
|
this.emit('ready');
|
||||||
|
}
|
||||||
|
start() {
|
||||||
|
this.scheduler.start();
|
||||||
|
this.scheduler.subscribe('update.logic', (dt) => {
|
||||||
|
this.sceneManager.currentScene.update(dt);
|
||||||
|
this.world.update(dt);
|
||||||
|
});
|
||||||
|
this.scheduler.subscribe('update.draw', (dt) => this.sceneManager.currentScene.updateDraw());
|
||||||
|
this.sceneManager.init();
|
||||||
|
}
|
||||||
|
addScene(scene) {
|
||||||
|
this.sceneManager.addScene(scene);
|
||||||
|
}
|
||||||
|
addDefaultScene(scene) {
|
||||||
|
this.sceneManager.addScene(scene);
|
||||||
|
this.sceneManager.setDefaultScene(scene);
|
||||||
|
}
|
||||||
|
setWorld(world) {
|
||||||
|
this.world = world;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { Scene } from '../../scene/scene';
|
||||||
|
import { World } from '../../ecs/index';
|
||||||
|
import { Game } from '..';
|
||||||
|
import { Component } from '../../ecs/component';
|
||||||
|
import { System } from '../../ecs/system';
|
||||||
|
import { Entity } from '../../ecs/entity';
|
||||||
|
import { Query } from '../../ecs/query';
|
||||||
|
import { SceneManager } from '../../scene/manager';
|
||||||
|
export declare class ECSScene implements Scene {
|
||||||
|
instance: Game;
|
||||||
|
id: string;
|
||||||
|
world: World;
|
||||||
|
running: boolean;
|
||||||
|
data: any;
|
||||||
|
constructor(instance: Game);
|
||||||
|
update(): void;
|
||||||
|
updateDraw(): boolean;
|
||||||
|
onActivate(manager: SceneManager): void;
|
||||||
|
onDeactivate(): void;
|
||||||
|
onSwitch(): void;
|
||||||
|
createEntity(components: Array<Component<any>>): Entity;
|
||||||
|
createComponent<T>(props: T): Component<T>;
|
||||||
|
createSystem(systemExecutor: Function): void;
|
||||||
|
addSystem(system: System): void;
|
||||||
|
addEntity(entity: Entity): void;
|
||||||
|
removeEntity(entity: Entity): void;
|
||||||
|
createQuery(include: Array<Component<any>>, exclude: Array<Component<any>>): Query;
|
||||||
|
extendEntity(entity: Entity, components: Array<Component<any>>): Entity;
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { World } from '../../ecs/index';
|
||||||
|
export class ECSScene {
|
||||||
|
constructor(instance) {
|
||||||
|
this.instance = instance;
|
||||||
|
this.running = true;
|
||||||
|
this.id = 'ECSScene';
|
||||||
|
this.world = new World();
|
||||||
|
}
|
||||||
|
update() {
|
||||||
|
if (this.running)
|
||||||
|
this.world.run();
|
||||||
|
}
|
||||||
|
updateDraw() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
onActivate(manager) {
|
||||||
|
this.running = true;
|
||||||
|
}
|
||||||
|
onDeactivate() {
|
||||||
|
this.running = false;
|
||||||
|
}
|
||||||
|
onSwitch() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
createEntity(components) {
|
||||||
|
return this.world.createEntity(components);
|
||||||
|
}
|
||||||
|
createComponent(props) {
|
||||||
|
return this.world.createComponent(props);
|
||||||
|
}
|
||||||
|
createSystem(systemExecutor) {
|
||||||
|
return this.world.createSystem(systemExecutor);
|
||||||
|
}
|
||||||
|
addSystem(system) {
|
||||||
|
return this.world.addSystem(system);
|
||||||
|
}
|
||||||
|
addEntity(entity) {
|
||||||
|
return this.world.addEntity(entity);
|
||||||
|
}
|
||||||
|
removeEntity(entity) {
|
||||||
|
return this.world.removeEntity(entity);
|
||||||
|
}
|
||||||
|
createQuery(include, exclude) {
|
||||||
|
return this.world.createQuery(include, exclude);
|
||||||
|
}
|
||||||
|
extendEntity(entity, components) {
|
||||||
|
return this.world.extendEntity(entity, components);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
export * from './asset-manager';
|
||||||
|
export * from './ecs';
|
||||||
|
export * from './event-bus';
|
||||||
|
export * from './input';
|
||||||
|
export * from './resonator';
|
||||||
|
export * from './tts';
|
||||||
|
export * from './ui';
|
|
@ -0,0 +1,7 @@
|
||||||
|
export * from './asset-manager';
|
||||||
|
export * from './ecs';
|
||||||
|
export * from './event-bus';
|
||||||
|
export * from './input';
|
||||||
|
export * from './resonator';
|
||||||
|
export * from './tts';
|
||||||
|
export * from './ui';
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { BaseInput } from './inputs/base-input';
|
||||||
|
import { State } from './interfaces/state';
|
||||||
|
export declare class Input {
|
||||||
|
private InputIDs;
|
||||||
|
private element;
|
||||||
|
private inputs;
|
||||||
|
constructor(InputIDs: string[], element: HTMLElement);
|
||||||
|
private init;
|
||||||
|
addInput(id: string, input: BaseInput): this;
|
||||||
|
capture(preventDefault?: boolean): void;
|
||||||
|
release(): void;
|
||||||
|
getState(): State;
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { createInput } from './input-factory';
|
||||||
|
export class Input {
|
||||||
|
constructor(InputIDs, element) {
|
||||||
|
this.InputIDs = InputIDs;
|
||||||
|
this.element = element;
|
||||||
|
this.inputs = new Map();
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
init() {
|
||||||
|
this.InputIDs.forEach((inputID) => {
|
||||||
|
const thing = createInput(inputID);
|
||||||
|
const instance = new thing(this.element);
|
||||||
|
this.inputs.set(inputID, instance);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
addInput(id, input) {
|
||||||
|
this.inputs.set(id, input);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
capture(preventDefault = true) {
|
||||||
|
this.inputs.forEach((input) => input.capture(preventDefault));
|
||||||
|
}
|
||||||
|
release() {
|
||||||
|
this.inputs.forEach((input) => input.release());
|
||||||
|
}
|
||||||
|
getState() {
|
||||||
|
const state = {};
|
||||||
|
this.inputs.forEach((input, inputID) => {
|
||||||
|
if (input)
|
||||||
|
state[inputID] = input.getState();
|
||||||
|
});
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export declare function createInput(key: string): any;
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { Keyboard } from './inputs/keyboard';
|
||||||
|
import { Mouse } from './inputs/mouse';
|
||||||
|
export function createInput(key) {
|
||||||
|
switch (key) {
|
||||||
|
case 'keyboard':
|
||||||
|
return Keyboard;
|
||||||
|
break;
|
||||||
|
case 'mouse':
|
||||||
|
return Mouse;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
export declare class BaseInput {
|
||||||
|
protected element: HTMLElement;
|
||||||
|
protected active: boolean;
|
||||||
|
constructor(element: HTMLElement);
|
||||||
|
getState(): any;
|
||||||
|
capture(preventDefault: boolean): void;
|
||||||
|
release(): void;
|
||||||
|
}
|
||||||
|
export interface IBaseInput {
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
export class BaseInput {
|
||||||
|
constructor(element) {
|
||||||
|
this.element = element;
|
||||||
|
}
|
||||||
|
getState() { }
|
||||||
|
capture(preventDefault) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
release() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { BaseInput } from './base-input';
|
||||||
|
export declare class Joystick extends BaseInput {
|
||||||
|
constructor(element: HTMLElement);
|
||||||
|
}
|
||||||
|
export interface IJoystick {
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { BaseInput } from './base-input';
|
||||||
|
export class Joystick extends BaseInput {
|
||||||
|
constructor(element) {
|
||||||
|
super(element);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { BaseInput } from './base-input';
|
||||||
|
export declare class Keyboard extends BaseInput {
|
||||||
|
private keysDown;
|
||||||
|
private keysJustPressed;
|
||||||
|
private keysJustReleased;
|
||||||
|
private preventDefault;
|
||||||
|
constructor(element: HTMLElement);
|
||||||
|
capture(preventDefault: boolean): void;
|
||||||
|
release(): void;
|
||||||
|
getState(): IKeyboard;
|
||||||
|
private handleKeyDown;
|
||||||
|
private handleKeyUp;
|
||||||
|
}
|
||||||
|
export interface IKeyboard {
|
||||||
|
keysDown: Map<number, boolean>;
|
||||||
|
keysJustPressed: Map<number, boolean>;
|
||||||
|
keysJustReleased: Map<number, boolean>;
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { BaseInput } from './base-input';
|
||||||
|
export class Keyboard extends BaseInput {
|
||||||
|
constructor(element) {
|
||||||
|
super(element);
|
||||||
|
this.keysDown = new Map();
|
||||||
|
this.keysJustPressed = new Map();
|
||||||
|
this.keysJustReleased = new Map();
|
||||||
|
this.handleKeyDown = this.handleKeyDown.bind(this);
|
||||||
|
this.handleKeyUp = this.handleKeyUp.bind(this);
|
||||||
|
}
|
||||||
|
capture(preventDefault) {
|
||||||
|
this.active = true;
|
||||||
|
this.preventDefault = preventDefault;
|
||||||
|
this.element.addEventListener('keydown', this.handleKeyDown);
|
||||||
|
this.element.addEventListener('keyup', this.handleKeyUp);
|
||||||
|
}
|
||||||
|
release() {
|
||||||
|
this.active = false;
|
||||||
|
this.element.removeEventListener('keydown', this.handleKeyDown);
|
||||||
|
this.element.removeEventListener('keyup', this.handleKeyUp);
|
||||||
|
}
|
||||||
|
getState() {
|
||||||
|
const state = {
|
||||||
|
keysDown: new Map(this.keysDown),
|
||||||
|
keysJustPressed: new Map(this.keysJustPressed),
|
||||||
|
keysJustReleased: new Map(this.keysJustReleased)
|
||||||
|
};
|
||||||
|
this.keysJustPressed.clear();
|
||||||
|
this.keysJustReleased.clear();
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
handleKeyDown(event) {
|
||||||
|
if (this.active && this.preventDefault)
|
||||||
|
event.preventDefault();
|
||||||
|
if (this.keysDown.get(event.keyCode))
|
||||||
|
return;
|
||||||
|
this.keysDown.set(event.keyCode, true);
|
||||||
|
this.keysJustPressed.set(event.keyCode, true);
|
||||||
|
this.keysJustReleased.set(event.keyCode, false);
|
||||||
|
}
|
||||||
|
handleKeyUp(event) {
|
||||||
|
if (this.active && this.preventDefault)
|
||||||
|
event.preventDefault();
|
||||||
|
if (!this.keysDown.get(event.keyCode))
|
||||||
|
return;
|
||||||
|
this.keysDown.set(event.keyCode, false);
|
||||||
|
this.keysJustPressed.set(event.keyCode, false);
|
||||||
|
this.keysJustReleased.set(event.keyCode, true);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { BaseInput } from './base-input';
|
||||||
|
export declare class Mouse extends BaseInput {
|
||||||
|
private mousePosition;
|
||||||
|
private mouseDelta;
|
||||||
|
private mouseWheel;
|
||||||
|
private mouseButtons;
|
||||||
|
constructor(element: HTMLElement);
|
||||||
|
capture(): void;
|
||||||
|
release(): void;
|
||||||
|
getState(): IMouse;
|
||||||
|
private handleMouseDown;
|
||||||
|
private handleMouseMove;
|
||||||
|
private handleMouseUp;
|
||||||
|
private handlePointerChange;
|
||||||
|
}
|
||||||
|
export declare class Position {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
export declare class MouseButtons {
|
||||||
|
keysDown: Map<number, boolean>;
|
||||||
|
keysJustPressed: Map<number, boolean>;
|
||||||
|
keysJustReleased: Map<number, boolean>;
|
||||||
|
}
|
||||||
|
export declare class Delta {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
export interface IMouse {
|
||||||
|
mouseButtons: MouseButtons;
|
||||||
|
mousePosition: Position;
|
||||||
|
mouseWheel: Delta;
|
||||||
|
mouseDelta: Delta;
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
import { BaseInput } from './base-input';
|
||||||
|
export class Mouse extends BaseInput {
|
||||||
|
constructor(element) {
|
||||||
|
super(element);
|
||||||
|
this.mousePosition = new Position();
|
||||||
|
this.mouseDelta = new Delta();
|
||||||
|
this.mouseWheel = new Delta();
|
||||||
|
this.mouseButtons = new MouseButtons();
|
||||||
|
}
|
||||||
|
capture() {
|
||||||
|
this.handleMouseDown = this.handleMouseDown.bind(this);
|
||||||
|
this.handleMouseMove = this.handleMouseMove.bind(this);
|
||||||
|
this.handleMouseUp = this.handleMouseUp.bind(this);
|
||||||
|
this.handlePointerChange = this.handlePointerChange.bind(this);
|
||||||
|
this.active = true;
|
||||||
|
this.element.addEventListener('mousedown', this.handleMouseDown);
|
||||||
|
this.element.addEventListener('mousemove', this.handleMouseMove);
|
||||||
|
this.element.addEventListener('mouseup', this.handleMouseUp);
|
||||||
|
document.addEventListener('pointerlockchange', this.handlePointerChange);
|
||||||
|
}
|
||||||
|
release() {
|
||||||
|
this.active = false;
|
||||||
|
this.element.removeEventListener('mousedown', this.handleMouseDown);
|
||||||
|
this.element.removeEventListener('mousemove', this.handleMouseMove);
|
||||||
|
this.element.removeEventListener('mouseup', this.handleMouseUp);
|
||||||
|
}
|
||||||
|
getState() {
|
||||||
|
const { mouseButtons, mouseDelta, mousePosition, mouseWheel } = this;
|
||||||
|
const state = {
|
||||||
|
mouseButtons: {
|
||||||
|
keysDown: new Map(this.mouseButtons.keysDown),
|
||||||
|
keysJustPressed: new Map(this.mouseButtons.keysJustPressed),
|
||||||
|
keysJustReleased: new Map(this.mouseButtons.keysJustReleased)
|
||||||
|
},
|
||||||
|
mouseDelta,
|
||||||
|
mousePosition,
|
||||||
|
mouseWheel
|
||||||
|
};
|
||||||
|
this.mouseButtons.keysJustPressed.clear();
|
||||||
|
this.mouseButtons.keysJustReleased.clear();
|
||||||
|
this.mouseDelta.x = 0;
|
||||||
|
this.mouseDelta.y = 0;
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
handleMouseDown(event) {
|
||||||
|
if (this.active)
|
||||||
|
event.preventDefault();
|
||||||
|
this.mouseButtons.keysDown.set(event.button, true);
|
||||||
|
this.mouseButtons.keysJustPressed.set(event.button, true);
|
||||||
|
this.mouseButtons.keysJustReleased.set(event.button, false);
|
||||||
|
}
|
||||||
|
handleMouseMove(event) {
|
||||||
|
if (this.active)
|
||||||
|
event.preventDefault();
|
||||||
|
this.mousePosition.x = event.clientX;
|
||||||
|
this.mousePosition.y = event.clientY;
|
||||||
|
this.mouseDelta.x = event.movementX;
|
||||||
|
this.mouseDelta.y = event.movementY;
|
||||||
|
}
|
||||||
|
handleMouseUp(event) {
|
||||||
|
if (this.active)
|
||||||
|
event.preventDefault();
|
||||||
|
this.mouseButtons.keysJustReleased.set(event.button, true);
|
||||||
|
this.mouseButtons.keysDown.set(event.button, false);
|
||||||
|
this.mouseButtons.keysJustPressed.set(event.button, false);
|
||||||
|
}
|
||||||
|
handlePointerChange() {
|
||||||
|
if (document.pointerLockElement !== this.element) {
|
||||||
|
this.element.addEventListener('click', () => {
|
||||||
|
this.element.requestPointerLock();
|
||||||
|
}, {
|
||||||
|
once: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class Position {
|
||||||
|
}
|
||||||
|
export class MouseButtons {
|
||||||
|
constructor() {
|
||||||
|
this.keysDown = new Map();
|
||||||
|
this.keysJustPressed = new Map();
|
||||||
|
this.keysJustReleased = new Map();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class Delta {
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { IKeyboard } from '../inputs/keyboard';
|
||||||
|
export interface State {
|
||||||
|
keyboard?: IKeyboard;
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export {};
|
|
@ -0,0 +1,122 @@
|
||||||
|
export declare let KeyCodes: {
|
||||||
|
CANCEL: number;
|
||||||
|
HELP: number;
|
||||||
|
BACK_SPACE: number;
|
||||||
|
TAB: number;
|
||||||
|
CLEAR: number;
|
||||||
|
RETURN: number;
|
||||||
|
ENTER: number;
|
||||||
|
SHIFT: number;
|
||||||
|
CONTROL: number;
|
||||||
|
ALT: number;
|
||||||
|
PAUSE: number;
|
||||||
|
CAPS_LOCK: number;
|
||||||
|
ESCAPE: number;
|
||||||
|
SPACE: number;
|
||||||
|
PAGE_UP: number;
|
||||||
|
PAGE_DOWN: number;
|
||||||
|
END: number;
|
||||||
|
HOME: number;
|
||||||
|
LEFT: number;
|
||||||
|
UP: number;
|
||||||
|
RIGHT: number;
|
||||||
|
DOWN: number;
|
||||||
|
PRINTSCREEN: number;
|
||||||
|
INSERT: number;
|
||||||
|
DELETE: number;
|
||||||
|
0: number;
|
||||||
|
1: number;
|
||||||
|
2: number;
|
||||||
|
3: number;
|
||||||
|
4: number;
|
||||||
|
5: number;
|
||||||
|
6: number;
|
||||||
|
7: number;
|
||||||
|
8: number;
|
||||||
|
9: number;
|
||||||
|
SEMICOLON: number;
|
||||||
|
EQUALS: number;
|
||||||
|
A: number;
|
||||||
|
B: number;
|
||||||
|
C: number;
|
||||||
|
D: number;
|
||||||
|
E: number;
|
||||||
|
F: number;
|
||||||
|
G: number;
|
||||||
|
H: number;
|
||||||
|
I: number;
|
||||||
|
J: number;
|
||||||
|
K: number;
|
||||||
|
L: number;
|
||||||
|
M: number;
|
||||||
|
N: number;
|
||||||
|
O: number;
|
||||||
|
P: number;
|
||||||
|
Q: number;
|
||||||
|
R: number;
|
||||||
|
S: number;
|
||||||
|
T: number;
|
||||||
|
U: number;
|
||||||
|
V: number;
|
||||||
|
W: number;
|
||||||
|
X: number;
|
||||||
|
Y: number;
|
||||||
|
Z: number;
|
||||||
|
CONTEXT_MENU: number;
|
||||||
|
NUMPAD0: number;
|
||||||
|
NUMPAD1: number;
|
||||||
|
NUMPAD2: number;
|
||||||
|
NUMPAD3: number;
|
||||||
|
NUMPAD4: number;
|
||||||
|
NUMPAD5: number;
|
||||||
|
NUMPAD6: number;
|
||||||
|
NUMPAD7: number;
|
||||||
|
NUMPAD8: number;
|
||||||
|
NUMPAD9: number;
|
||||||
|
MULTIPLY: number;
|
||||||
|
ADD: number;
|
||||||
|
SEPARATOR: number;
|
||||||
|
SUBTRACT: number;
|
||||||
|
DECIMAL: number;
|
||||||
|
DIVIDE: number;
|
||||||
|
F1: number;
|
||||||
|
F2: number;
|
||||||
|
F3: number;
|
||||||
|
F4: number;
|
||||||
|
F5: number;
|
||||||
|
F6: number;
|
||||||
|
F7: number;
|
||||||
|
F8: number;
|
||||||
|
F9: number;
|
||||||
|
F10: number;
|
||||||
|
F11: number;
|
||||||
|
F12: number;
|
||||||
|
F13: number;
|
||||||
|
F14: number;
|
||||||
|
F15: number;
|
||||||
|
F16: number;
|
||||||
|
F17: number;
|
||||||
|
F18: number;
|
||||||
|
F19: number;
|
||||||
|
F20: number;
|
||||||
|
F21: number;
|
||||||
|
F22: number;
|
||||||
|
F23: number;
|
||||||
|
F24: number;
|
||||||
|
NUM_LOCK: number;
|
||||||
|
SCROLL_LOCK: number;
|
||||||
|
COMMA: number;
|
||||||
|
PERIOD: number;
|
||||||
|
SLASH: number;
|
||||||
|
BACK_QUOTE: number;
|
||||||
|
OPEN_BRACKET: number;
|
||||||
|
BACK_SLASH: number;
|
||||||
|
CLOSE_BRACKET: number;
|
||||||
|
QUOTE: number;
|
||||||
|
META: number;
|
||||||
|
};
|
||||||
|
export declare let MouseCodes: {
|
||||||
|
LEFT: number;
|
||||||
|
RIGHT: number;
|
||||||
|
MIDDLE: number;
|
||||||
|
};
|
|
@ -0,0 +1,122 @@
|
||||||
|
export let KeyCodes = {
|
||||||
|
CANCEL: 3,
|
||||||
|
HELP: 6,
|
||||||
|
BACK_SPACE: 8,
|
||||||
|
TAB: 9,
|
||||||
|
CLEAR: 12,
|
||||||
|
RETURN: 13,
|
||||||
|
ENTER: 14,
|
||||||
|
SHIFT: 16,
|
||||||
|
CONTROL: 17,
|
||||||
|
ALT: 18,
|
||||||
|
PAUSE: 19,
|
||||||
|
CAPS_LOCK: 20,
|
||||||
|
ESCAPE: 27,
|
||||||
|
SPACE: 32,
|
||||||
|
PAGE_UP: 33,
|
||||||
|
PAGE_DOWN: 34,
|
||||||
|
END: 35,
|
||||||
|
HOME: 36,
|
||||||
|
LEFT: 37,
|
||||||
|
UP: 38,
|
||||||
|
RIGHT: 39,
|
||||||
|
DOWN: 40,
|
||||||
|
PRINTSCREEN: 44,
|
||||||
|
INSERT: 45,
|
||||||
|
DELETE: 46,
|
||||||
|
0: 48,
|
||||||
|
1: 49,
|
||||||
|
2: 50,
|
||||||
|
3: 51,
|
||||||
|
4: 52,
|
||||||
|
5: 53,
|
||||||
|
6: 54,
|
||||||
|
7: 55,
|
||||||
|
8: 56,
|
||||||
|
9: 57,
|
||||||
|
SEMICOLON: 59,
|
||||||
|
EQUALS: 61,
|
||||||
|
A: 65,
|
||||||
|
B: 66,
|
||||||
|
C: 67,
|
||||||
|
D: 68,
|
||||||
|
E: 69,
|
||||||
|
F: 70,
|
||||||
|
G: 71,
|
||||||
|
H: 72,
|
||||||
|
I: 73,
|
||||||
|
J: 74,
|
||||||
|
K: 75,
|
||||||
|
L: 76,
|
||||||
|
M: 77,
|
||||||
|
N: 78,
|
||||||
|
O: 79,
|
||||||
|
P: 80,
|
||||||
|
Q: 81,
|
||||||
|
R: 82,
|
||||||
|
S: 83,
|
||||||
|
T: 84,
|
||||||
|
U: 85,
|
||||||
|
V: 86,
|
||||||
|
W: 87,
|
||||||
|
X: 88,
|
||||||
|
Y: 89,
|
||||||
|
Z: 90,
|
||||||
|
CONTEXT_MENU: 93,
|
||||||
|
NUMPAD0: 96,
|
||||||
|
NUMPAD1: 97,
|
||||||
|
NUMPAD2: 98,
|
||||||
|
NUMPAD3: 99,
|
||||||
|
NUMPAD4: 100,
|
||||||
|
NUMPAD5: 101,
|
||||||
|
NUMPAD6: 102,
|
||||||
|
NUMPAD7: 103,
|
||||||
|
NUMPAD8: 104,
|
||||||
|
NUMPAD9: 105,
|
||||||
|
MULTIPLY: 106,
|
||||||
|
ADD: 107,
|
||||||
|
SEPARATOR: 108,
|
||||||
|
SUBTRACT: 109,
|
||||||
|
DECIMAL: 110,
|
||||||
|
DIVIDE: 111,
|
||||||
|
F1: 112,
|
||||||
|
F2: 113,
|
||||||
|
F3: 114,
|
||||||
|
F4: 115,
|
||||||
|
F5: 116,
|
||||||
|
F6: 117,
|
||||||
|
F7: 118,
|
||||||
|
F8: 119,
|
||||||
|
F9: 120,
|
||||||
|
F10: 121,
|
||||||
|
F11: 122,
|
||||||
|
F12: 123,
|
||||||
|
F13: 124,
|
||||||
|
F14: 125,
|
||||||
|
F15: 126,
|
||||||
|
F16: 127,
|
||||||
|
F17: 128,
|
||||||
|
F18: 129,
|
||||||
|
F19: 130,
|
||||||
|
F20: 131,
|
||||||
|
F21: 132,
|
||||||
|
F22: 133,
|
||||||
|
F23: 134,
|
||||||
|
F24: 135,
|
||||||
|
NUM_LOCK: 144,
|
||||||
|
SCROLL_LOCK: 145,
|
||||||
|
COMMA: 188,
|
||||||
|
PERIOD: 190,
|
||||||
|
SLASH: 191,
|
||||||
|
BACK_QUOTE: 192,
|
||||||
|
OPEN_BRACKET: 219,
|
||||||
|
BACK_SLASH: 220,
|
||||||
|
CLOSE_BRACKET: 221,
|
||||||
|
QUOTE: 222,
|
||||||
|
META: 224
|
||||||
|
};
|
||||||
|
export let MouseCodes = {
|
||||||
|
LEFT: 0,
|
||||||
|
RIGHT: 1,
|
||||||
|
MIDDLE: 2
|
||||||
|
};
|
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"name": "audiogame-tools",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"lint": "eslint . --ext .ts",
|
||||||
|
"format": "prettier --config .prettierrc engine/**/*.ts --write",
|
||||||
|
"compile:engine": "tsc",
|
||||||
|
"watch:engine": "tsc -w",
|
||||||
|
"bundle:engine": "webpack --config webpack.engine.js"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"eventemitter3": "^4.0.7",
|
||||||
|
"yaml": "^1.10.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.12.3",
|
||||||
|
"@babel/preset-env": "^7.12.1",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^4.7.0",
|
||||||
|
"@typescript-eslint/parser": "^4.7.0",
|
||||||
|
"eslint": "^7.13.0",
|
||||||
|
"prettier": "^2.1.2",
|
||||||
|
"ts-loader": "^8.0.11",
|
||||||
|
"typescript": "^4.0.5",
|
||||||
|
"webpack": "^5.4.0",
|
||||||
|
"webpack-cli": "^4.2.0"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
import { PhysicsObject } from './object';
|
||||||
|
export declare function AABB(obj1: PhysicsObject, obj2: PhysicsObject): boolean;
|
|
@ -0,0 +1,17 @@
|
||||||
|
export function AABB(obj1, obj2) {
|
||||||
|
if (checkOverlap(obj1.position.x, obj1.dimensions.x, obj2.position.x, obj2.dimensions.x)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (checkOverlap(obj1.position.y, obj1.dimensions.y, obj2.position.y, obj2.dimensions.y)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (checkOverlap(obj1.position.z, obj1.dimensions.z, obj2.position.z, obj2.dimensions.z)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
function checkOverlap(x, w, yx, yw) {
|
||||||
|
if (x > yx || x < yx + yw || x + w > x || x + w < yx + yw) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { Vec3 } from './vec3';
|
||||||
|
export declare class PhysicsObject {
|
||||||
|
position: Vec3;
|
||||||
|
dimensions: Vec3;
|
||||||
|
velocity: Vec3;
|
||||||
|
affectedByGravity: boolean;
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
export class PhysicsObject {
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { PhysicsObject } from './object';
|
||||||
|
import { Vec3 } from './vec3';
|
||||||
|
export declare class Octree {
|
||||||
|
private dimensions;
|
||||||
|
private maxObjects;
|
||||||
|
private maxLevels;
|
||||||
|
root: OcTreeNode;
|
||||||
|
constructor(dimensions: Vec3, maxObjects?: number, maxLevels?: number);
|
||||||
|
insert(obj: PhysicsObject): void;
|
||||||
|
find(position: Vec3, dimensions: Vec3): PhysicsObject[];
|
||||||
|
}
|
||||||
|
export declare class OcTreeNode {
|
||||||
|
private position;
|
||||||
|
private dimensions;
|
||||||
|
private maxLevels;
|
||||||
|
private maxObjects;
|
||||||
|
private currentLevel;
|
||||||
|
objects: PhysicsObject[];
|
||||||
|
nodes: OcTreeNode[];
|
||||||
|
constructor(position: Vec3, dimensions: Vec3, maxLevels: number, maxObjects: number, currentLevel?: number);
|
||||||
|
insert(obj: PhysicsObject): any;
|
||||||
|
find(x: number, y: number, z: number, xw: number, yh: number, zd: number): PhysicsObject[];
|
||||||
|
split(): void;
|
||||||
|
private distributeObjectsToNodes;
|
||||||
|
getIndex(x: number, y: number, z: number): Direction;
|
||||||
|
getIndeciesForRect(x: number, y: number, z: number, xw: number, yh: number, zd: number): Direction[];
|
||||||
|
}
|
||||||
|
declare enum Direction {
|
||||||
|
AboveUpperLeft = 0,
|
||||||
|
AboveUpperRight = 1,
|
||||||
|
AboveLowerRight = 2,
|
||||||
|
AboveLowerLeft = 3,
|
||||||
|
BelowUpperLeft = 4,
|
||||||
|
BelowUpperRight = 5,
|
||||||
|
BelowLowerRight = 6,
|
||||||
|
BelowLowerLeft = 7,
|
||||||
|
Here = 8
|
||||||
|
}
|
||||||
|
export {};
|
|
@ -0,0 +1,206 @@
|
||||||
|
import { Vec3 } from './vec3';
|
||||||
|
export class Octree {
|
||||||
|
constructor(dimensions, maxObjects = 10, maxLevels = 10) {
|
||||||
|
this.dimensions = dimensions;
|
||||||
|
this.maxObjects = maxObjects;
|
||||||
|
this.maxLevels = maxLevels;
|
||||||
|
this.root = new OcTreeNode(new Vec3({
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
z: 0
|
||||||
|
}), this.dimensions, maxLevels, maxObjects, 0);
|
||||||
|
}
|
||||||
|
insert(obj) {
|
||||||
|
this.root.insert(obj);
|
||||||
|
}
|
||||||
|
find(position, dimensions) {
|
||||||
|
return this.root.find(position.x, position.y, position.z, dimensions.x, dimensions.y, dimensions.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class OcTreeNode {
|
||||||
|
constructor(position, dimensions, maxLevels, maxObjects, currentLevel = 0) {
|
||||||
|
this.position = position;
|
||||||
|
this.dimensions = dimensions;
|
||||||
|
this.maxLevels = maxLevels;
|
||||||
|
this.maxObjects = maxObjects;
|
||||||
|
this.currentLevel = currentLevel;
|
||||||
|
this.objects = [];
|
||||||
|
this.nodes = [];
|
||||||
|
}
|
||||||
|
insert(obj) {
|
||||||
|
const index = this.getIndex(obj.position.x, obj.position.y, obj.position.z);
|
||||||
|
if (index === Direction.Here) {
|
||||||
|
this.objects.push(obj);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return this.nodes[index].insert(obj);
|
||||||
|
}
|
||||||
|
if (this.objects.length > this.maxObjects &&
|
||||||
|
this.currentLevel < this.maxLevels) {
|
||||||
|
this.split();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
find(x, y, z, xw, yh, zd) {
|
||||||
|
if (this.nodes.length < 1) {
|
||||||
|
return this.objects;
|
||||||
|
}
|
||||||
|
const indecies = this.getIndeciesForRect(x, y, z, xw, yh, zd);
|
||||||
|
let results = [];
|
||||||
|
for (let i = 0; i < indecies.length - 1; i++) {
|
||||||
|
let res = this.nodes[indecies[i]].find(x, y, z, xw, yh, zd);
|
||||||
|
results.push([...res]);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
split() {
|
||||||
|
const halfWidth = this.dimensions.x / 2;
|
||||||
|
const halfHeight = this.dimensions.y / 2;
|
||||||
|
const halfDepth = this.dimensions.z / 2;
|
||||||
|
this.nodes[Direction.AboveUpperLeft] = new OcTreeNode(new Vec3({
|
||||||
|
x: this.position.x,
|
||||||
|
y: this.position.y,
|
||||||
|
z: this.position.z + halfDepth
|
||||||
|
}), new Vec3({
|
||||||
|
x: halfWidth,
|
||||||
|
y: halfHeight,
|
||||||
|
z: halfDepth
|
||||||
|
}), this.maxLevels, this.maxObjects, this.currentLevel++);
|
||||||
|
this.nodes[Direction.AboveUpperRight] = new OcTreeNode(new Vec3({
|
||||||
|
x: this.position.x + halfWidth,
|
||||||
|
y: this.position.y,
|
||||||
|
z: this.position.z + halfDepth
|
||||||
|
}), new Vec3({
|
||||||
|
x: halfWidth,
|
||||||
|
y: halfHeight,
|
||||||
|
z: halfDepth
|
||||||
|
}), this.maxLevels, this.maxObjects, this.currentLevel++);
|
||||||
|
this.nodes[Direction.AboveLowerRight] = new OcTreeNode(new Vec3({
|
||||||
|
x: this.position.x + halfWidth,
|
||||||
|
y: this.position.y + halfHeight,
|
||||||
|
z: this.position.z + halfDepth
|
||||||
|
}), new Vec3({
|
||||||
|
x: halfWidth,
|
||||||
|
y: halfHeight,
|
||||||
|
z: halfDepth
|
||||||
|
}), this.maxLevels, this.maxObjects, this.currentLevel++);
|
||||||
|
this.nodes[Direction.AboveLowerLeft] = new OcTreeNode(new Vec3({
|
||||||
|
x: this.position.x,
|
||||||
|
y: this.position.y + halfHeight,
|
||||||
|
z: this.position.z + halfDepth
|
||||||
|
}), new Vec3({
|
||||||
|
x: halfWidth,
|
||||||
|
y: halfHeight,
|
||||||
|
z: halfDepth
|
||||||
|
}), this.maxLevels, this.maxObjects, this.currentLevel++);
|
||||||
|
this.nodes[Direction.BelowUpperLeft] = new OcTreeNode(new Vec3({
|
||||||
|
x: this.position.x,
|
||||||
|
y: this.position.y,
|
||||||
|
z: this.position.z
|
||||||
|
}), new Vec3({
|
||||||
|
x: halfWidth,
|
||||||
|
y: halfHeight,
|
||||||
|
z: halfDepth
|
||||||
|
}), this.maxLevels, this.maxObjects, this.currentLevel++);
|
||||||
|
this.nodes[Direction.BelowUpperRight] = new OcTreeNode(new Vec3({
|
||||||
|
x: this.position.x + halfWidth,
|
||||||
|
y: this.position.y,
|
||||||
|
z: this.position.z
|
||||||
|
}), new Vec3({
|
||||||
|
x: halfWidth,
|
||||||
|
y: halfHeight,
|
||||||
|
z: halfDepth
|
||||||
|
}), this.maxLevels, this.maxObjects, this.currentLevel++);
|
||||||
|
this.nodes[Direction.BelowLowerRight] = new OcTreeNode(new Vec3({
|
||||||
|
x: this.position.x + halfWidth,
|
||||||
|
y: this.position.y + halfHeight,
|
||||||
|
z: this.position.z
|
||||||
|
}), new Vec3({
|
||||||
|
x: halfWidth,
|
||||||
|
y: halfHeight,
|
||||||
|
z: halfDepth
|
||||||
|
}), this.maxLevels, this.maxObjects, this.currentLevel++);
|
||||||
|
this.nodes[Direction.BelowLowerLeft] = new OcTreeNode(new Vec3({
|
||||||
|
x: this.position.x,
|
||||||
|
y: this.position.y + halfHeight,
|
||||||
|
z: this.position.z
|
||||||
|
}), new Vec3({
|
||||||
|
x: halfWidth,
|
||||||
|
y: halfHeight,
|
||||||
|
z: halfDepth
|
||||||
|
}), this.maxLevels, this.maxObjects, this.currentLevel++);
|
||||||
|
this.distributeObjectsToNodes();
|
||||||
|
}
|
||||||
|
distributeObjectsToNodes() {
|
||||||
|
if (this.nodes.length < 8) {
|
||||||
|
this.split();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.objects.forEach((obj) => {
|
||||||
|
const direction = this.getIndex(obj.position.x, obj.position.y, obj.position.z);
|
||||||
|
this.nodes[direction].insert(obj);
|
||||||
|
});
|
||||||
|
this.objects = [];
|
||||||
|
}
|
||||||
|
getIndex(x, y, z) {
|
||||||
|
if (this.nodes.length === 0) {
|
||||||
|
return Direction.Here;
|
||||||
|
}
|
||||||
|
const halfWidth = this.dimensions.x / 2;
|
||||||
|
const halfHeight = this.dimensions.y / 2;
|
||||||
|
const halfDepth = this.dimensions.z / 2;
|
||||||
|
const isBelow = z < this.position.z + halfDepth;
|
||||||
|
const isLeft = x < this.position.x + halfWidth;
|
||||||
|
const isUpper = y > this.position.y + halfHeight;
|
||||||
|
if (isBelow) {
|
||||||
|
if (isLeft) {
|
||||||
|
if (isUpper)
|
||||||
|
return Direction.AboveUpperLeft;
|
||||||
|
else
|
||||||
|
return Direction.AboveLowerLeft;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (isUpper)
|
||||||
|
return Direction.AboveUpperRight;
|
||||||
|
else
|
||||||
|
return Direction.AboveLowerRight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (isLeft) {
|
||||||
|
if (isUpper)
|
||||||
|
return Direction.BelowUpperLeft;
|
||||||
|
else
|
||||||
|
return Direction.BelowLowerLeft;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (isUpper)
|
||||||
|
return Direction.BelowUpperRight;
|
||||||
|
else
|
||||||
|
return Direction.BelowLowerRight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getIndeciesForRect(x, y, z, xw, yh, zd) {
|
||||||
|
if (!(x > this.position.x && x < this.position.x + this.dimensions.x) ||
|
||||||
|
!(y > this.position.y && y < this.position.y + this.dimensions.y) ||
|
||||||
|
!(z > this.position.z && z < this.position.z + this.dimensions.z)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
let indecies = [];
|
||||||
|
indecies.push(this.getIndex(x, y, z));
|
||||||
|
indecies.push(this.getIndex(x + xw, y + yh, z + zd));
|
||||||
|
return indecies;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var Direction;
|
||||||
|
(function (Direction) {
|
||||||
|
Direction[Direction["AboveUpperLeft"] = 0] = "AboveUpperLeft";
|
||||||
|
Direction[Direction["AboveUpperRight"] = 1] = "AboveUpperRight";
|
||||||
|
Direction[Direction["AboveLowerRight"] = 2] = "AboveLowerRight";
|
||||||
|
Direction[Direction["AboveLowerLeft"] = 3] = "AboveLowerLeft";
|
||||||
|
Direction[Direction["BelowUpperLeft"] = 4] = "BelowUpperLeft";
|
||||||
|
Direction[Direction["BelowUpperRight"] = 5] = "BelowUpperRight";
|
||||||
|
Direction[Direction["BelowLowerRight"] = 6] = "BelowLowerRight";
|
||||||
|
Direction[Direction["BelowLowerLeft"] = 7] = "BelowLowerLeft";
|
||||||
|
Direction[Direction["Here"] = 8] = "Here";
|
||||||
|
})(Direction || (Direction = {}));
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { PhysicsObject } from "./object";
|
||||||
|
import { Vec3 } from "./vec3";
|
||||||
|
export declare class Octtree {
|
||||||
|
private dimensions;
|
||||||
|
private maxObjects;
|
||||||
|
private maxLevels;
|
||||||
|
root: OctTreeNode;
|
||||||
|
constructor(dimensions: Vec3, maxObjects?: number, maxLevels?: number);
|
||||||
|
insert(obj: PhysicsObject): void;
|
||||||
|
find(position: Vec3, dimensions: Vec3): PhysicsObject[];
|
||||||
|
}
|
||||||
|
export declare class OctTreeNode {
|
||||||
|
private position;
|
||||||
|
private dimensions;
|
||||||
|
private maxLevels;
|
||||||
|
private maxObjects;
|
||||||
|
objects: PhysicsObject[];
|
||||||
|
nodes: OctTreeNode[];
|
||||||
|
constructor(position: Vec3, dimensions: Vec3, maxLevels: number, maxObjects: number);
|
||||||
|
insert(obj: PhysicsObject): void;
|
||||||
|
find(position: Vec3, dimensions: Vec3): PhysicsObject[];
|
||||||
|
split(): void;
|
||||||
|
private distributeObjectsToNodes;
|
||||||
|
getIndex(x: number, y: number, z: number): Direction;
|
||||||
|
getIndeciesForRect(position: Vec3, dimensions: Vec3): Set<Direction>;
|
||||||
|
}
|
||||||
|
declare enum Direction {
|
||||||
|
AboveUpperLeft = 0,
|
||||||
|
AboveUpperRight = 1,
|
||||||
|
AboveLowerRight = 2,
|
||||||
|
AboveLowerLeft = 3,
|
||||||
|
BelowUpperLeft = 4,
|
||||||
|
BelowUpperRight = 5,
|
||||||
|
BelowLowerRight = 6,
|
||||||
|
BelowLowerLeft = 7,
|
||||||
|
Here = 8
|
||||||
|
}
|
||||||
|
export {};
|
|
@ -0,0 +1,193 @@
|
||||||
|
import { Vec3 } from "./vec3";
|
||||||
|
export class Octtree {
|
||||||
|
constructor(dimensions, maxObjects = 10, maxLevels = 10) {
|
||||||
|
this.dimensions = dimensions;
|
||||||
|
this.maxObjects = maxObjects;
|
||||||
|
this.maxLevels = maxLevels;
|
||||||
|
this.root = new OctTreeNode(new Vec3({
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
z: 0
|
||||||
|
}), this.dimensions, maxLevels, maxObjects);
|
||||||
|
}
|
||||||
|
insert(obj) {
|
||||||
|
this.root.insert(obj);
|
||||||
|
}
|
||||||
|
find(position, dimensions) {
|
||||||
|
return this.root.find(position, dimensions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class OctTreeNode {
|
||||||
|
constructor(position, dimensions, maxLevels, maxObjects) {
|
||||||
|
this.position = position;
|
||||||
|
this.dimensions = dimensions;
|
||||||
|
this.maxLevels = maxLevels;
|
||||||
|
this.maxObjects = maxObjects;
|
||||||
|
this.objects = [];
|
||||||
|
this.nodes = [];
|
||||||
|
}
|
||||||
|
insert(obj) {
|
||||||
|
this.objects.push(obj);
|
||||||
|
if (this.objects.length > this.maxObjects) {
|
||||||
|
this.split();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
find(position, dimensions) {
|
||||||
|
if (!this.nodes.length) {
|
||||||
|
return this.objects;
|
||||||
|
}
|
||||||
|
const indecies = this.getIndeciesForRect(position, dimensions);
|
||||||
|
let results = [];
|
||||||
|
indecies.forEach((index) => {
|
||||||
|
let res = this.nodes[index].find(position, dimensions);
|
||||||
|
res.forEach((obj) => results.push(obj));
|
||||||
|
});
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
split() {
|
||||||
|
const halfWidth = this.dimensions.x / 2;
|
||||||
|
const halfHeight = this.dimensions.y / 2;
|
||||||
|
const halfDepth = this.dimensions.z / 2;
|
||||||
|
this.nodes[Direction.AboveUpperLeft] = new OctTreeNode(new Vec3({
|
||||||
|
x: this.position.x,
|
||||||
|
y: this.position.y,
|
||||||
|
z: this.position.z + halfDepth
|
||||||
|
}), new Vec3({
|
||||||
|
x: halfWidth,
|
||||||
|
y: halfHeight,
|
||||||
|
z: halfDepth
|
||||||
|
}), this.maxLevels, this.maxObjects);
|
||||||
|
this.nodes[Direction.AboveUpperRight] = new OctTreeNode(new Vec3({
|
||||||
|
x: this.position.x + halfWidth,
|
||||||
|
y: this.position.y,
|
||||||
|
z: this.position.z + halfDepth
|
||||||
|
}), new Vec3({
|
||||||
|
x: halfWidth,
|
||||||
|
y: halfHeight,
|
||||||
|
z: halfDepth
|
||||||
|
}), this.maxLevels, this.maxObjects);
|
||||||
|
this.nodes[Direction.AboveLowerRight] = new OctTreeNode(new Vec3({
|
||||||
|
x: this.position.x + halfWidth,
|
||||||
|
y: this.position.y + halfHeight,
|
||||||
|
z: this.position.z + halfDepth
|
||||||
|
}), new Vec3({
|
||||||
|
x: halfWidth,
|
||||||
|
y: halfHeight,
|
||||||
|
z: halfDepth
|
||||||
|
}), this.maxLevels, this.maxObjects);
|
||||||
|
this.nodes[Direction.AboveLowerLeft] = new OctTreeNode(new Vec3({
|
||||||
|
x: this.position.x,
|
||||||
|
y: this.position.y + halfHeight,
|
||||||
|
z: this.position.z + halfDepth
|
||||||
|
}), new Vec3({
|
||||||
|
x: halfWidth,
|
||||||
|
y: halfHeight,
|
||||||
|
z: halfDepth
|
||||||
|
}), this.maxLevels, this.maxObjects);
|
||||||
|
this.nodes[Direction.BelowUpperLeft] = new OctTreeNode(new Vec3({
|
||||||
|
x: this.position.x,
|
||||||
|
y: this.position.y,
|
||||||
|
z: this.position.z
|
||||||
|
}), new Vec3({
|
||||||
|
x: halfWidth,
|
||||||
|
y: halfHeight,
|
||||||
|
z: halfDepth
|
||||||
|
}), this.maxLevels, this.maxObjects);
|
||||||
|
this.nodes[Direction.BelowUpperRight] = new OctTreeNode(new Vec3({
|
||||||
|
x: this.position.x + halfWidth,
|
||||||
|
y: this.position.y,
|
||||||
|
z: this.position.z
|
||||||
|
}), new Vec3({
|
||||||
|
x: halfWidth,
|
||||||
|
y: halfHeight,
|
||||||
|
z: halfDepth
|
||||||
|
}), this.maxLevels, this.maxObjects);
|
||||||
|
this.nodes[Direction.BelowLowerRight] = new OctTreeNode(new Vec3({
|
||||||
|
x: this.position.x + halfWidth,
|
||||||
|
y: this.position.y + halfHeight,
|
||||||
|
z: this.position.z
|
||||||
|
}), new Vec3({
|
||||||
|
x: halfWidth,
|
||||||
|
y: halfHeight,
|
||||||
|
z: halfDepth
|
||||||
|
}), this.maxLevels, this.maxObjects);
|
||||||
|
this.nodes[Direction.BelowLowerLeft] = new OctTreeNode(new Vec3({
|
||||||
|
x: this.position.x,
|
||||||
|
y: this.position.y + halfHeight,
|
||||||
|
z: this.position.z
|
||||||
|
}), new Vec3({
|
||||||
|
x: halfWidth,
|
||||||
|
y: halfHeight,
|
||||||
|
z: halfDepth
|
||||||
|
}), this.maxLevels, this.maxObjects);
|
||||||
|
this.distributeObjectsToNodes();
|
||||||
|
}
|
||||||
|
distributeObjectsToNodes() {
|
||||||
|
if (this.nodes.length < 8) {
|
||||||
|
this.split();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.objects.forEach((obj) => {
|
||||||
|
const direction = this.getIndex(obj.position.x, obj.position.y, obj.position.z);
|
||||||
|
this.nodes[direction].insert(obj);
|
||||||
|
});
|
||||||
|
this.objects = [];
|
||||||
|
}
|
||||||
|
getIndex(x, y, z) {
|
||||||
|
if (this.nodes.length === 0) {
|
||||||
|
return Direction.Here;
|
||||||
|
}
|
||||||
|
const halfWidth = this.dimensions.x / 2;
|
||||||
|
const halfHeight = this.dimensions.y / 2;
|
||||||
|
const halfDepth = this.dimensions.z / 2;
|
||||||
|
const isBelow = (z < this.position.z + halfDepth);
|
||||||
|
const isLeft = (x < this.position.x + halfWidth);
|
||||||
|
const isUpper = (y > this.position.y + halfHeight);
|
||||||
|
if (isBelow) {
|
||||||
|
if (isLeft) {
|
||||||
|
if (isUpper)
|
||||||
|
return Direction.AboveUpperLeft;
|
||||||
|
else
|
||||||
|
return Direction.AboveLowerLeft;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (isUpper)
|
||||||
|
return Direction.AboveUpperRight;
|
||||||
|
else
|
||||||
|
return Direction.AboveLowerRight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (isLeft) {
|
||||||
|
if (isUpper)
|
||||||
|
return Direction.BelowUpperLeft;
|
||||||
|
else
|
||||||
|
return Direction.BelowLowerLeft;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (isUpper)
|
||||||
|
return Direction.BelowUpperRight;
|
||||||
|
else
|
||||||
|
return Direction.BelowLowerRight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getIndeciesForRect(position, dimensions) {
|
||||||
|
let indecies = new Set();
|
||||||
|
indecies.add(this.getIndex(position.x, position.y, position.z));
|
||||||
|
indecies.add(this.getIndex(position.x + dimensions.x, position.y + dimensions.y, position.z + dimensions.z));
|
||||||
|
return indecies;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var Direction;
|
||||||
|
(function (Direction) {
|
||||||
|
Direction[Direction["AboveUpperLeft"] = 0] = "AboveUpperLeft";
|
||||||
|
Direction[Direction["AboveUpperRight"] = 1] = "AboveUpperRight";
|
||||||
|
Direction[Direction["AboveLowerRight"] = 2] = "AboveLowerRight";
|
||||||
|
Direction[Direction["AboveLowerLeft"] = 3] = "AboveLowerLeft";
|
||||||
|
Direction[Direction["BelowUpperLeft"] = 4] = "BelowUpperLeft";
|
||||||
|
Direction[Direction["BelowUpperRight"] = 5] = "BelowUpperRight";
|
||||||
|
Direction[Direction["BelowLowerRight"] = 6] = "BelowLowerRight";
|
||||||
|
Direction[Direction["BelowLowerLeft"] = 7] = "BelowLowerLeft";
|
||||||
|
Direction[Direction["Here"] = 8] = "Here";
|
||||||
|
})(Direction || (Direction = {}));
|
|
@ -0,0 +1,13 @@
|
||||||
|
export declare class Vec3 {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
z: number;
|
||||||
|
constructor(values?: {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
z: number;
|
||||||
|
});
|
||||||
|
add(vector: Vec3): void;
|
||||||
|
multiply(vector: Vec3): void;
|
||||||
|
clone(): Vec3;
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
export class Vec3 {
|
||||||
|
constructor(values = {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
z: 0
|
||||||
|
}) {
|
||||||
|
this.x = values.x;
|
||||||
|
this.y = values.y;
|
||||||
|
this.z = values.z;
|
||||||
|
}
|
||||||
|
add(vector) {
|
||||||
|
this.x += vector.x;
|
||||||
|
this.y += vector.y;
|
||||||
|
this.z += vector.z;
|
||||||
|
}
|
||||||
|
multiply(vector) {
|
||||||
|
this.x *= vector.x;
|
||||||
|
this.y *= vector.y;
|
||||||
|
this.z *= vector.z;
|
||||||
|
}
|
||||||
|
clone() {
|
||||||
|
return new Vec3({
|
||||||
|
x: this.x,
|
||||||
|
y: this.y,
|
||||||
|
z: this.z
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { Octree } from './octree';
|
||||||
|
import { EventBus } from '../event-bus';
|
||||||
|
import { PhysicsObject } from './object';
|
||||||
|
import { Vec3 } from './vec3';
|
||||||
|
export declare class World extends EventBus {
|
||||||
|
objects: PhysicsObject[];
|
||||||
|
gravity: Vec3;
|
||||||
|
dimensions: Vec3;
|
||||||
|
octreeOptions: OctreeOptions;
|
||||||
|
constructor(dimensions: Vec3, octreeOptions: OctreeOptions);
|
||||||
|
setGravity(grav: Vec3): void;
|
||||||
|
addObject(obj: PhysicsObject): void;
|
||||||
|
removeObject(obj: PhysicsObject): void;
|
||||||
|
step(dt: number): void;
|
||||||
|
checkCollisions(obj: PhysicsObject, octree: Octree): void;
|
||||||
|
}
|
||||||
|
interface OctreeOptions {
|
||||||
|
position: Vec3;
|
||||||
|
dimensions: Vec3;
|
||||||
|
maxObjects: number;
|
||||||
|
maxLevels: number;
|
||||||
|
}
|
||||||
|
export {};
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { Octree } from './octree';
|
||||||
|
import { EventBus } from '../event-bus';
|
||||||
|
import { Vec3 } from './vec3';
|
||||||
|
import { AABB } from './aabb';
|
||||||
|
export class World extends EventBus {
|
||||||
|
constructor(dimensions, octreeOptions) {
|
||||||
|
super();
|
||||||
|
if (!octreeOptions) {
|
||||||
|
this.octreeOptions = {
|
||||||
|
position: new Vec3({ x: 0, y: 0, z: 0 }),
|
||||||
|
dimensions: new Vec3(this.dimensions),
|
||||||
|
maxLevels: 50,
|
||||||
|
maxObjects: 50
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.octreeOptions = octreeOptions;
|
||||||
|
}
|
||||||
|
this.dimensions = dimensions;
|
||||||
|
this.objects = [];
|
||||||
|
}
|
||||||
|
setGravity(grav) {
|
||||||
|
this.gravity = grav;
|
||||||
|
}
|
||||||
|
addObject(obj) {
|
||||||
|
this.objects.push(obj);
|
||||||
|
}
|
||||||
|
removeObject(obj) {
|
||||||
|
this.objects = this.objects.filter((val) => val !== obj);
|
||||||
|
}
|
||||||
|
step(dt) {
|
||||||
|
const octree = new Octree(this.octreeOptions.dimensions, this.octreeOptions.maxObjects, this.octreeOptions.maxLevels);
|
||||||
|
this.objects.forEach((obj) => octree.insert(obj));
|
||||||
|
this.objects.forEach((obj) => {
|
||||||
|
let velocity = obj.velocity.clone();
|
||||||
|
velocity.multiply(new Vec3({ x: dt, y: dt, z: dt }));
|
||||||
|
let gravity = this.gravity.clone();
|
||||||
|
gravity.multiply(new Vec3({ x: dt, y: dt, z: dt }));
|
||||||
|
obj.position.add(velocity);
|
||||||
|
if (obj.affectedByGravity) {
|
||||||
|
obj.velocity.add(gravity);
|
||||||
|
}
|
||||||
|
this.checkCollisions(obj, octree);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
checkCollisions(obj, octree) {
|
||||||
|
const potentialCandidates = octree.find(obj.position, obj.dimensions);
|
||||||
|
potentialCandidates.forEach((candidate) => {
|
||||||
|
if (AABB(obj, candidate)) {
|
||||||
|
this.emit('collision', [obj, candidate]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
export default class ResonatorAudioContext {
|
||||||
|
private context;
|
||||||
|
constructor();
|
||||||
|
getContext(): AudioContext;
|
||||||
|
createGain(): any;
|
||||||
|
getOutputDestination(): AudioNode;
|
||||||
|
createBufferSource(): AudioBufferSourceNode;
|
||||||
|
decodeAudioData(data: ArrayBuffer): Promise<AudioBuffer>;
|
||||||
|
createPanner(): any;
|
||||||
|
createMediaElementSource(element: HTMLMediaElement): MediaElementAudioSourceNode;
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
// simple wrapper around the AudioContext
|
||||||
|
// eventually will be used to deal with cross browser issues
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export default class ResonatorAudioContext {
|
||||||
|
constructor() {
|
||||||
|
this.context = new AudioContext();
|
||||||
|
}
|
||||||
|
getContext() {
|
||||||
|
return this.context;
|
||||||
|
}
|
||||||
|
createGain() {
|
||||||
|
return this.context.createGain();
|
||||||
|
}
|
||||||
|
getOutputDestination() {
|
||||||
|
return this.context.destination;
|
||||||
|
}
|
||||||
|
createBufferSource() {
|
||||||
|
return this.context.createBufferSource();
|
||||||
|
}
|
||||||
|
decodeAudioData(data) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
return yield this.context.decodeAudioData(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
createPanner() {
|
||||||
|
return this.context.createPanner();
|
||||||
|
}
|
||||||
|
createMediaElementSource(element) {
|
||||||
|
return this.context.createMediaElementSource(element);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
import ResonatorScene from './scenes/webaudio-scene';
|
||||||
|
import ResonatorAudioContext from './audio-context';
|
||||||
|
import BaseEffect from './effects/base-effect';
|
||||||
|
export default class AudioGraph {
|
||||||
|
private master;
|
||||||
|
private effectsBus;
|
||||||
|
private worldBus;
|
||||||
|
private secondaryBus;
|
||||||
|
private effects;
|
||||||
|
private scene;
|
||||||
|
private context;
|
||||||
|
private swapChannels;
|
||||||
|
private channelSplitter;
|
||||||
|
private channelMerger;
|
||||||
|
constructor(scene: ResonatorScene, context: ResonatorAudioContext, swapChannels?: boolean);
|
||||||
|
init(): void;
|
||||||
|
connectToMaster(input: any): void;
|
||||||
|
connectToUI(input: AudioNode): void;
|
||||||
|
applyEffect(effect: BaseEffect): void;
|
||||||
|
removeEffect(effect: BaseEffect): void;
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
// this is the mixer that takes all the different outputs and mixes them into the 2 busses:
|
||||||
|
// WorldBus: The directional audio
|
||||||
|
// SecondaryBus: All the UI and things that are non directional
|
||||||
|
import EffectChain from './effect-chain';
|
||||||
|
export default class AudioGraph {
|
||||||
|
constructor(scene, context, swapChannels = false) {
|
||||||
|
this.scene = scene;
|
||||||
|
this.context = context;
|
||||||
|
this.swapChannels = swapChannels;
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
init() {
|
||||||
|
this.effectsBus = this.context.createGain();
|
||||||
|
this.worldBus = this.context.createGain();
|
||||||
|
this.secondaryBus = this.context.createGain();
|
||||||
|
this.master = this.context.createGain();
|
||||||
|
this.scene.getOutput().connect(this.worldBus);
|
||||||
|
// this.worldBus.connect(this.master);
|
||||||
|
this.worldBus.connect(this.effectsBus);
|
||||||
|
this.effects = new EffectChain(this.context, this, this.effectsBus, this.master);
|
||||||
|
this.secondaryBus.connect(this.master);
|
||||||
|
if (this.swapChannels) {
|
||||||
|
this.channelSplitter = this.context.getContext().createChannelSplitter(2);
|
||||||
|
this.channelMerger = this.context.getContext().createChannelMerger(2);
|
||||||
|
this.master.connect(this.channelSplitter);
|
||||||
|
this.channelSplitter.connect(this.channelMerger, 0, 1);
|
||||||
|
this.channelSplitter.connect(this.channelMerger, 1, 0);
|
||||||
|
this.channelMerger.connect(this.context.getOutputDestination());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.master.connect(this.context.getOutputDestination());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
connectToMaster(input) {
|
||||||
|
input.connect(this.master);
|
||||||
|
}
|
||||||
|
connectToUI(input) {
|
||||||
|
input.connect(this.secondaryBus);
|
||||||
|
}
|
||||||
|
applyEffect(effect) {
|
||||||
|
this.effects.applyEffect(effect);
|
||||||
|
}
|
||||||
|
removeEffect(effect) {
|
||||||
|
this.effects.removeEffect(effect);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
export default class DataPoolItem {
|
||||||
|
private name;
|
||||||
|
private data;
|
||||||
|
private decodedData;
|
||||||
|
constructor(name: string, data?: any, decodedData?: any);
|
||||||
|
getData(): any;
|
||||||
|
setData(data: any): void;
|
||||||
|
getDecodedData(): any;
|
||||||
|
setDecodedData(data: any): void;
|
||||||
|
getName(): string;
|
||||||
|
setName(name: string): void;
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
// An item in the data pool
|
||||||
|
export default class DataPoolItem {
|
||||||
|
constructor(name, data = null, decodedData = null) {
|
||||||
|
this.name = name;
|
||||||
|
this.data = data;
|
||||||
|
this.decodedData = decodedData;
|
||||||
|
}
|
||||||
|
getData() {
|
||||||
|
return this.data;
|
||||||
|
}
|
||||||
|
setData(data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
getDecodedData() {
|
||||||
|
return this.decodedData;
|
||||||
|
}
|
||||||
|
setDecodedData(data) {
|
||||||
|
this.decodedData = this.decodedData;
|
||||||
|
}
|
||||||
|
getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
setName(name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import EventEmitter from 'eventemitter3';
|
||||||
|
import ResonatorAudioContext from './audio-context';
|
||||||
|
import { BaseLoader } from './loaders/base-loader';
|
||||||
|
export default class DataPool extends EventEmitter {
|
||||||
|
private loader;
|
||||||
|
private data;
|
||||||
|
private maxData;
|
||||||
|
private context;
|
||||||
|
constructor(context: ResonatorAudioContext, loader?: BaseLoader, maxData?: number);
|
||||||
|
get(path: string): Promise<AudioBuffer>;
|
||||||
|
clear(): void;
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
// a data pool holds frequently played sounds in memory together with decoded audio data to no longer have to decode them from the cache when loaded again
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
import EventEmitter from 'eventemitter3';
|
||||||
|
import DataPoolItem from './data-pool-item';
|
||||||
|
import { HTTPLoader } from './loaders/http-loader';
|
||||||
|
export default class DataPool extends EventEmitter {
|
||||||
|
constructor(context, loader = new HTTPLoader(), maxData = 512) {
|
||||||
|
super();
|
||||||
|
this.loader = loader;
|
||||||
|
this.data = {};
|
||||||
|
this.maxData = maxData;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
get(path) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
if (this.data[path]) {
|
||||||
|
return this.data[path].getDecodedData();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const buffer = yield this.loader.get(path);
|
||||||
|
const decoded = yield this.context.decodeAudioData(buffer);
|
||||||
|
const item = new DataPoolItem(path, buffer, decoded);
|
||||||
|
const length = Object.keys(this.data).length;
|
||||||
|
if (length < this.maxData) {
|
||||||
|
this.data[path] = item;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// TODO: figure out a more clever solution than just removing the first loaded data. Like tracking how much certain data is needed and prioritize them.
|
||||||
|
// const paths: string[] = Object.keys(this.data);
|
||||||
|
// delete this.data[paths[0]];
|
||||||
|
this.data[path] = item;
|
||||||
|
}
|
||||||
|
return item.getDecodedData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
clear() {
|
||||||
|
this.data = {};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import ResonatorAudioContext from './audio-context';
|
||||||
|
export default class EffectBus {
|
||||||
|
private context;
|
||||||
|
private inputNode;
|
||||||
|
private channelMerger;
|
||||||
|
constructor(context: ResonatorAudioContext, input: AudioNode, output: AudioNode);
|
||||||
|
connect(node: AudioNode): void;
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
// Currently unused, but eventually all the effect stuff will be moved from audio graph to here to make it easier to work on
|
||||||
|
export default class EffectBus {
|
||||||
|
constructor(context, input, output) {
|
||||||
|
this.context = context;
|
||||||
|
this.inputNode = input;
|
||||||
|
this.channelMerger = this.context.getContext().createChannelMerger(1);
|
||||||
|
this.inputNode.connect(this.channelMerger);
|
||||||
|
}
|
||||||
|
connect(node) {
|
||||||
|
this.channelMerger.connect(node);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import ResonatorAudioContext from './audio-context';
|
||||||
|
import AudioGraph from './audio-graph';
|
||||||
|
import BaseEffect from './effects/base-effect';
|
||||||
|
export default class EffectChain {
|
||||||
|
private context;
|
||||||
|
private graph;
|
||||||
|
private effects;
|
||||||
|
private inputNode;
|
||||||
|
private outputNode;
|
||||||
|
constructor(context: ResonatorAudioContext, graph: AudioGraph, input: AudioNode, output: AudioNode);
|
||||||
|
applyEffect(effect: BaseEffect): void;
|
||||||
|
removeEffect(effect: BaseEffect): void;
|
||||||
|
private updateConnections;
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
// A chain of effects that connect to the effect bus
|
||||||
|
export default class EffectChain {
|
||||||
|
constructor(context, graph, input, output) {
|
||||||
|
this.effects = [];
|
||||||
|
this.context = context;
|
||||||
|
this.graph = graph;
|
||||||
|
this.inputNode = input;
|
||||||
|
this.outputNode = output;
|
||||||
|
this.updateConnections();
|
||||||
|
}
|
||||||
|
applyEffect(effect) {
|
||||||
|
this.effects.push(effect);
|
||||||
|
this.updateConnections();
|
||||||
|
}
|
||||||
|
removeEffect(effect) {
|
||||||
|
this.effects.forEach((currEffect) => {
|
||||||
|
if (effect === currEffect) {
|
||||||
|
currEffect.disconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.effects = this.effects.filter((currEffect) => effect !== currEffect);
|
||||||
|
this.updateConnections();
|
||||||
|
}
|
||||||
|
updateConnections() {
|
||||||
|
if (this.effects.length == 0) {
|
||||||
|
this.inputNode.connect(this.outputNode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let current = null;
|
||||||
|
let previous = null;
|
||||||
|
this.effects.forEach((effect) => {
|
||||||
|
current = effect;
|
||||||
|
if (previous) {
|
||||||
|
current.connectInput(previous.getOutput());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
current.connectInput(this.inputNode);
|
||||||
|
}
|
||||||
|
previous = current;
|
||||||
|
});
|
||||||
|
if (current) {
|
||||||
|
current.connectOutput(this.outputNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import ResonatorAudioContext from '../audio-context';
|
||||||
|
import AudioGraph from '../audio-graph';
|
||||||
|
export default class BaseEffect {
|
||||||
|
protected ready: boolean;
|
||||||
|
protected effectNode: any;
|
||||||
|
protected effectParams: any;
|
||||||
|
protected context: ResonatorAudioContext;
|
||||||
|
protected graph: AudioGraph;
|
||||||
|
protected inputNode: AudioNode;
|
||||||
|
constructor(context: ResonatorAudioContext, graph: AudioGraph, params: any);
|
||||||
|
connectOutput(node: AudioNode): void;
|
||||||
|
connectInput(node: AudioNode): void;
|
||||||
|
getOutput(): AudioNode;
|
||||||
|
disconnect(): void;
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue