add Timers and Lines, and modified Registry

This commit is contained in:
Joe Weaver 2021-03-04 19:10:41 -05:00
parent 924469a2cd
commit c241aa99bc
28 changed files with 513 additions and 52 deletions

View File

@ -50,6 +50,8 @@ export default class Graph {
addEdge(x: number, y: number, weight?: number): void {
let edge = new EdgeNode(y, weight);
if(this.edges[x]){
edge.next = this.edges[x];
}
@ -69,6 +71,24 @@ export default class Graph {
this.numEdges += 1;
}
/**
* Checks whether or not an edge exists between two nodes.
* This check is directional if this is a directed graph.
* @param x The first node
* @param y The second node
* @returns true if an edge exists, false otherwise
*/
edgeExists(x: number, y: number): boolean {
let edge = this.edges[x];
while(edge !== null){
if(edge.y === y){
return true;
}
edge = edge.next;
}
}
/**
* Gets the edge list associated with node x
* @param x The index of the node

View File

@ -33,4 +33,11 @@ export default interface Actor {
* @param options An object that allows options to be pased to the activated AI
*/
setAIActive(active: boolean, options: Record<string, any>): void;
/**
* Moves this GameNode along a path
* @param speed The speed to move with
* @param path The path we're moving along
*/
moveOnPath(speed: number, path: NavigationPath): void;
}

View File

@ -109,4 +109,22 @@ export default class Queue<T> implements Collection {
i = (i + 1) % this.MAX_ELEMENTS;
}
}
/**
* Converts this queue into a string format
* @returns A string representing this queue
*/
toString(): string {
let retval = "";
this.forEach( (item, index) => {
let str = item.toString()
if(index !== 0){
str += " -> "
}
retval = str + retval;
});
return "Top -> " + retval;
}
}

View File

@ -25,8 +25,9 @@ export default abstract class State implements Updateable {
/**
* A method that is called when this state is entered. Use this to initialize any variables before updates occur.
* @param options Information to pass to this state
*/
abstract onEnter(): void;
abstract onEnter(options: Record<string, any>): void;
/**
* A lifecycle method that handles an input event, such as taking damage.
@ -42,11 +43,13 @@ export default abstract class State implements Updateable {
* @param stateName The name of the state to transition to
*/
protected finished(stateName: string): void {
console.log("Finished");
this.parent.changeState(stateName);
}
/**
* A lifecycle method is called when the state is ending.
* @returns info to pass to the next state
*/
abstract onExit(): void;
abstract onExit(): Record<string, any>;
}

View File

@ -67,9 +67,10 @@ export default class StateMachine implements Updateable {
* Initializes this state machine with an initial state and sets it running
* @param initialState The name of initial state of the state machine
*/
initialize(initialState: string): void {
initialize(initialState: string, options: Record<string, any> = {}): void {
this.stack.push(this.stateMap.get(initialState));
this.currentState = this.stack.peek();
this.currentState.onEnter(options);
this.setActive(true);
}
@ -88,7 +89,7 @@ export default class StateMachine implements Updateable {
*/
changeState(state: string): void {
// Exit the current state
this.currentState.onExit();
let options = this.currentState.onExit();
// Make sure the correct state is at the top of the stack
if(state === "previous"){
@ -109,7 +110,7 @@ export default class StateMachine implements Updateable {
}
// Enter the new state
this.currentState.onEnter();
this.currentState.onEnter(options);
}
/**
@ -124,6 +125,12 @@ export default class StateMachine implements Updateable {
// @implemented
update(deltaT: number): void {
// Distribute events
while(this.receiver.hasNextEvent()){
let event = this.receiver.getNextEvent();
this.handleEvent(event);
}
// Delegate the update to the current state
this.currentState.update(deltaT);
}

View File

@ -110,6 +110,10 @@ export default class Vec2 {
* @returns A new vector that is the unit vector for this one
*/
normalized(): Vec2 {
if(this.isZero()){
return this;
}
let mag = this.mag();
return new Vec2(this.x/mag, this.y/mag);
}
@ -235,6 +239,23 @@ export default class Vec2 {
return this;
}
/**
* Increments the fields of this vector. Both are incremented with a, if only a is provided.
* @param a The first number to increment by
* @param b The second number to increment by
* @returnss This vector after incrementing
*/
inc(a: number, b?: number): Vec2 {
if(b === undefined){
this.x += a;
this.y += a;
} else {
this.x += a;
this.y += b;
}
return this;
}
/**
* Subtracts another vector from this vector
* @param other The Vec2 to subtract from this one

View File

@ -260,7 +260,7 @@ export default class Input {
* @returns The mouse position stored as a Vec2
*/
static getMousePosition(): Vec2 {
return Input.mousePosition;
return Input.mousePosition.scaled(1/this.viewport.getZoomLevel());
}
/**
@ -269,7 +269,7 @@ export default class Input {
* @returns The mouse position stored as a Vec2
*/
static getGlobalMousePosition(): Vec2 {
return Input.mousePosition.clone().add(Input.viewport.getOrigin());
return Input.mousePosition.clone().scale(1/this.viewport.getZoomLevel()).add(Input.viewport.getOrigin());
}
/**

View File

@ -16,7 +16,7 @@ import GameLoop from "./GameLoop";
import FixedUpdateGameLoop from "./FixedUpdateGameLoop";
import EnvironmentInitializer from "./EnvironmentInitializer";
import Vec2 from "../DataTypes/Vec2";
import Registry from "../Registry/Registry";
import RegistryManager from "../Registry/RegistryManager";
import WebGLRenderer from "../Rendering/WebGLRenderer";
import Scene from "../Scene/Scene";
@ -139,7 +139,7 @@ export default class Game {
this.loop.doRender = () => this.render();
// Preload registry items
Registry.preload();
RegistryManager.preload();
// Load the items with the resource manager
this.resourceManager.loadResourcesFromQueue(() => {
@ -185,6 +185,11 @@ export default class Game {
this.sceneManager.render();
// Hacky debug mode
if(Input.isKeyJustPressed("g")){
this.showDebug = !this.showDebug;
}
// Debug render
if(this.showDebug){
Debug.render();

View File

@ -109,7 +109,6 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
inRelativeCoordinates(point: Vec2): Vec2 {
let origin = this.scene.getViewTranslation(this);
let zoom = this.scene.getViewScale();
return point.clone().sub(origin).scale(zoom);
}
@ -137,6 +136,14 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
this._velocity = velocity;
};
moveOnPath(speed: number, path: NavigationPath): void {
this.path = path;
let dir = path.getMoveDirection(this);
this.moving = true;
this.pathfinding = true;
this._velocity = dir.scale(speed);
}
// @implemented
/**
* @param velocity The velocity with which the object will move.
@ -146,6 +153,8 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
this.position.add(this._velocity);
if(this.pathfinding){
this.path.handlePathProgress(this);
this.path = null;
this.pathfinding = false;
}
}
@ -266,7 +275,9 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
// @implemented
setAIActive(active: boolean, options: Record<string, any>): void {
this.aiActive = active;
this.ai.activate(options);
if(this.aiActive){
this.ai.activate(options);
}
}
/*---------- TWEENABLE PROPERTIES ----------*/

View File

@ -1,4 +1,5 @@
export enum GraphicType {
POINT = "POINT",
RECT = "RECT",
LINE = "LINE",
}

View File

@ -0,0 +1,33 @@
import Vec2 from "../../DataTypes/Vec2";
import Graphic from "../Graphic";
export default class Line extends Graphic {
protected _end: Vec2;
thickness: number;
constructor(start: Vec2, end: Vec2){
super();
this.start = start;
this.end = end;
this.thickness = 2;
// Does this really have a meaning for lines?
this.size.set(5, 5);
}
set start(pos: Vec2){
this.position = pos;
}
get start(): Vec2 {
return this.position;
}
set end(pos: Vec2){
this._end = pos;
}
get end(): Vec2 {
return this._end;
}
}

View File

@ -31,7 +31,7 @@ export default abstract class Tilemap extends CanvasNode {
let tilecount = 0;
for(let tileset of tilesets){
tilecount += tileset.getTileCount();
tilecount += tileset.getTileCount() + 1;
}
this.collisionMap = new Array(tilecount);

View File

@ -19,9 +19,8 @@ export default class NavigationPath {
*/
constructor(path: Stack<Vec2>){
this.path = path;
console.log(path.toString())
this.currentMoveDirection = Vec2.ZERO;
this.distanceThreshold = 20;
this.distanceThreshold = 4;
}
/**
@ -52,4 +51,8 @@ export default class NavigationPath {
this.path.pop();
}
}
toString(): string {
return this.path.toString()
}
}

View File

@ -0,0 +1,21 @@
import Map from "../../DataTypes/Map";
export default abstract class Registry<T> extends Map<T>{
/** Preloads registry data */
public abstract preload(): void;
/**
* Registers an item and preloads any necessary files
* @param key The key to register this item with
* @param args Any additional arguments needed for registration
*/
public abstract registerAndPreloadItem(key: string, ...args: any): void;
/**
* Registers an item and preloads any necessary files
* @param key The key to register this item with
* @param args Any aditional arguments needed for registration
*/
public abstract registerItem(key: string, ...args: any): void;
}

View File

@ -5,11 +5,12 @@ import PointShaderType from "../../Rendering/WebGLRendering/ShaderTypes/PointSha
import RectShaderType from "../../Rendering/WebGLRendering/ShaderTypes/RectShaderType";
import SpriteShaderType from "../../Rendering/WebGLRendering/ShaderTypes/SpriteShaderType";
import ResourceManager from "../../ResourceManager/ResourceManager";
import Registry from "./Registry";
/**
* A registry that handles shaders
*/
export default class ShaderRegistry extends Map<ShaderType> {
export default class ShaderRegistry extends Registry<ShaderType> {
// Shader names
public static POINT_SHADER = "point";

View File

@ -1,16 +0,0 @@
import ShaderRegistry from "./Registries/ShaderRegistry";
/**
* The Registry is the system's way of converting classes and types into string
* representations for use elsewhere in the application.
* It allows classes to be accessed without explicitly using constructors in code,
* and for resources to be loaded at Game creation time.
*/
export default class Registry {
public static shaders = new ShaderRegistry();
static preload(){
this.shaders.preload();
}
}

View File

@ -0,0 +1,31 @@
import Map from "../DataTypes/Map";
import Registry from "./Registries/Registry";
import ShaderRegistry from "./Registries/ShaderRegistry";
/**
* The Registry is the system's way of converting classes and types into string
* representations for use elsewhere in the application.
* It allows classes to be accessed without explicitly using constructors in code,
* and for resources to be loaded at Game creation time.
*/
export default class RegistryManager {
public static shaders = new ShaderRegistry();
/** Additional custom registries to add to the registry manager */
protected static registries: Map<Registry<any>> = new Map();
static preload(){
this.shaders.preload();
this.registries.forEach((key: string) => this.registries.get(key).preload());
}
static addCustomRegistry(name: string, registry: Registry<any>){
this.registries.add(name, registry);
}
static getRegistry(key: string){
return this.registries.get(key);
}
}

View File

@ -20,6 +20,8 @@ import TextInput from "../Nodes/UIElements/TextInput";
import AnimatedSprite from "../Nodes/Sprites/AnimatedSprite";
import Vec2 from "../DataTypes/Vec2";
import Color from "../Utils/Color";
import Line from "../Nodes/Graphics/Line";
import Debug from "../Debug/Debug";
/**
* An implementation of the RenderingManager class using CanvasRenderingContext2D.
@ -112,9 +114,19 @@ export default class CanvasRenderer extends RenderingManager {
}
// Render the uiLayers on top of everything else
uiLayers.forEach(key => {
if(!uiLayers.get(key).isHidden())
uiLayers.get(key).getItems().forEach(node => this.renderNode(<CanvasNode>node))
let sortedUILayers = new Array<UILayer>();
uiLayers.forEach(key => sortedUILayers.push(uiLayers.get(key)));
sortedUILayers = sortedUILayers.sort((ui1, ui2) => ui1.getDepth() - ui2.getDepth());
sortedUILayers.forEach(uiLayer => {
if(!uiLayer.isHidden())
uiLayer.getItems().forEach(node => {
if((<CanvasNode>node).visible){
this.renderNode(<CanvasNode>node)
}
})
});
}
@ -141,7 +153,7 @@ export default class CanvasRenderer extends RenderingManager {
this.ctx.setTransform(xScale, 0, 0, yScale, (node.position.x - this.origin.x)*this.zoom, (node.position.y - this.origin.y)*this.zoom);
this.ctx.rotate(-node.rotation);
let globalAlpha = this.ctx.globalAlpha;
this.ctx.globalAlpha = node.alpha;
this.ctx.globalAlpha = ((<any>node).color ? (<any>node).color.a : 1) * node.alpha;
if(node instanceof AnimatedSprite){
this.renderAnimatedSprite(<AnimatedSprite>node);
@ -205,6 +217,8 @@ export default class CanvasRenderer extends RenderingManager {
protected renderGraphic(graphic: Graphic): void {
if(graphic instanceof Point){
this.graphicRenderer.renderPoint(<Point>graphic, this.zoom);
} else if(graphic instanceof Line){
this.graphicRenderer.renderLine(<Line>graphic, this.origin, this.zoom);
} else if(graphic instanceof Rect){
this.graphicRenderer.renderRect(<Rect>graphic, this.zoom);
}

View File

@ -1,3 +1,5 @@
import Vec2 from "../../DataTypes/Vec2";
import Line from "../../Nodes/Graphics/Line";
import Point from "../../Nodes/Graphics/Point";
import Rect from "../../Nodes/Graphics/Rect";
import ResourceManager from "../../ResourceManager/ResourceManager";
@ -38,6 +40,16 @@ export default class GraphicRenderer {
point.size.x*zoom, point.size.y*zoom);
}
renderLine(line: Line, origin: Vec2, zoom: number): void {
this.ctx.strokeStyle = line.color.toStringRGBA();
this.ctx.lineWidth = line.thickness;
this.ctx.beginPath();
this.ctx.moveTo(0, 0);
this.ctx.lineTo((line.end.x - line.start.x)*zoom, (line.end.y - line.start.y)*zoom);
this.ctx.closePath();
this.ctx.stroke();
}
/**
* Renders a rect
* @param rect The rect to render

View File

@ -13,7 +13,7 @@ import Tilemap from "../Nodes/Tilemap";
import UIElement from "../Nodes/UIElement";
import Label from "../Nodes/UIElements/Label";
import ShaderRegistry from "../Registry/Registries/ShaderRegistry";
import Registry from "../Registry/Registry";
import RegistryManager from "../Registry/RegistryManager";
import ResourceManager from "../ResourceManager/ResourceManager";
import ParallaxLayer from "../Scene/Layers/ParallaxLayer";
import UILayer from "../Scene/Layers/UILayer";
@ -106,13 +106,13 @@ export default class WebGLRenderer extends RenderingManager {
}
protected renderSprite(sprite: Sprite): void {
let shader = Registry.shaders.get(ShaderRegistry.SPRITE_SHADER);
let shader = RegistryManager.shaders.get(ShaderRegistry.SPRITE_SHADER);
let options = this.addOptions(shader.getOptions(sprite), sprite);
shader.render(this.gl, options);
}
protected renderAnimatedSprite(sprite: AnimatedSprite): void {
let shader = Registry.shaders.get(ShaderRegistry.SPRITE_SHADER);
let shader = RegistryManager.shaders.get(ShaderRegistry.SPRITE_SHADER);
let options = this.addOptions(shader.getOptions(sprite), sprite);
shader.render(this.gl, options);
}
@ -120,11 +120,11 @@ export default class WebGLRenderer extends RenderingManager {
protected renderGraphic(graphic: Graphic): void {
if(graphic instanceof Point){
let shader = Registry.shaders.get(ShaderRegistry.POINT_SHADER);
let shader = RegistryManager.shaders.get(ShaderRegistry.POINT_SHADER);
let options = this.addOptions(shader.getOptions(graphic), graphic);
shader.render(this.gl, options);
} else if(graphic instanceof Rect) {
let shader = Registry.shaders.get(ShaderRegistry.RECT_SHADER);
let shader = RegistryManager.shaders.get(ShaderRegistry.RECT_SHADER);
let options = this.addOptions(shader.getOptions(graphic), graphic);
shader.render(this.gl, options);
}
@ -136,7 +136,7 @@ export default class WebGLRenderer extends RenderingManager {
protected renderUIElement(uiElement: UIElement): void {
if(uiElement instanceof Label){
let shader = Registry.shaders.get(ShaderRegistry.LABEL_SHADER);
let shader = RegistryManager.shaders.get(ShaderRegistry.LABEL_SHADER);
let options = this.addOptions(shader.getOptions(uiElement), uiElement);
shader.render(this.gl, options);
@ -158,7 +158,7 @@ export default class WebGLRenderer extends RenderingManager {
}
protected renderCustom(node: CanvasNode): void {
let shader = Registry.shaders.get(node.customShaderKey);
let shader = RegistryManager.shaders.get(node.customShaderKey);
let options = this.addOptions(shader.getOptions(node), node);
shader.render(this.gl, options);
}

View File

@ -69,6 +69,11 @@ export default class ResourceManager {
/** The total number of "types" of things that need to be loaded (i.e. images and tilemaps) */
private loadonly_typesToLoad: number;
private loadonly_jsonLoaded: number;
private loadonly_jsonToLoad: number;
private loadonly_jsonLoadingQueue: Queue<KeyPathPair>;
private jsonObjects: Map<Record<string, any>>;
/* ########## INFORMATION SPECIAL TO WEBGL ########## */
private gl_WebGLActive: boolean;
@ -109,6 +114,11 @@ export default class ResourceManager {
this.loadonly_audioLoadingQueue = new Queue();
this.audioBuffers = new Map();
this.loadonly_jsonLoaded = 0;
this.loadonly_jsonToLoad = 0;
this.loadonly_jsonLoadingQueue = new Queue();
this.jsonObjects = new Map();
this.loadonly_gl_ShaderProgramsLoaded = 0;
this.loadonly_gl_ShaderProgramsToLoad = 0;
this.loadonly_gl_ShaderLoadingQueue = new Queue();
@ -222,6 +232,24 @@ export default class ResourceManager {
return this.tilemaps.get(key);
}
/**
* Loads an object from a json file.
* @param key The key to associate with the loaded object
* @param path The path to the json file to load
*/
public object(key: string, path: string){
this.loadonly_jsonLoadingQueue.enqueue({key: key, path: path});
}
/**
* Retreives a loaded object
* @param key The key of the loaded object
* @returns The object data associated with the key
*/
public getObject(key: string){
return this.jsonObjects.get(key);
}
/**
* Loads all resources currently in the queue
* @param callback The function to cal when the resources are finished loading
@ -240,15 +268,18 @@ export default class ResourceManager {
console.log("Loaded Images");
this.loadAudioFromQueue(() => {
console.log("Loaded Audio");
if(this.gl_WebGLActive){
this.gl_LoadShadersFromQueue(() => {
console.log("Loaded Shaders");
this.loadObjectsFromQueue(() => {
console.log("Loaded Objects");
if(this.gl_WebGLActive){
this.gl_LoadShadersFromQueue(() => {
console.log("Loaded Shaders");
this.finishLoading(callback);
});
} else {
this.finishLoading(callback);
});
} else {
this.finishLoading(callback);
}
}
})
});
});
});
@ -303,6 +334,7 @@ export default class ResourceManager {
// If no items to load, we're finished
if(this.loadonly_tilemapsToLoad === 0){
onFinishLoading();
return;
}
while(this.loadonly_tilemapLoadingQueue.hasItems()){
@ -368,6 +400,7 @@ export default class ResourceManager {
// If no items to load, we're finished
if(this.loadonly_spritesheetsToLoad === 0){
onFinishLoading();
return;
}
while(this.loadonly_spritesheetLoadingQueue.hasItems()){
@ -422,6 +455,7 @@ export default class ResourceManager {
// If no items to load, we're finished
if(this.loadonly_imagesToLoad === 0){
onFinishLoading();
return;
}
while(this.loadonly_imageLoadingQueue.hasItems()){
@ -479,6 +513,7 @@ export default class ResourceManager {
// If no items to load, we're finished
if(this.loadonly_audioToLoad === 0){
onFinishLoading();
return;
}
while(this.loadonly_audioLoadingQueue.hasItems()){
@ -527,6 +562,53 @@ export default class ResourceManager {
}
}
/**
* Loads all objects currently in the object loading queue
* @param onFinishLoading The function to call when there are no more objects to load
*/
private loadObjectsFromQueue(onFinishLoading: Function): void {
this.loadonly_jsonToLoad = this.loadonly_jsonLoadingQueue.getSize();
this.loadonly_jsonLoaded = 0;
// If no items to load, we're finished
if(this.loadonly_jsonToLoad === 0){
onFinishLoading();
return;
}
while(this.loadonly_jsonLoadingQueue.hasItems()){
let obj = this.loadonly_jsonLoadingQueue.dequeue();
this.loadObject(obj.key, obj.path, onFinishLoading);
}
}
/**
* Loads a singular object
* @param key The key of the object to load
* @param path The path to the object to load
* @param callbackIfLast The function to call if this is the last object
*/
public loadObject(key: string, path: string, callbackIfLast: Function): void {
this.loadTextFile(path, (fileText: string) => {
let obj = JSON.parse(fileText);
this.jsonObjects.add(key, obj);
this.finishLoadingObject(callbackIfLast);
});
}
/**
* Finish loading an object. If this is the last object, it calls the callback function
* @param callback The function to call if this is the last object
*/
private finishLoadingObject(callback: Function): void {
this.loadonly_jsonLoaded += 1;
if(this.loadonly_jsonLoaded === this.loadonly_jsonToLoad){
// We're done loading objects
callback();
}
}
/* ########## WEBGL SPECIFIC FUNCTIONS ########## */
public getTexture(key: string): number {
@ -631,6 +713,7 @@ export default class ResourceManager {
// If webGL isn'active or there are no items to load, we're finished
if(!this.gl_WebGLActive || this.loadonly_gl_ShaderProgramsToLoad === 0){
onFinishLoading();
return;
}
while(this.loadonly_gl_ShaderLoadingQueue.hasItems()){

View File

@ -13,6 +13,7 @@ import Slider from "../../Nodes/UIElements/Slider";
import TextInput from "../../Nodes/UIElements/TextInput";
import Rect from "../../Nodes/Graphics/Rect";
import ResourceManager from "../../ResourceManager/ResourceManager";
import Line from "../../Nodes/Graphics/Line";
// @ignorePage
@ -135,10 +136,13 @@ export default class CanvasNodeFactory {
switch(type){
case GraphicType.POINT:
instance = this.buildPoint(options);
break;
break;
case GraphicType.LINE:
instance = this.buildLine(options);
break;
case GraphicType.RECT:
instance = this.buildRect(options);
break;
break;
default:
throw `GraphicType '${type}' does not exist, or is registered incorrectly.`
}
@ -191,6 +195,13 @@ export default class CanvasNodeFactory {
return new Point(options.position);
}
buildLine(options?: Record<string, any>): Point {
this.checkIfPropExists("Line", options, "start", Vec2, "Vec2");
this.checkIfPropExists("Line", options, "end", Vec2, "Vec2");
return new Line(options.start, options.end);
}
buildRect(options?: Record<string, any>): Rect {
this.checkIfPropExists("Rect", options, "position", Vec2, "Vec2");
this.checkIfPropExists("Rect", options, "size", Vec2, "Vec2");

View File

@ -159,6 +159,24 @@ export default class Layer {
node.setLayer(this);
}
/**
* Removes a node from this layer
* @param node The node to remove
* @returns true if the node was removed, false otherwise
*/
removeNode(node: GameNode): boolean {
// Find and remove the node
for(let i = 0; i < this.items.length; i++){
if(this.items[i].id === node.id){
this.items.splice(i, 1);
node.setLayer(null);
return true;
}
}
return false;
}
/**
* Retreives all GameNodes from this layer
* @returns an Array that contains all of the GameNodes in this layer.

View File

@ -23,6 +23,7 @@ import GameNode from "../Nodes/GameNode";
import SceneOptions from "./SceneOptions";
import RenderingManager from "../Rendering/RenderingManager";
import Debug from "../Debug/Debug";
import TimerManager from "../Timing/TimerManager";
/**
* Scenes are the main container in the game engine.
@ -119,6 +120,9 @@ export default class Scene implements Updateable {
this.add = new FactoryManager(this, this.tilemaps);
this.load = ResourceManager.getInstance();
// Get the timer manager and clear any existing timers
TimerManager.getInstance().clearTimers();
}
/** A lifecycle method that gets called immediately after a new scene is created, before anything else. */
@ -142,6 +146,9 @@ export default class Scene implements Updateable {
update(deltaT: number): void {
this.updateScene(deltaT);
// Do time updates
TimerManager.getInstance().update(deltaT);
// Do all AI updates
this.aiManager.update(deltaT);

View File

@ -0,0 +1,80 @@
import Updateable from "../DataTypes/Interfaces/Updateable";
import MathUtils from "../Utils/MathUtils";
import TimerManager from "./TimerManager";
export default class Timer implements Updateable {
protected state: TimerState;
protected onEnd: Function;
protected loop: boolean;
protected totalTime: number;
protected timeLeft: number;
constructor(time: number, onEnd?: Function, loop: boolean = false){
// Register this timer
TimerManager.getInstance().addTimer(this);
this.totalTime = time;
this.timeLeft = 0;
this.onEnd = onEnd;
this.loop = loop;
this.state = TimerState.STOPPED;
}
isStopped(){
return this.state === TimerState.STOPPED;
}
isPaused(){
return this.state === TimerState.PAUSED;
}
start(time?: number){
if(time !== undefined){
this.totalTime = time;
}
this.state = TimerState.ACTIVE;
this.timeLeft = this.totalTime;
}
pause(): void {
this.state = TimerState.PAUSED;
}
update(deltaT: number){
if(this.state === TimerState.ACTIVE){
this.timeLeft -= deltaT*1000;
if(this.timeLeft <= 0){
this.timeLeft = MathUtils.clampLow0(this.timeLeft);
this.end();
}
}
}
protected end(){
// Update the state
this.state = TimerState.STOPPED;
// Call the end function if there is one
if(this.onEnd){
this.onEnd();
}
// Loop if we want to
if(this.loop){
this.state = TimerState.ACTIVE;
this.timeLeft = this.totalTime;
}
}
toString(): string{
return "Timer: " + this.state + " - Time Left: " + this.timeLeft + "ms of " + this.totalTime + "ms";
}
}
export enum TimerState {
ACTIVE = "ACTIVE",
PAUSED = "PAUSED",
STOPPED = "STOPPED"
}

View File

@ -0,0 +1,33 @@
import Updateable from "../DataTypes/Interfaces/Updateable";
import Timer from "./Timer";
export default class TimerManager implements Updateable {
protected timers: Array<Timer>;
constructor(){
this.timers = new Array();
}
protected static instance: TimerManager;
static getInstance(): TimerManager {
if(!this.instance){
this.instance = new TimerManager();
}
return this.instance;
}
addTimer(timer: Timer){
this.timers.push(timer);
}
clearTimers(){
this.timers = new Array();
}
update(deltaT: number): void {
this.timers.forEach(timer => timer.update(deltaT));
}
}

View File

@ -185,4 +185,13 @@ export default class Color {
this.a
]);
}
static fromStringHex(str: string): Color {
let i = 0;
if(str.charAt(0) == "#") i+= 1;
let r = MathUtils.fromHex(str.substring(i, i+2));
let g = MathUtils.fromHex(str.substring(i+2, i+4));
let b = MathUtils.fromHex(str.substring(i+4, i+6));
return new Color(r, g, b);
}
}

View File

@ -49,6 +49,25 @@ export default class MathUtils {
return MathUtils.clamp(x, 0, 1);
}
/**
* Clamps the lower end of the value of x to the range to min
* @param x The value to be clamped
* @param min The minimum allowed value of x
* @returns x, if it is greater than min, otherwise min
*/
static clampLow(x: number, min: number): number {
return x < min ? min : x;
}
/**
* Clamps the lower end of the value of x to zero
* @param x The value to be clamped
* @returns x, if it is greater than 0, otherwise 0
*/
static clampLow0(x: number): number {
return MathUtils.clampLow(x, 0);
}
static clampMagnitude(v: Vec2, m: number): Vec2 {
if(v.magSq() > m*m){
return v.scaleTo(m);
@ -104,6 +123,15 @@ export default class MathUtils {
}
/**
* Returns a number from a hex string
* @param str the string containing the hex number
* @returns the number in decimal represented by the hex string
*/
static fromHex(str: string): number {
return parseInt(str, 16);
}
/**
* Returns the number as a hexadecimal
* @param num The number to convert to hex