Rooms, items and some player stuff

master
Talon 2021-11-04 20:58:37 +01:00
parent 36bb9264b3
commit a745ff299e
277 changed files with 14665 additions and 5 deletions

2
app_web/game.js 100644

File diff suppressed because one or more lines are too long

View File

@ -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>

18
package-lock.json generated
View File

@ -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",

View File

@ -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"

View File

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

View File

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

View File

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

View File

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

View File

View File

@ -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();
}
}

View File

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

View File

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

20
src/engine/item.js 100644
View File

@ -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();
}
}

View File

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

View File

@ -0,0 +1,6 @@
export default class Player {
constructor() {
this.inventory = [];
this.currentRoom = "start";
}
}

75
src/engine/room.js 100644
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();
}
}

View File

@ -0,0 +1,2 @@
export declare function Manifest(manifestPath: string): Promise<any>;
export declare function CheckManifest(manifest: any): boolean;

View File

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

View File

@ -0,0 +1,8 @@
export declare class Queue {
private items;
constructor();
add(file: string): string[];
remove(file: string): string[];
pop(): string;
length(): number;
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
export {};

View File

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

View File

@ -0,0 +1 @@
export declare function buildPath(basePath: string, path: string): string;

View File

@ -0,0 +1,8 @@
export function buildPath(basePath, path) {
if (!basePath) {
return path;
}
else {
return `${basePath}/${path}`;
}
}

View File

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

View File

@ -0,0 +1,11 @@
export class BaseComponent {
constructor() {
this.id = 0;
this.properties = {};
}
clone() {
const comp = new BaseComponent();
comp.properties = this.properties;
return comp;
}
}

19
src/framework/ecs/entity.d.ts vendored 100644
View File

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

View File

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

28
src/framework/ecs/index.d.ts vendored 100644
View File

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

View File

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

15
src/framework/ecs/query.d.ts vendored 100644
View File

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

View File

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

6
src/framework/ecs/system.d.ts vendored 100644
View File

@ -0,0 +1,6 @@
import { World } from '.';
export declare class System {
executor: Function;
constructor(executor: Function);
execute(world: World): void;
}

View File

@ -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

View File

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

View File

@ -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 = [];
}
}

View File

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

View File

@ -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
src/framework/game/game.d.ts vendored 100644
View File

View File

26
src/framework/game/index.d.ts vendored 100644
View File

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

View File

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

View File

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

View File

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

7
src/framework/index.d.ts vendored 100644
View File

@ -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';

View File

@ -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';

13
src/framework/input/index.d.ts vendored 100644
View File

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

View File

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

View File

@ -0,0 +1 @@
export declare function createInput(key: string): any;

View File

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

View File

@ -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 {
}

View File

@ -0,0 +1,12 @@
export class BaseInput {
constructor(element) {
this.element = element;
}
getState() { }
capture(preventDefault) {
return;
}
release() {
return;
}
}

View File

@ -0,0 +1,6 @@
import { BaseInput } from './base-input';
export declare class Joystick extends BaseInput {
constructor(element: HTMLElement);
}
export interface IJoystick {
}

View File

@ -0,0 +1,6 @@
import { BaseInput } from './base-input';
export class Joystick extends BaseInput {
constructor(element) {
super(element);
}
}

View File

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

View File

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

View File

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

View File

@ -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 {
}

View File

@ -0,0 +1,4 @@
import { IKeyboard } from '../inputs/keyboard';
export interface State {
keyboard?: IKeyboard;
}

View File

@ -0,0 +1 @@
export {};

122
src/framework/input/keycodes.d.ts vendored 100644
View File

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

View File

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

View File

@ -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"
}
}

View File

@ -0,0 +1,2 @@
import { PhysicsObject } from './object';
export declare function AABB(obj1: PhysicsObject, obj2: PhysicsObject): boolean;

View File

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

View File

View File

View File

@ -0,0 +1,7 @@
import { Vec3 } from './vec3';
export declare class PhysicsObject {
position: Vec3;
dimensions: Vec3;
velocity: Vec3;
affectedByGravity: boolean;
}

View File

@ -0,0 +1,2 @@
export class PhysicsObject {
}

View File

@ -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 {};

View File

@ -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 = {}));

View File

@ -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 {};

View File

@ -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 = {}));

View File

View File

13
src/framework/physics/vec3.d.ts vendored 100644
View File

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

View File

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

View File

@ -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 {};

View File

@ -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]);
}
});
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = {};
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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