commented code and cleaned formatting
This commit is contained in:
parent
8cb4fb7972
commit
3a57a1acab
|
@ -1,3 +1,17 @@
|
||||||
|
|
||||||
|
// TODO - Is there already a way to do this in js/ts?
|
||||||
|
/**
|
||||||
|
* An interface for all iterable data custom data structures
|
||||||
|
*/
|
||||||
export default interface Collection {
|
export default interface Collection {
|
||||||
|
/**
|
||||||
|
* Iterates through all of the items in this data structure.
|
||||||
|
* @param func
|
||||||
|
*/
|
||||||
forEach(func: Function): void;
|
forEach(func: Function): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the contents of the data structure
|
||||||
|
*/
|
||||||
|
clear(): void;
|
||||||
}
|
}
|
|
@ -1,5 +1,8 @@
|
||||||
import Collection from "./Collection";
|
import Collection from "./Collection";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Associates strings with elements of type T
|
||||||
|
*/
|
||||||
export default class Map<T> implements Collection {
|
export default class Map<T> implements Collection {
|
||||||
private map: Record<string, T>;
|
private map: Record<string, T>;
|
||||||
|
|
||||||
|
@ -7,31 +10,52 @@ export default class Map<T> implements Collection {
|
||||||
this.map = {};
|
this.map = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a value T stored at a key.
|
||||||
|
* @param key
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
add(key: string, value: T): void {
|
add(key: string, value: T): void {
|
||||||
this.map[key] = value;
|
this.map[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value associated with a key.
|
||||||
|
* @param key
|
||||||
|
*/
|
||||||
get(key: string): T {
|
get(key: string): T {
|
||||||
return this.map[key];
|
return this.map[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value stored at key to the new specified value
|
||||||
|
* @param key
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
set(key: string, value: T): void {
|
set(key: string, value: T): void {
|
||||||
this.add(key, value);
|
this.add(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if there is a value stored at the specified key, false otherwise.
|
||||||
|
* @param key
|
||||||
|
*/
|
||||||
has(key: string): boolean {
|
has(key: string): boolean {
|
||||||
return this.map[key] !== undefined;
|
return this.map[key] !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of all of the keys in this map.
|
||||||
|
*/
|
||||||
keys(): Array<string> {
|
keys(): Array<string> {
|
||||||
return Object.keys(this.map);
|
return Object.keys(this.map);
|
||||||
}
|
}
|
||||||
|
|
||||||
forEach(func: Function): void {
|
forEach(func: (key: string) => void): void {
|
||||||
Object.keys(this.map).forEach(key => func(key));
|
Object.keys(this.map).forEach(key => func(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
clear(): void {
|
clear(): void {
|
||||||
this.forEach((key: string) => delete this.map[key]);
|
this.forEach(key => delete this.map[key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,9 @@
|
||||||
import Collection from "./Collection";
|
import Collection from "./Collection";
|
||||||
|
|
||||||
export default class Queue<T> implements Collection{
|
/**
|
||||||
|
* A FIFO queue with elements of type T
|
||||||
|
*/
|
||||||
|
export default class Queue<T> implements Collection {
|
||||||
private readonly MAX_ELEMENTS: number;
|
private readonly MAX_ELEMENTS: number;
|
||||||
private q: Array<T>;
|
private q: Array<T>;
|
||||||
private head: number;
|
private head: number;
|
||||||
|
@ -15,6 +18,10 @@ export default class Queue<T> implements Collection{
|
||||||
this.size = 0;
|
this.size = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an item to the back of the queue
|
||||||
|
* @param item
|
||||||
|
*/
|
||||||
enqueue(item: T): void{
|
enqueue(item: T): void{
|
||||||
if((this.tail + 1) % this.MAX_ELEMENTS === this.head){
|
if((this.tail + 1) % this.MAX_ELEMENTS === this.head){
|
||||||
throw "Queue full - cannot add element"
|
throw "Queue full - cannot add element"
|
||||||
|
@ -25,6 +32,9 @@ export default class Queue<T> implements Collection{
|
||||||
this.tail = (this.tail + 1) % this.MAX_ELEMENTS;
|
this.tail = (this.tail + 1) % this.MAX_ELEMENTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves an item from the front of the queue
|
||||||
|
*/
|
||||||
dequeue(): T {
|
dequeue(): T {
|
||||||
if(this.head === this.tail){
|
if(this.head === this.tail){
|
||||||
throw "Queue empty - cannot remove element"
|
throw "Queue empty - cannot remove element"
|
||||||
|
@ -33,11 +43,16 @@ export default class Queue<T> implements Collection{
|
||||||
|
|
||||||
this.size -= 1;
|
this.size -= 1;
|
||||||
let item = this.q[this.head];
|
let item = this.q[this.head];
|
||||||
|
// Now delete the item
|
||||||
|
delete this.q[this.head];
|
||||||
this.head = (this.head + 1) % this.MAX_ELEMENTS;
|
this.head = (this.head + 1) % this.MAX_ELEMENTS;
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the item at the front of the queue, but does not return it
|
||||||
|
*/
|
||||||
peekNext(): T {
|
peekNext(): T {
|
||||||
if(this.head === this.tail){
|
if(this.head === this.tail){
|
||||||
throw "Queue empty - cannot get element"
|
throw "Queue empty - cannot get element"
|
||||||
|
@ -48,24 +63,30 @@ export default class Queue<T> implements Collection{
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the queue has items in it, false otherwise
|
||||||
|
*/
|
||||||
hasItems(): boolean {
|
hasItems(): boolean {
|
||||||
return this.head !== this.tail;
|
return this.head !== this.tail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of elements in the queue.
|
||||||
|
*/
|
||||||
getSize(): number {
|
getSize(): number {
|
||||||
return this.size;
|
return this.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This should actually delete the items in the queue instead of leaving them here
|
|
||||||
clear(): void {
|
clear(): void {
|
||||||
|
this.forEach((item, index) => delete this.q[index]);
|
||||||
this.size = 0;
|
this.size = 0;
|
||||||
this.head = this.tail;
|
this.head = this.tail;
|
||||||
}
|
}
|
||||||
|
|
||||||
forEach(func: Function): void {
|
forEach(func: (item: T, index?: number) => void): void {
|
||||||
let i = this.head;
|
let i = this.head;
|
||||||
while(i !== this.tail){
|
while(i !== this.tail){
|
||||||
func(this.q[i]);
|
func(this.q[i], i);
|
||||||
i = (i + 1) % this.MAX_ELEMENTS;
|
i = (i + 1) % this.MAX_ELEMENTS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import Collection from "./Collection";
|
import Collection from "./Collection";
|
||||||
|
|
||||||
export default class Stack<T> implements Collection{
|
/**
|
||||||
|
* A LIFO stack with items of type T
|
||||||
|
*/
|
||||||
|
export default class Stack<T> implements Collection {
|
||||||
readonly MAX_ELEMENTS: number;
|
readonly MAX_ELEMENTS: number;
|
||||||
private stack: Array<T>;
|
private stack: Array<T>;
|
||||||
private head: number;
|
private head: number;
|
||||||
|
@ -13,7 +16,7 @@ export default class Stack<T> implements Collection{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an item to the top of the stack
|
* Adds an item to the top of the stack
|
||||||
* @param {*} item The new item to add to the stack
|
* @param item The new item to add to the stack
|
||||||
*/
|
*/
|
||||||
push(item: T): void {
|
push(item: T): void {
|
||||||
if(this.head + 1 === this.MAX_ELEMENTS){
|
if(this.head + 1 === this.MAX_ELEMENTS){
|
||||||
|
@ -44,10 +47,8 @@ export default class Stack<T> implements Collection{
|
||||||
return this.stack[this.head];
|
return this.stack[this.head];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
clear(): void {
|
||||||
* Removes all elements from the stack
|
this.forEach((item, index) => delete this.stack[index]);
|
||||||
*/
|
|
||||||
clear(): void{
|
|
||||||
this.head = -1;
|
this.head = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +59,7 @@ export default class Stack<T> implements Collection{
|
||||||
return this.head + 1;
|
return this.head + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
forEach(func: Function): void{
|
forEach(func: (item: T, index?: number) => void): void{
|
||||||
let i = 0;
|
let i = 0;
|
||||||
while(i <= this.head){
|
while(i <= this.head){
|
||||||
func(this.stack[i]);
|
func(this.stack[i]);
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
/**
|
||||||
|
* a representation of Tiled's tilemap data
|
||||||
|
*/
|
||||||
export class TiledTilemapData {
|
export class TiledTilemapData {
|
||||||
height: number;
|
height: number;
|
||||||
width: number;
|
width: number;
|
||||||
|
@ -8,12 +11,18 @@ export class TiledTilemapData {
|
||||||
tilesets: Array<TiledTilesetData>;
|
tilesets: Array<TiledTilesetData>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of a custom layer property in a Tiled tilemap
|
||||||
|
*/
|
||||||
export class TiledLayerProperty {
|
export class TiledLayerProperty {
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: string;
|
||||||
value: any;
|
value: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of a tileset in a Tiled tilemap
|
||||||
|
*/
|
||||||
export class TiledTilesetData {
|
export class TiledTilesetData {
|
||||||
columns: number;
|
columns: number;
|
||||||
tilewidth: number;
|
tilewidth: number;
|
||||||
|
@ -28,6 +37,9 @@ export class TiledTilesetData {
|
||||||
image: string;
|
image: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of a layer in a Tiled tilemap
|
||||||
|
*/
|
||||||
export class TiledLayerData {
|
export class TiledLayerData {
|
||||||
data: number[];
|
data: number[];
|
||||||
x: number;
|
x: number;
|
||||||
|
|
|
@ -17,9 +17,14 @@ export default class Tileset {
|
||||||
|
|
||||||
// TODO: Change this to be more general and work with other tileset formats
|
// TODO: Change this to be more general and work with other tileset formats
|
||||||
constructor(tilesetData: TiledTilesetData){
|
constructor(tilesetData: TiledTilesetData){
|
||||||
|
// Defer handling of the data to a helper class
|
||||||
this.initFromTiledData(tilesetData);
|
this.initFromTiledData(tilesetData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the tileset from the data from a Tiled json file
|
||||||
|
* @param tiledData The parsed object from a Tiled json file
|
||||||
|
*/
|
||||||
initFromTiledData(tiledData: TiledTilesetData): void {
|
initFromTiledData(tiledData: TiledTilesetData): void {
|
||||||
this.numRows = tiledData.tilecount/tiledData.columns;
|
this.numRows = tiledData.tilecount/tiledData.columns;
|
||||||
this.numCols = tiledData.columns;
|
this.numCols = tiledData.columns;
|
||||||
|
@ -58,23 +63,32 @@ export default class Tileset {
|
||||||
return this.numCols;
|
return this.numCols;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This should probably be a thing that is tracked in the resource loader, not here
|
|
||||||
isReady(): boolean {
|
|
||||||
return this.image !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasTile(tileIndex: number): boolean {
|
hasTile(tileIndex: number): boolean {
|
||||||
return tileIndex >= this.startIndex && tileIndex <= this.endIndex;
|
return tileIndex >= this.startIndex && tileIndex <= this.endIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a singular tile with index tileIndex from the tileset located at position dataIndex
|
||||||
|
* @param ctx The rendering context
|
||||||
|
* @param tileIndex The value of the tile to render
|
||||||
|
* @param dataIndex The index of the tile in the data array
|
||||||
|
* @param worldSize The size of the world
|
||||||
|
* @param origin The viewport origin in the current layer
|
||||||
|
* @param scale The scale of the tilemap
|
||||||
|
*/
|
||||||
renderTile(ctx: CanvasRenderingContext2D, tileIndex: number, dataIndex: number, worldSize: Vec2, origin: Vec2, scale: Vec2): void {
|
renderTile(ctx: CanvasRenderingContext2D, tileIndex: number, dataIndex: number, worldSize: Vec2, origin: Vec2, scale: Vec2): void {
|
||||||
|
// Get the true index
|
||||||
let index = tileIndex - this.startIndex;
|
let index = tileIndex - this.startIndex;
|
||||||
let row = Math.floor(index / this.numCols);
|
let row = Math.floor(index / this.numCols);
|
||||||
let col = index % this.numCols;
|
let col = index % this.numCols;
|
||||||
let width = this.tileSize.x;
|
let width = this.tileSize.x;
|
||||||
let height = this.tileSize.y;
|
let height = this.tileSize.y;
|
||||||
|
|
||||||
|
// Calculate the position to start a crop in the tileset image
|
||||||
let left = col * width;
|
let left = col * width;
|
||||||
let top = row * height;
|
let top = row * height;
|
||||||
|
|
||||||
|
// Calculate the position in the world to render the tile
|
||||||
let x = (dataIndex % worldSize.x) * width * scale.x;
|
let x = (dataIndex % worldSize.x) * width * scale.x;
|
||||||
let y = Math.floor(dataIndex / worldSize.x) * height * scale.y;
|
let y = Math.floor(dataIndex / worldSize.x) * height * scale.y;
|
||||||
ctx.drawImage(this.image, left, top, width, height, x - origin.x, y - origin.y, width * scale.x, height * scale.y);
|
ctx.drawImage(this.image, left, top, width, height, x - origin.x, y - origin.y, width * scale.x, height * scale.y);
|
||||||
|
|
|
@ -1,21 +1,51 @@
|
||||||
|
/**
|
||||||
|
* A two-dimensional vector (x, y)
|
||||||
|
*/
|
||||||
export default class Vec2 {
|
export default class Vec2 {
|
||||||
|
|
||||||
public x: number;
|
// Store x and y in an array
|
||||||
public y: number;
|
private vec: Float32Array;
|
||||||
|
|
||||||
constructor(x: number = 0, y: number = 0) {
|
constructor(x: number = 0, y: number = 0) {
|
||||||
this.x = x;
|
this.vec = new Float32Array(2);
|
||||||
this.y = y;
|
this.vec[0] = x;
|
||||||
|
this.vec[1] = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Expose x and y with getters and setters
|
||||||
|
get x() {
|
||||||
|
return this.vec[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
set x(x: number) {
|
||||||
|
this.vec[0] = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
get y() {
|
||||||
|
return this.vec[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
set y(y: number) {
|
||||||
|
this.vec[1] = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The squared magnitude of the vector
|
||||||
|
*/
|
||||||
magSq(): number {
|
magSq(): number {
|
||||||
return this.x*this.x + this.y*this.y;
|
return this.x*this.x + this.y*this.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The magnitude of the vector
|
||||||
|
*/
|
||||||
mag(): number {
|
mag(): number {
|
||||||
return Math.sqrt(this.magSq());
|
return Math.sqrt(this.magSq());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns this vector as a unit vector - Equivalent to dividing x and y by the magnitude
|
||||||
|
*/
|
||||||
normalize(): Vec2 {
|
normalize(): Vec2 {
|
||||||
if(this.x === 0 && this.y === 0) return this;
|
if(this.x === 0 && this.y === 0) return this;
|
||||||
let mag = this.mag();
|
let mag = this.mag();
|
||||||
|
@ -24,16 +54,29 @@ export default class Vec2 {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the vector's x and y based on the angle provided. Goes counter clockwise.
|
||||||
|
* @param angle The angle in radians
|
||||||
|
*/
|
||||||
setToAngle(angle: number): Vec2 {
|
setToAngle(angle: number): Vec2 {
|
||||||
this.x = Math.cos(angle);
|
this.x = Math.cos(angle);
|
||||||
this.y = Math.sin(angle);
|
this.y = Math.sin(angle);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keeps the vector's direction, but sets its magnitude to be the provided magnitude
|
||||||
|
* @param magnitude
|
||||||
|
*/
|
||||||
scaleTo(magnitude: number): Vec2 {
|
scaleTo(magnitude: number): Vec2 {
|
||||||
return this.normalize().scale(magnitude);
|
return this.normalize().scale(magnitude);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scales x and y by the number provided, or if two number are provided, scales them individually.
|
||||||
|
* @param factor
|
||||||
|
* @param yFactor
|
||||||
|
*/
|
||||||
scale(factor: number, yFactor: number = null): Vec2 {
|
scale(factor: number, yFactor: number = null): Vec2 {
|
||||||
if(yFactor !== null){
|
if(yFactor !== null){
|
||||||
this.x *= factor;
|
this.x *= factor;
|
||||||
|
@ -45,6 +88,10 @@ export default class Vec2 {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotates the vector counter-clockwise by the angle amount specified
|
||||||
|
* @param angle The angle to rotate by in radians
|
||||||
|
*/
|
||||||
rotateCCW(angle: number): Vec2 {
|
rotateCCW(angle: number): Vec2 {
|
||||||
let cs = Math.cos(angle);
|
let cs = Math.cos(angle);
|
||||||
let sn = Math.sin(angle);
|
let sn = Math.sin(angle);
|
||||||
|
@ -55,38 +102,65 @@ export default class Vec2 {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the vectors coordinates to be the ones provided
|
||||||
|
* @param x
|
||||||
|
* @param y
|
||||||
|
*/
|
||||||
set(x: number, y: number): Vec2 {
|
set(x: number, y: number): Vec2 {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds this vector the another vector
|
||||||
|
* @param other
|
||||||
|
*/
|
||||||
add(other: Vec2): Vec2 {
|
add(other: Vec2): Vec2 {
|
||||||
this.x += other.x;
|
this.x += other.x;
|
||||||
this.y += other.y;
|
this.y += other.y;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subtracts another vector from this vector
|
||||||
|
* @param other
|
||||||
|
*/
|
||||||
sub(other: Vec2): Vec2 {
|
sub(other: Vec2): Vec2 {
|
||||||
this.x -= other.x;
|
this.x -= other.x;
|
||||||
this.y -= other.y;
|
this.y -= other.y;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multiplies this vector with another vector element-wise
|
||||||
|
* @param other
|
||||||
|
*/
|
||||||
mult(other: Vec2): Vec2 {
|
mult(other: Vec2): Vec2 {
|
||||||
this.x *= other.x;
|
this.x *= other.x;
|
||||||
this.y *= other.y;
|
this.y *= other.y;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string representation of this vector rounded to 1 decimal point
|
||||||
|
*/
|
||||||
toString(): string {
|
toString(): string {
|
||||||
return this.toFixed();
|
return this.toFixed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string representation of this vector rounded to the specified number of decimal points
|
||||||
|
* @param numDecimalPoints
|
||||||
|
*/
|
||||||
toFixed(numDecimalPoints: number = 1): string {
|
toFixed(numDecimalPoints: number = 1): string {
|
||||||
return "(" + this.x.toFixed(numDecimalPoints) + ", " + this.y.toFixed(numDecimalPoints) + ")";
|
return "(" + this.x.toFixed(numDecimalPoints) + ", " + this.y.toFixed(numDecimalPoints) + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new vector with the same coordinates as this one.
|
||||||
|
*/
|
||||||
clone(): Vec2 {
|
clone(): Vec2 {
|
||||||
return new Vec2(this.x, this.y);
|
return new Vec2(this.x, this.y);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,18 +2,49 @@ import Vec2 from "./Vec2";
|
||||||
|
|
||||||
export default class Vec4{
|
export default class Vec4{
|
||||||
|
|
||||||
public x : number;
|
public vec: Float32Array;
|
||||||
public y : number;
|
|
||||||
public z : number;
|
|
||||||
public w : number;
|
|
||||||
|
|
||||||
constructor(x : number = 0, y : number = 0, z : number = 0, w : number = 0) {
|
constructor(x : number = 0, y : number = 0, z : number = 0, w : number = 0) {
|
||||||
this.x = x;
|
this.vec = new Float32Array(4);
|
||||||
this.y = y;
|
this.vec[0] = x;
|
||||||
this.z = z;
|
this.vec[1] = y;
|
||||||
this.w = w;
|
this.vec[2] = z;
|
||||||
|
this.vec[3] = w;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Expose x and y with getters and setters
|
||||||
|
get x() {
|
||||||
|
return this.vec[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
set x(x: number) {
|
||||||
|
this.vec[0] = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
get y() {
|
||||||
|
return this.vec[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
set y(y: number) {
|
||||||
|
this.vec[1] = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
get z() {
|
||||||
|
return this.vec[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
set z(x: number) {
|
||||||
|
this.vec[2] = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
get w() {
|
||||||
|
return this.vec[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
set w(y: number) {
|
||||||
|
this.vec[3] = y;
|
||||||
|
}
|
||||||
|
|
||||||
split() : [Vec2, Vec2] {
|
split() : [Vec2, Vec2] {
|
||||||
return [new Vec2(this.x, this.y), new Vec2(this.z, this.w)];
|
return [new Vec2(this.x, this.y), new Vec2(this.z, this.w)];
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import Map from "../DataTypes/Map";
|
||||||
|
|
||||||
export default class Debug {
|
export default class Debug {
|
||||||
|
|
||||||
|
// A map of log messages to display on the screen
|
||||||
private static logMessages: Map<string> = new Map();
|
private static logMessages: Map<string> = new Map();
|
||||||
|
|
||||||
static log(id: string, message: string): void {
|
static log(id: string, message: string): void {
|
||||||
|
|
|
@ -27,6 +27,11 @@ export default class EventQueue {
|
||||||
this.q.enqueue(event);
|
this.q.enqueue(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Associates a receiver with a type of event. Every time this event appears in the future, it will be given to the receiver (and any others watching that type)
|
||||||
|
* @param receiver
|
||||||
|
* @param type
|
||||||
|
*/
|
||||||
subscribe(receiver: Receiver, type: string | Array<string>): void {
|
subscribe(receiver: Receiver, type: string | Array<string>): void {
|
||||||
if(type instanceof Array){
|
if(type instanceof Array){
|
||||||
// If it is an array, subscribe to all event types
|
// If it is an array, subscribe to all event types
|
||||||
|
@ -38,6 +43,7 @@ export default class EventQueue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Associate the receiver and the type
|
||||||
private addListener(receiver: Receiver, type: string): void {
|
private addListener(receiver: Receiver, type: string): void {
|
||||||
if(this.receivers.has(type)){
|
if(this.receivers.has(type)){
|
||||||
this.receivers.get(type).push(receiver);
|
this.receivers.get(type).push(receiver);
|
||||||
|
@ -48,14 +54,17 @@ export default class EventQueue {
|
||||||
|
|
||||||
update(deltaT: number): void{
|
update(deltaT: number): void{
|
||||||
while(this.q.hasItems()){
|
while(this.q.hasItems()){
|
||||||
|
// Retrieve each event
|
||||||
let event = this.q.dequeue();
|
let event = this.q.dequeue();
|
||||||
|
|
||||||
|
// If a receiver has this event type, send it the event
|
||||||
if(this.receivers.has(event.type)){
|
if(this.receivers.has(event.type)){
|
||||||
for(let receiver of this.receivers.get(event.type)){
|
for(let receiver of this.receivers.get(event.type)){
|
||||||
receiver.receive(event);
|
receiver.receive(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If a receiver is subscribed to all events, send it the event
|
||||||
if(this.receivers.has("all")){
|
if(this.receivers.has("all")){
|
||||||
for(let receiver of this.receivers.get("all")){
|
for(let receiver of this.receivers.get("all")){
|
||||||
receiver.receive(event);
|
receiver.receive(event);
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
import Map from "../DataTypes/Map"
|
import Map from "../DataTypes/Map"
|
||||||
|
|
||||||
export default class GameEvent{
|
/**
|
||||||
|
* A representation of an in-game event
|
||||||
|
*/
|
||||||
|
export default class GameEvent {
|
||||||
public type: string;
|
public type: string;
|
||||||
public data: Map<any>;
|
public data: Map<any>;
|
||||||
public time: number;
|
public time: number;
|
||||||
|
|
||||||
constructor(type: string, data: Map<any> | Record<string, any> = null){
|
constructor(type: string, data: Map<any> | Record<string, any> = null) {
|
||||||
|
// Parse the game event data
|
||||||
if (data === null) {
|
if (data === null) {
|
||||||
this.data = new Map<any>();
|
this.data = new Map<any>();
|
||||||
} else if (!(data instanceof Map)){
|
} else if (!(data instanceof Map)){
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import Queue from "../DataTypes/Queue";
|
import Queue from "../DataTypes/Queue";
|
||||||
import GameEvent from "./GameEvent";
|
import GameEvent from "./GameEvent";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives subscribed events from the EventQueue
|
||||||
|
*/
|
||||||
export default class Receiver{
|
export default class Receiver{
|
||||||
readonly MAX_SIZE: number;
|
readonly MAX_SIZE: number;
|
||||||
private q: Queue<GameEvent>;
|
private q: Queue<GameEvent>;
|
||||||
|
@ -10,22 +13,37 @@ export default class Receiver{
|
||||||
this.q = new Queue(this.MAX_SIZE);
|
this.q = new Queue(this.MAX_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an event to the queue of this reciever
|
||||||
|
*/
|
||||||
receive(event: GameEvent): void {
|
receive(event: GameEvent): void {
|
||||||
this.q.enqueue(event);
|
this.q.enqueue(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the next event from the receiver's queue
|
||||||
|
*/
|
||||||
getNextEvent(): GameEvent {
|
getNextEvent(): GameEvent {
|
||||||
return this.q.dequeue();
|
return this.q.dequeue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks at the next event in the receiver's queue
|
||||||
|
*/
|
||||||
peekNextEvent(): GameEvent {
|
peekNextEvent(): GameEvent {
|
||||||
return this.q.peekNext()
|
return this.q.peekNext()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the receiver has any events in its queue
|
||||||
|
*/
|
||||||
hasNextEvent(): boolean {
|
hasNextEvent(): boolean {
|
||||||
return this.q.hasItems();
|
return this.q.hasItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ignore all events this frame
|
||||||
|
*/
|
||||||
ignoreEvents(): void {
|
ignoreEvents(): void {
|
||||||
this.q.clear();
|
this.q.clear();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,9 @@ import EventQueue from "../Events/EventQueue";
|
||||||
import Vec2 from "../DataTypes/Vec2";
|
import Vec2 from "../DataTypes/Vec2";
|
||||||
import GameEvent from "../Events/GameEvent";
|
import GameEvent from "../Events/GameEvent";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles communication with the web browser to receive asynchronous events and send them to the event queue
|
||||||
|
*/
|
||||||
export default class InputHandler{
|
export default class InputHandler{
|
||||||
private eventQueue: EventQueue;
|
private eventQueue: EventQueue;
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,9 @@ import Vec2 from "../DataTypes/Vec2";
|
||||||
import EventQueue from "../Events/EventQueue";
|
import EventQueue from "../Events/EventQueue";
|
||||||
import Viewport from "../SceneGraph/Viewport";
|
import Viewport from "../SceneGraph/Viewport";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives input events from the event queue and allows for easy access of information about input
|
||||||
|
*/
|
||||||
export default class InputReceiver{
|
export default class InputReceiver{
|
||||||
private static instance: InputReceiver = null;
|
private static instance: InputReceiver = null;
|
||||||
|
|
||||||
|
@ -27,6 +30,7 @@ export default class InputReceiver{
|
||||||
this.mousePressPosition = new Vec2(0, 0);
|
this.mousePressPosition = new Vec2(0, 0);
|
||||||
|
|
||||||
this.eventQueue = EventQueue.getInstance();
|
this.eventQueue = EventQueue.getInstance();
|
||||||
|
// Subscribe to all input events
|
||||||
this.eventQueue.subscribe(this.receiver, ["mouse_down", "mouse_up", "mouse_move", "key_down", "key_up", "canvas_blur"]);
|
this.eventQueue.subscribe(this.receiver, ["mouse_down", "mouse_up", "mouse_move", "key_down", "key_up", "canvas_blur"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +48,8 @@ export default class InputReceiver{
|
||||||
|
|
||||||
while(this.receiver.hasNextEvent()){
|
while(this.receiver.hasNextEvent()){
|
||||||
let event = this.receiver.getNextEvent();
|
let event = this.receiver.getNextEvent();
|
||||||
|
|
||||||
|
// Handle each event type
|
||||||
if(event.type === "mouse_down"){
|
if(event.type === "mouse_down"){
|
||||||
this.mouseJustPressed = true;
|
this.mouseJustPressed = true;
|
||||||
this.mousePressed = true;
|
this.mousePressed = true;
|
||||||
|
@ -77,7 +83,7 @@ export default class InputReceiver{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clearKeyPresses(): void {
|
private clearKeyPresses(): void {
|
||||||
this.keyJustPressed.forEach((key: string) => this.keyJustPressed.set(key, false));
|
this.keyJustPressed.forEach((key: string) => this.keyJustPressed.set(key, false));
|
||||||
this.keyPressed.forEach((key: string) => this.keyPressed.set(key, false));
|
this.keyPressed.forEach((key: string) => this.keyPressed.set(key, false));
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,11 +29,14 @@ export default class GameLoop{
|
||||||
private running: boolean;
|
private running: boolean;
|
||||||
private frameDelta: number;
|
private frameDelta: number;
|
||||||
|
|
||||||
|
// Game canvas and its width and height
|
||||||
readonly GAME_CANVAS: HTMLCanvasElement;
|
readonly GAME_CANVAS: HTMLCanvasElement;
|
||||||
readonly WIDTH: number;
|
readonly WIDTH: number;
|
||||||
readonly HEIGHT: number;
|
readonly HEIGHT: number;
|
||||||
private viewport: Viewport;
|
private viewport: Viewport;
|
||||||
private ctx: CanvasRenderingContext2D;
|
private ctx: CanvasRenderingContext2D;
|
||||||
|
|
||||||
|
// All of the necessary subsystems that need to run here
|
||||||
private eventQueue: EventQueue;
|
private eventQueue: EventQueue;
|
||||||
private inputHandler: InputHandler;
|
private inputHandler: InputHandler;
|
||||||
private inputReceiver: InputReceiver;
|
private inputReceiver: InputReceiver;
|
||||||
|
@ -54,15 +57,20 @@ export default class GameLoop{
|
||||||
this.started = false;
|
this.started = false;
|
||||||
this.running = false;
|
this.running = false;
|
||||||
|
|
||||||
|
// Get the game canvas and give it a background color
|
||||||
this.GAME_CANVAS = document.getElementById("game-canvas") as HTMLCanvasElement;
|
this.GAME_CANVAS = document.getElementById("game-canvas") as HTMLCanvasElement;
|
||||||
this.GAME_CANVAS.style.setProperty("background-color", "whitesmoke");
|
this.GAME_CANVAS.style.setProperty("background-color", "whitesmoke");
|
||||||
|
|
||||||
|
// Give the canvas a size and get the rendering context
|
||||||
this.WIDTH = 800;
|
this.WIDTH = 800;
|
||||||
this.HEIGHT = 500;
|
this.HEIGHT = 500;
|
||||||
this.ctx = this.initializeCanvas(this.GAME_CANVAS, this.WIDTH, this.HEIGHT);
|
this.ctx = this.initializeCanvas(this.GAME_CANVAS, this.WIDTH, this.HEIGHT);
|
||||||
|
|
||||||
|
// Size the viewport to the game canvas
|
||||||
this.viewport = new Viewport();
|
this.viewport = new Viewport();
|
||||||
this.viewport.setSize(this.WIDTH, this.HEIGHT);
|
this.viewport.setSize(this.WIDTH, this.HEIGHT);
|
||||||
|
|
||||||
|
// Initialize all necessary game subsystems
|
||||||
this.eventQueue = EventQueue.getInstance();
|
this.eventQueue = EventQueue.getInstance();
|
||||||
this.inputHandler = new InputHandler(this.GAME_CANVAS);
|
this.inputHandler = new InputHandler(this.GAME_CANVAS);
|
||||||
this.inputReceiver = InputReceiver.getInstance();
|
this.inputReceiver = InputReceiver.getInstance();
|
||||||
|
@ -77,10 +85,18 @@ export default class GameLoop{
|
||||||
canvas.width = width;
|
canvas.width = width;
|
||||||
canvas.height = height;
|
canvas.height = height;
|
||||||
let ctx = canvas.getContext("2d");
|
let ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
|
// For crisp pixel art
|
||||||
ctx.imageSmoothingEnabled = false;
|
ctx.imageSmoothingEnabled = false;
|
||||||
|
|
||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO - This currently also changes the rendering framerate
|
||||||
|
/**
|
||||||
|
* Changes the maximum allowed physics framerate of the game
|
||||||
|
* @param initMax
|
||||||
|
*/
|
||||||
setMaxFPS(initMax: number): void {
|
setMaxFPS(initMax: number): void {
|
||||||
this.maxFPS = initMax;
|
this.maxFPS = initMax;
|
||||||
this.simulationTimestep = Math.floor(1000/this.maxFPS);
|
this.simulationTimestep = Math.floor(1000/this.maxFPS);
|
||||||
|
@ -90,6 +106,10 @@ export default class GameLoop{
|
||||||
return this.sceneManager;
|
return this.sceneManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the frame count and sum of time for the framerate of the game
|
||||||
|
* @param timestep
|
||||||
|
*/
|
||||||
private updateFrameCount(timestep: number): void {
|
private updateFrameCount(timestep: number): void {
|
||||||
this.frame += 1;
|
this.frame += 1;
|
||||||
this.numFramesInSum += 1;
|
this.numFramesInSum += 1;
|
||||||
|
@ -103,6 +123,9 @@ export default class GameLoop{
|
||||||
Debug.log("fps", "FPS: " + this.fps.toFixed(1));
|
Debug.log("fps", "FPS: " + this.fps.toFixed(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts up the game loop and calls the first requestAnimationFrame
|
||||||
|
*/
|
||||||
start(): void {
|
start(): void {
|
||||||
if(!this.started){
|
if(!this.started){
|
||||||
this.started = true;
|
this.started = true;
|
||||||
|
@ -111,6 +134,10 @@ export default class GameLoop{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The first game frame - initializes the first frame time and begins the render
|
||||||
|
* @param timestamp
|
||||||
|
*/
|
||||||
startFrame = (timestamp: number): void => {
|
startFrame = (timestamp: number): void => {
|
||||||
this.running = true;
|
this.running = true;
|
||||||
|
|
||||||
|
@ -121,6 +148,10 @@ export default class GameLoop{
|
||||||
window.requestAnimationFrame(this.doFrame);
|
window.requestAnimationFrame(this.doFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main loop of the game. Updates and renders every frame
|
||||||
|
* @param timestamp
|
||||||
|
*/
|
||||||
doFrame = (timestamp: number): void => {
|
doFrame = (timestamp: number): void => {
|
||||||
// Request animation frame to prepare for another update or render
|
// Request animation frame to prepare for another update or render
|
||||||
window.requestAnimationFrame(this.doFrame);
|
window.requestAnimationFrame(this.doFrame);
|
||||||
|
@ -148,14 +179,30 @@ export default class GameLoop{
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates all necessary subsystems of the game. Defers scene updates to the sceneManager
|
||||||
|
* @param deltaT
|
||||||
|
*/
|
||||||
update(deltaT: number): void {
|
update(deltaT: number): void {
|
||||||
|
// Handle all events that happened since the start of the last loop
|
||||||
this.eventQueue.update(deltaT);
|
this.eventQueue.update(deltaT);
|
||||||
|
|
||||||
|
// Update the input data structures so game objects can see the input
|
||||||
this.inputReceiver.update(deltaT);
|
this.inputReceiver.update(deltaT);
|
||||||
|
|
||||||
|
// Update the recording of the game
|
||||||
this.recorder.update(deltaT);
|
this.recorder.update(deltaT);
|
||||||
|
|
||||||
|
// Update all scenes
|
||||||
this.sceneManager.update(deltaT);
|
this.sceneManager.update(deltaT);
|
||||||
|
|
||||||
|
// Load or unload any resources if needed
|
||||||
this.resourceManager.update(deltaT);
|
this.resourceManager.update(deltaT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the canvas and defers scene rendering to the sceneManager. Renders the debug
|
||||||
|
*/
|
||||||
render(): void {
|
render(): void {
|
||||||
this.ctx.clearRect(0, 0, this.WIDTH, this.HEIGHT);
|
this.ctx.clearRect(0, 0, this.WIDTH, this.HEIGHT);
|
||||||
this.sceneManager.render(this.ctx);
|
this.sceneManager.render(this.ctx);
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import GameNode from "./GameNode";
|
import GameNode from "./GameNode";
|
||||||
import Vec2 from "../DataTypes/Vec2";
|
import Vec2 from "../DataTypes/Vec2";
|
||||||
import Layer from "../Scene/Layer";
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The representation of an object in the game world that can be drawn to the screen
|
||||||
|
*/
|
||||||
export default abstract class CanvasNode extends GameNode{
|
export default abstract class CanvasNode extends GameNode{
|
||||||
protected size: Vec2;
|
protected size: Vec2;
|
||||||
|
|
||||||
|
@ -22,6 +24,11 @@ export default abstract class CanvasNode extends GameNode{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the point (x, y) is inside of this canvas object
|
||||||
|
* @param x
|
||||||
|
* @param y
|
||||||
|
*/
|
||||||
contains(x: number, y: number): boolean {
|
contains(x: number, y: number): boolean {
|
||||||
if(this.position.x < x && this.position.x + this.size.x > x){
|
if(this.position.x < x && this.position.x + this.size.x > x){
|
||||||
if(this.position.y < y && this.position.y + this.size.y > y){
|
if(this.position.y < y && this.position.y + this.size.y > y){
|
||||||
|
|
|
@ -7,6 +7,9 @@ import GameEvent from "../Events/GameEvent";
|
||||||
import Scene from "../Scene/Scene";
|
import Scene from "../Scene/Scene";
|
||||||
import Layer from "../Scene/Layer";
|
import Layer from "../Scene/Layer";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The representation of an object in the game world
|
||||||
|
*/
|
||||||
export default abstract class GameNode{
|
export default abstract class GameNode{
|
||||||
private eventQueue: EventQueue;
|
private eventQueue: EventQueue;
|
||||||
protected input: InputReceiver;
|
protected input: InputReceiver;
|
||||||
|
@ -49,17 +52,26 @@ export default abstract class GameNode{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe this object's receiver to the specified event type
|
||||||
|
* @param eventType
|
||||||
|
*/
|
||||||
subscribe(eventType: string): void {
|
subscribe(eventType: string): void {
|
||||||
this.eventQueue.subscribe(this.receiver, eventType);
|
this.eventQueue.subscribe(this.receiver, eventType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit and event of type eventType with the data packet data
|
||||||
|
* @param eventType
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
emit(eventType: string, data: Map<any> | Record<string, any> = null): void {
|
emit(eventType: string, data: Map<any> | Record<string, any> = null): void {
|
||||||
let event = new GameEvent(eventType, data);
|
let event = new GameEvent(eventType, data);
|
||||||
this.eventQueue.addEvent(event);
|
this.eventQueue.addEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - This doesn't seem ideal. Is there a better way to do this?
|
// TODO - This doesn't seem ideal. Is there a better way to do this?
|
||||||
getViewportOriginWithParallax(){
|
protected getViewportOriginWithParallax(): Vec2 {
|
||||||
return this.scene.getViewport().getPosition().clone().mult(this.layer.getParallax());
|
return this.scene.getViewport().getPosition().clone().mult(this.layer.getParallax());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import CanvasNode from "./CanvasNode";
|
import CanvasNode from "./CanvasNode";
|
||||||
import Color from "../Utils/Color";
|
import Color from "../Utils/Color";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The representation of a game object that doesn't rely on any resources to render - it is drawn to the screen by the canvas
|
||||||
|
*/
|
||||||
export default abstract class Graphic extends CanvasNode {
|
export default abstract class Graphic extends CanvasNode {
|
||||||
|
|
||||||
color: Color;
|
protected color: Color;
|
||||||
|
|
||||||
setColor(color: Color){
|
setColor(color: Color){
|
||||||
this.color = color;
|
this.color = color;
|
||||||
|
|
|
@ -2,6 +2,9 @@ import CanvasNode from "../CanvasNode";
|
||||||
import ResourceManager from "../../ResourceManager/ResourceManager";
|
import ResourceManager from "../../ResourceManager/ResourceManager";
|
||||||
import Vec2 from "../../DataTypes/Vec2";
|
import Vec2 from "../../DataTypes/Vec2";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The representation of a sprite - an in-game image
|
||||||
|
*/
|
||||||
export default class Sprite extends CanvasNode {
|
export default class Sprite extends CanvasNode {
|
||||||
private imageId: string;
|
private imageId: string;
|
||||||
private scale: Vec2;
|
private scale: Vec2;
|
||||||
|
@ -14,10 +17,17 @@ export default class Sprite extends CanvasNode {
|
||||||
this.scale = new Vec2(1, 1);
|
this.scale = new Vec2(1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the scale of the sprite
|
||||||
|
*/
|
||||||
getScale(): Vec2 {
|
getScale(): Vec2 {
|
||||||
return this.scale;
|
return this.scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the scale of the sprite to the value provided
|
||||||
|
* @param scale
|
||||||
|
*/
|
||||||
setScale(scale: Vec2): void {
|
setScale(scale: Vec2): void {
|
||||||
this.scale = scale;
|
this.scale = scale;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,10 @@ import Tileset from "../DataTypes/Tilesets/Tileset";
|
||||||
import { TiledTilemapData, TiledLayerData } from "../DataTypes/Tilesets/TiledData"
|
import { TiledTilemapData, TiledLayerData } from "../DataTypes/Tilesets/TiledData"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents one layer of tiles
|
* The representation of a tilemap - this can consist of a combination of tilesets in one layer
|
||||||
*/
|
*/
|
||||||
export default abstract class Tilemap extends GameNode {
|
export default abstract class Tilemap extends GameNode {
|
||||||
|
// A tileset represents the tiles within one specific image loaded from a file
|
||||||
protected tilesets: Array<Tileset>;
|
protected tilesets: Array<Tileset>;
|
||||||
protected worldSize: Vec2;
|
protected worldSize: Vec2;
|
||||||
protected tileSize: Vec2;
|
protected tileSize: Vec2;
|
||||||
|
@ -21,6 +22,8 @@ export default abstract class Tilemap extends GameNode {
|
||||||
this.tilesets = new Array<Tileset>();
|
this.tilesets = new Array<Tileset>();
|
||||||
this.worldSize = new Vec2(0, 0);
|
this.worldSize = new Vec2(0, 0);
|
||||||
this.tileSize = new Vec2(0, 0);
|
this.tileSize = new Vec2(0, 0);
|
||||||
|
|
||||||
|
// Defer parsing of the data to child classes - this allows for isometric vs. orthographic tilemaps and handling of Tiled data or other data
|
||||||
this.parseTilemapData(tilemapData, layer);
|
this.parseTilemapData(tilemapData, layer);
|
||||||
this.scale = new Vec2(4, 4);
|
this.scale = new Vec2(4, 4);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,16 @@ import Vec2 from "../../DataTypes/Vec2";
|
||||||
import { TiledTilemapData, TiledLayerData } from "../../DataTypes/Tilesets/TiledData";
|
import { TiledTilemapData, TiledLayerData } from "../../DataTypes/Tilesets/TiledData";
|
||||||
import Tileset from "../../DataTypes/Tilesets/Tileset";
|
import Tileset from "../../DataTypes/Tilesets/Tileset";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The representation of an orthogonal tilemap - i.e. a top down or platformer tilemap
|
||||||
|
*/
|
||||||
export default class OrthogonalTilemap extends Tilemap {
|
export default class OrthogonalTilemap extends Tilemap {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the tilemap data loaded from the json file. DOES NOT process images automatically - the ResourceManager class does this while loading tilemaps
|
||||||
|
* @param tilemapData
|
||||||
|
* @param layer
|
||||||
|
*/
|
||||||
protected parseTilemapData(tilemapData: TiledTilemapData, layer: TiledLayerData): void {
|
protected parseTilemapData(tilemapData: TiledTilemapData, layer: TiledLayerData): void {
|
||||||
this.worldSize.set(tilemapData.width, tilemapData.height);
|
this.worldSize.set(tilemapData.width, tilemapData.height);
|
||||||
this.tileSize.set(tilemapData.tilewidth, tilemapData.tileheight);
|
this.tileSize.set(tilemapData.tilewidth, tilemapData.tileheight);
|
||||||
|
@ -23,6 +30,10 @@ export default class OrthogonalTilemap extends Tilemap {
|
||||||
tilemapData.tilesets.forEach(tilesetData => this.tilesets.push(new Tileset(tilesetData)));
|
tilemapData.tilesets.forEach(tilesetData => this.tilesets.push(new Tileset(tilesetData)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value of the tile at the coordinates in the vector worldCoords
|
||||||
|
* @param worldCoords
|
||||||
|
*/
|
||||||
getTileAt(worldCoords: Vec2): number {
|
getTileAt(worldCoords: Vec2): number {
|
||||||
let localCoords = this.getColRowAt(worldCoords);
|
let localCoords = this.getColRowAt(worldCoords);
|
||||||
if(localCoords.x < 0 || localCoords.x >= this.worldSize.x || localCoords.y < 0 || localCoords.y >= this.worldSize.y){
|
if(localCoords.x < 0 || localCoords.x >= this.worldSize.x || localCoords.y < 0 || localCoords.y >= this.worldSize.y){
|
||||||
|
@ -33,6 +44,11 @@ export default class OrthogonalTilemap extends Tilemap {
|
||||||
return this.data[localCoords.y * this.worldSize.x + localCoords.x]
|
return this.data[localCoords.y * this.worldSize.x + localCoords.x]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the tile at the specified row and column of the tilemap is collidable
|
||||||
|
* @param indexOrCol
|
||||||
|
* @param row
|
||||||
|
*/
|
||||||
isTileCollidable(indexOrCol: number, row?: number): boolean {
|
isTileCollidable(indexOrCol: number, row?: number): boolean {
|
||||||
let index = 0;
|
let index = 0;
|
||||||
if(row){
|
if(row){
|
||||||
|
@ -53,6 +69,10 @@ export default class OrthogonalTilemap extends Tilemap {
|
||||||
return this.data[index] !== 0 && this.collidable;
|
return this.data[index] !== 0 && this.collidable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes in world coordinates and returns the row and column of the tile at that position
|
||||||
|
* @param worldCoords
|
||||||
|
*/
|
||||||
// TODO: Should this throw an error if someone tries to access an out of bounds value?
|
// TODO: Should this throw an error if someone tries to access an out of bounds value?
|
||||||
getColRowAt(worldCoords: Vec2): Vec2 {
|
getColRowAt(worldCoords: Vec2): Vec2 {
|
||||||
let col = Math.floor(worldCoords.x / this.tileSize.x / this.scale.x);
|
let col = Math.floor(worldCoords.x / this.tileSize.x / this.scale.x);
|
||||||
|
|
|
@ -2,6 +2,9 @@ import CanvasNode from "./CanvasNode";
|
||||||
import Color from "../Utils/Color";
|
import Color from "../Utils/Color";
|
||||||
import Vec2 from "../DataTypes/Vec2";
|
import Vec2 from "../DataTypes/Vec2";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The representation of a UIElement - the parent class of things like buttons
|
||||||
|
*/
|
||||||
export default class UIElement extends CanvasNode{
|
export default class UIElement extends CanvasNode{
|
||||||
// Style attributes
|
// Style attributes
|
||||||
protected textColor: Color;
|
protected textColor: Color;
|
||||||
|
@ -68,6 +71,7 @@ export default class UIElement extends CanvasNode{
|
||||||
}
|
}
|
||||||
|
|
||||||
update(deltaT: number): void {
|
update(deltaT: number): void {
|
||||||
|
// See of this object was just clicked
|
||||||
if(this.input.isMouseJustPressed()){
|
if(this.input.isMouseJustPressed()){
|
||||||
let clickPos = this.input.getMousePressPosition();
|
let clickPos = this.input.getMousePressPosition();
|
||||||
if(this.contains(clickPos.x, clickPos.y)){
|
if(this.contains(clickPos.x, clickPos.y)){
|
||||||
|
@ -83,12 +87,14 @@ export default class UIElement extends CanvasNode{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the mouse wasn't just pressed, then we definitely weren't clicked
|
||||||
if(!this.input.isMousePressed()){
|
if(!this.input.isMousePressed()){
|
||||||
if(this.isClicked){
|
if(this.isClicked){
|
||||||
this.isClicked = false;
|
this.isClicked = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the mouse is hovering over this element
|
||||||
let mousePos = this.input.getMousePosition();
|
let mousePos = this.input.getMousePosition();
|
||||||
if(mousePos && this.contains(mousePos.x, mousePos.y)){
|
if(mousePos && this.contains(mousePos.x, mousePos.y)){
|
||||||
this.isEntered = true;
|
this.isEntered = true;
|
||||||
|
@ -117,6 +123,10 @@ export default class UIElement extends CanvasNode{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the offset of the text - this is useful for rendering text with different alignments
|
||||||
|
*
|
||||||
|
*/
|
||||||
protected calculateOffset(ctx: CanvasRenderingContext2D): Vec2 {
|
protected calculateOffset(ctx: CanvasRenderingContext2D): Vec2 {
|
||||||
let textWidth = ctx.measureText(this.text).width;
|
let textWidth = ctx.measureText(this.text).width;
|
||||||
|
|
||||||
|
@ -143,19 +153,29 @@ export default class UIElement extends CanvasNode{
|
||||||
return offset;
|
return offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridable method for calculating background color - useful for elements that want to be colored on different after certain events
|
||||||
|
*/
|
||||||
protected calculateBackgroundColor(): string {
|
protected calculateBackgroundColor(): string {
|
||||||
return this.backgroundColor.toStringRGBA();
|
return this.backgroundColor.toStringRGBA();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridable method for calculating border color - useful for elements that want to be colored on different after certain events
|
||||||
|
*/
|
||||||
protected calculateBorderColor(): string {
|
protected calculateBorderColor(): string {
|
||||||
return this.borderColor.toStringRGBA();
|
return this.borderColor.toStringRGBA();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridable method for calculating text color - useful for elements that want to be colored on different after certain events
|
||||||
|
*/
|
||||||
protected calculateTextColor(): string {
|
protected calculateTextColor(): string {
|
||||||
return this.textColor.toStringRGBA();
|
return this.textColor.toStringRGBA();
|
||||||
}
|
}
|
||||||
|
|
||||||
render(ctx: CanvasRenderingContext2D): void {
|
render(ctx: CanvasRenderingContext2D): void {
|
||||||
|
// Grab the global alpha so we can adjust it for this render
|
||||||
let previousAlpha = ctx.globalAlpha;
|
let previousAlpha = ctx.globalAlpha;
|
||||||
ctx.globalAlpha = this.getLayer().getAlpha();
|
ctx.globalAlpha = this.getLayer().getAlpha();
|
||||||
|
|
||||||
|
@ -164,6 +184,7 @@ export default class UIElement extends CanvasNode{
|
||||||
ctx.font = this.fontSize + "px " + this.font;
|
ctx.font = this.fontSize + "px " + this.font;
|
||||||
let offset = this.calculateOffset(ctx);
|
let offset = this.calculateOffset(ctx);
|
||||||
|
|
||||||
|
// Stroke and fill a rounded rect and give it text
|
||||||
ctx.fillStyle = this.calculateBackgroundColor();
|
ctx.fillStyle = this.calculateBackgroundColor();
|
||||||
ctx.fillRoundedRect(this.position.x - origin.x, this.position.y - origin.y, this.size.x, this.size.y, this.borderRadius);
|
ctx.fillRoundedRect(this.position.x - origin.x, this.position.y - origin.y, this.size.x, this.size.y, this.borderRadius);
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ export default class Button extends UIElement{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected calculateBackgroundColor(): string {
|
protected calculateBackgroundColor(): string {
|
||||||
|
// Change the background color if clicked or hovered
|
||||||
if(this.isEntered && !this.isClicked){
|
if(this.isEntered && !this.isClicked){
|
||||||
return this.backgroundColor.lighten().toStringRGBA();
|
return this.backgroundColor.lighten().toStringRGBA();
|
||||||
} else if(this.isClicked){
|
} else if(this.isClicked){
|
||||||
|
|
|
@ -18,51 +18,85 @@ export default class PhysicsManager {
|
||||||
this.movements = new Array();
|
this.movements = new Array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a PhysicsNode to the manager to be handled in case of collisions
|
||||||
|
* @param node
|
||||||
|
*/
|
||||||
add(node: PhysicsNode): void {
|
add(node: PhysicsNode): void {
|
||||||
this.physicsNodes.push(node);
|
this.physicsNodes.push(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a tilemap node to the manager to be handled for collisions
|
||||||
|
* @param tilemap
|
||||||
|
*/
|
||||||
addTilemap(tilemap: Tilemap): void {
|
addTilemap(tilemap: Tilemap): void {
|
||||||
this.tilemaps.push(tilemap);
|
this.tilemaps.push(tilemap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a movement to this frame. All movements are handled at the end of the frame
|
||||||
|
* @param node
|
||||||
|
* @param velocity
|
||||||
|
*/
|
||||||
addMovement(node: PhysicsNode, velocity: Vec2): void {
|
addMovement(node: PhysicsNode, velocity: Vec2): void {
|
||||||
this.movements.push(new MovementData(node, velocity));
|
this.movements.push(new MovementData(node, velocity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a collision between a physics node and a tilemap
|
||||||
|
* @param node
|
||||||
|
* @param tilemap
|
||||||
|
* @param velocity
|
||||||
|
*/
|
||||||
private collideWithTilemap(node: PhysicsNode, tilemap: Tilemap, velocity: Vec2): void {
|
private collideWithTilemap(node: PhysicsNode, tilemap: Tilemap, velocity: Vec2): void {
|
||||||
if(tilemap instanceof OrthogonalTilemap){
|
if(tilemap instanceof OrthogonalTilemap){
|
||||||
this.collideWithOrthogonalTilemap(node, tilemap, velocity);
|
this.collideWithOrthogonalTilemap(node, tilemap, velocity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifically handles a collision for orthogonal tilemaps
|
||||||
|
* @param node
|
||||||
|
* @param tilemap
|
||||||
|
* @param velocity
|
||||||
|
*/
|
||||||
private collideWithOrthogonalTilemap(node: PhysicsNode, tilemap: OrthogonalTilemap, velocity: Vec2): void {
|
private collideWithOrthogonalTilemap(node: PhysicsNode, tilemap: OrthogonalTilemap, velocity: Vec2): void {
|
||||||
|
// Get the starting position of the moving node
|
||||||
let startPos = node.getPosition();
|
let startPos = node.getPosition();
|
||||||
|
|
||||||
|
// Get the end position of the moving node
|
||||||
let endPos = startPos.clone().add(velocity);
|
let endPos = startPos.clone().add(velocity);
|
||||||
let size = node.getCollider().getSize();
|
let size = node.getCollider().getSize();
|
||||||
|
|
||||||
|
// Get the min and max x and y coordinates of the moving node
|
||||||
let min = new Vec2(Math.min(startPos.x, endPos.x), Math.min(startPos.y, endPos.y));
|
let min = new Vec2(Math.min(startPos.x, endPos.x), Math.min(startPos.y, endPos.y));
|
||||||
let max = new Vec2(Math.max(startPos.x + size.x, endPos.x + size.x), Math.max(startPos.y + size.y, endPos.y + size.y));
|
let max = new Vec2(Math.max(startPos.x + size.x, endPos.x + size.x), Math.max(startPos.y + size.y, endPos.y + size.y));
|
||||||
|
|
||||||
|
// Convert the min/max x/y to the min and max row/col in the tilemap array
|
||||||
let minIndex = tilemap.getColRowAt(min);
|
let minIndex = tilemap.getColRowAt(min);
|
||||||
let maxIndex = tilemap.getColRowAt(max);
|
let maxIndex = tilemap.getColRowAt(max);
|
||||||
|
|
||||||
|
// Create an empty set of tilemap collisions (We'll handle all of them at the end)
|
||||||
let tilemapCollisions = new Array<TileCollisionData>();
|
let tilemapCollisions = new Array<TileCollisionData>();
|
||||||
let tileSize = tilemap.getTileSize();
|
let tileSize = tilemap.getTileSize();
|
||||||
|
|
||||||
Debug.log("tilemapCollision", "");
|
Debug.log("tilemapCollision", "");
|
||||||
|
|
||||||
// Loop over all possible tiles
|
// Loop over all possible tiles
|
||||||
for(let col = minIndex.x; col <= maxIndex.x; col++){
|
for(let col = minIndex.x; col <= maxIndex.x; col++){
|
||||||
for(let row = minIndex.y; row <= maxIndex.y; row++){
|
for(let row = minIndex.y; row <= maxIndex.y; row++){
|
||||||
if(tilemap.isTileCollidable(col, row)){
|
if(tilemap.isTileCollidable(col, row)){
|
||||||
Debug.log("tilemapCollision", "Colliding with Tile");
|
Debug.log("tilemapCollision", "Colliding with Tile");
|
||||||
|
|
||||||
// Tile position
|
// Get the position of this tile
|
||||||
let tilePos = new Vec2(col * tileSize.x, row * tileSize.y);
|
let tilePos = new Vec2(col * tileSize.x, row * tileSize.y);
|
||||||
|
|
||||||
// Calculate collision area
|
// Calculate collision area between the node and the tile
|
||||||
let dx = Math.min(startPos.x, tilePos.x) - Math.max(startPos.x + size.x, tilePos.x + size.x);
|
let dx = Math.min(startPos.x, tilePos.x) - Math.max(startPos.x + size.x, tilePos.x + size.x);
|
||||||
let dy = Math.min(startPos.y, tilePos.y) - Math.max(startPos.y + size.y, tilePos.y + size.y);
|
let dy = Math.min(startPos.y, tilePos.y) - Math.max(startPos.y + size.y, tilePos.y + size.y);
|
||||||
|
|
||||||
|
// If we overlap, how much do we overlap by?
|
||||||
let overlap = 0;
|
let overlap = 0;
|
||||||
if(dx * dy > 0){
|
if(dx * dy > 0){
|
||||||
overlap = dx * dy;
|
overlap = dx * dy;
|
||||||
|
@ -73,32 +107,35 @@ export default class PhysicsManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that we have all collisions, sort by collision area
|
// Now that we have all collisions, sort by collision area highest to lowest
|
||||||
tilemapCollisions = tilemapCollisions.sort((a, b) => a.overlapArea - b.overlapArea);
|
tilemapCollisions = tilemapCollisions.sort((a, b) => a.overlapArea - b.overlapArea);
|
||||||
|
|
||||||
// Resolve the collisions
|
// Resolve the collisions in order of collision area (i.e. "closest" tiles are collided with first, so we can slide along a surface of tiles)
|
||||||
tilemapCollisions.forEach(collision => {
|
tilemapCollisions.forEach(collision => {
|
||||||
let [firstContact, _, collidingX, collidingY] = this.getTimeOfAABBCollision(startPos, size, velocity, collision.position, tileSize, new Vec2(0, 0));
|
let [firstContact, _, collidingX, collidingY] = this.getTimeOfAABBCollision(startPos, size, velocity, collision.position, tileSize, new Vec2(0, 0));
|
||||||
|
|
||||||
// Handle collision
|
// Handle collision
|
||||||
if( (firstContact.x < 1 || collidingX) && (firstContact.y < 1 || collidingY)){
|
if( (firstContact.x < 1 || collidingX) && (firstContact.y < 1 || collidingY)){
|
||||||
if(collidingX && collidingY){
|
if(collidingX && collidingY){
|
||||||
// If we're already intersecting, freak out I guess?
|
// If we're already intersecting, freak out I guess? Probably should handle this in some way for if nodes get spawned inside of tiles
|
||||||
} else {
|
} else {
|
||||||
// let contactTime = Math.min(firstContact.x, firstContact.y);
|
// Get the amount to scale x and y based on their initial collision times
|
||||||
// velocity.scale(contactTime);
|
|
||||||
let xScale = MathUtils.clamp(firstContact.x, 0, 1);
|
let xScale = MathUtils.clamp(firstContact.x, 0, 1);
|
||||||
let yScale = MathUtils.clamp(firstContact.y, 0, 1);
|
let yScale = MathUtils.clamp(firstContact.y, 0, 1);
|
||||||
|
|
||||||
// Handle special case of stickiness on corner to corner collisions
|
// Handle special case of stickiness on perfect corner to corner collisions
|
||||||
if(xScale === yScale){
|
if(xScale === yScale){
|
||||||
xScale = 1;
|
xScale = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we are scaling y, we're on the ground, so tell the node it's grounded
|
||||||
|
// TODO - This is a bug, check to make sure our velocity is going downwards
|
||||||
|
// Maybe feed in a downward direction to check to be sure
|
||||||
if(yScale !== 1){
|
if(yScale !== 1){
|
||||||
node.setGrounded(true);
|
node.setGrounded(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scale the velocity of the node
|
||||||
velocity.scale(xScale, yScale);
|
velocity.scale(xScale, yScale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -264,6 +301,7 @@ export default class PhysicsManager {
|
||||||
|
|
||||||
// Helper classes for internal data
|
// Helper classes for internal data
|
||||||
// TODO: Move these to data
|
// TODO: Move these to data
|
||||||
|
// When an object moves, store it's data as MovementData so all movements can be processed at the same time at the end of the frame
|
||||||
class MovementData {
|
class MovementData {
|
||||||
node: PhysicsNode;
|
node: PhysicsNode;
|
||||||
velocity: Vec2;
|
velocity: Vec2;
|
||||||
|
@ -273,6 +311,7 @@ class MovementData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collision data objects for tilemaps
|
||||||
class TileCollisionData {
|
class TileCollisionData {
|
||||||
position: Vec2;
|
position: Vec2;
|
||||||
overlapArea: number;
|
overlapArea: number;
|
||||||
|
|
|
@ -3,6 +3,10 @@ import GameNode from "../Nodes/GameNode";
|
||||||
import PhysicsManager from "./PhysicsManager";
|
import PhysicsManager from "./PhysicsManager";
|
||||||
import Vec2 from "../DataTypes/Vec2";
|
import Vec2 from "../DataTypes/Vec2";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The representation of a physic-affected object in the game world. Sprites and other game nodes can be associated with
|
||||||
|
* a physics node to move them around as well.
|
||||||
|
*/
|
||||||
export default abstract class PhysicsNode extends GameNode {
|
export default abstract class PhysicsNode extends GameNode {
|
||||||
|
|
||||||
protected collider: Collider = null;
|
protected collider: Collider = null;
|
||||||
|
@ -42,11 +46,19 @@ export default abstract class PhysicsNode extends GameNode {
|
||||||
return this.moving;
|
return this.moving;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a movement to the physics manager that can be handled at the end of the frame
|
||||||
|
* @param velocity
|
||||||
|
*/
|
||||||
protected move(velocity: Vec2): void {
|
protected move(velocity: Vec2): void {
|
||||||
this.moving = true;
|
this.moving = true;
|
||||||
this.manager.addMovement(this, velocity);
|
this.manager.addMovement(this, velocity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the physics manager to finish the movement and actually move the physics object and its children
|
||||||
|
* @param velocity
|
||||||
|
*/
|
||||||
finishMove(velocity: Vec2): void {
|
finishMove(velocity: Vec2): void {
|
||||||
this.position.add(velocity);
|
this.position.add(velocity);
|
||||||
this.collider.getPosition().add(velocity);
|
this.collider.getPosition().add(velocity);
|
||||||
|
|
|
@ -6,30 +6,72 @@ import StringUtils from "../Utils/StringUtils";
|
||||||
import AudioManager from "../Sound/AudioManager";
|
import AudioManager from "../Sound/AudioManager";
|
||||||
|
|
||||||
export default class ResourceManager {
|
export default class ResourceManager {
|
||||||
|
// Instance for the singleton class
|
||||||
private static instance: ResourceManager;
|
private static instance: ResourceManager;
|
||||||
|
|
||||||
|
// Booleans to keep track of whether or not the ResourceManager is currently loading something
|
||||||
private loading: boolean;
|
private loading: boolean;
|
||||||
private justLoaded: boolean;
|
private justLoaded: boolean;
|
||||||
|
|
||||||
|
// Functions to do something when loading progresses or is completed such as render a loading screen
|
||||||
public onLoadProgress: Function;
|
public onLoadProgress: Function;
|
||||||
public onLoadComplete: Function;
|
public onLoadComplete: Function;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number to keep track of how many images need to be loaded
|
||||||
|
*/
|
||||||
private imagesLoaded: number;
|
private imagesLoaded: number;
|
||||||
|
/**
|
||||||
|
* Number to keep track of how many images are loaded
|
||||||
|
*/
|
||||||
private imagesToLoad: number;
|
private imagesToLoad: number;
|
||||||
|
/**
|
||||||
|
* The queue of images we must load
|
||||||
|
*/
|
||||||
private imageLoadingQueue: Queue<{key: string, path: string}>;
|
private imageLoadingQueue: Queue<{key: string, path: string}>;
|
||||||
|
/**
|
||||||
|
* A map of the images that are currently loaded and (presumably) being used by the scene
|
||||||
|
*/
|
||||||
private images: Map<HTMLImageElement>;
|
private images: Map<HTMLImageElement>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number to keep track of how many tilemaps need to be loaded
|
||||||
|
*/
|
||||||
private tilemapsLoaded: number;
|
private tilemapsLoaded: number;
|
||||||
|
/**
|
||||||
|
* Number to keep track of how many tilemaps are loaded
|
||||||
|
*/
|
||||||
private tilemapsToLoad: number;
|
private tilemapsToLoad: number;
|
||||||
|
/**
|
||||||
|
* The queue of tilemaps we must load
|
||||||
|
*/
|
||||||
private tilemapLoadingQueue: Queue<{key: string, path: string}>;
|
private tilemapLoadingQueue: Queue<{key: string, path: string}>;
|
||||||
|
/**
|
||||||
|
* A map of the tilemaps that are currently loaded and (presumably) being used by the scene
|
||||||
|
*/
|
||||||
private tilemaps: Map<TiledTilemapData>;
|
private tilemaps: Map<TiledTilemapData>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number to keep track of how many sounds need to be loaded
|
||||||
|
*/
|
||||||
private audioLoaded: number;
|
private audioLoaded: number;
|
||||||
|
/**
|
||||||
|
* Number to keep track of how many sounds are loaded
|
||||||
|
*/
|
||||||
private audioToLoad: number;
|
private audioToLoad: number;
|
||||||
|
/**
|
||||||
|
* The queue of sounds we must load
|
||||||
|
*/
|
||||||
private audioLoadingQueue: Queue<{key: string, path: string}>;
|
private audioLoadingQueue: Queue<{key: string, path: string}>;
|
||||||
|
/**
|
||||||
|
* A map of the sounds that are currently loaded and (presumably) being used by the scene
|
||||||
|
*/
|
||||||
private audioBuffers: Map<AudioBuffer>;
|
private audioBuffers: Map<AudioBuffer>;
|
||||||
|
|
||||||
// The number of different types of things to load
|
/**
|
||||||
|
* The total number of "types" of things that need to be loaded (i.e. images and tilemaps)
|
||||||
|
*/
|
||||||
private typesToLoad: number;
|
private typesToLoad: number;
|
||||||
|
|
||||||
private constructor(){
|
private constructor(){
|
||||||
|
@ -52,6 +94,9 @@ export default class ResourceManager {
|
||||||
this.audioBuffers = new Map();
|
this.audioBuffers = new Map();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current instance of this class or a new instance if none exist
|
||||||
|
*/
|
||||||
static getInstance(): ResourceManager {
|
static getInstance(): ResourceManager {
|
||||||
if(!this.instance){
|
if(!this.instance){
|
||||||
this.instance = new ResourceManager();
|
this.instance = new ResourceManager();
|
||||||
|
@ -60,11 +105,20 @@ export default class ResourceManager {
|
||||||
return this.instance;
|
return this.instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an image from file
|
||||||
|
* @param key The key to associate the loaded image with
|
||||||
|
* @param path The path to the image to load
|
||||||
|
*/
|
||||||
public image(key: string, path: string): void {
|
public image(key: string, path: string): void {
|
||||||
this.imageLoadingQueue.enqueue({key: key, path: path});
|
this.imageLoadingQueue.enqueue({key: key, path: path});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getImage(key: string): HTMLImageElement{
|
/**
|
||||||
|
* Retrieves a loaded image
|
||||||
|
* @param key The key of the loaded image
|
||||||
|
*/
|
||||||
|
public getImage(key: string): HTMLImageElement {
|
||||||
return this.images.get(key);
|
return this.images.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,23 +126,45 @@ export default class ResourceManager {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load an audio file
|
||||||
|
* @param key
|
||||||
|
* @param path
|
||||||
|
*/
|
||||||
public audio(key: string, path: string): void {
|
public audio(key: string, path: string): void {
|
||||||
this.audioLoadingQueue.enqueue({key: key, path: path});
|
this.audioLoadingQueue.enqueue({key: key, path: path});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a loaded audio file
|
||||||
|
* @param key
|
||||||
|
*/
|
||||||
public getAudio(key: string): AudioBuffer {
|
public getAudio(key: string): AudioBuffer {
|
||||||
return this.audioBuffers.get(key);
|
return this.audioBuffers.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a tilemap from a json file. Automatically loads related images
|
||||||
|
* @param key
|
||||||
|
* @param path
|
||||||
|
*/
|
||||||
public tilemap(key: string, path: string): void {
|
public tilemap(key: string, path: string): void {
|
||||||
this.tilemapLoadingQueue.enqueue({key: key, path: path});
|
this.tilemapLoadingQueue.enqueue({key: key, path: path});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retreives a loaded tilemap
|
||||||
|
* @param key
|
||||||
|
*/
|
||||||
public getTilemap(key: string): TiledTilemapData {
|
public getTilemap(key: string): TiledTilemapData {
|
||||||
return this.tilemaps.get(key);
|
return this.tilemaps.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - Should everything be loaded in order, one file at a time?
|
// TODO - Should everything be loaded in order, one file at a time?
|
||||||
|
/**
|
||||||
|
* Loads all resources currently in the queue
|
||||||
|
* @param callback
|
||||||
|
*/
|
||||||
loadResourcesFromQueue(callback: Function): void {
|
loadResourcesFromQueue(callback: Function): void {
|
||||||
this.typesToLoad = 3;
|
this.typesToLoad = 3;
|
||||||
|
|
||||||
|
@ -108,6 +184,9 @@ export default class ResourceManager {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes references to all resources in the resource manager
|
||||||
|
*/
|
||||||
unloadAllResources(): void {
|
unloadAllResources(): void {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.justLoaded = false;
|
this.justLoaded = false;
|
||||||
|
@ -125,7 +204,11 @@ export default class ResourceManager {
|
||||||
this.audioBuffers.clear();
|
this.audioBuffers.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadTilemapsFromQueue(onFinishLoading: Function){
|
/**
|
||||||
|
* Loads all tilemaps currently in the tilemap loading queue
|
||||||
|
* @param onFinishLoading
|
||||||
|
*/
|
||||||
|
private loadTilemapsFromQueue(onFinishLoading: Function): void {
|
||||||
this.tilemapsToLoad = this.tilemapLoadingQueue.getSize();
|
this.tilemapsToLoad = this.tilemapLoadingQueue.getSize();
|
||||||
this.tilemapsLoaded = 0;
|
this.tilemapsLoaded = 0;
|
||||||
|
|
||||||
|
@ -135,6 +218,12 @@ export default class ResourceManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a singular tilemap
|
||||||
|
* @param key
|
||||||
|
* @param pathToTilemapJSON
|
||||||
|
* @param callbackIfLast
|
||||||
|
*/
|
||||||
private loadTilemap(key: string, pathToTilemapJSON: string, callbackIfLast: Function): void {
|
private loadTilemap(key: string, pathToTilemapJSON: string, callbackIfLast: Function): void {
|
||||||
this.loadTextFile(pathToTilemapJSON, (fileText: string) => {
|
this.loadTextFile(pathToTilemapJSON, (fileText: string) => {
|
||||||
let tilemapObject = <TiledTilemapData>JSON.parse(fileText);
|
let tilemapObject = <TiledTilemapData>JSON.parse(fileText);
|
||||||
|
@ -154,7 +243,11 @@ export default class ResourceManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private finishLoadingTilemap(callback: Function){
|
/**
|
||||||
|
* Finish loading a tilemap. Calls the callback function if this is the last tilemap being loaded
|
||||||
|
* @param callback
|
||||||
|
*/
|
||||||
|
private finishLoadingTilemap(callback: Function): void {
|
||||||
this.tilemapsLoaded += 1;
|
this.tilemapsLoaded += 1;
|
||||||
|
|
||||||
if(this.tilemapsLoaded === this.tilemapsToLoad){
|
if(this.tilemapsLoaded === this.tilemapsToLoad){
|
||||||
|
@ -163,6 +256,10 @@ export default class ResourceManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads all images currently in the tilemap loading queue
|
||||||
|
* @param onFinishLoading
|
||||||
|
*/
|
||||||
private loadImagesFromQueue(onFinishLoading: Function): void {
|
private loadImagesFromQueue(onFinishLoading: Function): void {
|
||||||
this.imagesToLoad = this.imageLoadingQueue.getSize();
|
this.imagesToLoad = this.imageLoadingQueue.getSize();
|
||||||
this.imagesLoaded = 0;
|
this.imagesLoaded = 0;
|
||||||
|
@ -173,7 +270,12 @@ export default class ResourceManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: When you switch to WebGL, make sure to make this private and make a "loadTexture" function
|
/**
|
||||||
|
* Loads a singular image
|
||||||
|
* @param key
|
||||||
|
* @param path
|
||||||
|
* @param callbackIfLast
|
||||||
|
*/
|
||||||
public loadImage(key: string, path: string, callbackIfLast: Function): void {
|
public loadImage(key: string, path: string, callbackIfLast: Function): void {
|
||||||
var image = new Image();
|
var image = new Image();
|
||||||
|
|
||||||
|
@ -188,6 +290,10 @@ export default class ResourceManager {
|
||||||
image.src = path;
|
image.src = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finish loading an image. If this is the last image, it calls the callback function
|
||||||
|
* @param callback
|
||||||
|
*/
|
||||||
private finishLoadingImage(callback: Function): void {
|
private finishLoadingImage(callback: Function): void {
|
||||||
this.imagesLoaded += 1;
|
this.imagesLoaded += 1;
|
||||||
|
|
||||||
|
@ -197,6 +303,10 @@ export default class ResourceManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads all audio currently in the tilemap loading queue
|
||||||
|
* @param onFinishLoading
|
||||||
|
*/
|
||||||
private loadAudioFromQueue(onFinishLoading: Function){
|
private loadAudioFromQueue(onFinishLoading: Function){
|
||||||
this.audioToLoad = this.audioLoadingQueue.getSize();
|
this.audioToLoad = this.audioLoadingQueue.getSize();
|
||||||
this.audioLoaded = 0;
|
this.audioLoaded = 0;
|
||||||
|
@ -207,6 +317,12 @@ export default class ResourceManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a singular audio file
|
||||||
|
* @param key
|
||||||
|
* @param path
|
||||||
|
* @param callbackIfLast
|
||||||
|
*/
|
||||||
private loadAudio(key: string, path: string, callbackIfLast: Function): void {
|
private loadAudio(key: string, path: string, callbackIfLast: Function): void {
|
||||||
let audioCtx = AudioManager.getInstance().getAudioContext();
|
let audioCtx = AudioManager.getInstance().getAudioContext();
|
||||||
|
|
||||||
|
@ -228,6 +344,10 @@ export default class ResourceManager {
|
||||||
request.send();
|
request.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finish loading an audio file. Calls the callback functon if this is the last audio sample being loaded.
|
||||||
|
* @param callback
|
||||||
|
*/
|
||||||
private finishLoadingAudio(callback: Function): void {
|
private finishLoadingAudio(callback: Function): void {
|
||||||
this.audioLoaded += 1;
|
this.audioLoaded += 1;
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,11 @@ export default class AudioFactory {
|
||||||
this.audioManager = AudioManager.getInstance();
|
this.audioManager = AudioManager.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
addAudio = (key: string, ...args: any): Audio => {
|
/**
|
||||||
|
* Returns an audio element created using the previously loaded audio file specified by the key.
|
||||||
|
* @param key The key of the loaded audio file
|
||||||
|
*/
|
||||||
|
addAudio = (key: string): Audio => {
|
||||||
let audio = new Audio(key);
|
let audio = new Audio(key);
|
||||||
return audio;
|
return audio;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,12 @@ export default class CanvasNodeFactory {
|
||||||
this.sceneGraph = sceneGraph;
|
this.sceneGraph = sceneGraph;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an instance of a UIElement to the current scene - i.e. any class that extends UIElement
|
||||||
|
* @param constr The constructor of the UIElement to be created
|
||||||
|
* @param layer The layer to add the UIElement to
|
||||||
|
* @param args Any additional arguments to feed to the constructor
|
||||||
|
*/
|
||||||
addUIElement = <T extends UIElement>(constr: new (...a: any) => T, layer: Layer, ...args: any): T => {
|
addUIElement = <T extends UIElement>(constr: new (...a: any) => T, layer: Layer, ...args: any): T => {
|
||||||
let instance = new constr(...args);
|
let instance = new constr(...args);
|
||||||
|
|
||||||
|
@ -27,8 +33,13 @@ export default class CanvasNodeFactory {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
addSprite = (imageId: string, layer: Layer, ...args: any): Sprite => {
|
/**
|
||||||
let instance = new Sprite(imageId);
|
* Adds a sprite to the current scene
|
||||||
|
* @param key The key of the image the sprite will represent
|
||||||
|
* @param layer The layer on which to add the sprite
|
||||||
|
*/
|
||||||
|
addSprite = (key: string, layer: Layer): Sprite => {
|
||||||
|
let instance = new Sprite(key);
|
||||||
|
|
||||||
// Add instance to scene
|
// Add instance to scene
|
||||||
instance.setScene(this.scene);
|
instance.setScene(this.scene);
|
||||||
|
@ -40,6 +51,12 @@ export default class CanvasNodeFactory {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new graphic element to the current Scene
|
||||||
|
* @param constr The constructor of the graphic element to add
|
||||||
|
* @param layer The layer on which to add the graphic
|
||||||
|
* @param args Any additional arguments to send to the graphic constructor
|
||||||
|
*/
|
||||||
addGraphic = <T extends Graphic>(constr: new (...a: any) => T, layer: Layer, ...args: any): T => {
|
addGraphic = <T extends Graphic>(constr: new (...a: any) => T, layer: Layer, ...args: any): T => {
|
||||||
let instance = new constr(...args);
|
let instance = new constr(...args);
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import Tilemap from "../../Nodes/Tilemap";
|
||||||
|
|
||||||
export default class FactoryManager {
|
export default class FactoryManager {
|
||||||
|
|
||||||
|
// Constructors are called here to allow assignment of their functions to functions in this class
|
||||||
private canvasNodeFactory: CanvasNodeFactory = new CanvasNodeFactory();
|
private canvasNodeFactory: CanvasNodeFactory = new CanvasNodeFactory();
|
||||||
private physicsNodeFactory: PhysicsNodeFactory = new PhysicsNodeFactory();
|
private physicsNodeFactory: PhysicsNodeFactory = new PhysicsNodeFactory();
|
||||||
private tilemapFactory: TilemapFactory = new TilemapFactory();
|
private tilemapFactory: TilemapFactory = new TilemapFactory();
|
||||||
|
@ -21,6 +22,7 @@ export default class FactoryManager {
|
||||||
this.audioFactory.init(scene);
|
this.audioFactory.init(scene);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Expose all of the factories through the factory manager
|
||||||
uiElement = this.canvasNodeFactory.addUIElement;
|
uiElement = this.canvasNodeFactory.addUIElement;
|
||||||
sprite = this.canvasNodeFactory.addSprite;
|
sprite = this.canvasNodeFactory.addSprite;
|
||||||
graphic = this.canvasNodeFactory.addGraphic;
|
graphic = this.canvasNodeFactory.addGraphic;
|
||||||
|
|
|
@ -13,6 +13,12 @@ export default class PhysicsNodeFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Currently this doesn't care about layers
|
// TODO: Currently this doesn't care about layers
|
||||||
|
/**
|
||||||
|
* Adds a new PhysicsNode to the scene on the specified Layer
|
||||||
|
* @param constr The constructor of the PhysicsNode to be added to the scene
|
||||||
|
* @param layer The layer on which to add the PhysicsNode
|
||||||
|
* @param args Any additional arguments to send to the PhysicsNode constructor
|
||||||
|
*/
|
||||||
add = <T extends PhysicsNode>(constr: new (...a: any) => T, layer: Layer, ...args: any): T => {
|
add = <T extends PhysicsNode>(constr: new (...a: any) => T, layer: Layer, ...args: any): T => {
|
||||||
let instance = new constr(...args);
|
let instance = new constr(...args);
|
||||||
instance.setScene(this.scene);
|
instance.setScene(this.scene);
|
||||||
|
|
|
@ -16,6 +16,12 @@ export default class TilemapFactory {
|
||||||
this.resourceManager = ResourceManager.getInstance();
|
this.resourceManager = ResourceManager.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a tilemap to the scene
|
||||||
|
* @param key The key of the loaded tilemap to load
|
||||||
|
* @param constr The constructor of the desired tilemap
|
||||||
|
* @param args Additional arguments to send to the tilemap constructor
|
||||||
|
*/
|
||||||
add = <T extends Tilemap>(key: string, constr: new (...a: any) => T, ...args: any): Array<Tilemap> => {
|
add = <T extends Tilemap>(key: string, constr: new (...a: any) => T, ...args: any): Array<Tilemap> => {
|
||||||
// Get Tilemap Data
|
// Get Tilemap Data
|
||||||
let tilemapData = this.resourceManager.getTilemap(key);
|
let tilemapData = this.resourceManager.getTilemap(key);
|
||||||
|
|
|
@ -3,6 +3,9 @@ import Scene from "./Scene";
|
||||||
import MathUtils from "../Utils/MathUtils";
|
import MathUtils from "../Utils/MathUtils";
|
||||||
import GameNode from "../Nodes/GameNode";
|
import GameNode from "../Nodes/GameNode";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A layer in the scene. Has its own alpha value and parallax.
|
||||||
|
*/
|
||||||
export default class Layer {
|
export default class Layer {
|
||||||
protected scene: Scene;
|
protected scene: Scene;
|
||||||
protected parallax: Vec2;
|
protected parallax: Vec2;
|
||||||
|
@ -66,6 +69,4 @@ export default class Layer {
|
||||||
this.items.push(node);
|
this.items.push(node);
|
||||||
node.setLayer(this);
|
node.setLayer(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
render(ctx: CanvasRenderingContext2D): void {}
|
|
||||||
}
|
}
|
|
@ -1,3 +0,0 @@
|
||||||
import Layer from "../Layer";
|
|
||||||
|
|
||||||
export default class ObjectLayer extends Layer {}
|
|
|
@ -1,6 +0,0 @@
|
||||||
import Layer from "../Layer";
|
|
||||||
import Tilemap from "../../Nodes/Tilemap";
|
|
||||||
|
|
||||||
export default class TiledLayer extends Layer {
|
|
||||||
private tilemap: Tilemap;
|
|
||||||
}
|
|
|
@ -20,10 +20,21 @@ export default class Scene{
|
||||||
protected sceneManager: SceneManager;
|
protected sceneManager: SceneManager;
|
||||||
|
|
||||||
protected tilemaps: Array<Tilemap>;
|
protected tilemaps: Array<Tilemap>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The scene graph of the Scene - can be exchanged with other SceneGraphs for more variation
|
||||||
|
*/
|
||||||
protected sceneGraph: SceneGraph;
|
protected sceneGraph: SceneGraph;
|
||||||
protected physicsManager: PhysicsManager;
|
protected physicsManager: PhysicsManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface that allows the adding of different nodes to the scene
|
||||||
|
*/
|
||||||
public add: FactoryManager;
|
public add: FactoryManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface that allows the loading of different files for use in the scene
|
||||||
|
*/
|
||||||
public load: ResourceManager;
|
public load: ResourceManager;
|
||||||
|
|
||||||
constructor(viewport: Viewport, sceneManager: SceneManager, game: GameLoop){
|
constructor(viewport: Viewport, sceneManager: SceneManager, game: GameLoop){
|
||||||
|
@ -39,19 +50,38 @@ export default class Scene{
|
||||||
this.sceneGraph = new SceneGraphArray(this.viewport, this);
|
this.sceneGraph = new SceneGraphArray(this.viewport, this);
|
||||||
this.physicsManager = new PhysicsManager();
|
this.physicsManager = new PhysicsManager();
|
||||||
|
|
||||||
// Factories for this scene
|
|
||||||
this.add = new FactoryManager(this, this.sceneGraph, this.physicsManager, this.tilemaps);
|
this.add = new FactoryManager(this, this.sceneGraph, this.physicsManager, this.tilemaps);
|
||||||
|
|
||||||
|
|
||||||
this.load = ResourceManager.getInstance();
|
this.load = ResourceManager.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that gets called when a new scene is created. Load all files you wish to access in the scene here.
|
||||||
|
*/
|
||||||
loadScene(): void {}
|
loadScene(): void {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that gets called on scene destruction. Specify which files you no longer need for garbage collection.
|
||||||
|
*/
|
||||||
unloadScene(): void {}
|
unloadScene(): void {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called strictly after loadScene() is called. Create any game objects you wish to use in the scene here.
|
||||||
|
*/
|
||||||
startScene(): void {}
|
startScene(): void {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called every frame of the game. This is where you can dynamically do things like add in new enemies
|
||||||
|
* @param delta
|
||||||
|
*/
|
||||||
updateScene(delta: number): void {}
|
updateScene(delta: number): void {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates all scene elements
|
||||||
|
* @param deltaT
|
||||||
|
*/
|
||||||
update(deltaT: number): void {
|
update(deltaT: number): void {
|
||||||
this.updateScene(deltaT);
|
this.updateScene(deltaT);
|
||||||
|
|
||||||
|
@ -72,6 +102,10 @@ export default class Scene{
|
||||||
this.viewport.update(deltaT);
|
this.viewport.update(deltaT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render all CanvasNodes and Tilemaps in the Scene
|
||||||
|
* @param ctx
|
||||||
|
*/
|
||||||
render(ctx: CanvasRenderingContext2D): void {
|
render(ctx: CanvasRenderingContext2D): void {
|
||||||
// For webGL, pass a visible set to the renderer
|
// For webGL, pass a visible set to the renderer
|
||||||
// We need to keep track of the order of things.
|
// We need to keep track of the order of things.
|
||||||
|
@ -94,12 +128,18 @@ export default class Scene{
|
||||||
return this.running;
|
return this.running;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new layer to the scene and returns it
|
||||||
|
*/
|
||||||
addLayer(): Layer {
|
addLayer(): Layer {
|
||||||
let layer = new Layer(this);
|
let layer = new Layer(this);
|
||||||
this.layers.push(layer);
|
this.layers.push(layer);
|
||||||
return layer;
|
return layer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the viewport associated with this scene
|
||||||
|
*/
|
||||||
getViewport(): Viewport {
|
getViewport(): Viewport {
|
||||||
return this.viewport;
|
return this.viewport;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import ResourceManager from "../ResourceManager/ResourceManager";
|
||||||
import Viewport from "../SceneGraph/Viewport";
|
import Viewport from "../SceneGraph/Viewport";
|
||||||
import GameLoop from "../Loop/GameLoop";
|
import GameLoop from "../Loop/GameLoop";
|
||||||
|
|
||||||
export default class SceneManager{
|
export default class SceneManager {
|
||||||
|
|
||||||
private currentScene: Scene;
|
private currentScene: Scene;
|
||||||
private viewport: Viewport;
|
private viewport: Viewport;
|
||||||
|
@ -16,6 +16,10 @@ export default class SceneManager{
|
||||||
this.game = game;
|
this.game = game;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a scene as the main scene
|
||||||
|
* @param constr The constructor of the scene to add
|
||||||
|
*/
|
||||||
public addScene<T extends Scene>(constr: new (...args: any) => T): void {
|
public addScene<T extends Scene>(constr: new (...args: any) => T): void {
|
||||||
let scene = new constr(this.viewport, this, this.game);
|
let scene = new constr(this.viewport, this, this.game);
|
||||||
this.currentScene = scene;
|
this.currentScene = scene;
|
||||||
|
@ -30,6 +34,10 @@ export default class SceneManager{
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change from the current scene to this new scene
|
||||||
|
* @param constr The constructor of the scene to change to
|
||||||
|
*/
|
||||||
public changeScene<T extends Scene>(constr: new (...args: any) => T): void {
|
public changeScene<T extends Scene>(constr: new (...args: any) => T): void {
|
||||||
// unload current scene
|
// unload current scene
|
||||||
this.currentScene.unloadScene();
|
this.currentScene.unloadScene();
|
||||||
|
|
|
@ -4,7 +4,10 @@ import Map from "../DataTypes/Map";
|
||||||
import Vec2 from "../DataTypes/Vec2";
|
import Vec2 from "../DataTypes/Vec2";
|
||||||
import Scene from "../Scene/Scene";
|
import Scene from "../Scene/Scene";
|
||||||
|
|
||||||
export default abstract class SceneGraph{
|
/**
|
||||||
|
* An abstract interface of a SceneGraph. Exposes methods for use by other code, but leaves the implementation up to the subclasses.
|
||||||
|
*/
|
||||||
|
export default abstract class SceneGraph {
|
||||||
protected viewport: Viewport;
|
protected viewport: Viewport;
|
||||||
protected nodeMap: Map<CanvasNode>;
|
protected nodeMap: Map<CanvasNode>;
|
||||||
protected idCounter: number;
|
protected idCounter: number;
|
||||||
|
@ -17,6 +20,10 @@ export default abstract class SceneGraph{
|
||||||
this.idCounter = 0;
|
this.idCounter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a node to the SceneGraph
|
||||||
|
* @param node The CanvasNode to add to the SceneGraph
|
||||||
|
*/
|
||||||
addNode(node: CanvasNode): number {
|
addNode(node: CanvasNode): number {
|
||||||
this.nodeMap.add(this.idCounter.toString(), node);
|
this.nodeMap.add(this.idCounter.toString(), node);
|
||||||
this.addNodeSpecific(node, this.idCounter.toString());
|
this.addNodeSpecific(node, this.idCounter.toString());
|
||||||
|
@ -24,8 +31,17 @@ export default abstract class SceneGraph{
|
||||||
return this.idCounter - 1;
|
return this.idCounter - 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An overridable method to add a CanvasNode to the specific data structure of the SceneGraph
|
||||||
|
* @param node The node to add to the data structure
|
||||||
|
* @param id The id of the CanvasNode
|
||||||
|
*/
|
||||||
protected abstract addNodeSpecific(node: CanvasNode, id: string): void;
|
protected abstract addNodeSpecific(node: CanvasNode, id: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a node from the SceneGraph
|
||||||
|
* @param node The node to remove
|
||||||
|
*/
|
||||||
removeNode(node: CanvasNode): void {
|
removeNode(node: CanvasNode): void {
|
||||||
// Find and remove node in O(n)
|
// Find and remove node in O(n)
|
||||||
// TODO: Can this be better?
|
// TODO: Can this be better?
|
||||||
|
@ -36,12 +52,26 @@ export default abstract class SceneGraph{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The specific implementation of removing a node
|
||||||
|
* @param node The node to remove
|
||||||
|
* @param id The id of the node to remove
|
||||||
|
*/
|
||||||
protected abstract removeNodeSpecific(node: CanvasNode, id: string): void;
|
protected abstract removeNodeSpecific(node: CanvasNode, id: string): void;
|
||||||
|
|
||||||
getNode(id: string): CanvasNode{
|
/**
|
||||||
|
* Get a specific node using its id
|
||||||
|
* @param id The id of the CanvasNode to retrieve
|
||||||
|
*/
|
||||||
|
getNode(id: string): CanvasNode {
|
||||||
return this.nodeMap.get(id);
|
return this.nodeMap.get(id);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the node at specific coordinates
|
||||||
|
* @param vecOrX
|
||||||
|
* @param y
|
||||||
|
*/
|
||||||
getNodeAt(vecOrX: Vec2 | number, y: number = null): CanvasNode {
|
getNodeAt(vecOrX: Vec2 | number, y: number = null): CanvasNode {
|
||||||
if(vecOrX instanceof Vec2){
|
if(vecOrX instanceof Vec2){
|
||||||
return this.getNodeAtCoords(vecOrX.x, vecOrX.y);
|
return this.getNodeAtCoords(vecOrX.x, vecOrX.y);
|
||||||
|
@ -50,9 +80,17 @@ export default abstract class SceneGraph{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The specific implementation of getting a node at certain coordinates
|
||||||
|
* @param x
|
||||||
|
* @param y
|
||||||
|
*/
|
||||||
protected abstract getNodeAtCoords(x: number, y: number): CanvasNode;
|
protected abstract getNodeAtCoords(x: number, y: number): CanvasNode;
|
||||||
|
|
||||||
abstract update(deltaT: number): void;
|
abstract update(deltaT: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the visible set of CanvasNodes based on the viewport
|
||||||
|
*/
|
||||||
abstract getVisibleSet(): Array<CanvasNode>;
|
abstract getVisibleSet(): Array<CanvasNode>;
|
||||||
}
|
}
|
|
@ -4,7 +4,7 @@ import GameNode from "../Nodes/GameNode";
|
||||||
import CanvasNode from "../Nodes/CanvasNode";
|
import CanvasNode from "../Nodes/CanvasNode";
|
||||||
import MathUtils from "../Utils/MathUtils";
|
import MathUtils from "../Utils/MathUtils";
|
||||||
|
|
||||||
export default class Viewport{
|
export default class Viewport {
|
||||||
private position: Vec2;
|
private position: Vec2;
|
||||||
private size: Vec2;
|
private size: Vec2;
|
||||||
private bounds: Vec4;
|
private bounds: Vec4;
|
||||||
|
@ -16,10 +16,18 @@ export default class Viewport{
|
||||||
this.bounds = new Vec4(0, 0, 0, 0);
|
this.bounds = new Vec4(0, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the position of the viewport as a Vec2
|
||||||
|
*/
|
||||||
getPosition(): Vec2 {
|
getPosition(): Vec2 {
|
||||||
return this.position;
|
return this.position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the position of the viewport
|
||||||
|
* @param vecOrX
|
||||||
|
* @param y
|
||||||
|
*/
|
||||||
setPosition(vecOrX: Vec2 | number, y: number = null): void {
|
setPosition(vecOrX: Vec2 | number, y: number = null): void {
|
||||||
if(vecOrX instanceof Vec2){
|
if(vecOrX instanceof Vec2){
|
||||||
this.position.set(vecOrX.x, vecOrX.y);
|
this.position.set(vecOrX.x, vecOrX.y);
|
||||||
|
@ -28,10 +36,18 @@ export default class Viewport{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the size of the viewport as a Vec2
|
||||||
|
*/
|
||||||
getSize(): Vec2{
|
getSize(): Vec2{
|
||||||
return this.size;
|
return this.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the size of the viewport
|
||||||
|
* @param vecOrX
|
||||||
|
* @param y
|
||||||
|
*/
|
||||||
setSize(vecOrX: Vec2 | number, y: number = null): void {
|
setSize(vecOrX: Vec2 | number, y: number = null): void {
|
||||||
if(vecOrX instanceof Vec2){
|
if(vecOrX instanceof Vec2){
|
||||||
this.size.set(vecOrX.x, vecOrX.y);
|
this.size.set(vecOrX.x, vecOrX.y);
|
||||||
|
@ -40,6 +56,10 @@ export default class Viewport{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the CanvasNode is inside of the viewport
|
||||||
|
* @param node
|
||||||
|
*/
|
||||||
includes(node: CanvasNode): boolean {
|
includes(node: CanvasNode): boolean {
|
||||||
let nodePos = node.getPosition();
|
let nodePos = node.getPosition();
|
||||||
let nodeSize = node.getSize();
|
let nodeSize = node.getSize();
|
||||||
|
@ -56,17 +76,30 @@ export default class Viewport{
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Put some error handling on this for trying to make the bounds too small for the viewport
|
// TODO: Put some error handling on this for trying to make the bounds too small for the viewport
|
||||||
// TODO: This should probably be done automatically, or should consider the aspect ratio or something
|
// TODO: This should probably be done automatically, or should consider the aspect ratio or something
|
||||||
|
/**
|
||||||
|
* Sets the bounds of the viewport
|
||||||
|
* @param lowerX
|
||||||
|
* @param lowerY
|
||||||
|
* @param upperX
|
||||||
|
* @param upperY
|
||||||
|
*/
|
||||||
setBounds(lowerX: number, lowerY: number, upperX: number, upperY: number): void {
|
setBounds(lowerX: number, lowerY: number, upperX: number, upperY: number): void {
|
||||||
this.bounds = new Vec4(lowerX, lowerY, upperX, upperY);
|
this.bounds = new Vec4(lowerX, lowerY, upperX, upperY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make the viewport follow the specified GameNode
|
||||||
|
* @param node The GameNode to follow
|
||||||
|
*/
|
||||||
follow(node: GameNode): void {
|
follow(node: GameNode): void {
|
||||||
this.following = node;
|
this.following = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
update(deltaT: number): void {
|
update(deltaT: number): void {
|
||||||
|
// If viewport is following an object
|
||||||
if(this.following){
|
if(this.following){
|
||||||
|
// Set this position either to the object or to its bounds
|
||||||
this.position.x = this.following.getPosition().x - this.size.x/2;
|
this.position.x = this.following.getPosition().x - this.size.x/2;
|
||||||
this.position.y = this.following.getPosition().y - this.size.y/2;
|
this.position.y = this.following.getPosition().y - this.size.y/2;
|
||||||
let [min, max] = this.bounds.split();
|
let [min, max] = this.bounds.split();
|
||||||
|
|
|
@ -8,7 +8,11 @@ export default class Audio {
|
||||||
this.key = key;
|
this.key = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
play(loop?: boolean){
|
/**
|
||||||
|
* Play the sound this audio represents
|
||||||
|
* @param loop A boolean for whether or not to loop the sound
|
||||||
|
*/
|
||||||
|
play(loop?: boolean): void {
|
||||||
this.sound = AudioManager.getInstance().createSound(this.key);
|
this.sound = AudioManager.getInstance().createSound(this.key);
|
||||||
|
|
||||||
if(loop){
|
if(loop){
|
||||||
|
@ -18,7 +22,10 @@ export default class Audio {
|
||||||
this.sound.start();
|
this.sound.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
stop(){
|
/**
|
||||||
|
* Stop the sound this audio represents
|
||||||
|
*/
|
||||||
|
stop(): void {
|
||||||
if(this.sound){
|
if(this.sound){
|
||||||
this.sound.stop();
|
this.sound.stop();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,9 @@ export default class AudioManager {
|
||||||
this.initAudio();
|
this.initAudio();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the instance of the AudioManager class or create a new one if none exists
|
||||||
|
*/
|
||||||
public static getInstance(): AudioManager {
|
public static getInstance(): AudioManager {
|
||||||
if(!this.instance){
|
if(!this.instance){
|
||||||
this.instance = new AudioManager();
|
this.instance = new AudioManager();
|
||||||
|
@ -16,6 +19,9 @@ export default class AudioManager {
|
||||||
return this.instance;
|
return this.instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the webAudio context
|
||||||
|
*/
|
||||||
private initAudio(): void {
|
private initAudio(): void {
|
||||||
try {
|
try {
|
||||||
window.AudioContext = window.AudioContext;// || window.webkitAudioContext;
|
window.AudioContext = window.AudioContext;// || window.webkitAudioContext;
|
||||||
|
@ -26,22 +32,28 @@ export default class AudioManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current audio context
|
||||||
|
*/
|
||||||
public getAudioContext(): AudioContext {
|
public getAudioContext(): AudioContext {
|
||||||
return this.audioCtx;
|
return this.audioCtx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new sound from the key of a loaded audio file
|
||||||
|
* @param key The key of the loaded audio file to create a new sound for
|
||||||
|
*/
|
||||||
createSound(key: string): AudioBufferSourceNode {
|
createSound(key: string): AudioBufferSourceNode {
|
||||||
// Get audio buffer
|
// Get audio buffer
|
||||||
let buffer = ResourceManager.getInstance().getAudio(key);
|
let buffer = ResourceManager.getInstance().getAudio(key);
|
||||||
|
|
||||||
// creates a sound source
|
// Create a sound source
|
||||||
var source = this.audioCtx.createBufferSource();
|
var source = this.audioCtx.createBufferSource();
|
||||||
|
|
||||||
// tell the source which sound to play
|
// Tell the source which sound to play
|
||||||
source.buffer = buffer;
|
source.buffer = buffer;
|
||||||
|
|
||||||
// connect the source to the context's destination
|
// Connect the source to the context's destination
|
||||||
// i.e. the speakers
|
|
||||||
source.connect(this.audioCtx.destination);
|
source.connect(this.audioCtx.destination);
|
||||||
|
|
||||||
return source;
|
return source;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import MathUtils from "./MathUtils";
|
import MathUtils from "./MathUtils";
|
||||||
|
|
||||||
// TODO: This should be moved to the datatypes folder
|
// TODO: This should be moved to the datatypes folder
|
||||||
export default class Color{
|
export default class Color {
|
||||||
public r: number;
|
public r: number;
|
||||||
public g: number;
|
public g: number;
|
||||||
public b: number;
|
public b: number;
|
||||||
|
@ -14,22 +14,37 @@ export default class Color{
|
||||||
this.a = a;
|
this.a = a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new color slightly lighter than the current color
|
||||||
|
*/
|
||||||
lighten(): Color {
|
lighten(): Color {
|
||||||
return new Color(MathUtils.clamp(this.r + 40, 0, 255), MathUtils.clamp(this.g + 40, 0, 255), MathUtils.clamp(this.b + 40, 0, 255), this.a);
|
return new Color(MathUtils.clamp(this.r + 40, 0, 255), MathUtils.clamp(this.g + 40, 0, 255), MathUtils.clamp(this.b + 40, 0, 255), this.a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new color slightly darker than the current color
|
||||||
|
*/
|
||||||
darken(): Color {
|
darken(): Color {
|
||||||
return new Color(MathUtils.clamp(this.r - 40, 0, 255), MathUtils.clamp(this.g - 40, 0, 255), MathUtils.clamp(this.b - 40, 0, 255), this.a);
|
return new Color(MathUtils.clamp(this.r - 40, 0, 255), MathUtils.clamp(this.g - 40, 0, 255), MathUtils.clamp(this.b - 40, 0, 255), this.a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the color as a string of the form #RRGGBB
|
||||||
|
*/
|
||||||
toString(): string {
|
toString(): string {
|
||||||
return "#" + MathUtils.toHex(this.r, 2) + MathUtils.toHex(this.g, 2) + MathUtils.toHex(this.b, 2);
|
return "#" + MathUtils.toHex(this.r, 2) + MathUtils.toHex(this.g, 2) + MathUtils.toHex(this.b, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the color as a string of the form rgb(r, g, b)
|
||||||
|
*/
|
||||||
toStringRGB(): string {
|
toStringRGB(): string {
|
||||||
return "rgb(" + this.r.toString() + ", " + this.g.toString() + ", " + this.b.toString() + ")";
|
return "rgb(" + this.r.toString() + ", " + this.g.toString() + ", " + this.b.toString() + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the color as a string of the form rgba(r, g, b, a)
|
||||||
|
*/
|
||||||
toStringRGBA(): string {
|
toStringRGBA(): string {
|
||||||
if(this.a === null){
|
if(this.a === null){
|
||||||
return this.toStringRGB();
|
return this.toStringRGB();
|
||||||
|
|
|
@ -1,10 +1,21 @@
|
||||||
export default class MathUtils{
|
export default class MathUtils {
|
||||||
|
/**
|
||||||
|
* Clamps the value x to the range [min, max], rounding up or down if needed
|
||||||
|
* @param x The value to be clamped
|
||||||
|
* @param min The min of the range
|
||||||
|
* @param max The max of the range
|
||||||
|
*/
|
||||||
static clamp(x: number, min: number, max: number): number {
|
static clamp(x: number, min: number, max: number): number {
|
||||||
if(x < min) return min;
|
if(x < min) return min;
|
||||||
if(x > max) return max;
|
if(x > max) return max;
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number as a hexadecimal
|
||||||
|
* @param num The number to convert to hex
|
||||||
|
* @param minLength The length of the returned hex string (adds zero padding if needed)
|
||||||
|
*/
|
||||||
static toHex(num: number, minLength: number = null): string {
|
static toHex(num: number, minLength: number = null): string {
|
||||||
let factor = 1;
|
let factor = 1;
|
||||||
while(factor*16 < num){
|
while(factor*16 < num){
|
||||||
|
@ -27,6 +38,10 @@ export default class MathUtils{
|
||||||
return hexStr;
|
return hexStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the number to hexadecimal
|
||||||
|
* @param num The number to convert to hexadecimal
|
||||||
|
*/
|
||||||
static toHexDigit(num: number): string {
|
static toHexDigit(num: number): string {
|
||||||
if(num < 10){
|
if(num < 10){
|
||||||
return "" + num;
|
return "" + num;
|
||||||
|
|
|
@ -1,15 +1,28 @@
|
||||||
import MathUtils from "./MathUtils";
|
import MathUtils from "./MathUtils";
|
||||||
import Color from "./Color";
|
import Color from "./Color";
|
||||||
|
|
||||||
export default class RandUtils{
|
export default class RandUtils {
|
||||||
|
/**
|
||||||
|
* Generates a random integer in the specified range
|
||||||
|
* @param min The min of the range (inclusive)
|
||||||
|
* @param max The max of the range (exclusive)
|
||||||
|
*/
|
||||||
static randInt(min: number, max: number): number {
|
static randInt(min: number, max: number): number {
|
||||||
return Math.floor(Math.random()*(max - min) + min);
|
return Math.floor(Math.random()*(max - min) + min);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a random hexadecimal number in the specified range
|
||||||
|
* @param min The min of the range (inclusive)
|
||||||
|
* @param max The max of the range (exclusive)
|
||||||
|
*/
|
||||||
static randHex(min: number, max: number): string {
|
static randHex(min: number, max: number): string {
|
||||||
return MathUtils.toHex(RandUtils.randInt(min, max));
|
return MathUtils.toHex(RandUtils.randInt(min, max));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a random color
|
||||||
|
*/
|
||||||
static randColor(): Color {
|
static randColor(): Color {
|
||||||
let r = RandUtils.randInt(0, 256);
|
let r = RandUtils.randInt(0, 256);
|
||||||
let g = RandUtils.randInt(0, 256);
|
let g = RandUtils.randInt(0, 256);
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
export default class StringUtils {
|
export default class StringUtils {
|
||||||
|
/**
|
||||||
|
* Extracts the path from a filepath that includes the file
|
||||||
|
* @param filePath the filepath to extract the path form
|
||||||
|
*/
|
||||||
static getPathFromFilePath(filePath: string): string {
|
static getPathFromFilePath(filePath: string): string {
|
||||||
let splitPath = filePath.split("/");
|
let splitPath = filePath.split("/");
|
||||||
splitPath.pop();
|
splitPath.pop();
|
||||||
|
|
Loading…
Reference in New Issue
Block a user