transferred game engine code to typescript
This commit is contained in:
		
							parent
							
								
									e5d0678b5d
								
							
						
					
					
						commit
						40a05fdbdb
					
				
							
								
								
									
										3
									
								
								src/DataTypes/Collection.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/DataTypes/Collection.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
export default interface Collection{
 | 
			
		||||
	forEach(func: Function): void;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								src/DataTypes/Map.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/DataTypes/Map.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
import Collection from "./Collection";
 | 
			
		||||
 | 
			
		||||
export default class Map<T> implements Collection{
 | 
			
		||||
	private map: Record<string, T>;
 | 
			
		||||
 | 
			
		||||
	constructor(){
 | 
			
		||||
		this.map = {};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	add(key: string, value: T): void {
 | 
			
		||||
		this.map[key] = value;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	get(key: string): T {
 | 
			
		||||
		return this.map[key];
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	set(key: string, value: T): void {
 | 
			
		||||
		this.add(key, value);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	has(key: string): boolean {
 | 
			
		||||
		return this.map[key] !== undefined;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	keys(): Array<string> {
 | 
			
		||||
		return Object.keys(this.map);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	forEach(func: Function): void {
 | 
			
		||||
		Object.keys(this.map).forEach(key => func(key));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										61
									
								
								src/DataTypes/Queue.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/DataTypes/Queue.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,61 @@
 | 
			
		|||
import Collection from "./Collection";
 | 
			
		||||
 | 
			
		||||
export default class Queue<T> implements Collection{
 | 
			
		||||
	readonly MAX_ELEMENTS: number;
 | 
			
		||||
	private q: Array<T>;
 | 
			
		||||
	private head: number;
 | 
			
		||||
	private tail: number;
 | 
			
		||||
 | 
			
		||||
    constructor(maxElements: number = 100){
 | 
			
		||||
        this.MAX_ELEMENTS = maxElements;
 | 
			
		||||
        this.q = new Array(this.MAX_ELEMENTS);
 | 
			
		||||
        this.head = 0;
 | 
			
		||||
        this.tail = 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    enqueue(item: T): void{
 | 
			
		||||
        if((this.tail + 1) % this.MAX_ELEMENTS === this.head){
 | 
			
		||||
            throw "Queue full - cannot add element"
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.q[this.tail] = item;
 | 
			
		||||
        this.tail = (this.tail + 1) % this.MAX_ELEMENTS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    dequeue(): T {
 | 
			
		||||
        if(this.head === this.tail){
 | 
			
		||||
            throw "Queue empty - cannot remove element"
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let item = this.q[this.head];
 | 
			
		||||
        this.head = (this.head + 1) % this.MAX_ELEMENTS;
 | 
			
		||||
        
 | 
			
		||||
        return item;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    peekNext(): T {
 | 
			
		||||
        if(this.head === this.tail){
 | 
			
		||||
            throw "Queue empty - cannot get element"
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let item = this.q[this.head];
 | 
			
		||||
        
 | 
			
		||||
        return item;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    hasItems(): boolean {
 | 
			
		||||
        return this.head !== this.tail;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    clear(): void {
 | 
			
		||||
        this.head = this.tail;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    forEach(func: Function): void {
 | 
			
		||||
        let i = this.head;
 | 
			
		||||
        while(i !== this.tail){
 | 
			
		||||
            func(this.q[i]);
 | 
			
		||||
            i = (i + 1) % this.MAX_ELEMENTS;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										68
									
								
								src/DataTypes/Stack.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								src/DataTypes/Stack.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,68 @@
 | 
			
		|||
import Collection from "./Collection";
 | 
			
		||||
 | 
			
		||||
export default class Stack<T> implements Collection{
 | 
			
		||||
	readonly MAX_ELEMENTS: number;
 | 
			
		||||
	private stack: Array<T>;
 | 
			
		||||
	private head: number;
 | 
			
		||||
 | 
			
		||||
    constructor(maxElements: number = 100){
 | 
			
		||||
        this.MAX_ELEMENTS = maxElements;
 | 
			
		||||
        this.stack = new Array<T>(this.MAX_ELEMENTS);
 | 
			
		||||
        this.head = -1;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds an item to the top of the stack
 | 
			
		||||
     * @param {*} item The new item to add to the stack
 | 
			
		||||
     */
 | 
			
		||||
    push(item: T): void {
 | 
			
		||||
        if(this.head + 1 === this.MAX_ELEMENTS){
 | 
			
		||||
            throw "Stack full - cannot add element";
 | 
			
		||||
        }
 | 
			
		||||
        this.head += 1;
 | 
			
		||||
        this.stack[this.head] = item;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Removes an item from the top of the stack
 | 
			
		||||
     */
 | 
			
		||||
    pop(): T{
 | 
			
		||||
        if(this.head === -1){
 | 
			
		||||
            throw "Stack empty - cannot remove element";
 | 
			
		||||
        }
 | 
			
		||||
        this.head -= 1;
 | 
			
		||||
        return this.stack[this.head + 1];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Removes all elements from the stack
 | 
			
		||||
     */
 | 
			
		||||
    clear(): void{
 | 
			
		||||
        this.head = -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the element currently at the top of the stack
 | 
			
		||||
     */
 | 
			
		||||
    peek(): T {
 | 
			
		||||
        if(this.head === -1){
 | 
			
		||||
            throw "Stack empty - cannot get element";
 | 
			
		||||
        }
 | 
			
		||||
        return this.stack[this.head];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the number of items currently in the stack
 | 
			
		||||
     */
 | 
			
		||||
    size(): number {
 | 
			
		||||
        return this.head + 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    forEach(func: Function): void{
 | 
			
		||||
        let i = 0;
 | 
			
		||||
        while(i <= this.head){
 | 
			
		||||
            func(this.stack[i]);
 | 
			
		||||
            i += 1;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										79
									
								
								src/DataTypes/Vec2.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/DataTypes/Vec2.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,79 @@
 | 
			
		|||
export default class Vec2{
 | 
			
		||||
 | 
			
		||||
	public x : number;
 | 
			
		||||
	public y : number;
 | 
			
		||||
 | 
			
		||||
	constructor(x : number = 0, y : number = 0){
 | 
			
		||||
		this.x = x;
 | 
			
		||||
		this.y = y;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	magSq() : number{
 | 
			
		||||
		return this.x*this.x + this.y*this.y;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mag() : number {
 | 
			
		||||
		return Math.sqrt(this.magSq());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	normalize() : Vec2{
 | 
			
		||||
		if(this.x === 0 && this.y === 0) return this;
 | 
			
		||||
		let mag = this.mag();
 | 
			
		||||
		this.x /= mag;
 | 
			
		||||
		this.y /= mag;
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	setToAngle(angle : number) : Vec2{
 | 
			
		||||
		this.x = Math.cos(angle);
 | 
			
		||||
		this.y = Math.sin(angle);
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	scaleTo(magnitude : number) : Vec2{
 | 
			
		||||
		return this.normalize().scale(magnitude);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	scale(factor : number, yFactor : number = null) : Vec2{
 | 
			
		||||
		if(yFactor !== null){
 | 
			
		||||
			this.x *= factor;
 | 
			
		||||
			this.y *= yFactor;
 | 
			
		||||
			return this;
 | 
			
		||||
		}
 | 
			
		||||
		this.x *= factor;
 | 
			
		||||
		this.y *= factor;
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rotate(angle : number) : Vec2{
 | 
			
		||||
		let cs = Math.cos(angle);
 | 
			
		||||
		let sn = Math.sin(angle);
 | 
			
		||||
		let tempX = this.x*cs - this.y*sn;
 | 
			
		||||
		let tempY = this.x*sn + this.y*cs;
 | 
			
		||||
		this.x = tempX;
 | 
			
		||||
		this.y = tempY;
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	set(x : number, y : number) : Vec2{
 | 
			
		||||
		this.x = x;
 | 
			
		||||
		this.y = y;
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	add(other : Vec2) : Vec2{
 | 
			
		||||
		this.x += other.x;
 | 
			
		||||
		this.y += other.y;
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sub(other : Vec2) : Vec2{
 | 
			
		||||
		this.x -= other.x;
 | 
			
		||||
		this.y -= other.y;
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	toString() : string{
 | 
			
		||||
		return "(" + this.x + ", " + this.y + ")";
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								src/DataTypes/Vec4.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/DataTypes/Vec4.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,20 @@
 | 
			
		|||
import Vec2 from "./Vec2";
 | 
			
		||||
 | 
			
		||||
export default class Vec4{
 | 
			
		||||
 | 
			
		||||
	public x : number;
 | 
			
		||||
	public y : number;
 | 
			
		||||
	public z : number;
 | 
			
		||||
	public w : number;
 | 
			
		||||
 | 
			
		||||
    constructor(x : number = 0, y : number = 0, z : number = 0, w : number = 0){
 | 
			
		||||
        this.x = x;
 | 
			
		||||
        this.y = y;
 | 
			
		||||
        this.z = z;
 | 
			
		||||
        this.w = w;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    split() : [Vec2, Vec2]{
 | 
			
		||||
        return [new Vec2(this.x, this.y), new Vec2(this.z, this.w)];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										66
									
								
								src/Events/EventQueue.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/Events/EventQueue.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,66 @@
 | 
			
		|||
import Queue from "../DataTypes/Queue";
 | 
			
		||||
import Map from "../DataTypes/Map";
 | 
			
		||||
import GameEvent from "./GameEvent";
 | 
			
		||||
import Receiver from "./Receiver";
 | 
			
		||||
 | 
			
		||||
export default class EventQueue {
 | 
			
		||||
	private static instance: EventQueue = null;
 | 
			
		||||
	private readonly MAX_SIZE: number;
 | 
			
		||||
	private q: Queue<GameEvent>;
 | 
			
		||||
	private receivers: Map<Array<Receiver>>
 | 
			
		||||
 | 
			
		||||
    private constructor(){
 | 
			
		||||
        this.MAX_SIZE = 100;
 | 
			
		||||
        this.q = new Queue<GameEvent>(this.MAX_SIZE);
 | 
			
		||||
        this.receivers = new Map<Array<Receiver>>();
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	static getInstance(): EventQueue {
 | 
			
		||||
		if(this.instance === null){
 | 
			
		||||
			this.instance = new EventQueue();
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		return this.instance;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    addEvent(event: GameEvent): void {
 | 
			
		||||
        this.q.enqueue(event);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    subscribe(receiver: Receiver, type: string | Array<string>): void {
 | 
			
		||||
        if(type instanceof Array){
 | 
			
		||||
            // If it is an array, subscribe to all event types
 | 
			
		||||
            for(let t of type){
 | 
			
		||||
                this.addListener(receiver, t);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            this.addListener(receiver, type);
 | 
			
		||||
        }
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private addListener(receiver: Receiver, type: string): void {
 | 
			
		||||
		if(this.receivers.has(type)){
 | 
			
		||||
			this.receivers.get(type).push(receiver);
 | 
			
		||||
		} else {
 | 
			
		||||
			this.receivers.add(type, [receiver]);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
    update(deltaT: number): void{
 | 
			
		||||
        while(this.q.hasItems()){
 | 
			
		||||
			let event = this.q.dequeue();
 | 
			
		||||
			
 | 
			
		||||
            if(this.receivers.has(event.type)){
 | 
			
		||||
                for(let receiver of this.receivers.get(event.type)){
 | 
			
		||||
                    receiver.receive(event);
 | 
			
		||||
                }
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
            if(this.receivers.has("all")){
 | 
			
		||||
                for(let receiver of this.receivers.get("all")){
 | 
			
		||||
                    receiver.receive(event);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										28
									
								
								src/Events/GameEvent.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/Events/GameEvent.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,28 @@
 | 
			
		|||
import Map from "../DataTypes/Map"
 | 
			
		||||
 | 
			
		||||
export default class GameEvent{
 | 
			
		||||
	public type: string;
 | 
			
		||||
	public data: Map<any>;
 | 
			
		||||
	public time: number;
 | 
			
		||||
 | 
			
		||||
    constructor(type: string, data: Map<any> | Record<string, any> = null){
 | 
			
		||||
        if (data === null) {
 | 
			
		||||
            this.data = new Map<any>();
 | 
			
		||||
        } else if (!(data instanceof Map)){
 | 
			
		||||
            // data is a raw object, unpack
 | 
			
		||||
            this.data = new Map<any>();
 | 
			
		||||
            for(let key in data){
 | 
			
		||||
                this.data.add(key, data[key]);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            this.data = data;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.type = type;
 | 
			
		||||
        this.time = Date.now();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    toString(): string {
 | 
			
		||||
        return this.type + ": @" + this.time;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								src/Events/Receiver.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/Events/Receiver.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,32 @@
 | 
			
		|||
import Queue from "../DataTypes/Queue";
 | 
			
		||||
import GameEvent from "./GameEvent";
 | 
			
		||||
 | 
			
		||||
export default class Receiver{
 | 
			
		||||
	readonly MAX_SIZE: number;
 | 
			
		||||
	private q: Queue<GameEvent>;
 | 
			
		||||
 | 
			
		||||
	constructor(){
 | 
			
		||||
		this.MAX_SIZE = 100;
 | 
			
		||||
        this.q = new Queue(this.MAX_SIZE);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	receive(event: GameEvent): void {
 | 
			
		||||
		this.q.enqueue(event);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	getNextEvent(): GameEvent {
 | 
			
		||||
		return this.q.dequeue();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	peekNextEvent(): GameEvent {
 | 
			
		||||
		return this.q.peekNext()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hasNextEvent(): boolean {
 | 
			
		||||
		return this.q.hasItems();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ignoreEvents(): void {
 | 
			
		||||
		this.q.clear();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								src/GameState/GameState.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/GameState/GameState.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
import Stack from "../DataTypes/Stack";
 | 
			
		||||
import Scene from "./Scene";
 | 
			
		||||
 | 
			
		||||
export default class GameState{
 | 
			
		||||
	private sceneStack: Stack<Scene>;
 | 
			
		||||
 | 
			
		||||
    constructor(){
 | 
			
		||||
        this.sceneStack = new Stack(10);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    addScene(scene: Scene, pauseScenesBelow: boolean = true): void {
 | 
			
		||||
        this.sceneStack.forEach((scene: Scene) => scene.setPaused(pauseScenesBelow));
 | 
			
		||||
        this.sceneStack.push(scene);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    removeScene(startNewTopScene: boolean = true): void {
 | 
			
		||||
        this.sceneStack.pop();
 | 
			
		||||
        this.sceneStack.peek().setPaused(!startNewTopScene);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    changeScene(scene: Scene): void {
 | 
			
		||||
        this.sceneStack.clear();
 | 
			
		||||
        this.sceneStack.push(scene);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    update(deltaT: number): void {
 | 
			
		||||
        this.sceneStack.forEach((scene: Scene) => scene.update(deltaT));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render(ctx: CanvasRenderingContext2D): void {
 | 
			
		||||
        this.sceneStack.forEach((scene: Scene) => scene.render(ctx));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										56
									
								
								src/GameState/Scene.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/GameState/Scene.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,56 @@
 | 
			
		|||
import Vec2 from "../DataTypes/Vec2";
 | 
			
		||||
import Viewport from "../SceneGraph/Viewport";
 | 
			
		||||
import SceneGraph from "../SceneGraph/SceneGraph";
 | 
			
		||||
import SceneGraphArray from "../SceneGraph/SceneGraphArray";
 | 
			
		||||
import GameNode from "../Nodes/GameNode";
 | 
			
		||||
 | 
			
		||||
export default class Scene{
 | 
			
		||||
	private viewport: Viewport
 | 
			
		||||
	private worldSize: Vec2;
 | 
			
		||||
	private sceneGraph: SceneGraph;
 | 
			
		||||
	private paused: boolean;
 | 
			
		||||
 | 
			
		||||
    constructor(){
 | 
			
		||||
        this.viewport = new Viewport();
 | 
			
		||||
        this.viewport.setSize(800, 500);
 | 
			
		||||
        // TODO: Find a way to make this not a hard-coded value
 | 
			
		||||
        this.worldSize = new Vec2(1600, 1000);
 | 
			
		||||
        this.viewport.setBounds(0, 0, 1600, 1000);
 | 
			
		||||
        this.sceneGraph = new SceneGraphArray(this.viewport);
 | 
			
		||||
        this.paused = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setPaused(pauseValue: boolean): void {
 | 
			
		||||
        this.paused = pauseValue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    isPaused(): boolean {
 | 
			
		||||
        return this.paused;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    getViewport(): Viewport {
 | 
			
		||||
        return this.viewport;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    add(children: Array<GameNode> | GameNode): void {
 | 
			
		||||
        if(children instanceof Array){
 | 
			
		||||
            for(let child of children){
 | 
			
		||||
                this.sceneGraph.addNode(child);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            this.sceneGraph.addNode(children);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    update(deltaT: number): void {
 | 
			
		||||
        if(!this.paused){
 | 
			
		||||
            this.viewport.update(deltaT);
 | 
			
		||||
            this.sceneGraph.update(deltaT);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render(ctx: CanvasRenderingContext2D): void {
 | 
			
		||||
        let visibleSet = this.sceneGraph.getVisibleSet();
 | 
			
		||||
        visibleSet.forEach(node => node.render(ctx, this.viewport.getPosition(), this.viewport.getSize()));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										51
									
								
								src/Input/InputHandler.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/Input/InputHandler.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,51 @@
 | 
			
		|||
import EventQueue from "../Events/EventQueue";
 | 
			
		||||
import Vec2 from "../DataTypes/Vec2";
 | 
			
		||||
import GameEvent from "../Events/GameEvent";
 | 
			
		||||
 | 
			
		||||
export default class InputHandler{
 | 
			
		||||
	private eventQueue: EventQueue;
 | 
			
		||||
	 
 | 
			
		||||
    constructor(canvas: HTMLCanvasElement){
 | 
			
		||||
		this.eventQueue = EventQueue.getInstance();
 | 
			
		||||
		
 | 
			
		||||
        canvas.onmousedown = (event) => this.handleMouseDown(event, canvas);
 | 
			
		||||
        canvas.onmouseup = (event) => this.handleMouseUp(event, canvas);
 | 
			
		||||
        document.onkeydown = this.handleKeyDown;
 | 
			
		||||
        document.onkeyup = this.handleKeyUp;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private handleMouseDown = (event: MouseEvent, canvas: HTMLCanvasElement): void => {
 | 
			
		||||
		let pos = this.getMousePosition(event, canvas);
 | 
			
		||||
        let gameEvent = new GameEvent("mouse_down", {position: pos});
 | 
			
		||||
        this.eventQueue.addEvent(gameEvent);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private handleMouseUp = (event: MouseEvent, canvas: HTMLCanvasElement): void => {
 | 
			
		||||
        let pos = this.getMousePosition(event, canvas);
 | 
			
		||||
        let gameEvent = new GameEvent("mouse_up", {position: pos});
 | 
			
		||||
        this.eventQueue.addEvent(gameEvent);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private handleKeyDown = (event: KeyboardEvent): void => {
 | 
			
		||||
        let key = this.getKey(event);
 | 
			
		||||
        let gameEvent = new GameEvent("key_down", {key: key});
 | 
			
		||||
        this.eventQueue.addEvent(gameEvent);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private handleKeyUp = (event: KeyboardEvent): void => {
 | 
			
		||||
        let key = this.getKey(event);
 | 
			
		||||
        let gameEvent = new GameEvent("key_up", {key: key});
 | 
			
		||||
        this.eventQueue.addEvent(gameEvent);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private getKey(keyEvent: KeyboardEvent){
 | 
			
		||||
        return keyEvent.key.toLowerCase();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private getMousePosition(mouseEvent: MouseEvent, canvas: HTMLCanvasElement): Vec2 {
 | 
			
		||||
        let rect = canvas.getBoundingClientRect();
 | 
			
		||||
        let x = mouseEvent.clientX - rect.left;
 | 
			
		||||
        let y = mouseEvent.clientY - rect.top;
 | 
			
		||||
        return new Vec2(x, y);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										93
									
								
								src/Input/InputReceiver.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								src/Input/InputReceiver.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,93 @@
 | 
			
		|||
import Receiver from "../Events/Receiver";
 | 
			
		||||
import Map from "../DataTypes/Map";
 | 
			
		||||
import Vec2 from "../DataTypes/Vec2";
 | 
			
		||||
import EventQueue from "../Events/EventQueue";
 | 
			
		||||
 | 
			
		||||
export default class InputReceiver{
 | 
			
		||||
	private static instance: InputReceiver = null;
 | 
			
		||||
 | 
			
		||||
	private mousePressed: boolean;
 | 
			
		||||
	private mouseJustPressed: boolean;
 | 
			
		||||
	private keyJustPressed: Map<boolean>;
 | 
			
		||||
	private keyPressed: Map<boolean>;
 | 
			
		||||
	private mousePressPosition: Vec2;
 | 
			
		||||
	private eventQueue: EventQueue;
 | 
			
		||||
	private receiver: Receiver;
 | 
			
		||||
 | 
			
		||||
	private constructor(){
 | 
			
		||||
		this.mousePressed = false;
 | 
			
		||||
		this.mouseJustPressed = false;
 | 
			
		||||
		this.receiver = new Receiver();
 | 
			
		||||
		this.keyJustPressed = new Map<boolean>();
 | 
			
		||||
		this.keyPressed = new Map<boolean>();
 | 
			
		||||
		this.mousePressPosition = null;
 | 
			
		||||
 | 
			
		||||
		this.eventQueue = EventQueue.getInstance();
 | 
			
		||||
		this.eventQueue.subscribe(this.receiver, ["mouse_down", "mouse_up", "key_down", "key_up"]);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static getInstance(): InputReceiver{
 | 
			
		||||
		if(this.instance === null){
 | 
			
		||||
			this.instance = new InputReceiver();
 | 
			
		||||
		}
 | 
			
		||||
		return this.instance;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	update(deltaT: number): void {
 | 
			
		||||
		// Reset the justPressed values to false
 | 
			
		||||
		this.mouseJustPressed = false;
 | 
			
		||||
		this.keyJustPressed.forEach((key: string) => this.keyJustPressed.set(key, false));
 | 
			
		||||
 | 
			
		||||
		while(this.receiver.hasNextEvent()){
 | 
			
		||||
			let event = this.receiver.getNextEvent();
 | 
			
		||||
			if(event.type === "mouse_down"){
 | 
			
		||||
				this.mouseJustPressed = true;
 | 
			
		||||
				this.mousePressed = true;
 | 
			
		||||
				this.mousePressPosition = event.data.get("position");	
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if(event.type === "mouse_up"){
 | 
			
		||||
				this.mousePressed = false;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if(event.type === "key_down"){
 | 
			
		||||
				let key = event.data.get("key")
 | 
			
		||||
				this.keyJustPressed.set(key, true);
 | 
			
		||||
				this.keyPressed.set(key, true);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if(event.type === "key_up"){
 | 
			
		||||
				let key = event.data.get("key")
 | 
			
		||||
				this.keyPressed.set(key, false);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	isJustPressed(key: string): boolean {
 | 
			
		||||
		if(this.keyJustPressed.has(key)){
 | 
			
		||||
			return this.keyJustPressed.get(key)
 | 
			
		||||
		} else {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	isPressed(key: string): boolean {
 | 
			
		||||
		if(this.keyPressed.has(key)){
 | 
			
		||||
			return this.keyPressed.get(key)
 | 
			
		||||
		} else {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	isMouseJustPressed(): boolean {
 | 
			
		||||
		return this.mouseJustPressed;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	isMousePressed(): boolean {
 | 
			
		||||
		return this.mousePressed;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	getMousePressPosition(): Vec2 {
 | 
			
		||||
		return this.mousePressPosition;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										153
									
								
								src/Loop/GameLoop.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								src/Loop/GameLoop.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,153 @@
 | 
			
		|||
import EventQueue from "../Events/EventQueue";
 | 
			
		||||
import InputReceiver from "../Input/InputReceiver";
 | 
			
		||||
import InputHandler from "../Input/InputHandler";
 | 
			
		||||
import Recorder from "../Playback/Recorder";
 | 
			
		||||
import GameState from "../GameState/GameState";
 | 
			
		||||
 | 
			
		||||
export default class GameLoop{
 | 
			
		||||
	// The amount of time to spend on a physics step
 | 
			
		||||
	private maxFPS: number;
 | 
			
		||||
	private simulationTimestep: number;
 | 
			
		||||
 | 
			
		||||
	// The time when the last frame was drawn
 | 
			
		||||
	private lastFrameTime: number;
 | 
			
		||||
 | 
			
		||||
	// The current frame of the game
 | 
			
		||||
	private frame: number;
 | 
			
		||||
 | 
			
		||||
	// Keeping track of the fps
 | 
			
		||||
	private runningFrameSum: number;
 | 
			
		||||
	private numFramesInSum: number;
 | 
			
		||||
	private maxFramesInSum: number;
 | 
			
		||||
	private fps: number;
 | 
			
		||||
 | 
			
		||||
	private started: boolean;
 | 
			
		||||
	private running: boolean;
 | 
			
		||||
	private frameDelta: number;
 | 
			
		||||
 | 
			
		||||
	readonly GAME_CANVAS: HTMLCanvasElement;
 | 
			
		||||
	readonly WIDTH: number;
 | 
			
		||||
	readonly HEIGHT: number;
 | 
			
		||||
	private ctx: CanvasRenderingContext2D;
 | 
			
		||||
	private eventQueue: EventQueue;
 | 
			
		||||
	private inputHandler: InputHandler;
 | 
			
		||||
	private inputReceiver: InputReceiver;
 | 
			
		||||
	private recorder: Recorder;
 | 
			
		||||
	private gameState: GameState;
 | 
			
		||||
 | 
			
		||||
    constructor(){
 | 
			
		||||
        this.maxFPS = 60;
 | 
			
		||||
        this.simulationTimestep = Math.floor(1000/this.maxFPS);
 | 
			
		||||
        this.frame = 0;
 | 
			
		||||
        this.runningFrameSum = 0;
 | 
			
		||||
        this.numFramesInSum = 0;
 | 
			
		||||
        this.maxFramesInSum = 30;
 | 
			
		||||
        this.fps = this.maxFPS;
 | 
			
		||||
 | 
			
		||||
        this.started = false;
 | 
			
		||||
        this.running = false;
 | 
			
		||||
 | 
			
		||||
        this.GAME_CANVAS = document.getElementById("game-canvas") as HTMLCanvasElement;
 | 
			
		||||
        this.GAME_CANVAS.style.setProperty("background-color", "whitesmoke");
 | 
			
		||||
    
 | 
			
		||||
        this.WIDTH = 800;
 | 
			
		||||
        this.HEIGHT = 500;
 | 
			
		||||
        this.ctx = this.initializeCanvas(this.GAME_CANVAS, this.WIDTH, this.HEIGHT);
 | 
			
		||||
 | 
			
		||||
        this.eventQueue = EventQueue.getInstance();
 | 
			
		||||
        this.inputHandler = new InputHandler(this.GAME_CANVAS);
 | 
			
		||||
        this.inputReceiver = InputReceiver.getInstance();
 | 
			
		||||
        this.recorder = new Recorder();
 | 
			
		||||
        this.gameState = new GameState();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private initializeCanvas(canvas: HTMLCanvasElement, width: number, height: number): CanvasRenderingContext2D {
 | 
			
		||||
        canvas.width = width;
 | 
			
		||||
        canvas.height = height;
 | 
			
		||||
        return canvas.getContext("2d");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setMaxFPS(initMax: number): void {
 | 
			
		||||
        this.maxFPS = initMax;
 | 
			
		||||
        this.simulationTimestep = Math.floor(1000/this.maxFPS);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getGameState(): GameState {
 | 
			
		||||
        return this.gameState;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private renderFPS(ctx: CanvasRenderingContext2D): void {
 | 
			
		||||
        ctx.fillStyle = "black";
 | 
			
		||||
		ctx.font = "30px Arial"
 | 
			
		||||
		ctx.fillText(this.fps.toFixed(1), 5, 5 + 30);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private updateFrameCount(timestep: number): void {
 | 
			
		||||
        this.frame += 1;
 | 
			
		||||
        this.numFramesInSum += 1;
 | 
			
		||||
        this.runningFrameSum += timestep;
 | 
			
		||||
        if(this.numFramesInSum >= this.maxFramesInSum){
 | 
			
		||||
            this.fps = 1000 * this.numFramesInSum / this.runningFrameSum;
 | 
			
		||||
            this.numFramesInSum = 0;
 | 
			
		||||
            this.runningFrameSum = 0;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    start(): void {
 | 
			
		||||
        if(!this.started){
 | 
			
		||||
            this.started = true;
 | 
			
		||||
 | 
			
		||||
            window.requestAnimationFrame(this.startFrame);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    startFrame = (timestamp: number): void => {
 | 
			
		||||
        this.running = true;
 | 
			
		||||
 | 
			
		||||
        this.render();
 | 
			
		||||
 | 
			
		||||
        this.lastFrameTime = timestamp;
 | 
			
		||||
 | 
			
		||||
        window.requestAnimationFrame(this.doFrame);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    doFrame = (timestamp: number): void => {
 | 
			
		||||
        // Request animation frame to prepare for another update or render
 | 
			
		||||
        window.requestAnimationFrame(this.doFrame);
 | 
			
		||||
 | 
			
		||||
        // If we are trying to update too soon, return and do nothing
 | 
			
		||||
        if(timestamp < this.lastFrameTime + this.simulationTimestep){
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Currently, update and draw are synced - eventually it would probably be good to desync these
 | 
			
		||||
        this.frameDelta = timestamp - this.lastFrameTime;
 | 
			
		||||
        this.lastFrameTime = timestamp;
 | 
			
		||||
 | 
			
		||||
        // Update while we can (This will present problems if we leave the window)
 | 
			
		||||
        let i = 0;
 | 
			
		||||
        while(this.frameDelta >= this.simulationTimestep){
 | 
			
		||||
            this.update(this.simulationTimestep/1000);
 | 
			
		||||
            this.frameDelta -= this.simulationTimestep;
 | 
			
		||||
 | 
			
		||||
            // Update the frame of the game
 | 
			
		||||
            this.updateFrameCount(this.simulationTimestep);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Updates are done, draw
 | 
			
		||||
        this.render();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    update(deltaT: number): void {
 | 
			
		||||
        this.eventQueue.update(deltaT);
 | 
			
		||||
        this.inputReceiver.update(deltaT);
 | 
			
		||||
        this.recorder.update(deltaT);
 | 
			
		||||
        this.gameState.update(deltaT);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render(): void {
 | 
			
		||||
        this.ctx.clearRect(0, 0, this.WIDTH, this.HEIGHT);
 | 
			
		||||
        this.gameState.render(this.ctx);
 | 
			
		||||
        this.renderFPS(this.ctx);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										26
									
								
								src/Nodes/ColoredCircle.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/Nodes/ColoredCircle.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,26 @@
 | 
			
		|||
import GameNode from "./GameNode";
 | 
			
		||||
import Color from "../Utils/Color";
 | 
			
		||||
import Vec2 from "../DataTypes/Vec2";
 | 
			
		||||
import RandUtils from "../Utils/RandUtils";
 | 
			
		||||
 | 
			
		||||
export default class ColoredCircle extends GameNode{
 | 
			
		||||
	private color: Color;
 | 
			
		||||
 | 
			
		||||
    constructor(){
 | 
			
		||||
        super();
 | 
			
		||||
        this.position = new Vec2(RandUtils.randInt(0, 1000), RandUtils.randInt(0, 1000));
 | 
			
		||||
        this.color = RandUtils.randColor();
 | 
			
		||||
        console.log(this.color.toStringRGB());
 | 
			
		||||
        this.size = new Vec2(50, 50);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    update(deltaT: number): void {}
 | 
			
		||||
 | 
			
		||||
    render(ctx: CanvasRenderingContext2D, viewportOrigin: Vec2, viewportSize: Vec2){
 | 
			
		||||
        ctx.fillStyle = this.color.toStringRGB();
 | 
			
		||||
        ctx.beginPath();
 | 
			
		||||
        ctx.arc(this.position.x + this.size.x/2 - viewportOrigin.x, this.position.y + this.size.y/2 - viewportOrigin.y, this.size.x/2, 0, Math.PI*2, false);
 | 
			
		||||
        ctx.fill();
 | 
			
		||||
        ctx.closePath();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								src/Nodes/GameNode.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/Nodes/GameNode.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
import EventQueue from "../Events/EventQueue";
 | 
			
		||||
import InputReceiver from "../Input/InputReceiver";
 | 
			
		||||
import Vec2 from "../DataTypes/Vec2";
 | 
			
		||||
 | 
			
		||||
export default abstract class GameNode{
 | 
			
		||||
	protected eventQueue: EventQueue;
 | 
			
		||||
	protected input: InputReceiver;
 | 
			
		||||
	protected position: Vec2;
 | 
			
		||||
	protected size: Vec2;
 | 
			
		||||
 | 
			
		||||
	constructor(){
 | 
			
		||||
		this.eventQueue = EventQueue.getInstance();
 | 
			
		||||
		this.input = InputReceiver.getInstance();
 | 
			
		||||
		this.position = new Vec2(0, 0);
 | 
			
		||||
		this.size = new Vec2(0, 0);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	getPosition(): Vec2 {
 | 
			
		||||
		return this.position;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	getSize(): Vec2 {
 | 
			
		||||
		return this.size;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	contains(x: number, y: number): boolean {
 | 
			
		||||
		if(x > this.position.x && x < this.position.x + this.size.x){
 | 
			
		||||
			if(y > this.position.y && y < this.position.y + this.size.y){
 | 
			
		||||
				return true;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	abstract update(deltaT: number): void;
 | 
			
		||||
 | 
			
		||||
	abstract render(ctx: CanvasRenderingContext2D, viewportOrigin: Vec2, viewportSize: Vec2): void;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								src/Nodes/Player.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/Nodes/Player.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,32 @@
 | 
			
		|||
import GameNode from "./GameNode";
 | 
			
		||||
import Vec2 from "../DataTypes/Vec2";
 | 
			
		||||
 | 
			
		||||
export default class Player extends GameNode{
 | 
			
		||||
	velocity: Vec2;
 | 
			
		||||
	speed: number;
 | 
			
		||||
 | 
			
		||||
	constructor(){
 | 
			
		||||
		super();
 | 
			
		||||
		this.velocity = new Vec2(0, 0);
 | 
			
		||||
		this.speed = 300;
 | 
			
		||||
		this.size = new Vec2(50, 50);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	update(deltaT: number): void {
 | 
			
		||||
		let dir = new Vec2(0, 0);
 | 
			
		||||
		dir.x += this.input.isPressed('a') ? -1 : 0;
 | 
			
		||||
		dir.x += this.input.isPressed('d') ? 1 : 0;
 | 
			
		||||
		dir.y += this.input.isPressed('s') ? 1 : 0;
 | 
			
		||||
		dir.y += this.input.isPressed('w') ? -1 : 0;
 | 
			
		||||
 | 
			
		||||
		dir.normalize();
 | 
			
		||||
 | 
			
		||||
		this.velocity = dir.scale(this.speed);
 | 
			
		||||
		this.position = this.position.add(this.velocity.scale(deltaT));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	render(ctx: CanvasRenderingContext2D, viewportOrigin: Vec2, viewportSize: Vec2){
 | 
			
		||||
		ctx.fillStyle = "#FF0000";
 | 
			
		||||
		ctx.fillRect(this.position.x - viewportOrigin.x, this.position.y - viewportOrigin.y, this.size.x, this.size.y);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										89
									
								
								src/Nodes/UIElement.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/Nodes/UIElement.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,89 @@
 | 
			
		|||
import GameNode from "./GameNode";
 | 
			
		||||
import Color from "../Utils/Color";
 | 
			
		||||
import Vec2 from "../DataTypes/Vec2";
 | 
			
		||||
import GameEvent from "../Events/GameEvent";
 | 
			
		||||
 | 
			
		||||
export default class UIElement extends GameNode{
 | 
			
		||||
	parent: GameNode;
 | 
			
		||||
	children: Array<GameNode>;
 | 
			
		||||
	text: string;
 | 
			
		||||
	backgroundColor: Color;
 | 
			
		||||
	textColor: Color;
 | 
			
		||||
	onPress: Function;
 | 
			
		||||
	onPressSignal: string;
 | 
			
		||||
	onHover: Function;
 | 
			
		||||
 | 
			
		||||
	constructor(){
 | 
			
		||||
		super();
 | 
			
		||||
		
 | 
			
		||||
		this.parent = null;
 | 
			
		||||
		this.children = [];
 | 
			
		||||
		this.text = "";
 | 
			
		||||
		this.backgroundColor = new Color(0, 0, 0, 0);
 | 
			
		||||
		this.textColor = new Color(0, 0, 0, 1);
 | 
			
		||||
 | 
			
		||||
		this.onPress = null;
 | 
			
		||||
		this.onPressSignal = null;
 | 
			
		||||
 | 
			
		||||
		this.onHover = null;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	setPosition(vecOrX: Vec2 | number, y: number = null): void {
 | 
			
		||||
		if(vecOrX instanceof Vec2){
 | 
			
		||||
			this.position.set(vecOrX.x, vecOrX.y);
 | 
			
		||||
		} else {
 | 
			
		||||
			this.position.set(vecOrX, y);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	setSize(vecOrX: Vec2 | number, y: number = null): void {
 | 
			
		||||
		if(vecOrX instanceof Vec2){
 | 
			
		||||
			this.size.set(vecOrX.x, vecOrX.y);
 | 
			
		||||
		} else {
 | 
			
		||||
			this.size.set(vecOrX, y);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	setText(text: string): void {
 | 
			
		||||
		this.text = text;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	setBackgroundColor(color: Color): void {
 | 
			
		||||
		this.backgroundColor = color;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	setTextColor(color: Color): void {
 | 
			
		||||
		this.textColor = color;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	update(deltaT: number): void {
 | 
			
		||||
		if(this.input.isMouseJustPressed()){
 | 
			
		||||
			let mousePos =  this.input.getMousePressPosition();
 | 
			
		||||
			if(mousePos.x >= this.position.x && mousePos.x <= this.position.x + this.size.x){
 | 
			
		||||
				// Inside x bounds
 | 
			
		||||
				if(mousePos.y >= this.position.y && mousePos.y <= this.position.y + this.size.y){
 | 
			
		||||
					// Inside element
 | 
			
		||||
					if(this.onHover !== null){
 | 
			
		||||
						this.onHover();
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					if(this.onPress !== null){
 | 
			
		||||
						this.onPress();
 | 
			
		||||
					}
 | 
			
		||||
					if(this.onPressSignal !== null){
 | 
			
		||||
						let event = new GameEvent(this.onPressSignal, {});
 | 
			
		||||
						this.eventQueue.addEvent(event);
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	render(ctx: CanvasRenderingContext2D, viewportOrigin: Vec2, viewportSize: Vec2){
 | 
			
		||||
		ctx.fillStyle = this.backgroundColor.toStringRGBA();
 | 
			
		||||
		ctx.fillRect(this.position.x - viewportOrigin.x, this.position.y - viewportOrigin.y, this.size.x, this.size.y);
 | 
			
		||||
		ctx.fillStyle = this.textColor.toStringRGBA();
 | 
			
		||||
		ctx.font = "30px Arial"
 | 
			
		||||
		ctx.fillText(this.text, this.position.x - viewportOrigin.x, this.position.y - viewportOrigin.y + 30);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										90
									
								
								src/Playback/Recorder.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/Playback/Recorder.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,90 @@
 | 
			
		|||
import Queue from "../DataTypes/Queue";
 | 
			
		||||
import Receiver from "../Events/Receiver";
 | 
			
		||||
import GameEvent from "../Events/GameEvent";
 | 
			
		||||
import EventQueue from "../Events/EventQueue";
 | 
			
		||||
 | 
			
		||||
export default class Recorder{
 | 
			
		||||
	private receiver: Receiver;
 | 
			
		||||
	private log: Queue<LogItem>;
 | 
			
		||||
	private recording: boolean;
 | 
			
		||||
	private eventQueue: EventQueue;
 | 
			
		||||
	private frame: number;
 | 
			
		||||
	private playing: boolean;
 | 
			
		||||
 | 
			
		||||
	constructor(){
 | 
			
		||||
		this.receiver = new Receiver();
 | 
			
		||||
		this.log = new Queue(1000);
 | 
			
		||||
		this.recording = false;
 | 
			
		||||
		this.playing = false;
 | 
			
		||||
		this.frame = 0;
 | 
			
		||||
 | 
			
		||||
		this.eventQueue = EventQueue.getInstance();
 | 
			
		||||
		this.eventQueue.subscribe(this.receiver, "all");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	update(deltaT: number): void {
 | 
			
		||||
		if(this.recording){
 | 
			
		||||
			this.frame += 1;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if(this.playing){
 | 
			
		||||
			// If playing, ignore events, just feed the record to the event queue
 | 
			
		||||
			this.receiver.ignoreEvents();
 | 
			
		||||
 | 
			
		||||
			/*
 | 
			
		||||
				While there is a next item, and while it should occur in this frame,
 | 
			
		||||
				send the event. i.e., while current_frame * current_delta_t is greater
 | 
			
		||||
				than recorded_frame * recorded_delta_t
 | 
			
		||||
			*/
 | 
			
		||||
			while(this.log.hasItems()
 | 
			
		||||
					&& this.log.peekNext().frame * this.log.peekNext().delta < this.frame * deltaT){
 | 
			
		||||
				let event = this.log.dequeue().event;
 | 
			
		||||
				console.log(event);
 | 
			
		||||
				this.eventQueue.addEvent(event);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if(!this.log.hasItems()){
 | 
			
		||||
				this.playing = false;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			this.frame += 1;
 | 
			
		||||
		} else {
 | 
			
		||||
			// If not playing, handle events
 | 
			
		||||
			while(this.receiver.hasNextEvent()){
 | 
			
		||||
				let event = this.receiver.getNextEvent();
 | 
			
		||||
 | 
			
		||||
				if(event.type === "stop_button_press"){
 | 
			
		||||
					this.recording = false;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if(this.recording){
 | 
			
		||||
					this.log.enqueue(new LogItem(this.frame, deltaT, event));
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if(event.type === "record_button_press"){
 | 
			
		||||
					this.log.clear();
 | 
			
		||||
					this.recording = true;
 | 
			
		||||
					this.frame = 0
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if(event.type === "play_button_press"){
 | 
			
		||||
					this.frame = 0;
 | 
			
		||||
					this.recording = false;
 | 
			
		||||
					this.playing = true;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class LogItem {
 | 
			
		||||
	frame: number;
 | 
			
		||||
	delta: number;
 | 
			
		||||
	event: GameEvent;
 | 
			
		||||
 | 
			
		||||
	constructor(frame: number, deltaT: number, event: GameEvent){
 | 
			
		||||
		this.frame = frame;
 | 
			
		||||
		this.delta = deltaT;
 | 
			
		||||
		this.event = event;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										55
									
								
								src/SceneGraph/SceneGraph.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/SceneGraph/SceneGraph.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,55 @@
 | 
			
		|||
import Viewport from "./Viewport";
 | 
			
		||||
import GameNode from "../Nodes/GameNode";
 | 
			
		||||
import Map from "../DataTypes/Map";
 | 
			
		||||
import Vec2 from "../DataTypes/Vec2";
 | 
			
		||||
 | 
			
		||||
export default abstract class SceneGraph{
 | 
			
		||||
	protected viewport: Viewport;
 | 
			
		||||
	protected nodeMap: Map<GameNode>;
 | 
			
		||||
	protected idCounter: number;
 | 
			
		||||
 | 
			
		||||
    constructor(viewport: Viewport){
 | 
			
		||||
		this.viewport = viewport;
 | 
			
		||||
		this.nodeMap = new Map<GameNode>();
 | 
			
		||||
		this.idCounter = 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    addNode(node: GameNode): number {
 | 
			
		||||
		this.nodeMap.add(this.idCounter.toString(), node);
 | 
			
		||||
		this.addNodeSpecific(node, this.idCounter.toString());
 | 
			
		||||
		this.idCounter += 1;
 | 
			
		||||
		return this.idCounter - 1;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	protected abstract addNodeSpecific(node: GameNode, id: string): void;
 | 
			
		||||
 | 
			
		||||
    removeNode(node: GameNode): void {
 | 
			
		||||
		// Find and remove node in O(n)
 | 
			
		||||
		// TODO: Can this be better?
 | 
			
		||||
		let id = this.nodeMap.keys().filter((key: string) => this.nodeMap.get(key) === node)[0];
 | 
			
		||||
		if(id !== undefined){
 | 
			
		||||
			this.nodeMap.set(id, undefined);
 | 
			
		||||
			this.removeNodeSpecific(node, id);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	protected abstract removeNodeSpecific(node: GameNode, id: string): void;
 | 
			
		||||
 | 
			
		||||
	getNode(id: string): GameNode{
 | 
			
		||||
		return this.nodeMap.get(id);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
    getNodeAt(vecOrX: Vec2 | number, y: number = null): GameNode{
 | 
			
		||||
		if(vecOrX instanceof Vec2){
 | 
			
		||||
			return this.getNodeAtCoords(vecOrX.x, vecOrX.y);
 | 
			
		||||
		} else {
 | 
			
		||||
			return this.getNodeAtCoords(vecOrX, y);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
    
 | 
			
		||||
    protected abstract getNodeAtCoords(x: number, y: number): GameNode;
 | 
			
		||||
    
 | 
			
		||||
    abstract update(deltaT: number): void;
 | 
			
		||||
 | 
			
		||||
    abstract getVisibleSet(): Array<GameNode>;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										67
									
								
								src/SceneGraph/SceneGraphArray.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/SceneGraph/SceneGraphArray.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,67 @@
 | 
			
		|||
import SceneGraph from "./SceneGraph";
 | 
			
		||||
import GameNode from "../Nodes/GameNode";
 | 
			
		||||
import Viewport from "./Viewport";
 | 
			
		||||
 | 
			
		||||
export default class SceneGraphArray extends SceneGraph{
 | 
			
		||||
	private nodeList: Array<GameNode>;
 | 
			
		||||
	private turnOffViewportCulling_demoTool: boolean;
 | 
			
		||||
 | 
			
		||||
    constructor(viewport: Viewport){
 | 
			
		||||
        super(viewport);
 | 
			
		||||
 | 
			
		||||
        this.nodeList = new Array<GameNode>();
 | 
			
		||||
        this.turnOffViewportCulling_demoTool = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setViewportCulling_demoTool(bool: boolean): void {
 | 
			
		||||
        this.turnOffViewportCulling_demoTool = bool;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    addNodeSpecific(node: GameNode, id: string): void {
 | 
			
		||||
        this.nodeList.push(node);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    removeNodeSpecific(node: GameNode, id: string): void {
 | 
			
		||||
        let index = this.nodeList.indexOf(node);
 | 
			
		||||
        if(index > -1){
 | 
			
		||||
            this.nodeList.splice(index, 1);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getNodeAtCoords(x: number, y: number): GameNode {
 | 
			
		||||
        // TODO: This only returns the first node found. There is no notion of z coordinates
 | 
			
		||||
        for(let node of this.nodeList){
 | 
			
		||||
            if(node.contains(x, y)){
 | 
			
		||||
                return node;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    update(deltaT: number): void {
 | 
			
		||||
        for(let node of this.nodeList){
 | 
			
		||||
            node.update(deltaT);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getVisibleSet(): Array<GameNode> {
 | 
			
		||||
        // If viewport culling is turned off for demonstration
 | 
			
		||||
        if(this.turnOffViewportCulling_demoTool){
 | 
			
		||||
            let visibleSet = new Array<GameNode>();
 | 
			
		||||
            for(let node of this.nodeList){
 | 
			
		||||
                visibleSet.push(node);
 | 
			
		||||
            }
 | 
			
		||||
            return visibleSet;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let visibleSet = new Array<GameNode>();
 | 
			
		||||
 | 
			
		||||
        for(let node of this.nodeList){
 | 
			
		||||
            if(this.viewport.includes(node)){
 | 
			
		||||
                visibleSet.push(node);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return visibleSet;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										73
									
								
								src/SceneGraph/Viewport.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								src/SceneGraph/Viewport.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,73 @@
 | 
			
		|||
import Vec2 from "../DataTypes/Vec2";
 | 
			
		||||
import Vec4 from "../DataTypes/Vec4";
 | 
			
		||||
import GameNode from "../Nodes/GameNode";
 | 
			
		||||
import MathUtils from "../Utils/MathUtils";
 | 
			
		||||
 | 
			
		||||
export default class Viewport{
 | 
			
		||||
	private position: Vec2;
 | 
			
		||||
	private size: Vec2;
 | 
			
		||||
	private bounds: Vec4;
 | 
			
		||||
	private following: GameNode;
 | 
			
		||||
 | 
			
		||||
    constructor(){
 | 
			
		||||
        this.position = new Vec2(0, 0);
 | 
			
		||||
        this.size = new Vec2(0, 0);
 | 
			
		||||
        this.bounds = new Vec4(0, 0, 0, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getPosition(): Vec2 {
 | 
			
		||||
        return this.position;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setPosition(vecOrX: Vec2 | number, y: number = null): void {
 | 
			
		||||
		if(vecOrX instanceof Vec2){
 | 
			
		||||
			this.position.set(vecOrX.x, vecOrX.y);
 | 
			
		||||
		} else {
 | 
			
		||||
			this.position.set(vecOrX, y);
 | 
			
		||||
		}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getSize(): Vec2{
 | 
			
		||||
        return this.size;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    setSize(vecOrX: Vec2 | number, y: number = null): void {
 | 
			
		||||
		if(vecOrX instanceof Vec2){
 | 
			
		||||
			this.size.set(vecOrX.x, vecOrX.y);
 | 
			
		||||
		} else {
 | 
			
		||||
			this.size.set(vecOrX, y);
 | 
			
		||||
		}
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    includes(node: GameNode): boolean {
 | 
			
		||||
        let nodePos = node.getPosition();
 | 
			
		||||
        let nodeSize = node.getSize();
 | 
			
		||||
        if(nodePos.x + nodeSize.x > this.position.x && nodePos.x < this.position.x + this.size.x){
 | 
			
		||||
            if(nodePos.y + nodeSize.y > this.position.y && nodePos.y < this.position.y + this.size.y){
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	// 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
 | 
			
		||||
    setBounds(lowerX: number, lowerY: number, upperX: number, upperY: number): void {
 | 
			
		||||
        this.bounds = new Vec4(lowerX, lowerY, upperX, upperY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    follow(node: GameNode): void {
 | 
			
		||||
        this.following = node;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    update(deltaT: number): void {
 | 
			
		||||
        if(this.following){
 | 
			
		||||
            this.position.x = this.following.getPosition().x - this.size.x/2;
 | 
			
		||||
            this.position.y = this.following.getPosition().y - this.size.y/2;
 | 
			
		||||
            let [min, max] = this.bounds.split();
 | 
			
		||||
            this.position.x = MathUtils.clamp(this.position.x, min.x, max.x - this.size.x/2);
 | 
			
		||||
            this.position.y = MathUtils.clamp(this.position.y, min.y, max.y - this.size.y/2);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								src/Utils/Color.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/Utils/Color.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,30 @@
 | 
			
		|||
import MathUtils from "./MathUtils";
 | 
			
		||||
 | 
			
		||||
export default class Color{
 | 
			
		||||
	public r: number;
 | 
			
		||||
	public g: number;
 | 
			
		||||
	public b: number;
 | 
			
		||||
	public a: number;
 | 
			
		||||
 | 
			
		||||
	constructor(r: number = 0, g: number = 0, b: number = 0, a: number = null){
 | 
			
		||||
        this.r = r;
 | 
			
		||||
        this.g = g;
 | 
			
		||||
        this.b = b;
 | 
			
		||||
        this.a = a;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	toString(): string{
 | 
			
		||||
		return "#" + MathUtils.toHex(this.r, 2) + MathUtils.toHex(this.g, 2) + MathUtils.toHex(this.b, 2);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	toStringRGB(){
 | 
			
		||||
		return "rgb(" + this.r.toString() + ", " + this.g.toString() + ", " + this.b.toString() + ")";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	toStringRGBA(){
 | 
			
		||||
		if(this.a === null){
 | 
			
		||||
			throw "No alpha value assigned to color";
 | 
			
		||||
		}
 | 
			
		||||
		return "rgb(" + this.r.toString() + ", " + this.g.toString() + ", " + this.b.toString() + ", " + this.a.toString() +")"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								src/Utils/MathUtils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/Utils/MathUtils.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,37 @@
 | 
			
		|||
export default class MathUtils{
 | 
			
		||||
    static clamp(x: number, min: number, max: number): number{
 | 
			
		||||
        if(x < min) return min;
 | 
			
		||||
        if(x > max) return max;
 | 
			
		||||
        return x;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static toHex(num: number, minLength: number = null): string{
 | 
			
		||||
        let factor = 1;
 | 
			
		||||
        while(factor*16 < num){
 | 
			
		||||
            factor *= 16;
 | 
			
		||||
        }
 | 
			
		||||
        let hexStr = "";
 | 
			
		||||
        while(num > 0){
 | 
			
		||||
            let digit = Math.floor(num/factor);
 | 
			
		||||
            hexStr += MathUtils.toHexDigit(digit);
 | 
			
		||||
            num -= digit * factor;
 | 
			
		||||
            factor /= 16;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		if(minLength !== null){
 | 
			
		||||
			while(hexStr.length < minLength){
 | 
			
		||||
				hexStr = "0" + hexStr;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
        return hexStr;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static toHexDigit(num: number): string{
 | 
			
		||||
        if(num < 10){
 | 
			
		||||
            return "" + num;
 | 
			
		||||
        } else {
 | 
			
		||||
            return String.fromCharCode(65 + num - 10);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								src/Utils/RandUtils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/Utils/RandUtils.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
import MathUtils from "./MathUtils";
 | 
			
		||||
import Color from "./Color";
 | 
			
		||||
 | 
			
		||||
export default class RandUtils{
 | 
			
		||||
	static randInt(min: number, max: number): number{
 | 
			
		||||
        return Math.floor(Math.random()*(max - min) + min);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    static randHex(min: number, max: number): string{
 | 
			
		||||
        return MathUtils.toHex(RandUtils.randInt(min, max));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	static randColor(): Color{
 | 
			
		||||
        let r = RandUtils.randInt(0, 256);
 | 
			
		||||
        let g = RandUtils.randInt(0, 256);
 | 
			
		||||
        let b = RandUtils.randInt(0, 256);
 | 
			
		||||
        return new Color(r, g, b);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,3 +0,0 @@
 | 
			
		|||
export function sayHello(name: string){
 | 
			
		||||
    return `Hello from ${name}`;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,11 +1,11 @@
 | 
			
		|||
<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
    <head>
 | 
			
		||||
        <meta charset="UTF-8" />
 | 
			
		||||
        <title>Hello World!</title>
 | 
			
		||||
    </head>
 | 
			
		||||
    <body>
 | 
			
		||||
        <p id="greeting">Loading ...</p>
 | 
			
		||||
        <script src="bundle.js"></script>
 | 
			
		||||
    </body>
 | 
			
		||||
  <head>
 | 
			
		||||
    <meta charset="UTF-8" />
 | 
			
		||||
    <title>Hello World!</title>
 | 
			
		||||
  </head>
 | 
			
		||||
  <body>
 | 
			
		||||
    <canvas id="game-canvas"></canvas>
 | 
			
		||||
    <script src="bundle.js"></script>
 | 
			
		||||
  </body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										91
									
								
								src/main.ts
									
									
									
									
									
								
							
							
						
						
									
										91
									
								
								src/main.ts
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -1,8 +1,89 @@
 | 
			
		|||
import { sayHello } from './greet';
 | 
			
		||||
import GameLoop from "./Loop/GameLoop";
 | 
			
		||||
import Scene from "./GameState/Scene";
 | 
			
		||||
import Player from "./Nodes/Player";
 | 
			
		||||
import UIElement from "./Nodes/UIElement";
 | 
			
		||||
import ColoredCircle from "./Nodes/ColoredCircle";
 | 
			
		||||
import Color from "./Utils/Color";
 | 
			
		||||
 | 
			
		||||
function showHello(divName: string, name: string) {
 | 
			
		||||
    const elt = document.getElementById(divName);
 | 
			
		||||
    elt.innerText = sayHello(name);
 | 
			
		||||
function main(){
 | 
			
		||||
    // Create the game object
 | 
			
		||||
    let game = new GameLoop();
 | 
			
		||||
 | 
			
		||||
    let mainScene = new Scene();
 | 
			
		||||
    let pauseMenu = new Scene();
 | 
			
		||||
 | 
			
		||||
    // Initialize GameObjects
 | 
			
		||||
    let player = new Player();
 | 
			
		||||
 | 
			
		||||
    let recordButton = new UIElement();
 | 
			
		||||
    recordButton.setSize(100, 50);
 | 
			
		||||
    recordButton.setText("Record");
 | 
			
		||||
    recordButton.setBackgroundColor(new Color(200, 100, 0, 0.3));
 | 
			
		||||
    recordButton.setPosition(400, 30);
 | 
			
		||||
    recordButton.onPressSignal = "record_button_press";
 | 
			
		||||
 | 
			
		||||
    let stopButton = new UIElement();
 | 
			
		||||
    stopButton.setSize(100, 50);
 | 
			
		||||
    stopButton.setText("Stop");
 | 
			
		||||
    stopButton.setBackgroundColor(new Color(200, 0, 0, 0.3));
 | 
			
		||||
    stopButton.setPosition(550, 30);
 | 
			
		||||
    stopButton.onPressSignal = "stop_button_press";
 | 
			
		||||
 | 
			
		||||
    let playButton = new UIElement();
 | 
			
		||||
    playButton.setSize(100, 50);
 | 
			
		||||
    playButton.setText("Play");
 | 
			
		||||
    playButton.setBackgroundColor(new Color(0, 200, 0, 0.3));
 | 
			
		||||
    playButton.setPosition(700, 30);
 | 
			
		||||
    playButton.onPressSignal = "play_button_press";
 | 
			
		||||
 | 
			
		||||
    let cycleFramerateButton = new UIElement();
 | 
			
		||||
    cycleFramerateButton.setSize(150, 50);
 | 
			
		||||
    cycleFramerateButton.setText("Cycle FPS");
 | 
			
		||||
    cycleFramerateButton.setBackgroundColor(new Color(200, 0, 200, 0.3));
 | 
			
		||||
    cycleFramerateButton.setPosition(5, 400);
 | 
			
		||||
    let i = 0;
 | 
			
		||||
    let fps = [15, 30, 60];
 | 
			
		||||
    cycleFramerateButton.onPress = () => {
 | 
			
		||||
        game.setMaxFPS(fps[i]);
 | 
			
		||||
        i = (i + 1) % 3;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let pauseButton = new UIElement();
 | 
			
		||||
    pauseButton.setSize(100, 50);
 | 
			
		||||
    pauseButton.setText("Pause");
 | 
			
		||||
    pauseButton.setBackgroundColor(new Color(200, 0, 200, 1));
 | 
			
		||||
    pauseButton.setPosition(700, 400);
 | 
			
		||||
    pauseButton.onPress = () => {
 | 
			
		||||
        game.getGameState().addScene(pauseMenu);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let modalBackground = new UIElement();
 | 
			
		||||
    modalBackground.setSize(400, 200);
 | 
			
		||||
    modalBackground.setBackgroundColor(new Color(0, 0, 0, 0.4));
 | 
			
		||||
    modalBackground.setPosition(200, 100);
 | 
			
		||||
 | 
			
		||||
    let resumeButton = new UIElement();
 | 
			
		||||
    resumeButton.setSize(100, 50);
 | 
			
		||||
    resumeButton.setText("Resume");
 | 
			
		||||
    resumeButton.setBackgroundColor(new Color(200, 0, 200, 1));
 | 
			
		||||
    resumeButton.setPosition(400, 200);
 | 
			
		||||
    resumeButton.onPress = () => {
 | 
			
		||||
        game.getGameState().removeScene();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let lotsOfCircs = [];
 | 
			
		||||
    for(let i = 0; i < 10; i++){
 | 
			
		||||
        lotsOfCircs.push(new ColoredCircle());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    mainScene.add([...lotsOfCircs, player, recordButton, stopButton, playButton, cycleFramerateButton, pauseButton]);
 | 
			
		||||
    mainScene.getViewport().follow(player);
 | 
			
		||||
    pauseMenu.add([modalBackground, resumeButton]);
 | 
			
		||||
 | 
			
		||||
    game.getGameState().changeScene(mainScene);
 | 
			
		||||
 | 
			
		||||
    game.start();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
showHello('greeting', 'TypeScript');
 | 
			
		||||
main();
 | 
			
		||||
| 
						 | 
				
			
			@ -1,7 +1,40 @@
 | 
			
		|||
{
 | 
			
		||||
    "files": [
 | 
			
		||||
        "src/main.ts",
 | 
			
		||||
        "src/greet.ts"
 | 
			
		||||
 | 
			
		||||
        "src/DataTypes/Collection.ts",
 | 
			
		||||
        "src/DataTypes/Map.ts",
 | 
			
		||||
        "src/DataTypes/Queue.ts",
 | 
			
		||||
        "src/DataTypes/Stack.ts",
 | 
			
		||||
        "src/DataTypes/Vec2.ts",
 | 
			
		||||
        "src/DataTypes/Vec4.ts",
 | 
			
		||||
 | 
			
		||||
        "src/Events/EventQueue.ts",
 | 
			
		||||
        "src/Events/GameEvent.ts",
 | 
			
		||||
        "src/Events/Receiver.ts",
 | 
			
		||||
 | 
			
		||||
        "src/GameState/GameState.ts",
 | 
			
		||||
        "src/GameState/Scene.ts",
 | 
			
		||||
 | 
			
		||||
        "src/Input/InputHandler.ts",
 | 
			
		||||
        "src/Input/InputReceiver.ts",
 | 
			
		||||
        
 | 
			
		||||
        "src/Loop/GameLoop.ts",
 | 
			
		||||
 | 
			
		||||
        "src/Nodes/ColoredCircle.ts",
 | 
			
		||||
        "src/Nodes/GameNode.ts",
 | 
			
		||||
        "src/Nodes/Player.ts",
 | 
			
		||||
        "src/Nodes/UIElement.ts",
 | 
			
		||||
 | 
			
		||||
        "src/Playback/Recorder.ts",
 | 
			
		||||
 | 
			
		||||
        "src/SceneGraph/SceneGraph.ts",
 | 
			
		||||
        "src/SceneGraph/SceneGraphArray.ts",
 | 
			
		||||
        "src/SceneGraph/Viewport.ts",
 | 
			
		||||
        
 | 
			
		||||
        "src/Utils/Color.ts",
 | 
			
		||||
        "src/Utils/MathUtils.ts",
 | 
			
		||||
        "src/Utils/RandUtils.ts"
 | 
			
		||||
    ],
 | 
			
		||||
    "compilerOptions": {
 | 
			
		||||
        "noImplicitAny": true,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user