From 9e0ebae63cd7c66737ec5b06b7560c0045c6b5ce Mon Sep 17 00:00:00 2001 From: Joe Weaver Date: Wed, 13 Jan 2021 13:30:45 -0500 Subject: [PATCH] added annotations to all files --- src/AI/AIManager.ts | 23 +++- src/AI/StateMachineAI.ts | 5 + src/DataTypes/Collection.ts | 4 +- src/DataTypes/Graphs/EdgeNode.ts | 22 ++++ src/DataTypes/Graphs/Graph.ts | 55 +++++++-- src/DataTypes/Graphs/PositionGraph.ts | 54 ++++++++- src/DataTypes/Interfaces/Descriptors.ts | 4 +- src/DataTypes/Map.ts | 24 ++-- src/DataTypes/Physics/AreaCollision.ts | 21 ++++ src/DataTypes/Physics/Hit.ts | 18 +++ src/DataTypes/QuadTree.ts | 2 + src/DataTypes/Queue.ts | 29 ++++- src/DataTypes/RegionQuadTree.ts | 46 ++++--- src/DataTypes/Shapes/AABB.ts | 92 +++++++------- src/DataTypes/Shapes/Circle.ts | 12 ++ src/DataTypes/Shapes/Shape.ts | 58 ++++++++- src/DataTypes/Spritesheet.ts | 11 ++ src/DataTypes/Stack.ts | 26 +++- src/DataTypes/State/State.ts | 18 ++- src/DataTypes/State/StateMachine.ts | 16 ++- src/DataTypes/Tilesets/TiledData.ts | 1 + src/DataTypes/Tilesets/Tileset.ts | 33 +++++ src/DataTypes/Vec2.ts | 90 +++++++++----- src/DataTypes/Vec4.ts | 3 +- src/Debug/Debug.ts | 35 +++++- src/Debug/Stats.ts | 3 +- src/Events/Emitter.ts | 10 +- src/Events/EventQueue.ts | 49 ++++++-- src/Events/GameEvent.ts | 24 +++- src/Events/GameEventType.ts | 2 + src/Events/Receiver.ts | 16 ++- src/Input/InputHandler.ts | 10 +- src/Input/InputReceiver.ts | 60 +++++++++- src/Loop/GameLoop.ts | 50 ++++---- src/Loop/GameOptions.ts | 24 ++++ src/Nodes/Tilemap.ts | 8 +- src/Nodes/Tilemaps/OrthogonalTilemap.ts | 6 +- src/Nodes/UIElements/Label.ts | 2 +- src/Nodes/UIElements/Slider.ts | 2 +- src/Pathfinding/NavigationManager.ts | 21 +++- src/Pathfinding/NavigationPath.ts | 22 +++- src/Pathfinding/Navmesh.ts | 16 ++- src/Physics/BasicPhysicsManager.ts | 2 + .../BroadPhaseAlgorithms/BroadPhase.ts | 2 + .../BroadPhaseAlgorithms/SweepAndPrune.ts | 2 + src/Physics/Collisions.ts | 2 + src/Physics/PhysicsManager.ts | 23 ++-- src/Physics/TestPhysicsManager.ts | 105 ++++++++-------- src/Playback/Recorder.ts | 4 +- src/Rendering/Animations/AnimationManager.ts | 28 ++++- src/Rendering/Animations/AnimationTypes.ts | 2 + src/Rendering/Animations/TweenManager.ts | 17 ++- src/Rendering/CanvasRenderer.ts | 31 +++-- .../CanvasRendering/GraphicRenderer.ts | 21 +++- .../CanvasRendering/TilemapRenderer.ts | 21 ++++ .../CanvasRendering/UIElementRenderer.ts | 23 ++++ src/Rendering/RenderingManager.ts | 47 +++++++- src/ResourceManager/ResourceManager.ts | 98 +++++++++------ src/Scene/Factories/CanvasNodeFactory.ts | 17 ++- src/Scene/Factories/FactoryManager.ts | 68 ++++++++++- src/Scene/Factories/TilemapFactory.ts | 7 ++ src/Scene/Layer.ts | 62 +++++++++- src/Scene/Layers/ParallaxLayer.ts | 11 ++ src/Scene/Layers/UILayer.ts | 11 ++ src/Scene/Scene.ts | 113 ++++++++++++------ src/Scene/SceneManager.ts | 36 +++++- src/Scene/SceneOptions.ts | 32 +++++ src/SceneGraph/SceneGraph.ts | 38 ++++-- src/SceneGraph/SceneGraphArray.ts | 39 +++--- src/SceneGraph/SceneGraphQuadTree.ts | 20 +++- src/SceneGraph/Viewport.ts | 75 ++++++++---- src/Sound/AudioManager.ts | 26 ++-- src/Utils/ArrayUtils.ts | 13 +- src/Utils/Color.ts | 67 +++++++++++ src/Utils/EaseFunctions.ts | 2 + src/Utils/GraphUtils.ts | 10 +- src/Utils/MathUtils.ts | 19 ++- src/Utils/PhysicsUtils.ts | 7 -- src/Utils/Rand/Perlin.ts | 12 +- src/Utils/RandUtils.ts | 5 + src/Utils/SortingUtils.ts | 19 ++- src/Utils/StringUtils.ts | 4 +- 82 files changed, 1693 insertions(+), 475 deletions(-) create mode 100644 src/DataTypes/Graphs/EdgeNode.ts create mode 100644 src/DataTypes/Physics/AreaCollision.ts create mode 100644 src/DataTypes/Physics/Hit.ts create mode 100644 src/Loop/GameOptions.ts create mode 100644 src/Scene/SceneOptions.ts delete mode 100644 src/Utils/PhysicsUtils.ts diff --git a/src/AI/AIManager.ts b/src/AI/AIManager.ts index afb8664..ef45538 100644 --- a/src/AI/AIManager.ts +++ b/src/AI/AIManager.ts @@ -1,10 +1,15 @@ import { Actor, AI, Updateable } from "../DataTypes/Interfaces/Descriptors"; import Map from "../DataTypes/Map"; +/** + * A manager class for all of the AI in a scene. + * Keeps a list of registered actors and handles AI generation for actors. + */ export default class AIManager implements Updateable { + /** The array of registered actors */ actors: Array; - - registeredAI: Map() => T>; + /** Maps AI names to their constructors */ + registeredAI: Map; constructor(){ this.actors = new Array(); @@ -20,10 +25,20 @@ export default class AIManager implements Updateable { this.actors.push(actor); } + /** + * Registers an AI with the AIManager for use later on + * @param name The name of the AI to register + * @param constr The constructor for the AI + */ registerAI(name: string, constr: new () => T ): void { this.registeredAI.add(name, constr); } + /** + * Generates an AI instance from its name + * @param name The name of the AI to add + * @returns A new AI instance + */ generateAI(name: string): AI { if(this.registeredAI.has(name)){ return new (this.registeredAI.get(name))(); @@ -36,4 +51,6 @@ export default class AIManager implements Updateable { // Run the ai for every active actor this.actors.forEach(actor => { if(actor.aiActive) actor.ai.update(deltaT) }); } -} \ No newline at end of file +} + +type AIConstructor = new () => T; \ No newline at end of file diff --git a/src/AI/StateMachineAI.ts b/src/AI/StateMachineAI.ts index 0e4f464..eafd448 100644 --- a/src/AI/StateMachineAI.ts +++ b/src/AI/StateMachineAI.ts @@ -2,8 +2,13 @@ import { AI } from "../DataTypes/Interfaces/Descriptors"; import StateMachine from "../DataTypes/State/StateMachine"; import GameNode from "../Nodes/GameNode"; +/** + * A version of a @reference[StateMachine] that is configured to work as an AI controller for a @reference[GameNode] + */ export default class StateMachineAI extends StateMachine implements AI { + /** The GameNode that uses this StateMachine for its AI */ protected owner: GameNode; + // @implemented initializeAI(owner: GameNode, config: Record): void {} } \ No newline at end of file diff --git a/src/DataTypes/Collection.ts b/src/DataTypes/Collection.ts index 749a24b..25b0635 100644 --- a/src/DataTypes/Collection.ts +++ b/src/DataTypes/Collection.ts @@ -1,12 +1,12 @@ +// @ignorePage -// TODO - Is there already a way to do this in js/ts? /** * An interface for all iterable data custom data structures */ export default interface Collection { /** * Iterates through all of the items in this data structure. - * @param func + * @param func The function to evaluate of every item in the collection */ forEach(func: Function): void; diff --git a/src/DataTypes/Graphs/EdgeNode.ts b/src/DataTypes/Graphs/EdgeNode.ts new file mode 100644 index 0000000..3011ad9 --- /dev/null +++ b/src/DataTypes/Graphs/EdgeNode.ts @@ -0,0 +1,22 @@ +/** + * A linked-list for the edges in a @reference[Graph]. + */ +export default class EdgeNode { + /** The node in the Graph this edge connects to */ + y: number; + /** The weight of this EdgeNode */ + weight: number; + /** The next EdgeNode in the linked-list */ + next: EdgeNode; + + /** + * Creates a new EdgeNode + * @param index The index of the node this edge connects to + * @param weight The weight of this edge + */ + constructor(index: number, weight?: number){ + this.y = index; + this.next = null; + this.weight = weight ? weight : 1; + } +} \ No newline at end of file diff --git a/src/DataTypes/Graphs/Graph.ts b/src/DataTypes/Graphs/Graph.ts index 802c63b..39ba18e 100644 --- a/src/DataTypes/Graphs/Graph.ts +++ b/src/DataTypes/Graphs/Graph.ts @@ -1,13 +1,28 @@ +import EdgeNode from "./EdgeNode"; + export const MAX_V = 100; +/** + * An implementation of a graph data structure using edge lists. Inspired by The Algorithm Design Manual. + */ export default class Graph { + /** An array of edges at the node specified by the index */ edges: Array; + /** An array representing the degree of the node specified by the index */ degree: Array; + /** The number of vertices in the graph */ numVertices: number; + /** The number of edges in the graph */ numEdges: number; + /** Whether or not the graph is directed */ directed: boolean; + /** Whether or not the graph is weighted */ weighted: boolean; + /** + * Constructs a new graph + * @param directed Whether or not this graph is directed + */ constructor(directed: boolean = false){ this.directed = directed; this.weighted = false; @@ -19,12 +34,20 @@ export default class Graph { this.degree = new Array(MAX_V); } + /** Adds a node to this graph and returns the index of it + * @returns The index of the new node + */ addNode(): number { this.numVertices++; return this.numVertices; } - addEdge(x: number, y: number, weight?: number){ + /** Adds an edge between node x and y, with an optional weight + * @param x The index of the start of the edge + * @param y The index of the end of the edge + * @param weight The optional weight of the new edge + */ + addEdge(x: number, y: number, weight?: number): void { let edge = new EdgeNode(y, weight); if(this.edges[x]){ @@ -46,18 +69,36 @@ export default class Graph { this.numEdges += 1; } + /** + * Gets the edge list associated with node x + * @param x The index of the node + * @returns The head of a linked-list of edges + */ getEdges(x: number): EdgeNode { return this.edges[x]; } + /** + * Gets the degree associated with node x + * @param x The index of the node + */ getDegree(x: number): number { return this.degree[x]; } + /** + * Converts the specifed node into a string + * @param index The index of the node to convert to a string + * @returns The string representation of the node: "Node x" + */ protected nodeToString(index: number): string { return "Node " + index; } + /** + * Converts the Graph into a string format + * @returns The graph as a string + */ toString(): string { let retval = ""; @@ -81,16 +122,4 @@ export default class Graph { return retval; } -} - -export class EdgeNode { - y: number; - next: EdgeNode; - weight: number; - - constructor(index: number, weight?: number){ - this.y = index; - this.next = null; - this.weight = weight ? weight : 1; - } } \ No newline at end of file diff --git a/src/DataTypes/Graphs/PositionGraph.ts b/src/DataTypes/Graphs/PositionGraph.ts index e7eb943..c6fb453 100644 --- a/src/DataTypes/Graphs/PositionGraph.ts +++ b/src/DataTypes/Graphs/PositionGraph.ts @@ -2,27 +2,74 @@ import Graph, { MAX_V } from "./Graph"; import Vec2 from "../Vec2"; import { DebugRenderable } from "../Interfaces/Descriptors"; -export default class PositionGraph extends Graph implements DebugRenderable{ +/** + * An extension of Graph that has nodes with positions in 2D space. + * This is a weighted graph (though not inherently directd) +*/ +export default class PositionGraph extends Graph implements DebugRenderable { + /** An array of the positions of the nodes in this graph */ positions: Array; + /** + * Createes a new PositionGraph + * @param directed Whether or not this graph is directed + */ constructor(directed: boolean = false){ super(directed); this.positions = new Array(MAX_V); } - addPositionedNode(position: Vec2){ + /** + * Adds a positioned node to this graph + * @param position The position of the node to add + * @returns The index of the added node + */ + addPositionedNode(position: Vec2): number { this.positions[this.numVertices] = position; - this.addNode(); + return this.addNode(); } + /** + * Changes the position of a node. + * Automatically adjusts the weights of the graph tied to this node. + * As such, be warned that this function has an O(n + m) running time, and use it sparingly. + * @param index The index of the node + * @param position The new position of the node + */ setNodePosition(index: number, position: Vec2): void { this.positions[index] = position; + + // Recalculate all weights associated with this index + for(let i = 0; i < this.numEdges; i++){ + + let edge = this.edges[i]; + + while(edge !== null){ + // If this node is on either side of the edge, recalculate weight + if(i === index || edge.y === index){ + edge.weight = this.positions[i].distanceTo(this.positions[edge.y]); + } + + edge = edge.next; + } + } } + /** + * Gets the position of a node + * @param index The index of the node + * @returns The position of the node + */ getNodePosition(index: number): Vec2 { return this.positions[index]; } + /** + * Adds an edge to this graph between node x and y. + * Automatically calculates the weight of the edge as the distance between the nodes. + * @param x The beginning of the edge + * @param y The end of the edge + */ addEdge(x: number, y: number): void { if(!this.positions[x] || !this.positions[y]){ throw "Can't add edge to un-positioned node!"; @@ -34,6 +81,7 @@ export default class PositionGraph extends Graph implements DebugRenderable{ super.addEdge(x, y, weight); } + // @override protected nodeToString(index: number): string { return "Node " + index + " - " + this.positions[index].toString(); } diff --git a/src/DataTypes/Interfaces/Descriptors.ts b/src/DataTypes/Interfaces/Descriptors.ts index 3892c4a..dc08c60 100644 --- a/src/DataTypes/Interfaces/Descriptors.ts +++ b/src/DataTypes/Interfaces/Descriptors.ts @@ -5,6 +5,8 @@ import Vec2 from "../Vec2"; import NavigationPath from "../../Pathfinding/NavigationPath"; import GameNode from "../../Nodes/GameNode"; +// @ignorePage + export interface Unique { /** The unique id of this object. */ id: number; @@ -194,4 +196,4 @@ export interface Updateable { export interface DebugRenderable { /** Renders the debugging infor for this object. */ debugRender(): void; -} +} \ No newline at end of file diff --git a/src/DataTypes/Map.ts b/src/DataTypes/Map.ts index ddd8c17..7937de6 100644 --- a/src/DataTypes/Map.ts +++ b/src/DataTypes/Map.ts @@ -6,14 +6,15 @@ import Collection from "./Collection"; export default class Map implements Collection { private map: Record; + /** Creates a new map */ constructor(){ this.map = {}; } /** * Adds a value T stored at a key. - * @param key - * @param value + * @param key The key of the item to be stored + * @param value The item to be stored */ add(key: string, value: T): void { this.map[key] = value; @@ -21,16 +22,17 @@ export default class Map implements Collection { /** * Get the value associated with a key. - * @param key + * @param key The key of the item + * @returns The item at the key or undefined */ get(key: string): T { return this.map[key]; } /** - * Sets the value stored at key to the new specified value - * @param key - * @param value + * An alias of add. Sets the value stored at key to the new specified value + * @param key The key of the item to be stored + * @param value The item to be stored */ set(key: string, value: T): void { this.add(key, value); @@ -38,7 +40,8 @@ export default class Map implements Collection { /** * Returns true if there is a value stored at the specified key, false otherwise. - * @param key + * @param key The key to check + * @returns A boolean representing whether or not there is an item at the given key. */ has(key: string): boolean { return this.map[key] !== undefined; @@ -46,11 +49,13 @@ export default class Map implements Collection { /** * Returns an array of all of the keys in this map. + * @returns An array containing all keys in the map. */ keys(): Array { return Object.keys(this.map); } + // @implemented forEach(func: (key: string) => void): void { Object.keys(this.map).forEach(key => func(key)); } @@ -63,10 +68,15 @@ export default class Map implements Collection { delete this.map[key]; } + // @implemented clear(): void { this.forEach(key => delete this.map[key]); } + /** + * Converts this map to a string representation. + * @returns The string representation of this map. + */ toString(): string { let str = ""; diff --git a/src/DataTypes/Physics/AreaCollision.ts b/src/DataTypes/Physics/AreaCollision.ts new file mode 100644 index 0000000..260f247 --- /dev/null +++ b/src/DataTypes/Physics/AreaCollision.ts @@ -0,0 +1,21 @@ +import AABB from "../Shapes/AABB"; + +/** + * A class that contains the area of overlap of two colliding objects to allow for sorting by the physics system. + */ +export default class AreaCollision { + /** The area of the overlap for the colliding objects */ + area: number; + /** The AABB of the other collider in this collision */ + collider: AABB; + + /** + * Creates a new AreaCollision object + * @param area The area of the collision + * @param collider The other collider + */ + constructor(area: number, collider: AABB){ + this.area = area; + this.collider = collider; + } +} \ No newline at end of file diff --git a/src/DataTypes/Physics/Hit.ts b/src/DataTypes/Physics/Hit.ts new file mode 100644 index 0000000..7ee1a6a --- /dev/null +++ b/src/DataTypes/Physics/Hit.ts @@ -0,0 +1,18 @@ +import Vec2 from "../Vec2"; + +/** + * An object representing the data collected from a physics hit between two geometric objects. + * Inspired by the helpful collision documentation @link(here)(https://noonat.github.io/intersect/). + */ +export default class Hit { + /** The time of the collision. Only numbers 0 through 1 happen in this frame. */ + time: number; + /** The near times of the collision */ + nearTimes: Vec2 = Vec2.ZERO; + /** The position of the collision */ + pos: Vec2 = Vec2.ZERO; + /** The overlap distance of the hit */ + delta: Vec2 = Vec2.ZERO; + /** The normal vector of the hit */ + normal: Vec2 = Vec2.ZERO; +} \ No newline at end of file diff --git a/src/DataTypes/QuadTree.ts b/src/DataTypes/QuadTree.ts index 1d7f78f..a84f4d4 100644 --- a/src/DataTypes/QuadTree.ts +++ b/src/DataTypes/QuadTree.ts @@ -5,6 +5,8 @@ import { Positioned } from "./Interfaces/Descriptors"; // TODO - Make max depth +// @ignorePage + /** * Primarily used to organize the scene graph */ diff --git a/src/DataTypes/Queue.ts b/src/DataTypes/Queue.ts index 90b054d..8378746 100644 --- a/src/DataTypes/Queue.ts +++ b/src/DataTypes/Queue.ts @@ -4,12 +4,25 @@ import Collection from "./Collection"; * A FIFO queue with elements of type T */ export default class Queue implements Collection { - private readonly MAX_ELEMENTS: number; - private q: Array; - private head: number; + /** The maximum number of elements in the Queue */ + private readonly MAX_ELEMENTS: number; + + /** The internal representation of the queue */ + private q: Array; + + /** The head of the queue */ + private head: number; + + /** The tail of the queue */ private tail: number; + + /** The current number of items in the queue */ private size: number; + /** + * Constructs a new queue + * @param maxElements The maximum size of the stack + */ constructor(maxElements: number = 100){ this.MAX_ELEMENTS = maxElements; this.q = new Array(this.MAX_ELEMENTS); @@ -20,7 +33,7 @@ export default class Queue implements Collection { /** * Adds an item to the back of the queue - * @param item + * @param item The item to add to the back of the queue */ enqueue(item: T): void{ if((this.tail + 1) % this.MAX_ELEMENTS === this.head){ @@ -34,6 +47,7 @@ export default class Queue implements Collection { /** * Retrieves an item from the front of the queue + * @returns The item at the front of the queue */ dequeue(): T { if(this.head === this.tail){ @@ -51,7 +65,8 @@ export default class Queue implements Collection { } /** - * Returns the item at the front of the queue, but does not return it + * Returns the item at the front of the queue, but does not remove it + * @returns The item at the front of the queue */ peekNext(): T { if(this.head === this.tail){ @@ -65,6 +80,7 @@ export default class Queue implements Collection { /** * Returns true if the queue has items in it, false otherwise + * @returns A boolean representing whether or not this queue has items */ hasItems(): boolean { return this.head !== this.tail; @@ -72,17 +88,20 @@ export default class Queue implements Collection { /** * Returns the number of elements in the queue. + * @returns The size of the queue */ getSize(): number { return this.size; } + // @implemented clear(): void { this.forEach((item, index) => delete this.q[index]); this.size = 0; this.head = this.tail; } + // @implemented forEach(func: (item: T, index?: number) => void): void { let i = this.head; while(i !== this.tail){ diff --git a/src/DataTypes/RegionQuadTree.ts b/src/DataTypes/RegionQuadTree.ts index 66560cf..9107356 100644 --- a/src/DataTypes/RegionQuadTree.ts +++ b/src/DataTypes/RegionQuadTree.ts @@ -6,38 +6,34 @@ import Map from "./Map"; import Stats from "../Debug/Stats"; /** - * Primarily used to organize the scene graph + * A quadtree data structure implemented to work with regions rather than points. + * Elements in this quadtree have a position and an area, and thus can span multiple + * quadtree branches. */ export default class QuadTree implements Collection { - /** - * The center of this quadtree - */ + /** The center of this quadtree */ protected boundary: AABB; - /** - * The number of elements this quadtree root can hold before splitting - */ + /** The number of elements this quadtree root can hold before splitting */ protected capacity: number; - /** - * The maximum height of the quadtree from this root - */ + /** The maximum height of the quadtree from this root */ protected maxDepth: number; - /** - * Represents whether the quadtree is a root or a leaf - */ + /** Represents whether the quadtree is a root or a leaf */ protected divided: boolean; - /** - * The array of the items in this quadtree - */ + /** The array of the items in this quadtree */ protected items: Array; // The child quadtrees of this one + /** The top left child */ protected nw: QuadTree; + /** The top right child */ protected ne: QuadTree; + /** The bottom left child */ protected sw: QuadTree; + /** The bottom right child */ protected se: QuadTree; constructor(center: Vec2, size: Vec2, maxDepth?: number, capacity?: number){ @@ -92,6 +88,7 @@ export default class QuadTree implements Collection { /** * Returns all items at this point. * @param point The point to query at + * @returns A list of all elements in the quadtree that contain the specified point */ queryPoint(point: Vec2): Array { // A matrix to keep track of our results @@ -105,6 +102,7 @@ export default class QuadTree implements Collection { return results; } + // @ignoreFunction /** * A recursive function called by queryPoint * @param point The point being queried @@ -135,10 +133,11 @@ export default class QuadTree implements Collection { } } -/** + /** * Returns all items in this region * @param boundary The region to check * @param inclusionCheck Allows for additional inclusion checks to further refine searches + * @returns A list of all elements in the specified region */ queryRegion(boundary: AABB): Array { // A matrix to keep track of our results @@ -152,6 +151,7 @@ export default class QuadTree implements Collection { return results; } + // @ignoreFunction /** * A recursive function called by queryPoint * @param point The point being queried @@ -213,7 +213,7 @@ export default class QuadTree implements Collection { /** * Defers this insertion to the children of this quadtree - * @param item + * @param item The item to insert */ protected deferInsert(item: T): void { this.nw.insert(item); @@ -222,10 +222,6 @@ export default class QuadTree implements Collection { this.se.insert(item); } - /** - * Renders the quadtree for demo purposes. - * @param ctx - */ public render_demo(ctx: CanvasRenderingContext2D, origin: Vec2, zoom: number): void { ctx.strokeStyle = "#0000FF"; ctx.strokeRect((this.boundary.x - this.boundary.hw - origin.x)*zoom, (this.boundary.y - this.boundary.hh - origin.y)*zoom, 2*this.boundary.hw*zoom, 2*this.boundary.hh*zoom); @@ -238,6 +234,7 @@ export default class QuadTree implements Collection { } } + // @implemented forEach(func: Function): void { // If divided, send the call down if(this.divided){ @@ -253,9 +250,7 @@ export default class QuadTree implements Collection { } } - /** - * Clear the items in this quadtree - */ + // @implemented clear(): void { if(this.nw){ this.nw.clear(); @@ -271,7 +266,6 @@ export default class QuadTree implements Collection { this.items.length = 0; this.divided = false; - } } \ No newline at end of file diff --git a/src/DataTypes/Shapes/AABB.ts b/src/DataTypes/Shapes/AABB.ts index f53f845..a0a4cce 100644 --- a/src/DataTypes/Shapes/AABB.ts +++ b/src/DataTypes/Shapes/AABB.ts @@ -2,77 +2,64 @@ import Shape from "./Shape"; import Vec2 from "../Vec2"; import MathUtils from "../../Utils/MathUtils"; import Circle from "./Circle"; -import Debug from "../../Debug/Debug"; +import Hit from "../Physics/Hit"; +/** + * An Axis-Aligned Bounding Box. In other words, a rectangle that is always aligned to the x-y grid. + * Inspired by the helpful collision documentation @link(here)(https://noonat.github.io/intersect/). + */ export default class AABB extends Shape { - center: Vec2; halfSize: Vec2; + /** + * Creates a new AABB + * @param center The center of the AABB + * @param halfSize The half size of the AABB - The distance from the center to an edge in x and y + */ constructor(center?: Vec2, halfSize?: Vec2){ super(); this.center = center ? center : new Vec2(0, 0); this.halfSize = halfSize ? halfSize : new Vec2(0, 0); } - get x(): number { - return this.center.x; - } - - get y(): number { - return this.center.y; - } - - get hw(): number { - return this.halfSize.x; - } - - get hh(): number { - return this.halfSize.y; - } - - get top(): number { - return this.y - this.hh; - } - - get bottom(): number { - return this.y + this.hh; - } - - get left(): number { - return this.x - this.hw; - } - - get right(): number { - return this.x + this.hw; - } - + // @override getBoundingRect(): AABB { return this.clone(); } + // @override getBoundingCircle(): Circle { let r = Math.max(this.hw, this.hh) return new Circle(this.center.clone(), r); } + // @deprecated getHalfSize(): Vec2 { return this.halfSize; } + // @deprecated setHalfSize(halfSize: Vec2): void { this.halfSize = halfSize; } + // TODO - move these all to the Shape class /** * A simple boolean check of whether this AABB contains a point - * @param point + * @param point The point to check + * @returns A boolean representing whether this AABB contains the specified point */ containsPoint(point: Vec2): boolean { return point.x >= this.x - this.hw && point.x <= this.x + this.hw && point.y >= this.y - this.hh && point.y <= this.y + this.hh } + /** + * A simple boolean check of whether this AABB contains a point + * @param point The point to check + * @returns A boolean representing whether this AABB contains the specified point + */ intersectPoint(point: Vec2): boolean { let dx = point.x - this.x; let px = this.hw - Math.abs(dx); @@ -94,7 +81,8 @@ export default class AABB extends Shape { /** * A boolean check of whether this AABB contains a point with soft left and top boundaries. * In other words, if the top left is (0, 0), the point (0, 0) is not in the AABB - * @param point + * @param point The point to check + * @returns A boolean representing whether this AABB contains the specified point */ containsPointSoft(point: Vec2): boolean { return point.x > this.x - this.hw && point.x <= this.x + this.hw @@ -105,10 +93,9 @@ export default class AABB extends Shape { /** * Returns the data from the intersection of this AABB with a line segment from a point in a direction * @param point The point that the line segment starts from - * @param direction The direction the point will go - * @param distance The length of the line segment, if the direction is a unit vector - * @param paddingX Pads the AABB in the x axis - * @param paddingY Pads the AABB in the y axis + * @param delta The direction and distance of the segment + * @param padding Pads the AABB to make it wider for the intersection test + * @returns The Hit object representing the intersection, or null if there was no intersection */ intersectSegment(point: Vec2, delta: Vec2, padding?: Vec2): Hit { let paddingX = padding ? padding.x : 0; @@ -172,6 +159,7 @@ export default class AABB extends Shape { return hit; } + // @override overlaps(other: Shape): boolean { if(other instanceof AABB){ return this.overlapsAABB(other); @@ -181,9 +169,10 @@ export default class AABB extends Shape { /** * A simple boolean check of whether this AABB overlaps another - * @param other + * @param other The other AABB to check against + * @returns True if this AABB overlaps the other, false otherwise */ - overlapsAABB(other: AABB): boolean { + protected overlapsAABB(other: AABB): boolean { let dx = other.x - this.x; let px = this.hw + other.hw - Math.abs(dx); @@ -201,7 +190,11 @@ export default class AABB extends Shape { return true; } - // TODO - Implement this generally and use it in the tilemap + /** + * Calculates the area of the overlap between this AABB and another + * @param other The other AABB + * @returns The area of the overlap between the AABBs + */ overlapArea(other: AABB): number { let leftx = Math.max(this.x - this.hw, other.x - other.hw); let rightx = Math.min(this.x + this.hw, other.x + other.hw); @@ -241,19 +234,16 @@ export default class AABB extends Shape { this.halfSize.set(centerX - minX, centerY - minY); } + // @override clone(): AABB { return new AABB(this.center.clone(), this.halfSize.clone()); } + /** + * Converts this AABB to a string format + * @returns (center: (x, y), halfSize: (x, y)) + */ toString(): string { return "(center: " + this.center.toString() + ", half-size: " + this.halfSize.toString() + ")" } -} - -export class Hit { - time: number; - nearTimes: Vec2 = Vec2.ZERO; - pos: Vec2 = Vec2.ZERO; - delta: Vec2 = Vec2.ZERO; - normal: Vec2 = Vec2.ZERO; } \ No newline at end of file diff --git a/src/DataTypes/Shapes/Circle.ts b/src/DataTypes/Shapes/Circle.ts index c6e6f17..8cdca38 100644 --- a/src/DataTypes/Shapes/Circle.ts +++ b/src/DataTypes/Shapes/Circle.ts @@ -2,10 +2,18 @@ import Vec2 from "../Vec2"; import AABB from "./AABB"; import Shape from "./Shape"; +/** + * A Circle + */ export default class Circle extends Shape { private _center: Vec2; private radius: number; + /** + * Creates a new Circle + * @param center The center of the circle + * @param radius The radius of the circle + */ constructor(center: Vec2, radius: number) { super(); this._center = center ? center : new Vec2(0, 0); @@ -24,18 +32,22 @@ export default class Circle extends Shape { return new Vec2(this.radius, this.radius); } + // @override getBoundingRect(): AABB { return new AABB(this._center.clone(), new Vec2(this.radius, this.radius)); } + // @override getBoundingCircle(): Circle { return this.clone(); } + // @override overlaps(other: Shape): boolean { throw new Error("Method not implemented."); } + // @override clone(): Circle { return new Circle(this._center.clone(), this.radius); } diff --git a/src/DataTypes/Shapes/Shape.ts b/src/DataTypes/Shapes/Shape.ts index b6fb91e..93c00b9 100644 --- a/src/DataTypes/Shapes/Shape.ts +++ b/src/DataTypes/Shapes/Shape.ts @@ -2,6 +2,9 @@ import Vec2 from "../Vec2"; import AABB from "./AABB"; import Circle from "./Circle"; +/** + * An abstract Shape class that acts as an interface for better interactions with subclasses. + */ export default abstract class Shape { abstract get center(): Vec2; @@ -9,16 +12,63 @@ export default abstract class Shape { abstract get halfSize(): Vec2; - /** Gets a bounding rectangle for this shape */ + get x(): number { + return this.center.x; + } + + get y(): number { + return this.center.y; + } + + get hw(): number { + return this.halfSize.x; + } + + get hh(): number { + return this.halfSize.y; + } + + get top(): number { + return this.y - this.hh; + } + + get bottom(): number { + return this.y + this.hh; + } + + get left(): number { + return this.x - this.hw; + } + + get right(): number { + return this.x + this.hw; + } + + /** + * Gets a bounding rectangle for this shape. Warning - may be the same as this Shape. + * For instance, the bounding circle of an AABB is itself. Use clone() if you need a new shape. + * @returns An AABB that bounds this shape + */ abstract getBoundingRect(): AABB; - /** Gets a bounding circle for this shape */ + /** + * Gets a bounding circle for this shape. Warning - may be the same as this Shape. + * For instance, the bounding circle of a Circle is itself. Use clone() if you need a new shape. + * @returns A Circle that bounds this shape + */ abstract getBoundingCircle(): Circle; - /** Returns a copy of this Shape */ + /** + * Returns a copy of this Shape + * @returns A new copy of this shape + */ abstract clone(): Shape; - /** Checks if this shape overlaps another */ + /** + * Checks if this shape overlaps another + * @param other The other shape to check against + * @returns a boolean that represents whether this Shape overlaps the other one + */ abstract overlaps(other: Shape): boolean; static getTimeOfCollision(A: Shape, velA: Vec2, B: Shape, velB: Vec2): [Vec2, Vec2, boolean, boolean] { diff --git a/src/DataTypes/Spritesheet.ts b/src/DataTypes/Spritesheet.ts index 31b3798..d1f845c 100644 --- a/src/DataTypes/Spritesheet.ts +++ b/src/DataTypes/Spritesheet.ts @@ -1,11 +1,22 @@ import { AnimationData } from "../Rendering/Animations/AnimationTypes"; +/** A class representing data contained in a spritesheet. + * Spritesheets are the images associated with sprites, and contain images indexed in a grid, which + * correspond to animations. + */ export default class Spritesheet { + /** The name of the spritesheet */ name: string; + /** The image key of the spritesheet */ spriteSheetImage: string; + /** The width of the sprite */ spriteWidth: number; + /** The height of the sprite */ spriteHeight: number; + /** The number of columns in the spritesheet */ columns: number; + /** The number of rows in the spritesheet */ rows: number; + /** An array of the animations associated with this spritesheet */ animations: Array; } \ No newline at end of file diff --git a/src/DataTypes/Stack.ts b/src/DataTypes/Stack.ts index 7c05771..362f310 100644 --- a/src/DataTypes/Stack.ts +++ b/src/DataTypes/Stack.ts @@ -4,10 +4,19 @@ import Collection from "./Collection"; * A LIFO stack with items of type T */ export default class Stack implements Collection { - readonly MAX_ELEMENTS: number; - private stack: Array; + /** The maximum number of elements in the Stack */ + private readonly MAX_ELEMENTS: number; + + /** The internal representation of the stack */ + private stack: Array; + + /** The head of the stack */ private head: number; + /** + * Constructs a new stack + * @param maxElements The maximum size of the stack + */ constructor(maxElements: number = 100){ this.MAX_ELEMENTS = maxElements; this.stack = new Array(this.MAX_ELEMENTS); @@ -28,6 +37,7 @@ export default class Stack implements Collection { /** * Removes an item from the top of the stack + * @returns The item at the top of the stack */ pop(): T { if(this.head === -1){ @@ -39,6 +49,7 @@ export default class Stack implements Collection { /** * Returns the element currently at the top of the stack + * @returns The item at the top of the stack */ peek(): T { if(this.head === -1){ @@ -47,11 +58,14 @@ export default class Stack implements Collection { return this.stack[this.head]; } - /** Returns true if this stack is empty */ + /** Returns true if this stack is empty + * @returns A boolean that represents whether or not the stack is empty + */ isEmpty(): boolean { return this.head === -1; } + // @implemented clear(): void { this.forEach((item, index) => delete this.stack[index]); this.head = -1; @@ -59,11 +73,13 @@ export default class Stack implements Collection { /** * Returns the number of items currently in the stack + * @returns The number of items in the stack */ size(): number { return this.head + 1; } + // @implemented forEach(func: (item: T, index?: number) => void): void{ let i = 0; while(i <= this.head){ @@ -72,6 +88,10 @@ export default class Stack implements Collection { } } + /** + * Converts this stack into a string format + * @returns A string representing this stack + */ toString(): string { let retval = ""; diff --git a/src/DataTypes/State/State.ts b/src/DataTypes/State/State.ts index 969ab26..57b3f0a 100644 --- a/src/DataTypes/State/State.ts +++ b/src/DataTypes/State/State.ts @@ -3,10 +3,21 @@ import GameEvent from "../../Events/GameEvent"; import { Updateable } from "../Interfaces/Descriptors"; import StateMachine from "./StateMachine"; +/** + * An abstract implementation of a state for a @reference[StateMachine]. + * This class should be extended to allow for custom state behaviors. + */ export default abstract class State implements Updateable { + /** The StateMachine that uses this State */ protected parent: StateMachine; + + /** An event emitter */ protected emitter: Emitter; + /** + * Constructs a new State + * @param parent The parent StateMachine of this state + */ constructor(parent: StateMachine) { this.parent = parent; this.emitter = new Emitter(); @@ -18,11 +29,12 @@ export default abstract class State implements Updateable { abstract onEnter(): void; /** - * Handles an input event, such as taking damage. - * @param event + * A lifecycle method that handles an input event, such as taking damage. + * @param event The GameEvent to process */ abstract handleInput(event: GameEvent): void; + // @implemented abstract update(deltaT: number): void; /** @@ -34,7 +46,7 @@ export default abstract class State implements Updateable { } /** - * This is called when the state is ending. + * A lifecycle method is called when the state is ending. */ abstract onExit(): void; } \ No newline at end of file diff --git a/src/DataTypes/State/StateMachine.ts b/src/DataTypes/State/StateMachine.ts index f221e0f..b3772b8 100644 --- a/src/DataTypes/State/StateMachine.ts +++ b/src/DataTypes/State/StateMachine.ts @@ -8,18 +8,29 @@ import { Updateable } from "../Interfaces/Descriptors"; /** * An implementation of a Push Down Automata State machine. States can also be hierarchical - * for more flexibility, as described in Game Programming Principles. + * for more flexibility, as described in @link(Game Programming Patterns)(https://gameprogrammingpatterns.com/state.html). */ export default class StateMachine implements Updateable { + /** A stack of the current states */ protected stack: Stack; + /** A mape of state keys to actual state instances */ protected stateMap: Map; + /** The current state */ protected currentState: State; + /** An event receiver */ protected receiver: Receiver; + /** An event emitter */ protected emitter: Emitter; + /** A boolean representing whether or not this StateMachine is currently active */ protected active: boolean; + /** A boolean representing whether or not this StateMachine should emit an event on state change */ protected emitEventOnStateChange: boolean; + /** The name of the event to be emitted on state change */ protected stateChangeEventName: string; + /** + * Creates a new StateMachine + */ constructor(){ this.stack = new Stack(); this.stateMap = new Map(); @@ -56,7 +67,7 @@ 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){ + initialize(initialState: string): void { this.stack.push(this.stateMap.get(initialState)); this.currentState = this.stack.peek(); this.setActive(true); @@ -109,6 +120,7 @@ export default class StateMachine implements Updateable { this.currentState.handleInput(event); } + // @implemented update(deltaT: number): void { // If the state machine isn't currently active, ignore all events and don't update if(!this.active){ diff --git a/src/DataTypes/Tilesets/TiledData.ts b/src/DataTypes/Tilesets/TiledData.ts index 6b4125d..25146db 100644 --- a/src/DataTypes/Tilesets/TiledData.ts +++ b/src/DataTypes/Tilesets/TiledData.ts @@ -1,3 +1,4 @@ +// @ignorePage /** * a representation of Tiled's tilemap data */ diff --git a/src/DataTypes/Tilesets/Tileset.ts b/src/DataTypes/Tilesets/Tileset.ts index 849b0d0..e3d6419 100644 --- a/src/DataTypes/Tilesets/Tileset.ts +++ b/src/DataTypes/Tilesets/Tileset.ts @@ -7,12 +7,19 @@ import { TiledTilesetData } from "./TiledData"; * with a startIndex if required (as it is with Tiled using two images in one tilset). */ export default class Tileset { + /** The key of the image used by this tileset */ protected imageKey: string; + /** The size of the tileset image */ protected imageSize: Vec2; + /** The index of 0th image of this tileset */ protected startIndex: number; + /** The index of the last image of this tilset */ protected endIndex: number; + /** The size of the tiles in this tileset */ protected tileSize: Vec2; + /** The number of rows in this tileset */ protected numRows: number; + /** The number of columns in this tileset */ protected numCols: number; // TODO: Change this to be more general and work with other tileset formats @@ -35,6 +42,10 @@ export default class Tileset { this.imageSize = new Vec2(tiledData.imagewidth, tiledData.imageheight); } + /** + * Gets the image key associated with this tilemap + * @returns The image key of this tilemap + */ getImageKey(): string { return this.imageKey; } @@ -42,6 +53,7 @@ export default class Tileset { /** * Returns a Vec2 containing the left and top offset from the image origin for this tile. * @param tileIndex The index of the tile from startIndex to endIndex of this tileset + * @returns A Vec2 containing the offset for the specified tile. */ getImageOffsetForTile(tileIndex: number): Vec2 { // Get the true index @@ -58,18 +70,34 @@ export default class Tileset { return new Vec2(left, top); } + /** + * Gets the start index + * @returns The start index + */ getStartIndex(): number { return this.startIndex; } + /** + * Gets the tile set + * @returns A Vec2 containing the tile size + */ getTileSize(): Vec2 { return this.tileSize; } + /** + * Gets the number of rows in the tileset + * @returns The number of rows + */ getNumRows(): number { return this.numRows; } + /** + * Gets the number of columns in the tilset + * @returns The number of columns + */ getNumCols(): number { return this.numCols; } @@ -78,6 +106,11 @@ export default class Tileset { return this.endIndex - this.startIndex + 1; } + /** + * Checks whether or not this tilset contains the specified tile index. This is used for rendering. + * @param tileIndex The index of the tile to check + * @returns A boolean representing whether or not this tilset uses the specified index + */ hasTile(tileIndex: number): boolean { return tileIndex >= this.startIndex && tileIndex <= this.endIndex; } diff --git a/src/DataTypes/Vec2.ts b/src/DataTypes/Vec2.ts index 7119cee..b618781 100644 --- a/src/DataTypes/Vec2.ts +++ b/src/DataTypes/Vec2.ts @@ -6,7 +6,7 @@ import MathUtils from "../Utils/MathUtils"; export default class Vec2 { // Store x and y in an array - /** The array that stores the actual vector values */ + /** The array that stores the actual vector values x and y */ private vec: Float32Array; /** @@ -77,21 +77,25 @@ export default class Vec2 { } /** - * The squared magnitude of the vector + * The squared magnitude of the vector. This tends to be faster, so use it in situations where taking the + * square root doesn't matter, like for comparing distances. + * @returns The squared magnitude of the vector */ magSq(): number { return this.x*this.x + this.y*this.y; } /** - * The magnitude of the vector + * The magnitude of the vector. + * @returns The magnitude of the vector. */ mag(): number { return Math.sqrt(this.magSq()); } /** - * Returns this vector as a unit vector - Equivalent to dividing x and y by the magnitude + * Divdes x and y by the magnitude to obtain the unit vector in the direction of this vector. + * @returns This vector as a unit vector. */ normalize(): Vec2 { if(this.x === 0 && this.y === 0) return this; @@ -102,7 +106,8 @@ export default class Vec2 { } /** - * Returns a new vector that is the normalized version of this one + * Works like normalize(), but returns a new Vec2 + * @returns A new vector that is the unit vector for this one */ normalized(): Vec2 { let mag = this.mag(); @@ -110,7 +115,8 @@ export default class Vec2 { } /** - * Sets the x and y elements of this vector to zero + * Sets the x and y elements of this vector to zero. + * @returns This vector, with x and y set to zero. */ zero(): Vec2 { return this.set(0, 0); @@ -120,6 +126,7 @@ export default class Vec2 { * Sets the vector's x and y based on the angle provided. Goes counter clockwise. * @param angle The angle in radians * @param radius The magnitude of the vector at the specified angle + * @returns This vector. */ setToAngle(angle: number, radius: number = 1): Vec2 { this.x = MathUtils.floorToPlace(Math.cos(angle)*radius, 5); @@ -129,7 +136,8 @@ export default class Vec2 { /** * Returns a vector that point from this vector to another one - * @param other + * @param other The vector to point to + * @returns A new Vec2 that points from this vector to the one provided */ vecTo(other: Vec2): Vec2 { return new Vec2(other.x - this.x, other.y - this.y); @@ -137,7 +145,8 @@ export default class Vec2 { /** * Returns a vector containing the direction from this vector to another - * @param other + * @param other The vector to point to + * @returns A new Vec2 that points from this vector to the one provided. This new Vec2 will be a unit vector. */ dirTo(other: Vec2): Vec2 { return this.vecTo(other).normalize(); @@ -145,7 +154,8 @@ export default class Vec2 { /** * Keeps the vector's direction, but sets its magnitude to be the provided magnitude - * @param magnitude + * @param magnitude The magnitude the vector should be + * @returns This vector with its magnitude set to the new magnitude */ scaleTo(magnitude: number): Vec2 { return this.normalize().scale(magnitude); @@ -153,8 +163,9 @@ export default class Vec2 { /** * Scales x and y by the number provided, or if two number are provided, scales them individually. - * @param factor - * @param yFactor + * @param factor The scaling factor for the vector, or for only the x-component if yFactor is provided + * @param yFactor The scaling factor for the y-component of the vector + * @returns This vector after scaling */ scale(factor: number, yFactor: number = null): Vec2 { if(yFactor !== null){ @@ -169,8 +180,9 @@ export default class Vec2 { /** * Returns a scaled version of this vector without modifying it. - * @param factor - * @param yFactor + * @param factor The scaling factor for the vector, or for only the x-component if yFactor is provided + * @param yFactor The scaling factor for the y-component of the vector + * @returns A new vector that has the values of this vector after scaling */ scaled(factor: number, yFactor: number = null): Vec2 { return this.clone().scale(factor, yFactor); @@ -179,6 +191,7 @@ export default class Vec2 { /** * Rotates the vector counter-clockwise by the angle amount specified * @param angle The angle to rotate by in radians + * @returns This vector after rotation. */ rotateCCW(angle: number): Vec2 { let cs = Math.cos(angle); @@ -192,8 +205,9 @@ export default class Vec2 { /** * Sets the vectors coordinates to be the ones provided - * @param x - * @param y + * @param x The new x value for this vector + * @param y The new y value for this vector + * @returns This vector */ set(x: number, y: number): Vec2 { this.x = x; @@ -204,6 +218,7 @@ export default class Vec2 { /** * Copies the values of the other Vec2 into this one. * @param other The Vec2 to copy + * @returns This vector with its values set to the vector provided */ copy(other: Vec2): Vec2 { return this.set(other.x, other.y); @@ -211,7 +226,8 @@ export default class Vec2 { /** * Adds this vector the another vector - * @param other + * @param other The Vec2 to add to this one + * @returns This vector after adding the one provided */ add(other: Vec2): Vec2 { this.x += other.x; @@ -221,7 +237,8 @@ export default class Vec2 { /** * Subtracts another vector from this vector - * @param other + * @param other The Vec2 to subtract from this one + * @returns This vector after subtracting the one provided */ sub(other: Vec2): Vec2 { this.x -= other.x; @@ -230,8 +247,9 @@ export default class Vec2 { } /** - * Multiplies this vector with another vector element-wise - * @param other + * Multiplies this vector with another vector element-wise. In other words, this.x *= other.x and this.y *= other.y + * @param other The Vec2 to multiply this one by + * @returns This vector after multiplying its components by this one */ mult(other: Vec2): Vec2 { this.x *= other.x; @@ -240,8 +258,9 @@ export default class Vec2 { } /** - * Divides this vector with another vector element-wise - * @param other + * Divides this vector with another vector element-wise. In other words, this.x /= other.x and this.y /= other.y + * @param other The vector to divide this one by + * @returns This vector after division */ div(other: Vec2): Vec2 { if(other.x === 0 || other.y === 0) throw "Divide by zero error"; @@ -252,7 +271,8 @@ export default class Vec2 { /** * Returns the squared distance between this vector and another vector - * @param other + * @param other The vector to compute distance squared to + * @returns The squared distance between this vector and the one provided */ distanceSqTo(other: Vec2): number { return (this.x - other.x)*(this.x - other.x) + (this.y - other.y)*(this.y - other.y); @@ -260,7 +280,8 @@ export default class Vec2 { /** * Returns the distance between this vector and another vector - * @param other + * @param other The vector to compute distance to + * @returns The distance between this vector and the one provided */ distanceTo(other: Vec2): number { return Math.sqrt(this.distanceSqTo(other)); @@ -268,7 +289,8 @@ export default class Vec2 { /** * Returns the dot product of this vector and another - * @param other + * @param other The vector to compute the dot product with + * @returns The dot product of this vector and the one provided. */ dot(other: Vec2): number { return this.x*other.x + this.y*other.y; @@ -276,7 +298,8 @@ export default class Vec2 { /** * Returns the angle counter-clockwise in radians from this vector to another vector - * @param other + * @param other The vector to compute the angle to + * @returns The angle, rotating CCW, from this vector to the other vector */ angleToCCW(other: Vec2): number { let dot = this.dot(other); @@ -292,6 +315,7 @@ export default class Vec2 { /** * Returns a string representation of this vector rounded to 1 decimal point + * @returns This vector as a string */ toString(): string { return this.toFixed(); @@ -299,7 +323,8 @@ export default class Vec2 { /** * Returns a string representation of this vector rounded to the specified number of decimal points - * @param numDecimalPoints + * @param numDecimalPoints The number of decimal points to create a string to + * @returns This vector as a string */ toFixed(numDecimalPoints: number = 1): string { return "(" + this.x.toFixed(numDecimalPoints) + ", " + this.y.toFixed(numDecimalPoints) + ")"; @@ -307,6 +332,7 @@ export default class Vec2 { /** * Returns a new vector with the same coordinates as this one. + * @returns A new Vec2 with the same values as this one */ clone(): Vec2 { return new Vec2(this.x, this.y); @@ -315,6 +341,7 @@ export default class Vec2 { /** * Returns true if this vector and other have the EXACT same x and y (not assured to be safe for floats) * @param other The vector to check against + * @returns A boolean representing the equality of the two vectors */ strictEquals(other: Vec2): boolean { return this.x === other.x && this.y === other.y; @@ -323,6 +350,7 @@ export default class Vec2 { /** * Returns true if this vector and other have the same x and y * @param other The vector to check against + * @returns A boolean representing the equality of the two vectors */ equals(other: Vec2): boolean { let xEq = Math.abs(this.x - other.x) < 0.0000001; @@ -333,6 +361,7 @@ export default class Vec2 { /** * Returns true if this vector is the zero vector exactly (not assured to be safe for floats). + * @returns A boolean representing the equality of this vector and the zero vector */ strictIsZero(): boolean { return this.x === 0 && this.y === 0; @@ -340,6 +369,7 @@ export default class Vec2 { /** * Returns true if this x and y for this vector are both zero. + * @returns A boolean representing the equality of this vector and the zero vector */ isZero(): boolean { return Math.abs(this.x) < 0.0000001 && Math.abs(this.y) < 0.0000001; @@ -352,19 +382,13 @@ export default class Vec2 { setOnChange(f: Function): void { this.onChange = f; } - - /** - * Gets the function that is called whenever this vector is changed - */ - getOnChange(): string { - return this.onChange.toString(); - } /** * Performs linear interpolation between two vectors * @param a The first vector * @param b The second vector * @param t The time of the lerp, with 0 being vector A, and 1 being vector B + * @returns A new Vec2 representing the lerp between vector a and b. */ static lerp(a: Vec2, b: Vec2, t: number): Vec2 { return new Vec2(MathUtils.lerp(a.x, b.x, t), MathUtils.lerp(a.y, b.y, t)); diff --git a/src/DataTypes/Vec4.ts b/src/DataTypes/Vec4.ts index 6f0e150..66793f4 100644 --- a/src/DataTypes/Vec4.ts +++ b/src/DataTypes/Vec4.ts @@ -1,6 +1,7 @@ import Vec2 from "./Vec2"; -export default class Vec4{ +// @ignorePage +export default class Vec4 { public vec: Float32Array; diff --git a/src/Debug/Debug.ts b/src/Debug/Debug.ts index d7928d4..a4cefd3 100644 --- a/src/Debug/Debug.ts +++ b/src/Debug/Debug.ts @@ -1,11 +1,11 @@ import Map from "../DataTypes/Map"; import Vec2 from "../DataTypes/Vec2"; -import InputHandler from "../Input/InputHandler"; import GameNode from "../Nodes/GameNode"; import Color from "../Utils/Color"; -type DebugRenderFunction = (ctx: CanvasRenderingContext2D) => void; - +/** + * A util class for rendering Debug messages to the canvas. + */ export default class Debug { /** A map of log messages to display on the screen */ @@ -40,7 +40,7 @@ export default class Debug { /** * Deletes a a key from the log and stops it from keeping up space on the screen - * @param id + * @param id The id of the log item to clear */ static clearLogItem(id: string): void { this.logMessages.delete(id); @@ -59,6 +59,7 @@ export default class Debug { * @param center The center of the box * @param halfSize The dimensions of the box * @param filled A boolean for whether or not the box is filled + * @param color The color of the box to draw */ static drawBox(center: Vec2, halfSize: Vec2, filled: boolean, color: Color): void { if(filled){ @@ -72,6 +73,12 @@ export default class Debug { } } + /** + * Draws a ray at the specified position + * @param from The starting position of the ray + * @param to The ending position of the ray + * @param color The color of the ray + */ static drawRay(from: Vec2, to: Vec2, color: Color): void { this.debugRenderingContext.lineWidth = 2; this.debugRenderingContext.strokeStyle = color.toString(); @@ -83,16 +90,32 @@ export default class Debug { this.debugRenderingContext.stroke(); } + /** + * Draws a point at the specified position + * @param pos The position of the point + * @param color The color of the point + */ static drawPoint(pos: Vec2, color: Color): void { let pointSize = 6; this.debugRenderingContext.fillStyle = color.toString(); this.debugRenderingContext.fillRect(pos.x - pointSize/2, pos.y - pointSize/2, pointSize, pointSize); } + /** + * Sets the default rendering color for text for the debugger + * @param color The color to render the text + */ static setDefaultTextColor(color: Color): void { this.defaultTextColor = color; } + /** + * Performs any necessary setup operations on the Debug canvas + * @param canvas The debug canvas + * @param width The desired width of the canvas + * @param height The desired height of the canvas + * @returns The rendering context extracted from the canvas + */ static initializeDebugCanvas(canvas: HTMLCanvasElement, width: number, height: number): CanvasRenderingContext2D { canvas.width = width; canvas.height = height; @@ -104,15 +127,18 @@ export default class Debug { return this.debugRenderingContext; } + /** Clears the debug canvas */ static clearCanvas(): void { this.debugRenderingContext.clearRect(0, 0, this.debugCanvasSize.x, this.debugCanvasSize.y); } + /** Renders the text and nodes sent to the Debug system */ static render(): void { this.renderText(); this.renderNodes(); } + /** Renders the text sent to the Debug canvas */ static renderText(): void { let y = 20; this.debugRenderingContext.font = "20px Arial"; @@ -125,6 +151,7 @@ export default class Debug { }); } + /** Renders the nodes registered with the debug canvas */ static renderNodes(): void { if(this.nodes){ this.nodes.forEach(node => { diff --git a/src/Debug/Stats.ts b/src/Debug/Stats.ts index a7c1ecd..e9febd4 100644 --- a/src/Debug/Stats.ts +++ b/src/Debug/Stats.ts @@ -1,7 +1,8 @@ import Color from "../Utils/Color"; +// @ignorePage export default class Stats extends Object { - /** The fps of the game. */ + // The fps of the game. private static prevfps: Array; private static readonly NUM_POINTS: number = 60; private static ctx: CanvasRenderingContext2D; diff --git a/src/Events/Emitter.ts b/src/Events/Emitter.ts index 35f6b8a..ce6abc5 100644 --- a/src/Events/Emitter.ts +++ b/src/Events/Emitter.ts @@ -2,17 +2,23 @@ import Map from "../DataTypes/Map"; import EventQueue from "./EventQueue"; import GameEvent from "./GameEvent"; +/** + * An event emitter object other systems can use to hook into the EventQueue. + * Provides an easy interface for firing off events. + */ export default class Emitter { + /** A reference to the EventQueue */ private eventQueue: EventQueue; + /** Creates a new Emitter */ constructor(){ this.eventQueue = EventQueue.getInstance(); } /** * Emit and event of type eventType with the data packet data - * @param eventType - * @param data + * @param eventType The name of the event to fire off + * @param data A @reference[Map] or record containing any data about the event */ fireEvent(eventType: string, data: Map | Record = null): void { this.eventQueue.addEvent(new GameEvent(eventType, data)); diff --git a/src/Events/EventQueue.ts b/src/Events/EventQueue.ts index 2d88f8d..557bc25 100644 --- a/src/Events/EventQueue.ts +++ b/src/Events/EventQueue.ts @@ -4,18 +4,43 @@ import GameEvent from "./GameEvent"; import Receiver from "./Receiver"; import { GameEventType } from "./GameEventType"; +/** + * The main event system of the game engine. + * Events are sent to the EventQueue, which handles distribution to any systems that are listening for those events. + * This allows for handling of input without having classes directly hook into javascript event handles, + * and allows otherwise separate classes to communicate with each other cleanly, such as a Player object + * requesting a sound be played by the audio system. + * + * The distribution of @reference[GameEvent]s happens as follows: + * + * Events are recieved throughout a frame and are queued up by the EventQueue. + * At the beginning of the next frame, events are sent out to any receivers that are hooked into the event type. + * @reference[Receiver]s are then free to process events as they see fit. + * + * Overall, the EventQueue can be considered as something similar to an email server, + * and the @reference[Receiver]s can be considered as the client inboxes. + * + * See @link(Game Programming Patterns)(https://gameprogrammingpatterns.com/event-queue.html) for more discussion on EventQueues + */ export default class EventQueue { - private static instance: EventQueue = null; - private readonly MAX_SIZE: number; - private q: Queue; - private receivers: Map> + private static instance: EventQueue = null; + + /** The maximum number of events visible */ + private readonly MAX_SIZE: number; + + /** The actual queue of events */ + private q: Queue; + + /** The map of receivers registered for an event name */ + private receivers: Map>; private constructor(){ this.MAX_SIZE = 100; this.q = new Queue(this.MAX_SIZE); this.receivers = new Map>(); } - + + /** Retrieves the instance of the Singleton EventQueue */ static getInstance(): EventQueue { if(this.instance === null){ this.instance = new EventQueue(); @@ -24,14 +49,18 @@ export default class EventQueue { return this.instance; } + /** Adds an event to the EventQueue. + * This is exposed to the rest of the game engine through the @reference[Emitter] class */ addEvent(event: GameEvent): void { this.q.enqueue(event); } /** - * Associates a receiver with a type of event. Every time this event appears in the future, it will be given to the receiver (and any others watching that type) - * @param receiver - * @param type + * Associates a receiver with a type of event. Every time this event appears in the future, + * it will be given to the receiver (and any others watching that type). + * This is exposed to the rest of the game engine through the @reference[Receiver] class + * @param receiver The event receiver + * @param type The type or types of events to subscribe to */ subscribe(receiver: Receiver, type: string | Array): void { if(type instanceof Array){ @@ -52,8 +81,8 @@ export default class EventQueue { this.receivers.add(type, [receiver]); } } - - update(deltaT: number): void{ + + update(deltaT: number): void { while(this.q.hasItems()){ // Retrieve each event let event = this.q.dequeue(); diff --git a/src/Events/GameEvent.ts b/src/Events/GameEvent.ts index 4d2dd67..93eaa21 100644 --- a/src/Events/GameEvent.ts +++ b/src/Events/GameEvent.ts @@ -1,13 +1,22 @@ import Map from "../DataTypes/Map" /** - * A representation of an in-game event + * A representation of an in-game event that is passed through the @reference[EventQueue] */ export default class GameEvent { - public type: string; - public data: Map; + /** The type of the event */ + public type: string; + /** The data contained by the event */ + public data: Map; + /** The time of the event in ms */ public time: number; + /** + * Creates a new GameEvent. + * This is handled implicitly through the @reference[Emitter] class + * @param type The type of the GameEvent + * @param data The data contained by the GameEvent + */ constructor(type: string, data: Map | Record = null) { // Parse the game event data if (data === null) { @@ -26,10 +35,19 @@ export default class GameEvent { this.time = Date.now(); } + /** + * Checks the type of the GameEvent + * @param type The type to check + * @returns True if the GameEvent is the specified type, false otherwise. + */ isType(type: string): boolean { return this.type === type; } + /** + * Returns this GameEvent as a string + * @returns The string representation of the GameEvent + */ toString(): string { return this.type + ": @" + this.time; } diff --git a/src/Events/GameEventType.ts b/src/Events/GameEventType.ts index c231308..91ddf0c 100644 --- a/src/Events/GameEventType.ts +++ b/src/Events/GameEventType.ts @@ -1,3 +1,5 @@ +// @ignorePage + export enum GameEventType { /** * Mouse Down event. Has data: {position: Vec2 - Mouse Position} diff --git a/src/Events/Receiver.ts b/src/Events/Receiver.ts index 4970f7c..d92296e 100644 --- a/src/Events/Receiver.ts +++ b/src/Events/Receiver.ts @@ -3,12 +3,16 @@ import EventQueue from "./EventQueue"; import GameEvent from "./GameEvent"; /** - * Receives subscribed events from the EventQueue + * Receives subscribed events from the EventQueue. */ -export default class Receiver{ +export default class Receiver { + /** The maximum number of events this Receiver can hold at one time */ readonly MAX_SIZE: number; + + /** The inbox of the Receiver */ private q: Queue; + /** Creates a new Receiver */ constructor(){ this.MAX_SIZE = 100; this.q = new Queue(this.MAX_SIZE); @@ -23,7 +27,8 @@ export default class Receiver{ } /** - * Adds an event to the queue of this reciever + * Adds an event to the queue of this reciever. This is used by the @reference[EventQueue] to distribute events + * @param event The event to receive */ receive(event: GameEvent): void { this.q.enqueue(event); @@ -31,13 +36,15 @@ export default class Receiver{ /** * Retrieves the next event from the receiver's queue + * @returns The next GameEvent */ getNextEvent(): GameEvent { return this.q.dequeue(); } /** - * Looks at the next event in the receiver's queue + * Looks at the next event in the receiver's queue, but doesn't remove it from the queue + * @returns The next GameEvent */ peekNextEvent(): GameEvent { return this.q.peekNext() @@ -45,6 +52,7 @@ export default class Receiver{ /** * Returns true if the receiver has any events in its queue + * @returns True if the receiver has another event, false otherwise */ hasNextEvent(): boolean { return this.q.hasItems(); diff --git a/src/Input/InputHandler.ts b/src/Input/InputHandler.ts index ba067fe..4d34811 100644 --- a/src/Input/InputHandler.ts +++ b/src/Input/InputHandler.ts @@ -4,11 +4,15 @@ import GameEvent from "../Events/GameEvent"; import { GameEventType } from "../Events/GameEventType"; /** - * Handles communication with the web browser to receive asynchronous events and send them to the event queue + * Handles communication with the web browser to receive asynchronous events and send them to the @reference[EventQueue] */ -export default class InputHandler{ +export default class InputHandler { private eventQueue: EventQueue; - + + /** + * Creates a new InputHandler + * @param canvas The game canvas + */ constructor(canvas: HTMLCanvasElement){ this.eventQueue = EventQueue.getInstance(); diff --git a/src/Input/InputReceiver.ts b/src/Input/InputReceiver.ts index 0ecd503..5108115 100644 --- a/src/Input/InputReceiver.ts +++ b/src/Input/InputReceiver.ts @@ -7,7 +7,7 @@ import GameEvent from "../Events/GameEvent"; import { GameEventType } from "../Events/GameEventType"; /** - * Receives input events from the event queue and allows for easy access of information about input + * Receives input events from the @reference[EventQueue] and allows for easy access of information about input by other systems */ export default class InputReceiver{ private static instance: InputReceiver = null; @@ -45,6 +45,10 @@ export default class InputReceiver{ GameEventType.KEY_DOWN, GameEventType.KEY_UP, GameEventType.CANVAS_BLUR, GameEventType.WHEEL_UP, GameEventType.WHEEL_DOWN]); } + /** + * Gets the statc instance of the Singleton InputReceiver + * @returns The InputReceiver instance + */ static getInstance(): InputReceiver{ if(this.instance === null){ this.instance = new InputReceiver(); @@ -117,6 +121,12 @@ export default class InputReceiver{ this.keyPressed.forEach((key: string) => this.keyPressed.set(key, false)); } + /** + * Returns whether or not a key was newly pressed this frame. + * If the key is still pressed from last frame and wasn't re-pressed, this will return false. + * @param key The key + * @returns True if the key was just pressed, false otherwise + */ isJustPressed(key: string): boolean { if(this.keyJustPressed.has(key)){ return this.keyJustPressed.get(key) @@ -125,6 +135,11 @@ export default class InputReceiver{ } } + /** + * Returns an array of all of the keys that are newly pressed this frame. + * If a key is still pressed from last frame and wasn't re-pressed, it will not be in this list. + * @returns An array of all of the newly pressed keys. + */ getKeysJustPressed(): Array { let keys = Array(); this.keyJustPressed.forEach(key => { @@ -135,6 +150,11 @@ export default class InputReceiver{ return keys; } + /** + * Returns whether or not a key is being pressed. + * @param key The key + * @returns True if the key is currently pressed, false otherwise + */ isPressed(key: string): boolean { if(this.keyPressed.has(key)){ return this.keyPressed.get(key) @@ -143,38 +163,76 @@ export default class InputReceiver{ } } + /** + * Returns whether or not the mouse was newly pressed this frame + * @returns True if the mouse was just pressed, false otherwise + */ isMouseJustPressed(): boolean { return this.mouseJustPressed; } + /** + * Returns whether or not the mouse is currently pressed + * @returns True if the mouse is currently pressed, false otherwise + */ isMousePressed(): boolean { return this.mousePressed; } + /** + * Returns whether the user scrolled or not + * @returns True if the user just scrolled this frame, false otherwise + */ didJustScroll(): boolean { return this.justScrolled; } + /** + * Gets the direction of the scroll + * @returns -1 if the user scrolled up, 1 if they scrolled down + */ getScrollDirection(): number { return this.scrollDirection; } + /** + * Gets the position of the player's mouse + * @returns The mouse position stored as a Vec2 + */ getMousePosition(): Vec2 { return this.mousePosition; } + /** + * Gets the position of the player's mouse in the game world, + * taking into consideration the scrolling of the viewport + * @returns The mouse position stored as a Vec2 + */ getGlobalMousePosition(): Vec2 { return this.mousePosition.clone().add(this.viewport.getOrigin()); } + /** + * Gets the position of the last mouse press + * @returns The mouse position stored as a Vec2 + */ getMousePressPosition(): Vec2 { return this.mousePressPosition; } + /** + * Gets the position of the last mouse press in the game world, + * taking into consideration the scrolling of the viewport + * @returns The mouse position stored as a Vec2 + */ getGlobalMousePressPosition(): Vec2 { return this.mousePressPosition.clone().add(this.viewport.getOrigin()); } + /** + * Gives the input receiver a reference to the viewport + * @param viewport The viewport + */ setViewport(viewport: Viewport): void { this.viewport = viewport; } diff --git a/src/Loop/GameLoop.ts b/src/Loop/GameLoop.ts index 6359d71..9501bf4 100644 --- a/src/Loop/GameLoop.ts +++ b/src/Loop/GameLoop.ts @@ -8,11 +8,16 @@ import Viewport from "../SceneGraph/Viewport"; import SceneManager from "../Scene/SceneManager"; import AudioManager from "../Sound/AudioManager"; import Stats from "../Debug/Stats"; -import ArrayUtils from "../Utils/ArrayUtils"; import RenderingManager from "../Rendering/RenderingManager"; import CanvasRenderer from "../Rendering/CanvasRenderer"; import Color from "../Utils/Color"; +import GameOptions from "./GameOptions"; +/** + * The main loop of the game engine. + * Handles the update order, and initializes all subsystems. + * The GameLoop manages the update cycle, and requests animation frames to render to the browser. + */ export default class GameLoop { gameOptions: GameOptions; @@ -77,6 +82,10 @@ export default class GameLoop { private audioManager: AudioManager; private renderingManager: RenderingManager; + /** + * Creates a new GameLoop + * @param options The options for GameLoop initialization + */ constructor(options?: Record){ // Typecast the config object to a GameConfig object this.gameOptions = GameOptions.parse(options); @@ -148,24 +157,32 @@ export default class GameLoop { /** * Changes the maximum allowed physics framerate of the game - * @param initMax + * @param initMax The max framerate */ setMaxUpdateFPS(initMax: number): void { this.maxUpdateFPS = initMax; this.simulationTimestep = Math.floor(1000/this.maxUpdateFPS); } + /** + * Sets the maximum rendering framerate + * @param maxFPS The max framerate + */ setMaxFPS(maxFPS: number): void { this.minFrameDelay = 1000/maxFPS; } + /** + * Retreives the SceneManager from the GameLoop + * @returns The SceneManager + */ getSceneManager(): SceneManager { return this.sceneManager; } /** * Updates the frame count and sum of time for the framerate of the game - * @param timestep + * @param timestep The current time in ms */ private updateFPS(timestamp: number): void { this.fps = 0.9 * this.framesSinceLastFpsUpdate * 1000 / (timestamp - this.lastFpsUpdate) +(1 - 0.9) * this.fps; @@ -189,9 +206,9 @@ export default class GameLoop { /** * The first game frame - initializes the first frame time and begins the render - * @param timestamp + * @param timestamp The current time in ms */ - startFrame = (timestamp: number): void => { + startFrame(timestamp: number): void { this.running = true; this.render(); @@ -207,7 +224,7 @@ export default class GameLoop { * The main loop of the game. Updates and renders every frame * @param timestamp */ - doFrame = (timestamp: number): void => { + doFrame(timestamp: number): void { // Request animation frame to prepare for another update or render window.requestAnimationFrame(this.doFrame); @@ -250,6 +267,9 @@ export default class GameLoop { this.panic = false; } + /** + * Ends the game loop + */ end(){ if(this.panic) { var discardedTime = Math.round(this.resetFrameDelta()); @@ -265,7 +285,7 @@ export default class GameLoop { /** * Updates all necessary subsystems of the game. Defers scene updates to the sceneManager - * @param deltaT + * @param deltaT The time sine the last update */ update(deltaT: number): void { // Handle all events that happened since the start of the last loop @@ -288,7 +308,7 @@ export default class GameLoop { } /** - * Clears the canvas and defers scene rendering to the sceneManager. Renders the debug + * Clears the canvas and defers scene rendering to the sceneManager. Renders the debug canvas */ render(): void { // Clear the canvases @@ -304,18 +324,4 @@ export default class GameLoop { Debug.render(); Stats.render(); } -} - -class GameOptions { - viewportSize: {x: number, y: number}; - clearColor: {r: number, g: number, b: number} - - static parse(options: Record): GameOptions { - let gOpt = new GameOptions(); - - gOpt.viewportSize = options.viewportSize ? options.viewportSize : {x: 800, y: 600}; - gOpt.clearColor = options.clearColor ? options.clearColor : {r: 255, g: 255, b: 255}; - - return gOpt; - } } \ No newline at end of file diff --git a/src/Loop/GameOptions.ts b/src/Loop/GameOptions.ts new file mode 100644 index 0000000..f4a5614 --- /dev/null +++ b/src/Loop/GameOptions.ts @@ -0,0 +1,24 @@ +// @ignorePage + +/** The options for initializing the @reference[GameLoop] */ +export default class GameOptions { + /** The size of the viewport */ + viewportSize: {x: number, y: number}; + + /** The color to clear the canvas to each frame */ + clearColor: {r: number, g: number, b: number} + + /** + * Parses the data in the raw options object + * @param options The game options as a Record + * @returns A version of the options converted to a GameOptions object + */ + static parse(options: Record): GameOptions { + let gOpt = new GameOptions(); + + gOpt.viewportSize = options.viewportSize ? options.viewportSize : {x: 800, y: 600}; + gOpt.clearColor = options.clearColor ? options.clearColor : {r: 255, g: 255, b: 255}; + + return gOpt; + } +} \ No newline at end of file diff --git a/src/Nodes/Tilemap.ts b/src/Nodes/Tilemap.ts index 52fa107..524a173 100644 --- a/src/Nodes/Tilemap.ts +++ b/src/Nodes/Tilemap.ts @@ -73,7 +73,7 @@ export default abstract class Tilemap extends CanvasNode { /** * Adds this tilemap to the physics system */ - addPhysics = (): void => { + addPhysics(): void { this.scene.getPhysicsManager().registerTilemap(this); } @@ -99,9 +99,9 @@ export default abstract class Tilemap extends CanvasNode { abstract getTile(index: number): number; /** - * Sets the value of the tile at the specified index - * @param index - * @param type + * Sets the tile at the specified index + * @param index The index of the tile + * @param type The new data value of the tile */ abstract setTile(index: number, type: number): void; diff --git a/src/Nodes/Tilemaps/OrthogonalTilemap.ts b/src/Nodes/Tilemaps/OrthogonalTilemap.ts index f842913..cda78f7 100644 --- a/src/Nodes/Tilemaps/OrthogonalTilemap.ts +++ b/src/Nodes/Tilemaps/OrthogonalTilemap.ts @@ -101,11 +101,7 @@ export default class OrthogonalTilemap extends Tilemap { return this.data[index]; } - /** - * Sets the tile at the specified index - * @param index The index of the tile - * @param type The new data value of the tile - */ + // @override setTile(index: number, type: number): void { this.data[index] = type; } diff --git a/src/Nodes/UIElements/Label.ts b/src/Nodes/UIElements/Label.ts index 0e79d86..37e558c 100644 --- a/src/Nodes/UIElements/Label.ts +++ b/src/Nodes/UIElements/Label.ts @@ -46,7 +46,7 @@ export default class Label extends UIElement{ * Gets a string containg the font details for rendering * @returns A string containing the font details */ - protected getFontString(): string { + getFontString(): string { return this.fontSize + "px " + this.font; } diff --git a/src/Nodes/UIElements/Slider.ts b/src/Nodes/UIElements/Slider.ts index db5fc75..605de19 100644 --- a/src/Nodes/UIElements/Slider.ts +++ b/src/Nodes/UIElements/Slider.ts @@ -12,7 +12,7 @@ export default class Slider extends UIElement { /** The color of the slider track */ public sliderColor: Color; /** The reaction of this UIElement to a value change */ - public onValueChange: (value: number) => void; + public onValueChange: Function; /** The event propagated by this UIElement when value changes */ public onValueChangeEventId: string; diff --git a/src/Pathfinding/NavigationManager.ts b/src/Pathfinding/NavigationManager.ts index 6c363da..5ebaff5 100644 --- a/src/Pathfinding/NavigationManager.ts +++ b/src/Pathfinding/NavigationManager.ts @@ -3,18 +3,35 @@ import Map from "../DataTypes/Map"; import Vec2 from "../DataTypes/Vec2"; import NavigationPath from "./NavigationPath"; +/** + * The manager class for navigation. + * Handles all navigable entities, such and allows them to be accessed by outside systems by requesting a path + * from one position to another. + */ export default class NavigationManager { - + /** The list of all navigable entities */ protected navigableEntities: Map; constructor(){ this.navigableEntities = new Map(); } - addNavigableEntity(navName: string, nav: Navigable){ + /** + * Adds a navigable entity to the NavigationManager + * @param navName The name of the navigable entitry + * @param nav The actual Navigable instance + */ + addNavigableEntity(navName: string, nav: Navigable): void { this.navigableEntities.add(navName, nav); } + /** + * Gets a path frome one point to another using a specified Navigable object + * @param navName The name of the registered Navigable object + * @param fromPosition The starting position of navigation + * @param toPosition The ending position of Navigation + * @returns A NavigationPath containing the route to take over the Navigable entity to get between the provided positions. + */ getPath(navName: string, fromPosition: Vec2, toPosition: Vec2): NavigationPath { let nav = this.navigableEntities.get(navName); return nav.getNavigationPath(fromPosition.clone(), toPosition.clone()); diff --git a/src/Pathfinding/NavigationPath.ts b/src/Pathfinding/NavigationPath.ts index 9277322..dcb1b62 100644 --- a/src/Pathfinding/NavigationPath.ts +++ b/src/Pathfinding/NavigationPath.ts @@ -6,10 +6,17 @@ import GameNode from "../Nodes/GameNode"; * A path that AIs can follow. Uses finishMove() in Physical to determine progress on the route */ export default class NavigationPath { - protected path: Stack + /** The navigation path, stored as a stack of next positions */ + protected path: Stack; + /** The current direction of movement */ protected currentMoveDirection: Vec2; + /** The distance a node must be to a point to consider it as having arrived */ protected distanceThreshold: number; + /** + * Constructs a new NavigationPath + * @param path The path of nodes to take + */ constructor(path: Stack){ this.path = path; console.log(path.toString()) @@ -17,15 +24,28 @@ export default class NavigationPath { this.distanceThreshold = 20; } + /** + * Returns the status of navigation along this NavigationPath + * @returns True if the node has reached the end of the path, false otherwise + */ isDone(): boolean { return this.path.isEmpty(); } + /** + * Gets the movement direction in the current position along the path + * @param node The node to move along the path + * @returns The movement direction as a Vec2 + */ getMoveDirection(node: GameNode): Vec2 { // Return direction to next point in the nav return node.position.dirTo(this.path.peek()); } + /** + * Updates this NavigationPath to the current state of the GameNode + * @param node The node moving along the path + */ handlePathProgress(node: GameNode): void { if(node.position.distanceSqTo(this.path.peek()) < this.distanceThreshold*this.distanceThreshold){ // We've reached our node, move on to the next destination diff --git a/src/Pathfinding/Navmesh.ts b/src/Pathfinding/Navmesh.ts index d6e19e2..58aa9ed 100644 --- a/src/Pathfinding/Navmesh.ts +++ b/src/Pathfinding/Navmesh.ts @@ -5,13 +5,22 @@ import Vec2 from "../DataTypes/Vec2"; import GraphUtils from "../Utils/GraphUtils"; import NavigationPath from "./NavigationPath"; +/** + * An implementation of a Navmesh. Navmeshes are graphs in the game world along which nodes can move. + */ export default class Navmesh implements Navigable { + /** The graph of points in the NavMesh */ protected graph: PositionGraph; + /** + * Creates a new Navmesh from the points in the speecified graph + * @param graph The graph to construct a navmesh from + */ constructor(graph: PositionGraph){ this.graph = graph; } + // @implemented getNavigationPath(fromPosition: Vec2, toPosition: Vec2): NavigationPath { let start = this.getClosestNode(fromPosition); let end = this.getClosestNode(toPosition); @@ -34,7 +43,12 @@ export default class Navmesh implements Navigable { return new NavigationPath(pathStack); } - getClosestNode(position: Vec2): number { + /** + * Gets the closest node in this Navmesh to the specified position + * @param position The position to query + * @returns The index of the closest node in the Navmesh to the position + */ + protected getClosestNode(position: Vec2): number { let n = this.graph.numVertices; let i = 1; let index = 0; diff --git a/src/Physics/BasicPhysicsManager.ts b/src/Physics/BasicPhysicsManager.ts index e50f229..9881dab 100644 --- a/src/Physics/BasicPhysicsManager.ts +++ b/src/Physics/BasicPhysicsManager.ts @@ -11,6 +11,8 @@ import OrthogonalTilemap from "../Nodes/Tilemaps/OrthogonalTilemap"; import AABB from "../DataTypes/Shapes/AABB"; import Debug from "../Debug/Debug"; +// @ignorePage + export default class BasicPhysicsManager extends PhysicsManager { /** The array of static nodes */ diff --git a/src/Physics/BroadPhaseAlgorithms/BroadPhase.ts b/src/Physics/BroadPhaseAlgorithms/BroadPhase.ts index de0bbd9..26baa4b 100644 --- a/src/Physics/BroadPhaseAlgorithms/BroadPhase.ts +++ b/src/Physics/BroadPhaseAlgorithms/BroadPhase.ts @@ -1,6 +1,8 @@ import { Physical } from "../../DataTypes/Interfaces/Descriptors"; import GameNode from "../../Nodes/GameNode"; +// @ignorePage + export default abstract class BroadPhase { /** * Runs the algorithm and returns an array of possible collision pairs. diff --git a/src/Physics/BroadPhaseAlgorithms/SweepAndPrune.ts b/src/Physics/BroadPhaseAlgorithms/SweepAndPrune.ts index 085444c..6d939e5 100644 --- a/src/Physics/BroadPhaseAlgorithms/SweepAndPrune.ts +++ b/src/Physics/BroadPhaseAlgorithms/SweepAndPrune.ts @@ -3,6 +3,8 @@ import PhysicsUtils from "../../Utils/PhysicsUtils"; import SortingUtils from "../../Utils/SortingUtils"; import BroadPhase from "./BroadPhase"; +// @ignorePage + export default class SweepAndPrune extends BroadPhase { protected xList: Array; protected yList: Array; diff --git a/src/Physics/Collisions.ts b/src/Physics/Collisions.ts index a4a5417..6342312 100644 --- a/src/Physics/Collisions.ts +++ b/src/Physics/Collisions.ts @@ -1,6 +1,8 @@ import { Physical } from "../DataTypes/Interfaces/Descriptors"; import Vec2 from "../DataTypes/Vec2"; +// @ignorePage + export class Collision { firstContact: Vec2; lastContact: Vec2; diff --git a/src/Physics/PhysicsManager.ts b/src/Physics/PhysicsManager.ts index 0863bfc..2583a9d 100644 --- a/src/Physics/PhysicsManager.ts +++ b/src/Physics/PhysicsManager.ts @@ -6,14 +6,20 @@ import Receiver from "../Events/Receiver"; import Emitter from "../Events/Emitter"; import Map from "../DataTypes/Map"; +/** + * An abstract physics manager. + * This class exposes functions for subclasses to implement that should allow for a working physics system to be created. + */ export default abstract class PhysicsManager implements Updateable { + /** The event receiver for the physics system */ protected receiver: Receiver; + /** The event emitter for the physics system */ protected emitter: Emitter; - /** Layer names to numbers */ + /** Maps layer names to numbers */ protected layerMap: Map; - /** Layer numbers to names */ + /** Maps layer numbers to names */ protected layerNames: Array; constructor(){ @@ -27,22 +33,23 @@ export default abstract class PhysicsManager implements Updateable { /** * Registers a gamenode with this physics manager - * @param object + * @param object The object to register */ abstract registerObject(object: GameNode): void; /** * Registers a tilemap with this physics manager - * @param tilemap + * @param tilemap The tilemap to register */ abstract registerTilemap(tilemap: Tilemap): void; - /** - * Updates the physics - * @param deltaT - */ abstract update(deltaT: number): void; + /** + * Sets the physics layer of the GameNode + * @param node The GameNode + * @param layer The layer that the GameNode should be on + */ setLayer(node: GameNode, layer: string): void { node.physicsLayer = this.layerMap.get(layer); } diff --git a/src/Physics/TestPhysicsManager.ts b/src/Physics/TestPhysicsManager.ts index a13e102..3e0c7e8 100644 --- a/src/Physics/TestPhysicsManager.ts +++ b/src/Physics/TestPhysicsManager.ts @@ -1,13 +1,46 @@ import GameNode from "../Nodes/GameNode"; -import { Physical, Updateable } from "../DataTypes/Interfaces/Descriptors"; +import { Physical } from "../DataTypes/Interfaces/Descriptors"; import Tilemap from "../Nodes/Tilemap"; import PhysicsManager from "./PhysicsManager"; import Vec2 from "../DataTypes/Vec2"; -import Debug from "../Debug/Debug"; -import Color from "../Utils/Color"; import AABB from "../DataTypes/Shapes/AABB"; import OrthogonalTilemap from "../Nodes/Tilemaps/OrthogonalTilemap"; +import AreaCollision from "../DataTypes/Physics/AreaCollision"; +/** + * ALGORITHM: + * In an effort to keep things simple and working effectively, each dynamic node will resolve its + * collisions considering the rest of the world as static. + * + * Collision detecting will happen first. This can be considered a broad phase, but it is not especially + * efficient, as it does not need to be for this game engine. Every dynamic node is checked against every + * other node for collision area. If collision area is non-zero (meaning the current node sweeps into another), + * it is added to a list of hits. + * + * INITIALIZATION: + * - Physics constants are reset + * - Swept shapes are recalculated. If a node isn't moving, it is skipped. + * + * COLLISION DETECTION: + * - For a node, collision area will be calculated using the swept AABB of the node against every other AABB in a static state + * - These collisions will be sorted by area in descending order + * + * COLLISION RESOLUTION: + * - For each hit, time of collision is calculated using a swept line through the AABB of the static node expanded + * with minkowski sums (discretely, but the concept is there) + * - The collision is resolved based on the near time of the collision (from method of separated axes) + * - X is resolved by near x, Y by near y. + * - There is some fudging to allow for sliding along walls of separate colliders. Sorting by area also helps with this. + * - Corner to corner collisions are resolve to favor x-movement. This is in consideration of platformers, to give + * the player some help with jumps + * + * Pros: + * - Everything happens with a consistent time. There is a distinct before and after for each resolution. + * - No back-tracking needs to be done. Once we resolve a node, it is definitively resolved. + * + * Cons: + * - Nodes that are processed early have movement priority over other nodes. This can lead to some undesirable interactions. + */ export default class TestPhysicsManager extends PhysicsManager { /** The array of static nodes */ @@ -26,10 +59,7 @@ export default class TestPhysicsManager extends PhysicsManager { this.tilemaps = new Array(); } - /** - * Add a new physics object to be updated with the physics system - * @param node The node to be added to the physics system - */ + // @override registerObject(node: GameNode): void { if(node.isStatic){ // Static and not collidable @@ -40,56 +70,18 @@ export default class TestPhysicsManager extends PhysicsManager { } } - /** - * Registers a tilemap with this physics manager - * @param tilemap - */ + // @override registerTilemap(tilemap: Tilemap): void { this.tilemaps.push(tilemap); } + // @override setLayer(node: GameNode, layer: string): void { node.physicsLayer = this.layerMap.get(layer); } - /** - * Updates the physics - * @param deltaT - */ + // @override update(deltaT: number): void { - /* ALGORITHM: - In an effort to keep things simple and working effectively, each dynamic node will resolve its - collisions considering the rest of the world as static. - - Collision detecting will happen first. This can be considered a broad phase, but it is not especially - efficient, as it does not need to be for this game engine. Every dynamic node is checked against every - other node for collision area. If collision area is non-zero (meaning the current node sweeps into another), - it is added to a list of hits. - - INITIALIZATION: - - Physics constants are reset - - Swept shapes are recalculated. If a node isn't moving, it is skipped. - - COLLISION DETECTION: - - For a node, collision area will be calculated using the swept AABB of the node against every other AABB in a static state - - These collisions will be sorted by area in descending order - - COLLISION RESOLUTION: - - For each hit, time of collision is calculated using a swept line through the AABB of the static node expanded - with minkowski sums (discretely, but the concept is there) - - The collision is resolved based on the near time of the collision (from method of separated axes) - - X is resolved by near x, Y by near y. - - There is some fudging to allow for sliding along walls of separate colliders. Sorting by area also helps with this. - - Corner to corner collisions are resolve to favor x-movement. This is in consideration of platformers, to give - the player some help with jumps - - Pros: - - Everything happens with a consistent time. There is a distinct before and after for each resolution. - - No back-tracking needs to be done. Once we resolve a node, it is definitively resolved. - - Cons: - - Nodes that are processed early have movement priority over other nodes. This can lead to some undesirable interactions. - */ for(let node of this.dynamicNodes){ /*---------- INITIALIZATION PHASE ----------*/ // Clear frame dependent boolean values for each node @@ -186,7 +178,13 @@ export default class TestPhysicsManager extends PhysicsManager { } } - collideWithOrthogonalTilemap(node: Physical, tilemap: OrthogonalTilemap, overlaps: Array): void { + /** + * Handles a collision between this node and an orthogonal tilemap + * @param node The node + * @param tilemap The tilemap the node may be colliding with + * @param overlaps The list of overlaps + */ + protected collideWithOrthogonalTilemap(node: Physical, tilemap: OrthogonalTilemap, overlaps: Array): void { // Get the min and max x and y coordinates of the moving node let min = new Vec2(node.sweptRect.left, node.sweptRect.top); let max = new Vec2(node.sweptRect.right, node.sweptRect.bottom); @@ -217,13 +215,4 @@ export default class TestPhysicsManager extends PhysicsManager { } } } -} - -class AreaCollision { - area: number; - collider: AABB; - constructor(area: number, collider: AABB){ - this.area = area; - this.collider = collider; - } } \ No newline at end of file diff --git a/src/Playback/Recorder.ts b/src/Playback/Recorder.ts index c9f2ff6..676cb09 100644 --- a/src/Playback/Recorder.ts +++ b/src/Playback/Recorder.ts @@ -4,7 +4,9 @@ import GameEvent from "../Events/GameEvent"; import EventQueue from "../Events/EventQueue"; import { GameEventType } from "../Events/GameEventType"; -export default class Recorder{ +// @ignorePage + +export default class Recorder { private receiver: Receiver; private log: Queue; private recording: boolean; diff --git a/src/Rendering/Animations/AnimationManager.ts b/src/Rendering/Animations/AnimationManager.ts index 236147b..e4cc0c5 100644 --- a/src/Rendering/Animations/AnimationManager.ts +++ b/src/Rendering/Animations/AnimationManager.ts @@ -3,6 +3,12 @@ import Emitter from "../../Events/Emitter"; import CanvasNode from "../../Nodes/CanvasNode"; import { AnimationData, AnimationState } from "./AnimationTypes"; +/** + * An animation manager class for an animated CanvasNode. + * This class keeps track of the possible animations, as well as the current animation state, + * and abstracts all interactions with playing, pausing, and stopping animations as well as + * creating new animations from the CanvasNode. + */ export default class AnimationManager { /** The owner of this animation manager */ protected owner: CanvasNode; @@ -40,6 +46,10 @@ export default class AnimationManager { /** The onEnd event of a pending animation */ protected pendingOnEnd: string; + /** + * Creates a new AnimationManager + * @param owner The owner of the AnimationManager + */ constructor(owner: CanvasNode){ this.owner = owner; this.animationState = AnimationState.STOPPED; @@ -61,7 +71,10 @@ export default class AnimationManager { this.animations.add(key, animation); } - /** Gets the index specified by the current animation and current frame */ + /** + * Gets the index specified by the current animation and current frame + * @returns The index in the current animation + */ getIndex(): number { if(this.animations.has(this.currentAnimation)){ return this.animations.get(this.currentAnimation).frames[this.currentFrame].index; @@ -72,6 +85,10 @@ export default class AnimationManager { } } + /** + * Retrieves the current animation index and advances the animation frame + * @returns The index of the animation frame + */ getIndexAndAdvanceAnimation(): number { // If we aren't playing, we won't be advancing the animation if(!(this.animationState === AnimationState.PLAYING)){ @@ -109,6 +126,7 @@ export default class AnimationManager { } } + /** Ends the current animation and fires any necessary events, as well as starting any new animations */ protected endCurrentAnimation(): void { this.currentFrame = 0; this.animationState = AnimationState.STOPPED; @@ -144,7 +162,13 @@ export default class AnimationManager { this.pendingAnimation = null; } - /** Queues a single animation to be played after the current one. Does NOT stack */ + /** + * Queues a single animation to be played after the current one. Does NOT stack. + * Queueing additional animations past 1 will just replace the queued animation + * @param animation The animation to queue + * @param loop Whether or not the loop the queued animation + * @param onEnd The event to fire when the queued animation ends + */ queue(animation: string, loop: boolean = false, onEnd?: string): void { this.pendingAnimation = animation; this.pendingLoop = loop; diff --git a/src/Rendering/Animations/AnimationTypes.ts b/src/Rendering/Animations/AnimationTypes.ts index 4b4d017..e96f7bd 100644 --- a/src/Rendering/Animations/AnimationTypes.ts +++ b/src/Rendering/Animations/AnimationTypes.ts @@ -1,6 +1,8 @@ import { TweenableProperties } from "../../Nodes/GameNode"; import { EaseFunctionType } from "../../Utils/EaseFunctions"; +// @ignorePage + export enum AnimationState { STOPPED = 0, PAUSED = 1, diff --git a/src/Rendering/Animations/TweenManager.ts b/src/Rendering/Animations/TweenManager.ts index b405a85..5e46d8f 100644 --- a/src/Rendering/Animations/TweenManager.ts +++ b/src/Rendering/Animations/TweenManager.ts @@ -4,11 +4,24 @@ import { AnimationState, TweenData } from "./AnimationTypes"; import EaseFunctions from "../../Utils/EaseFunctions"; import MathUtils from "../../Utils/MathUtils"; +/** + * A manager for the tweens of a GameNode. + * Tweens are short animations played by interpolating between two properties using an easing function. + * For a good visual representation of easing functions, check out @link(https://easings.net/)(https://easings.net/). + * Multiple tween can be played at the same time, as long as they don't change the same property. + * This allows for some interesting polishes or animations that may be very difficult to do with sprite work alone + * - especially pixel art (such as rotations or scaling). + */ export default class TweenManager { + /** The GameNode this TweenManager acts upon */ protected owner: GameNode; - + /** The list of created tweens */ protected tweens: Map; + /** + * Creates a new TweenManager + * @param owner The owner of the TweenManager + */ constructor(owner: GameNode){ this.owner = owner; this.tweens = new Map(); @@ -83,7 +96,7 @@ export default class TweenManager { /** * Stops a currently playing tween - * @param key + * @param key The key of the tween */ stop(key: string): void { if(this.tweens.has(key)){ diff --git a/src/Rendering/CanvasRenderer.ts b/src/Rendering/CanvasRenderer.ts index d373399..caa1cb5 100644 --- a/src/Rendering/CanvasRenderer.ts +++ b/src/Rendering/CanvasRenderer.ts @@ -20,6 +20,9 @@ import TextInput from "../Nodes/UIElements/TextInput"; import AnimatedSprite from "../Nodes/Sprites/AnimatedSprite"; import Vec2 from "../DataTypes/Vec2"; +/** + * An implementation of the RenderingManager class using CanvasRenderingContext2D. + */ export default class CanvasRenderer extends RenderingManager { protected ctx: CanvasRenderingContext2D; protected graphicRenderer: GraphicRenderer; @@ -33,6 +36,7 @@ export default class CanvasRenderer extends RenderingManager { super(); } + // @override setScene(scene: Scene){ this.scene = scene; this.graphicRenderer.setScene(scene); @@ -40,6 +44,7 @@ export default class CanvasRenderer extends RenderingManager { this.uiElementRenderer.setScene(scene); } + // @override initializeCanvas(canvas: HTMLCanvasElement, width: number, height: number): CanvasRenderingContext2D { canvas.width = width; canvas.height = height; @@ -56,6 +61,7 @@ export default class CanvasRenderer extends RenderingManager { return this.ctx; } + // @override render(visibleSet: CanvasNode[], tilemaps: Tilemap[], uiLayers: Map): void { // Sort by depth, then by visible set by y-value visibleSet.sort((a, b) => { @@ -104,6 +110,10 @@ export default class CanvasRenderer extends RenderingManager { uiLayers.forEach(key => uiLayers.get(key).getItems().forEach(node => this.renderNode(node))); } + /** + * Renders a specified CanvasNode + * @param node The CanvasNode to render + */ protected renderNode(node: CanvasNode): void { // Calculate the origin of the viewport according to this sprite this.origin = this.scene.getViewTranslation(node); @@ -139,6 +149,7 @@ export default class CanvasRenderer extends RenderingManager { this.ctx.setTransform(1, 0, 0, 1, 0, 0); } + // @override protected renderSprite(sprite: Sprite): void { // Get the image from the resource manager let image = this.resourceManager.getImage(sprite.imageId); @@ -156,16 +167,9 @@ export default class CanvasRenderer extends RenderingManager { sprite.size.x, sprite.size.y, (-sprite.size.x*sprite.scale.x/2)*this.zoom, (-sprite.size.y*sprite.scale.y/2)*this.zoom, sprite.size.x * sprite.scale.x*this.zoom, sprite.size.y * sprite.scale.y*this.zoom); - - // Debug mode - if(this.debug){ - this.ctx.lineWidth = 4; - this.ctx.strokeStyle = "#00FF00" - let b = sprite.boundary; - this.ctx.strokeRect(-b.hw*this.zoom, -b.hh*this.zoom, b.hw*2*this.zoom, b.hh*2*this.zoom); - } } + // @override protected renderAnimatedSprite(sprite: AnimatedSprite): void { // Get the image from the resource manager let image = this.resourceManager.getImage(sprite.imageId); @@ -187,16 +191,9 @@ export default class CanvasRenderer extends RenderingManager { sprite.size.x, sprite.size.y, (-sprite.size.x*sprite.scale.x/2)*this.zoom, (-sprite.size.y*sprite.scale.y/2)*this.zoom, sprite.size.x * sprite.scale.x*this.zoom, sprite.size.y * sprite.scale.y*this.zoom); - - // Debug mode - if(this.debug){ - this.ctx.lineWidth = 4; - this.ctx.strokeStyle = "#00FF00" - let b = sprite.boundary; - this.ctx.strokeRect(-b.hw*this.zoom, -b.hh*this.zoom, b.hw*2*this.zoom, b.hh*2*this.zoom); - } } + // @override protected renderGraphic(graphic: Graphic): void { if(graphic instanceof Point){ this.graphicRenderer.renderPoint(graphic, this.zoom); @@ -205,12 +202,14 @@ export default class CanvasRenderer extends RenderingManager { } } + // @override protected renderTilemap(tilemap: Tilemap): void { if(tilemap instanceof OrthogonalTilemap){ this.tilemapRenderer.renderOrthogonalTilemap(tilemap); } } + // @override protected renderUIElement(uiElement: UIElement): void { if(uiElement instanceof Label){ this.uiElementRenderer.renderLabel(uiElement); diff --git a/src/Rendering/CanvasRendering/GraphicRenderer.ts b/src/Rendering/CanvasRendering/GraphicRenderer.ts index 4835272..62e21ba 100644 --- a/src/Rendering/CanvasRendering/GraphicRenderer.ts +++ b/src/Rendering/CanvasRendering/GraphicRenderer.ts @@ -1,12 +1,17 @@ -import Vec2 from "../../DataTypes/Vec2"; import Point from "../../Nodes/Graphics/Point"; import Rect from "../../Nodes/Graphics/Rect"; import ResourceManager from "../../ResourceManager/ResourceManager"; import Scene from "../../Scene/Scene"; +/** + * A utility class to help the @reference[CanvasRenderer] render @reference[Graphic]s + */ export default class GraphicRenderer { + /** The resource manager of the game engine */ protected resourceManager: ResourceManager; + /** The current scene */ protected scene: Scene; + /** The rendering context */ protected ctx: CanvasRenderingContext2D; constructor(ctx: CanvasRenderingContext2D){ @@ -14,16 +19,30 @@ export default class GraphicRenderer { this.ctx = ctx; } + /** + * Sets the scene of this GraphicRenderer + * @param scene The current scene + */ setScene(scene: Scene): void { this.scene = scene; } + /** + * Renders a point + * @param point The point to render + * @param zoom The zoom level + */ renderPoint(point: Point, zoom: number): void { this.ctx.fillStyle = point.color.toStringRGBA(); this.ctx.fillRect((-point.size.x/2)*zoom, (-point.size.y/2)*zoom, point.size.x*zoom, point.size.y*zoom); } + /** + * Renders a rect + * @param rect The rect to render + * @param zoom The zoom level + */ renderRect(rect: Rect, zoom: number): void { // Draw the interior of the rect if(rect.color.a !== 0){ diff --git a/src/Rendering/CanvasRendering/TilemapRenderer.ts b/src/Rendering/CanvasRendering/TilemapRenderer.ts index 6d53bf2..3741d12 100644 --- a/src/Rendering/CanvasRendering/TilemapRenderer.ts +++ b/src/Rendering/CanvasRendering/TilemapRenderer.ts @@ -4,6 +4,9 @@ import OrthogonalTilemap from "../../Nodes/Tilemaps/OrthogonalTilemap"; import Vec2 from "../../DataTypes/Vec2"; import Tileset from "../../DataTypes/Tilesets/Tileset"; +/** + * A utility class for the @reference[CanvasRenderer] to render @reference[Tilemap]s + */ export default class TilemapRenderer { protected resourceManager: ResourceManager; protected scene: Scene; @@ -14,10 +17,18 @@ export default class TilemapRenderer { this.ctx = ctx; } + /** + * Sets the scene of this TilemapRenderer + * @param scene The current scene + */ setScene(scene: Scene): void { this.scene = scene; } + /** + * Renders an orthogonal tilemap + * @param tilemap The tilemap to render + */ renderOrthogonalTilemap(tilemap: OrthogonalTilemap): void { let previousAlpha = this.ctx.globalAlpha; this.ctx.globalAlpha = tilemap.getLayer().getAlpha(); @@ -49,6 +60,16 @@ export default class TilemapRenderer { this.ctx.globalAlpha = previousAlpha; } + /** + * Renders a tile + * @param tileset The tileset this tile belongs to + * @param tileIndex The index of the tile + * @param tilemapRow The row of the tile in the tilemap + * @param tilemapCol The column of the tile in the tilemap + * @param origin The origin of the viewport + * @param scale The scale of the tilemap + * @param zoom The zoom level of the viewport + */ protected renderTile(tileset: Tileset, tileIndex: number, tilemapRow: number, tilemapCol: number, origin: Vec2, scale: Vec2, zoom: number): void { let image = this.resourceManager.getImage(tileset.getImageKey()); diff --git a/src/Rendering/CanvasRendering/UIElementRenderer.ts b/src/Rendering/CanvasRendering/UIElementRenderer.ts index 818eb60..f57cce9 100644 --- a/src/Rendering/CanvasRendering/UIElementRenderer.ts +++ b/src/Rendering/CanvasRendering/UIElementRenderer.ts @@ -7,6 +7,9 @@ import ResourceManager from "../../ResourceManager/ResourceManager"; import Scene from "../../Scene/Scene"; import MathUtils from "../../Utils/MathUtils"; +/** + * A utility class to help the @reference[CanvasRenderer] render @reference[UIElement]s + */ export default class UIElementRenderer { protected resourceManager: ResourceManager; protected scene: Scene; @@ -17,10 +20,18 @@ export default class UIElementRenderer { this.ctx = ctx; } + /** + * Sets the scene of this UIElementRenderer + * @param scene The current scene + */ setScene(scene: Scene): void { this.scene = scene; } + /** + * Renders a label + * @param label The label to render + */ renderLabel(label: Label): void { // If the size is unassigned (by the user or automatically) assign it label.handleInitialSizing(this.ctx); @@ -51,10 +62,18 @@ export default class UIElementRenderer { this.ctx.globalAlpha = previousAlpha; } + /** + * Renders a button + * @param button The button to render + */ renderButton(button: Button): void { this.renderLabel(button); } + /** + * Renders a slider + * @param slider The slider to render + */ renderSlider(slider: Slider): void { // Grab the global alpha so we can adjust it for this render let previousAlpha = this.ctx.globalAlpha; @@ -82,6 +101,10 @@ export default class UIElementRenderer { this.ctx.globalAlpha = previousAlpha; } + /** + * Renders a textInput + * @param textInput The textInput to render + */ renderTextInput(textInput: TextInput): void { // Show a cursor sometimes if(textInput.focused && textInput.cursorCounter % 60 > 30){ diff --git a/src/Rendering/RenderingManager.ts b/src/Rendering/RenderingManager.ts index 9f84c6a..62f1605 100644 --- a/src/Rendering/RenderingManager.ts +++ b/src/Rendering/RenderingManager.ts @@ -9,32 +9,73 @@ import ResourceManager from "../ResourceManager/ResourceManager"; import UILayer from "../Scene/Layers/UILayer"; import Scene from "../Scene/Scene"; +/** + * An abstract framework to put all rendering in once place in the application + */ export default abstract class RenderingManager { - // Give the renderer access to the resource manager + /** The ResourceManager */ protected resourceManager: ResourceManager; + + /** The scene currently being rendered */ protected scene: Scene; - debug: boolean; constructor(){ this.resourceManager = ResourceManager.getInstance(); - this.debug = false; } + /** + * Sets the scene currently being rendered + * @param scene The current Scene + */ setScene(scene: Scene): void { this.scene = scene; } + /** + * Initialize the canvas for the game + * @param canvas The canvas element + * @param width The desired width of the canvas + * @param height The desired height of the canvas + * @returns The rendering context of the canvas + */ abstract initializeCanvas(canvas: HTMLCanvasElement, width: number, height: number): any; + /** + * Renders the visible set of CanvasNodes and visible portions of tilemaps, as well as any UIElement in UILayers + * @param visibleSet The visible set of CanvasNodes + * @param tilemaps The tilemaps used in the application + * @param uiLayers The user interface layers + */ abstract render(visibleSet: Array, tilemaps: Array, uiLayers: Map): void; + /** + * Renders a sprite + * @param sprite The sprite to render + */ protected abstract renderSprite(sprite: Sprite): void; + /** + * Renders an animated sprite + * @param sprite The animated sprite to render + */ protected abstract renderAnimatedSprite(sprite: AnimatedSprite): void; + /** + * Renders a graphic + * @param graphic The graphic to render + */ protected abstract renderGraphic(graphic: Graphic): void; + /** + * Renders a tilemap + * @param tilemap The tilemap to render + */ protected abstract renderTilemap(tilemap: Tilemap): void; + + /** + * Renders a UIElement + * @param uiElement The UIElement to render + */ protected abstract renderUIElement(uiElement: UIElement): void; } \ No newline at end of file diff --git a/src/ResourceManager/ResourceManager.ts b/src/ResourceManager/ResourceManager.ts index 16ea752..6c5ca26 100644 --- a/src/ResourceManager/ResourceManager.ts +++ b/src/ResourceManager/ResourceManager.ts @@ -5,16 +5,27 @@ import StringUtils from "../Utils/StringUtils"; import AudioManager from "../Sound/AudioManager"; import Spritesheet from "../DataTypes/Spritesheet"; +/** + * The resource manager for the game engine. + * The resource manager interfaces with the loadable assets of a game such as images, data files, + * and sounds, which are all found in the dist folder. + * This class controls loading and updates the @reference[Scene] with the loading progress, so that the scene does + * not start before all necessary assets are loaded. + */ export default class ResourceManager { // Instance for the singleton class private static instance: ResourceManager; // Booleans to keep track of whether or not the ResourceManager is currently loading something + /** Whether or not any resources are loading */ private loading: boolean; + /** A boolean to indicate that the assets just finished loading */ private justLoaded: boolean; // Functions to do something when loading progresses or is completed such as render a loading screen + /** A function that is called when loading progresses */ public onLoadProgress: Function; + /** A function that is called when loading completes */ public onLoadComplete: Function; @@ -23,7 +34,7 @@ export default class ResourceManager { /** Number to keep track of how many images are loaded */ private loadonly_imagesToLoad: number; /** The queue of images we must load */ - private loadonly_imageLoadingQueue: Queue<{key: string, path: string}>; + private loadonly_imageLoadingQueue: Queue; /** A map of the images that are currently loaded and (presumably) being used by the scene */ private images: Map; @@ -32,7 +43,7 @@ export default class ResourceManager { /** Number to keep track of how many tilemaps are loaded */ private loadonly_spritesheetsToLoad: number; /** The queue of tilemaps we must load */ - private loadonly_spritesheetLoadingQueue: Queue<{key: string, path: string}>; + private loadonly_spritesheetLoadingQueue: Queue; /** A map of the tilemaps that are currently loaded and (presumably) being used by the scene */ private spritesheets: Map; @@ -41,7 +52,7 @@ export default class ResourceManager { /** Number to keep track of how many tilemaps are loaded */ private loadonly_tilemapsToLoad: number; /** The queue of tilemaps we must load */ - private loadonly_tilemapLoadingQueue: Queue<{key: string, path: string}>; + private loadonly_tilemapLoadingQueue: Queue; /** A map of the tilemaps that are currently loaded and (presumably) being used by the scene */ private tilemaps: Map; @@ -50,7 +61,7 @@ export default class ResourceManager { /** Number to keep track of how many sounds are loaded */ private loadonly_audioToLoad: number; /** The queue of sounds we must load */ - private loadonly_audioLoadingQueue: Queue<{key: string, path: string}>; + private loadonly_audioLoadingQueue: Queue; /** A map of the sounds that are currently loaded and (presumably) being used by the scene */ private audioBuffers: Map; @@ -84,6 +95,7 @@ export default class ResourceManager { /** * Returns the current instance of this class or a new instance if none exist + * @returns The resource manager */ static getInstance(): ResourceManager { if(!this.instance){ @@ -105,6 +117,7 @@ export default class ResourceManager { /** * Retrieves a loaded image * @param key The key of the loaded image + * @returns The image element associated with this key */ public getImage(key: string): HTMLImageElement { let image = this.images.get(key); @@ -114,18 +127,28 @@ export default class ResourceManager { return image; } + /** + * Loads a spritesheet from file + * @param key The key to associate the loaded spritesheet with + * @param path The path to the spritesheet to load + */ public spritesheet(key: string, path: string): void { this.loadonly_spritesheetLoadingQueue.enqueue({key: key, path: path}); } + /** + * Retrieves a loaded spritesheet + * @param key The key of the spritesheet to load + * @returns The loaded Spritesheet + */ public getSpritesheet(key: string): Spritesheet { return this.spritesheets.get(key); } /** - * Load an audio file - * @param key - * @param path + * Loads an audio file + * @param key The key to associate with the loaded audio file + * @param path The path to the audio file to load */ public audio(key: string, path: string): void { this.loadonly_audioLoadingQueue.enqueue({key: key, path: path}); @@ -133,7 +156,8 @@ export default class ResourceManager { /** * Retrieves a loaded audio file - * @param key + * @param key The key of the audio file to load + * @returns The AudioBuffer created from the loaded audio fle */ public getAudio(key: string): AudioBuffer { return this.audioBuffers.get(key); @@ -141,8 +165,8 @@ export default class ResourceManager { /** * Load a tilemap from a json file. Automatically loads related images - * @param key - * @param path + * @param key The key to associate with the loaded tilemap + * @param path The path to the tilemap to load */ public tilemap(key: string, path: string): void { this.loadonly_tilemapLoadingQueue.enqueue({key: key, path: path}); @@ -150,7 +174,8 @@ export default class ResourceManager { /** * Retreives a loaded tilemap - * @param key + * @param key The key of the loaded tilemap + * @returns The tilemap data associated with the key */ public getTilemap(key: string): TiledTilemapData { return this.tilemaps.get(key); @@ -158,7 +183,7 @@ export default class ResourceManager { /** * Loads all resources currently in the queue - * @param callback + * @param callback The function to cal when the resources are finished loading */ loadResourcesFromQueue(callback: Function): void { this.loadonly_typesToLoad = 3; @@ -211,7 +236,7 @@ export default class ResourceManager { /** * Loads all tilemaps currently in the tilemap loading queue - * @param onFinishLoading + * @param onFinishLoading The function to call when loading is complete */ private loadTilemapsFromQueue(onFinishLoading: Function): void { this.loadonly_tilemapsToLoad = this.loadonly_tilemapLoadingQueue.getSize(); @@ -230,9 +255,9 @@ export default class ResourceManager { /** * Loads a singular tilemap - * @param key - * @param pathToTilemapJSON - * @param callbackIfLast + * @param key The key of the tilemap + * @param pathToTilemapJSON The path to the tilemap JSON file + * @param callbackIfLast The function to call if this is the last tilemap to load */ private loadTilemap(key: string, pathToTilemapJSON: string, callbackIfLast: Function): void { this.loadTextFile(pathToTilemapJSON, (fileText: string) => { @@ -263,7 +288,7 @@ export default class ResourceManager { /** * Finish loading a tilemap. Calls the callback function if this is the last tilemap being loaded - * @param callback + * @param callback The function to call if this is the last tilemap to load */ private finishLoadingTilemap(callback: Function): void { this.loadonly_tilemapsLoaded += 1; @@ -274,9 +299,9 @@ export default class ResourceManager { } } - /** + /** * Loads all spritesheets currently in the spritesheet loading queue - * @param onFinishLoading + * @param onFinishLoading The function to call when the spritesheets are done loading */ private loadSpritesheetsFromQueue(onFinishLoading: Function): void { this.loadonly_spritesheetsToLoad = this.loadonly_spritesheetLoadingQueue.getSize(); @@ -295,9 +320,9 @@ export default class ResourceManager { /** * Loads a singular spritesheet - * @param key - * @param pathToSpritesheetJSON - * @param callbackIfLast + * @param key The key of the spritesheet to load + * @param pathToSpritesheetJSON The path to the spritesheet JSON file + * @param callbackIfLast The function to call if this is the last spritesheet */ private loadSpritesheet(key: string, pathToSpritesheetJSON: string, callbackIfLast: Function): void { this.loadTextFile(pathToSpritesheetJSON, (fileText: string) => { @@ -317,7 +342,7 @@ export default class ResourceManager { /** * Finish loading a spritesheet. Calls the callback function if this is the last spritesheet being loaded - * @param callback + * @param callback The function to call if this is the last spritesheet to load */ private finishLoadingSpritesheet(callback: Function): void { this.loadonly_spritesheetsLoaded += 1; @@ -330,7 +355,7 @@ export default class ResourceManager { /** * Loads all images currently in the image loading queue - * @param onFinishLoading + * @param onFinishLoading The function to call when there are no more images to load */ private loadImagesFromQueue(onFinishLoading: Function): void { this.loadonly_imagesToLoad = this.loadonly_imageLoadingQueue.getSize(); @@ -349,9 +374,9 @@ export default class ResourceManager { /** * Loads a singular image - * @param key - * @param path - * @param callbackIfLast + * @param key The key of the image to load + * @param path The path to the image to load + * @param callbackIfLast The function to call if this is the last image */ public loadImage(key: string, path: string, callbackIfLast: Function): void { var image = new Image(); @@ -369,7 +394,7 @@ export default class ResourceManager { /** * Finish loading an image. If this is the last image, it calls the callback function - * @param callback + * @param callback The function to call if this is the last image */ private finishLoadingImage(callback: Function): void { this.loadonly_imagesLoaded += 1; @@ -382,7 +407,7 @@ export default class ResourceManager { /** * Loads all audio currently in the tilemap loading queue - * @param onFinishLoading + * @param onFinishLoading The function to call when tilemaps are done loading */ private loadAudioFromQueue(onFinishLoading: Function){ this.loadonly_audioToLoad = this.loadonly_audioLoadingQueue.getSize(); @@ -401,9 +426,9 @@ export default class ResourceManager { /** * Load a singular audio file - * @param key - * @param path - * @param callbackIfLast + * @param key The key to the audio file to load + * @param path The path to the audio file to load + * @param callbackIfLast The function to call if this is the last audio file to load */ private loadAudio(key: string, path: string, callbackIfLast: Function): void { let audioCtx = AudioManager.getInstance().getAudioContext(); @@ -428,7 +453,7 @@ export default class ResourceManager { /** * Finish loading an audio file. Calls the callback functon if this is the last audio sample being loaded. - * @param callback + * @param callback The function to call if this is the last audio file to load */ private finishLoadingAudio(callback: Function): void { this.loadonly_audioLoaded += 1; @@ -459,7 +484,7 @@ export default class ResourceManager { / this.loadonly_typesToLoad; } - public update(deltaT: number): void { + update(deltaT: number): void { if(this.loading){ if(this.onLoadProgress){ this.onLoadProgress(this.getLoadPercent()); @@ -471,4 +496,9 @@ export default class ResourceManager { } } } +} + +class KeyPathPair { + key: string + path: string } \ No newline at end of file diff --git a/src/Scene/Factories/CanvasNodeFactory.ts b/src/Scene/Factories/CanvasNodeFactory.ts index 36d406e..6d2056b 100644 --- a/src/Scene/Factories/CanvasNodeFactory.ts +++ b/src/Scene/Factories/CanvasNodeFactory.ts @@ -13,9 +13,13 @@ import Slider from "../../Nodes/UIElements/Slider"; import TextInput from "../../Nodes/UIElements/TextInput"; import Rect from "../../Nodes/Graphics/Rect"; import ResourceManager from "../../ResourceManager/ResourceManager"; -import UILayer from "../Layers/UILayer"; -import ParallaxLayer from "../Layers/ParallaxLayer"; +// @ignorePage + +/** + * A factory that abstracts adding @reference[CanvasNode]s to the @reference[Scene]. + * Access methods in this factory through Scene.add.[methodName](). + */ export default class CanvasNodeFactory { protected scene: Scene; protected resourceManager: ResourceManager; @@ -30,6 +34,7 @@ export default class CanvasNodeFactory { * @param type The type of UIElement to add * @param layerName The layer to add the UIElement to * @param options Any additional arguments to feed to the constructor + * @returns A new UIElement */ addUIElement = (type: string | UIElementType, layerName: string, options?: Record): UIElement => { // Get the layer @@ -68,6 +73,7 @@ export default class CanvasNodeFactory { * Adds a sprite to the current scene * @param key The key of the image the sprite will represent * @param layerName The layer on which to add the sprite + * @returns A new Sprite */ addSprite = (key: string, layerName: string): Sprite => { let layer = this.scene.getLayer(layerName); @@ -88,6 +94,12 @@ export default class CanvasNodeFactory { return instance; } + /** + * Adds an AnimatedSprite to the current scene + * @param key The key of the image the sprite will represent + * @param layerName The layer on which to add the sprite + * @returns A new AnimatedSprite + */ addAnimatedSprite = (key: string, layerName: string): AnimatedSprite => { let layer = this.scene.getLayer(layerName); let spritesheet = this.resourceManager.getSpritesheet(key); @@ -112,6 +124,7 @@ export default class CanvasNodeFactory { * @param type The type of graphic to add * @param layerName The layer on which to add the graphic * @param options Any additional arguments to send to the graphic constructor + * @returns A new Graphic */ addGraphic = (type: GraphicType | string, layerName: string, options?: Record): Graphic => { // Get the layer diff --git a/src/Scene/Factories/FactoryManager.ts b/src/Scene/Factories/FactoryManager.ts index 6089f48..455e539 100644 --- a/src/Scene/Factories/FactoryManager.ts +++ b/src/Scene/Factories/FactoryManager.ts @@ -2,7 +2,18 @@ import Scene from "../Scene"; import CanvasNodeFactory from "./CanvasNodeFactory"; import TilemapFactory from "./TilemapFactory"; import Tilemap from "../../Nodes/Tilemap"; +import { UIElementType } from "../../Nodes/UIElements/UIElementTypes"; +import UIElement from "../../Nodes/UIElement"; +import Sprite from "../../Nodes/Sprites/Sprite"; +import { GraphicType } from "../../Nodes/Graphics/GraphicTypes"; +import Graphic from "../../Nodes/Graphic"; +import AnimatedSprite from "../../Nodes/Sprites/AnimatedSprite"; +import Vec2 from "../../DataTypes/Vec2"; +import Layer from "../Layer"; +/** + * The manager of all factories used for adding @reference[GameNode]s to the @reference[Scene]. + */ export default class FactoryManager { // Constructors are called here to allow assignment of their functions to functions in this class @@ -15,9 +26,56 @@ export default class FactoryManager { } // Expose all of the factories through the factory manager - uiElement = this.canvasNodeFactory.addUIElement; - sprite = this.canvasNodeFactory.addSprite; - animatedSprite = this.canvasNodeFactory.addAnimatedSprite; - graphic = this.canvasNodeFactory.addGraphic; - tilemap = this.tilemapFactory.add; + /** + * Adds an instance of a UIElement to the current scene - i.e. any class that extends UIElement + * @param type The type of UIElement to add + * @param layerName The layer to add the UIElement to + * @param options Any additional arguments to feed to the constructor + * @returns A new UIElement + */ + uiElement(type: string | UIElementType, layerName: string, options?: Record): UIElement { + return this.canvasNodeFactory.addUIElement(type, layerName, options); + } + + /** + * Adds a sprite to the current scene + * @param key The key of the image the sprite will represent + * @param layerName The layer on which to add the sprite + * @returns A new Sprite + */ + sprite(key: string, layerName: string): Sprite { + return this.canvasNodeFactory.addSprite(key, layerName); + } + + /** + * Adds an AnimatedSprite to the current scene + * @param key The key of the image the sprite will represent + * @param layerName The layer on which to add the sprite + * @returns A new AnimatedSprite + */ + animatedSprite(key: string, layerName: string): AnimatedSprite { + return this.canvasNodeFactory.addAnimatedSprite(key, layerName); + } + + /** + * Adds a new graphic element to the current Scene + * @param type The type of graphic to add + * @param layerName The layer on which to add the graphic + * @param options Any additional arguments to send to the graphic constructor + * @returns A new Graphic + */ + graphic(type: GraphicType | string, layerName: string, options?: Record): Graphic { + return this.canvasNodeFactory.addGraphic(type, layerName, options); + } + + /** + * Adds a tilemap to the scene + * @param key The key of the loaded tilemap to load + * @param constr The constructor of the desired tilemap + * @param args Additional arguments to send to the tilemap constructor + * @returns An array of Layers, each of which contains a layer of the tilemap as its own Tilemap instance. + */ + tilemap(key: string, scale?: Vec2): Array { + return this.tilemapFactory.add(key, scale); + } } \ No newline at end of file diff --git a/src/Scene/Factories/TilemapFactory.ts b/src/Scene/Factories/TilemapFactory.ts index 2311bb5..eafea11 100644 --- a/src/Scene/Factories/TilemapFactory.ts +++ b/src/Scene/Factories/TilemapFactory.ts @@ -10,6 +10,12 @@ import Sprite from "../../Nodes/Sprites/Sprite"; import PositionGraph from "../../DataTypes/Graphs/PositionGraph"; import Navmesh from "../../Pathfinding/Navmesh"; +// @ignorePage + +/** + * A factory that abstracts adding @reference[Tilemap]s to the @reference[Scene]. + * Access methods in this factory through Scene.add.[methodName](). + */ export default class TilemapFactory { private scene: Scene; private tilemaps: Array; @@ -30,6 +36,7 @@ export default class TilemapFactory { * @param key The key of the loaded tilemap to load * @param constr The constructor of the desired tilemap * @param args Additional arguments to send to the tilemap constructor + * @returns An array of Layers, each of which contains a layer of the tilemap as its own Tilemap instance. */ add = (key: string, scale: Vec2 = new Vec2(1, 1)): Array => { // Get Tilemap Data diff --git a/src/Scene/Layer.ts b/src/Scene/Layer.ts index 97529ca..d529f35 100644 --- a/src/Scene/Layer.ts +++ b/src/Scene/Layer.ts @@ -4,7 +4,7 @@ import GameNode from "../Nodes/GameNode"; /** - * A layer in the scene. Has its own alpha value and parallax. + * A layer in the scene. Layers are used for sorting @reference[GameNode]s by depth. */ export default class Layer { /** The scene this layer belongs to */ @@ -31,6 +31,11 @@ export default class Layer { /** The depth of this layer compared to other layers */ protected depth: number; + /** + * Creates a new layer. To do this in a game, use the addLayer() method in @refrence[Scene] + * @param scene The scene to add the layer to + * @param name The name of the layer + */ constructor(scene: Scene, name: string){ this.scene = scene; this.name = name; @@ -42,30 +47,57 @@ export default class Layer { this.depth = 0; } + /** + * Retreives the name of the layer + * @returns The name of the layer + */ getName(): string { return this.name; } + /** + * Pauses/Unpauses the layer. Affects all elements in this layer + * @param pauseValue True if the layer should be paused, false if not + */ setPaused(pauseValue: boolean): void { this.paused = pauseValue; } + /** + * Returns whether or not the layer is paused + */ isPaused(): boolean { return this.paused; } + /** + * Sets the opacity of the layer + * @param alpha The new opacity value in the range [0, 1] + */ setAlpha(alpha: number): void { this.alpha = MathUtils.clamp(alpha, 0, 1); } + /** + * Gets the opacity of the layer + * @returns The opacity + */ getAlpha(): number { return this.alpha; } + /** + * Sets the layer's hidden value. If hidden, a layer will not be rendered, but will still update + * @param hidden The hidden value of the layer + */ setHidden(hidden: boolean): void { this.hidden = hidden; } + /** + * Returns the hideen value of the lyaer + * @returns True if the scene is hidden, false otherwise + */ isHidden(): boolean { return this.hidden; } @@ -82,27 +114,55 @@ export default class Layer { this.hidden = false; } + /** + * Sets whether or not the scene will ySort automatically. + * ySorting means that CanvasNodes on this layer will have their depth sorted depending on their y-value. + * This means that if an object is "higher" in the scene, it will sort behind objects that are "lower". + * This is useful for 3/4 view games, or similar situations, where you sometimes want to be in front of objects, + * and other times want to be behind the same objects. + * @param ySort True if ySorting should be active, false if not + */ setYSort(ySort: boolean): void { this.ySort = ySort; } + /** + * Gets the ySort status of the scene + * @returns True if ySorting is occurring, false otherwise + */ getYSort(): boolean { return this.ySort; } + /** + * Sets the depth of the layer compared to other layers. A larger number means the layer will be closer to the screen. + * @param depth The depth of the layer. + */ setDepth(depth: number): void { this.depth = depth; } + /** + * Retrieves the depth of the layer. + * @returns The depth + */ getDepth(): number { return this.depth; } + /** + * Adds a node to this layer + * @param node The node to add to this layer. + */ addNode(node: GameNode): void { this.items.push(node); node.setLayer(this); } + /** + * Retreives all GameNodes from this layer + * @returns an Array that contains all of the GameNodes in this layer. + */ getItems(): Array { return this.items; } diff --git a/src/Scene/Layers/ParallaxLayer.ts b/src/Scene/Layers/ParallaxLayer.ts index 580f3df..0260139 100644 --- a/src/Scene/Layers/ParallaxLayer.ts +++ b/src/Scene/Layers/ParallaxLayer.ts @@ -2,9 +2,20 @@ import Layer from "../Layer"; import Vec2 from "../../DataTypes/Vec2"; import Scene from "../Scene"; +/** + * An extension of a Layer that has a parallax value. + */ export default class ParallaxLayer extends Layer { + /** The value of the parallax of the Layer */ parallax: Vec2; + /** + * Creates a new ParallaxLayer. + * Use addParallaxLayer() in @reference[Scene] to add a layer of this type to your game. + * @param scene The Scene to add this ParallaxLayer to + * @param name The name of the ParallaxLayer + * @param parallax The parallax level + */ constructor(scene: Scene, name: string, parallax: Vec2){ super(scene, name); this.parallax = parallax; diff --git a/src/Scene/Layers/UILayer.ts b/src/Scene/Layers/UILayer.ts index 5a2b072..5843129 100644 --- a/src/Scene/Layers/UILayer.ts +++ b/src/Scene/Layers/UILayer.ts @@ -2,7 +2,18 @@ import Vec2 from "../../DataTypes/Vec2"; import Scene from "../Scene"; import ParallaxLayer from "./ParallaxLayer"; +/** + * A Layer strictly to be used for managing UIElements. + * This is intended to be a Layer that always stays in the same place, + * and thus renders things like a HUD or an inventory without taking into consideration the \reference[Viewport] scroll. + */ export default class UILayer extends ParallaxLayer { + /** + * Creates a new UILayer. + * Use addUILayer() in @reference[Scene] to add a layer of this type to your game. + * @param scene The Scene to add this UILayer to + * @param name The name of the UILayer + */ constructor(scene: Scene, name: string){ super(scene, name, Vec2.ZERO); } diff --git a/src/Scene/Scene.ts b/src/Scene/Scene.ts index b531974..3280b0e 100644 --- a/src/Scene/Scene.ts +++ b/src/Scene/Scene.ts @@ -20,10 +20,16 @@ import ParallaxLayer from "./Layers/ParallaxLayer"; import UILayer from "./Layers/UILayer"; import CanvasNode from "../Nodes/CanvasNode"; import GameNode from "../Nodes/GameNode"; -import ArrayUtils from "../Utils/ArrayUtils"; +import SceneOptions from "./SceneOptions"; import RenderingManager from "../Rendering/RenderingManager"; import Debug from "../Debug/Debug"; +/** + * Scenes are the main container in the game engine. + * Your main scene is the current level or menu of the game, and will contain all of the GameNodes needed. + * Scenes provide an easy way to load assets, add assets to the game world, and unload assets, + * and have lifecycle methods exposed for these functions. + */ export default class Scene implements Updateable { /** The size of the game world. */ protected worldSize: Vec2; @@ -82,6 +88,14 @@ export default class Scene implements Updateable { /** The configuration options for this scene */ public sceneOptions: SceneOptions; + /** + * Creates a new Scene. To add a new Scene in your game, use addScene() in @reference[SceneManager] + * @param viewport The viewport of the game + * @param sceneManager The SceneManager that owns this Scene + * @param renderingManager The RenderingManager that will handle this Scene's rendering + * @param game The instance of the GameLoop + * @param options The options for Scene initialization + */ constructor(viewport: Viewport, sceneManager: SceneManager, renderingManager: RenderingManager, game: GameLoop, options: Record){ this.sceneOptions = SceneOptions.parse(options); @@ -122,7 +136,7 @@ export default class Scene implements Updateable { /** * A lifecycle method called every frame of the game. This is where you can dynamically do things like add in new enemies - * @param delta + * @param delta The time this frame represents */ updateScene(deltaT: number): void {} @@ -149,6 +163,9 @@ export default class Scene implements Updateable { this.viewport.update(deltaT); } + /** + * Collects renderable sets and coordinates with the RenderingManager to draw the Scene + */ render(): void { // Get the visible set of nodes let visibleSet = this.sceneGraph.getVisibleSet(); @@ -171,10 +188,18 @@ export default class Scene implements Updateable { Debug.setNodes(nodes); } + /** + * Sets the scene as running or not + * @param running True if the Scene should be running, false if not + */ setRunning(running: boolean): void { this.running = running; } + /** + * Returns whether or not the Scene is running + * @returns True if the scene is running, false otherwise + */ isRunning(): boolean { return this.running; } @@ -183,6 +208,7 @@ export default class Scene implements Updateable { * Adds a new layer to the scene and returns it * @param name The name of the new layer * @param depth The depth of the layer + * @returns The newly created Layer */ addLayer(name: string, depth?: number): Layer { if(this.layers.has(name) || this.parallaxLayers.has(name) || this.uiLayers.has(name)){ @@ -205,6 +231,7 @@ export default class Scene implements Updateable { * @param name The name of the parallax layer * @param parallax The parallax level * @param depth The depth of the layer + * @returns The newly created ParallaxLayer */ addParallaxLayer(name: string, parallax: Vec2, depth?: number): ParallaxLayer { if(this.layers.has(name) || this.parallaxLayers.has(name) || this.uiLayers.has(name)){ @@ -225,6 +252,7 @@ export default class Scene implements Updateable { /** * Adds a new UILayer to the scene * @param name The name of the new UIlayer + * @returns The newly created UILayer */ addUILayer(name: string): UILayer { if(this.layers.has(name) || this.parallaxLayers.has(name) || this.uiLayers.has(name)){ @@ -239,8 +267,10 @@ export default class Scene implements Updateable { } /** - * Gets a layer from the scene by name if it exists + * Gets a layer from the scene by name if it exists. + * This can be a Layer or any of its subclasses * @param name The name of the layer + * @returns The Layer found with that name */ getLayer(name: string): Layer { if(this.layers.has(name)){ @@ -256,7 +286,8 @@ export default class Scene implements Updateable { /** * Returns true if this layer is a ParallaxLayer - * @param name + * @param name The name of the layer + * @returns True if this layer is a ParallaxLayer */ isParallaxLayer(name: string): boolean { return this.parallaxLayers.has(name); @@ -264,15 +295,18 @@ export default class Scene implements Updateable { /** * Returns true if this layer is a UILayer - * @param name + * @param name The name of the layer + * @returns True if this layer is ParallaxLayer */ isUILayer(name: string): boolean { return this.uiLayers.has(name); } /** - * Returns the translation of this node with respect to camera space (due to the viewport moving); - * @param node + * Returns the translation of this node with respect to camera space (due to the viewport moving). + * This value is affected by the parallax level of the @reference[Layer] the node is on. + * @param node The node to check the viewport with respect to + * @returns A Vec2 containing the translation of viewport with respect to this node. */ getViewTranslation(node: GameNode): Vec2 { let layer = node.getLayer(); @@ -284,40 +318,75 @@ export default class Scene implements Updateable { } } - /** Returns the scale level of the view */ + /** + * Returns the scale level of the view + * @returns The zoom level of the viewport + */ getViewScale(): number { return this.viewport.getZoomLevel(); } - /** Returns the viewport associated with this scene */ + /** + * Returns the Viewport associated with this scene + * @returns The current Viewport + */ getViewport(): Viewport { return this.viewport; } + /** + * Gets the world size of this Scene + * @returns The world size in a Vec2 + */ getWorldSize(): Vec2 { return this.worldSize; } + /** + * Gets the SceneGraph associated with this Scene + * @returns The SceneGraph + */ getSceneGraph(): SceneGraph { return this.sceneGraph; } + /** + * Gets the PhysicsManager associated with this Scene + * @returns The PhysicsManager + */ getPhysicsManager(): PhysicsManager { return this.physicsManager; } + /** + * Gets the NavigationManager associated with this Scene + * @returns The NavigationManager + */ getNavigationManager(): NavigationManager { return this.navManager; } + /** + * Gets the AIManager associated with this Scene + * @returns The AIManager + */ getAIManager(): AIManager { return this.aiManager; } + /** + * Generates an ID for a GameNode + * @returns The new ID + */ generateId(): number { return this.sceneManager.generateId(); } + /** + * Retrieves a Tilemap in this Scene + * @param name The name of the Tilemap + * @returns The Tilemap, if one this name exists, otherwise null + */ getTilemap(name: string): Tilemap { for(let tilemap of this .tilemaps){ if(tilemap.name === name){ @@ -327,30 +396,4 @@ export default class Scene implements Updateable { return null; } -} - -class SceneOptions { - physics: { - numPhysicsLayers: number, - physicsLayerNames: Array, - physicsLayerCollisions: Array>; - } - - static parse(options: Record): SceneOptions{ - let sOpt = new SceneOptions(); - - sOpt.physics = { - numPhysicsLayers: 10, - physicsLayerNames: null, - physicsLayerCollisions: ArrayUtils.ones2d(10, 10) - }; - - if(options.physics){ - if(options.physics.numPhysicsLayers) sOpt.physics.numPhysicsLayers = options.physics.numPhysicsLayers; - if(options.physics.physicsLayerNames) sOpt.physics.physicsLayerNames = options.physics.physicsLayerNames; - if(options.physics.physicsLayerCollisions) sOpt.physics.physicsLayerCollisions = options.physics.physicsLayerCollisions; - } - - return sOpt; - } } \ No newline at end of file diff --git a/src/Scene/SceneManager.ts b/src/Scene/SceneManager.ts index 200c403..4267669 100644 --- a/src/Scene/SceneManager.ts +++ b/src/Scene/SceneManager.ts @@ -4,14 +4,31 @@ import Viewport from "../SceneGraph/Viewport"; import GameLoop from "../Loop/GameLoop"; import RenderingManager from "../Rendering/RenderingManager"; +/** + * The SceneManager of the game engine. There is only one of theses. + * The SceneManager acts as an interface to create Scenes, and handles the lifecycle methods of Scenes. + * The Scene manager keeps track of systems that are constant across scene, such as the @reference[ResourceManager] + */ export default class SceneManager { + /** The current Scene of the game */ protected currentScene: Scene; + /** The Viewport of the game */ protected viewport: Viewport; + /** A reference to the ResourceManager */ protected resourceManager: ResourceManager; + /** The GameLoop this SceneManager belongs to */ protected game: GameLoop; + /** A counter to keep track of game ids */ protected idCounter: number; + /** The RenderingManager of the game */ protected renderingManager: RenderingManager; + /** + * Creates a new SceneManager + * @param viewport The Viewport of the game + * @param game The GameLoop instance + * @param renderingManager The RenderingManager of the game + */ constructor(viewport: Viewport, game: GameLoop, renderingManager: RenderingManager){ this.resourceManager = ResourceManager.getInstance(); this.viewport = viewport; @@ -21,7 +38,8 @@ export default class SceneManager { } /** - * Add a scene as the main scene + * Add a scene as the main scene. + * Use this method if you've created a subclass of Scene, and you want to add it as the main Scene. * @param constr The constructor of the scene to add */ public addScene(constr: new (...args: any) => T, options: Record): void { @@ -43,7 +61,8 @@ export default class SceneManager { } /** - * Change from the current scene to this new scene + * Change from the current scene to this new scene. + * Use this method if you've created a subclass of Scene, and you want to add it as the main Scene. * @param constr The constructor of the scene to change to */ public changeScene(constr: new (...args: any) => T, options: Record): void { @@ -57,14 +76,25 @@ export default class SceneManager { this.addScene(constr, options); } + /** + * Generates a unique ID + * @returns A new ID + */ public generateId(): number { return this.idCounter++; } - public render(){ + /** + * Renders the current Scene + */ + public render(): void { this.currentScene.render(); } + /** + * Updates the current Scene + * @param deltaT The timestep of the Scene + */ public update(deltaT: number){ if(this.currentScene.isRunning()){ this.currentScene.update(deltaT); diff --git a/src/Scene/SceneOptions.ts b/src/Scene/SceneOptions.ts new file mode 100644 index 0000000..bbb7256 --- /dev/null +++ b/src/Scene/SceneOptions.ts @@ -0,0 +1,32 @@ +import ArrayUtils from "../Utils/ArrayUtils"; + +// @ignorePage + +/** + * The options to give a @reference[Scene] for initialization + */ +export default class SceneOptions { + physics: { + numPhysicsLayers: number, + physicsLayerNames: Array, + physicsLayerCollisions: Array>; + } + + static parse(options: Record): SceneOptions{ + let sOpt = new SceneOptions(); + + sOpt.physics = { + numPhysicsLayers: 10, + physicsLayerNames: null, + physicsLayerCollisions: ArrayUtils.ones2d(10, 10) + }; + + if(options.physics){ + if(options.physics.numPhysicsLayers) sOpt.physics.numPhysicsLayers = options.physics.numPhysicsLayers; + if(options.physics.physicsLayerNames) sOpt.physics.physicsLayerNames = options.physics.physicsLayerNames; + if(options.physics.physicsLayerCollisions) sOpt.physics.physicsLayerCollisions = options.physics.physicsLayerCollisions; + } + + return sOpt; + } +} \ No newline at end of file diff --git a/src/SceneGraph/SceneGraph.ts b/src/SceneGraph/SceneGraph.ts index 0851048..99b0a7c 100644 --- a/src/SceneGraph/SceneGraph.ts +++ b/src/SceneGraph/SceneGraph.ts @@ -6,14 +6,25 @@ import Scene from "../Scene/Scene"; import AABB from "../DataTypes/Shapes/AABB"; /** - * An abstract interface of a SceneGraph. Exposes methods for use by other code, but leaves the implementation up to the subclasses. + * An abstract interface of a SceneGraph. + * Exposes methods for use by other code, but leaves the implementation up to the subclasses. + * The SceneGraph manages the positions of all GameNodes, and can easily prune a visible set for rendering. */ export default abstract class SceneGraph { + /** A reference to the viewport */ protected viewport: Viewport; + /** A map of CanvasNodes in this SceneGraph */ protected nodeMap: Map; + /** A counter of IDs for nodes in this SceneGraph */ protected idCounter: number; + /** A reference to the Scene this SceneGraph belongs to */ protected scene: Scene; + /** + * Creates a new SceneGraph + * @param viewport The viewport + * @param scene The Scene this SceneGraph belongs to + */ constructor(viewport: Viewport, scene: Scene){ this.viewport = viewport; this.scene = scene; @@ -24,6 +35,7 @@ export default abstract class SceneGraph { /** * Add a node to the SceneGraph * @param node The CanvasNode to add to the SceneGraph + * @returns The SceneGraph ID of this newly added CanvasNode */ addNode(node: CanvasNode): number { this.nodeMap.add(this.idCounter.toString(), node); @@ -63,15 +75,17 @@ export default abstract class SceneGraph { /** * Get a specific node using its id * @param id The id of the CanvasNode to retrieve + * @returns The node with this ID */ getNode(id: string): CanvasNode { return this.nodeMap.get(id); } /** - * Returns the node at specific coordinates - * @param vecOrX - * @param y + * Returns the nodes at specific coordinates + * @param vecOrX The x-coordinate of the position, or the coordinates in a Vec2 + * @param y The y-coordinate of the position + * @returns An array of nodes found at the position provided */ getNodesAt(vecOrX: Vec2 | number, y: number = null): Array { if(vecOrX instanceof Vec2){ @@ -81,8 +95,17 @@ export default abstract class SceneGraph { } } + /** + * Returns the nodes that overlap a specific boundary + * @param boundary The region to check + * @returns An array of nodes found overlapping the provided boundary + */ abstract getNodesInRegion(boundary: AABB): Array; + /** + * Returns all nodes in the SceneGraph + * @returns An Array containing all nodes in the SceneGraph + */ getAllNodes(): Array { let arr = new Array(); this.nodeMap.forEach(key => arr.push(this.nodeMap.get(key))); @@ -91,8 +114,8 @@ export default abstract class SceneGraph { /** * The specific implementation of getting a node at certain coordinates - * @param x - * @param y + * @param x The x-coordinates of the node + * @param y The y-coordinates of the node */ protected abstract getNodesAtCoords(x: number, y: number): Array; @@ -101,7 +124,8 @@ export default abstract class SceneGraph { abstract render(ctx: CanvasRenderingContext2D): void; /** - * Gets the visible set of CanvasNodes based on the viewport + * Gets the visible set of CanvasNodes based on the @reference[Viewport] + * @returns An array containing all visible nodes in the SceneGraph */ abstract getVisibleSet(): Array; } \ No newline at end of file diff --git a/src/SceneGraph/SceneGraphArray.ts b/src/SceneGraph/SceneGraphArray.ts index aaf192c..c47b4b9 100644 --- a/src/SceneGraph/SceneGraphArray.ts +++ b/src/SceneGraph/SceneGraphArray.ts @@ -2,37 +2,41 @@ import SceneGraph from "./SceneGraph"; import CanvasNode from "../Nodes/CanvasNode"; import Viewport from "./Viewport"; import Scene from "../Scene/Scene"; -import Stack from "../DataTypes/Stack"; -import Layer from "../Scene/Layer" import AABB from "../DataTypes/Shapes/AABB"; import Stats from "../Debug/Stats"; -export default class SceneGraphArray extends SceneGraph{ - private nodeList: Array; - private turnOffViewportCulling_demoTool: boolean; +/** + * An implementation of a SceneGraph that simply stored CanvasNodes in an array. + */ +export default class SceneGraphArray extends SceneGraph { + /** The list of CanvasNodes in this SceneGraph */ + private nodeList: Array; + /** + * Creates a new SceneGraphArray + * @param viewport The Viewport + * @param scene The Scene this SceneGraph belongs to + */ constructor(viewport: Viewport, scene: Scene){ super(viewport, scene); this.nodeList = new Array(); - this.turnOffViewportCulling_demoTool = false; } - setViewportCulling_demoTool(bool: boolean): void { - this.turnOffViewportCulling_demoTool = bool; - } - - addNodeSpecific(node: CanvasNode, id: string): void { + // @override + protected addNodeSpecific(node: CanvasNode, id: string): void { this.nodeList.push(node); } - removeNodeSpecific(node: CanvasNode, id: string): void { + // @override + protected removeNodeSpecific(node: CanvasNode, id: string): void { let index = this.nodeList.indexOf(node); if(index > -1){ this.nodeList.splice(index, 1); } } + // @override getNodesAtCoords(x: number, y: number): Array { let results = []; @@ -45,6 +49,7 @@ export default class SceneGraphArray extends SceneGraph{ return results; } + // @override getNodesInRegion(boundary: AABB): Array { let t0 = performance.now(); let results = []; @@ -73,16 +78,8 @@ export default class SceneGraphArray extends SceneGraph{ render(ctx: CanvasRenderingContext2D): void {} + // @override getVisibleSet(): Array { - // If viewport culling is turned off for demonstration - if(this.turnOffViewportCulling_demoTool){ - let visibleSet = new Array(); - for(let node of this.nodeList){ - visibleSet.push(node); - } - return visibleSet; - } - let visibleSet = new Array(); for(let node of this.nodeList){ diff --git a/src/SceneGraph/SceneGraphQuadTree.ts b/src/SceneGraph/SceneGraphQuadTree.ts index 5f42c71..7805aad 100644 --- a/src/SceneGraph/SceneGraphQuadTree.ts +++ b/src/SceneGraph/SceneGraphQuadTree.ts @@ -7,10 +7,21 @@ import Vec2 from "../DataTypes/Vec2"; import AABB from "../DataTypes/Shapes/AABB"; import Stats from "../Debug/Stats"; +/** + * An implementation of a SceneGraph that uses a @reference[RegionQuadTree] to store @reference[CanvasNode]s. + */ export default class SceneGraphQuadTree extends SceneGraph { + /** The QuadTree used to store the CanvasNodes */ private qt: RegionQuadTree; + + /** A list of nodes to help out the QuadTree */ private nodes: Array; + /** + * Creates a new SceneGraphQuadTree + * @param viewport The Viewport + * @param scene The Scene this SceneGraph belongs to + */ constructor(viewport: Viewport, scene: Scene){ super(viewport, scene); @@ -19,21 +30,25 @@ export default class SceneGraphQuadTree extends SceneGraph { this.nodes = new Array(); } - addNodeSpecific(node: CanvasNode, id: string): void { + // @override + protected addNodeSpecific(node: CanvasNode, id: string): void { this.nodes.push(node); } - removeNodeSpecific(node: CanvasNode, id: string): void { + // @override + protected removeNodeSpecific(node: CanvasNode, id: string): void { let index = this.nodes.indexOf(node); if(index >= 0){ this.nodes.splice(index, 1); } } + // @override getNodesAtCoords(x: number, y: number): Array { return this.qt.queryPoint(new Vec2(x, y)); } + // @override getNodesInRegion(boundary: AABB): Array { let t0 = performance.now(); let res = this.qt.queryRegion(boundary); @@ -72,6 +87,7 @@ export default class SceneGraphQuadTree extends SceneGraph { this.qt.render_demo(ctx, origin, zoom); } + // @override getVisibleSet(): Array { let visibleSet = this.qt.queryRegion(this.viewport.getView()); diff --git a/src/SceneGraph/Viewport.ts b/src/SceneGraph/Viewport.ts index 89d4516..6cbf0d7 100644 --- a/src/SceneGraph/Viewport.ts +++ b/src/SceneGraph/Viewport.ts @@ -4,29 +4,37 @@ import CanvasNode from "../Nodes/CanvasNode"; import MathUtils from "../Utils/MathUtils"; import Queue from "../DataTypes/Queue"; import AABB from "../DataTypes/Shapes/AABB"; -import Debug from "../Debug/Debug"; import InputReceiver from "../Input/InputReceiver"; import ParallaxLayer from "../Scene/Layers/ParallaxLayer"; import UILayer from "../Scene/Layers/UILayer"; +/** + * The viewport of the game. Corresponds to the visible window displayed in the browser. + * The viewport keeps track of its position in the game world, and can act as a camera to follow objects. + */ export default class Viewport { + /** The AABB that contains the position and size of the viewport view */ private view: AABB; - private boundary: AABB; + /** The boundary for the viewport. This represents the limits to where the viewport can go */ + private boundary: AABB; + /** The GameNode the Viewport is following */ private following: GameNode; + /** The position the GameNode is focusing on. This is overridden if "following" is set. */ private focus: Vec2; - /** - * A queue of previous positions of what this viewport is following. Used for smoothing viewport movement - */ + /** A queue of previous positions of what this viewport is following. Used for smoothing viewport movement */ private lastPositions: Queue; - /** - * The number of previous positions this viewport tracks - */ + /** The number of previous positions this viewport tracks */ private smoothingFactor: number; + /** A boolean tha represents whether the player can zoom by scrolling with the mouse wheel */ private scrollZoomEnabled: boolean; + + /** The amount that is zoomed in or out. */ private ZOOM_FACTOR: number = 1.2; + + /** The size of the canvas */ private canvasSize: Vec2; constructor(){ @@ -39,24 +47,30 @@ export default class Viewport { this.focus = Vec2.ZERO; } + /** Enables the viewport to zoom in and out */ enableZoom(): void { this.scrollZoomEnabled = true; } /** - * Returns the position of the viewport as a Vec2 + * Returns the position of the viewport + * @returns The center of the viewport as a Vec2 */ getCenter(): Vec2 { return this.view.center; } - /** Returns a new Vec2 with the origin of the viewport */ + /** + * Returns a new Vec2 with the origin of the viewport + * @returns The top left cornder of the Vieport as a Vec2 + */ getOrigin(): Vec2 { return new Vec2(this.view.left, this.view.top); } /** * Returns the region visible to this viewport + * @returns The AABB containing the region visible to the viewport */ getView(): AABB { return this.view; @@ -64,8 +78,8 @@ export default class Viewport { /** * Set the position of the viewport - * @param vecOrX - * @param y + * @param vecOrX The new position or the x-coordinate of the new position + * @param y The y-coordinate of the new position */ setCenter(vecOrX: Vec2 | number, y: number = null): void { let pos: Vec2; @@ -81,6 +95,7 @@ export default class Viewport { /** * Returns the size of the viewport as a Vec2 + * @returns The half-size of the viewport as a Vec2 */ getHalfSize(): Vec2 { return this.view.getHalfSize(); @@ -88,8 +103,8 @@ export default class Viewport { /** * Sets the size of the viewport - * @param vecOrX - * @param y + * @param vecOrX The new width of the viewport or the new size as a Vec2 + * @param y The new height of the viewport */ setSize(vecOrX: Vec2 | number, y: number = null): void { if(vecOrX instanceof Vec2){ @@ -99,6 +114,11 @@ export default class Viewport { } } + /** + * Sets the half-size of the viewport + * @param vecOrX The new half-width of the viewport or the new half-size as a Vec2 + * @param y The new height of the viewport + */ setHalfSize(vecOrX: Vec2 | number, y: number = null): void { if(vecOrX instanceof Vec2){ this.view.setHalfSize(vecOrX.clone()); @@ -108,9 +128,9 @@ export default class Viewport { } /** - * Sets the size of the canvas that the viewport is projecting to. - * @param vecOrX - * @param y + * Updates the viewport with the size of the current Canvas + * @param vecOrX The width of the canvas, or the canvas size as a Vec2 + * @param y The height of the canvas */ setCanvasSize(vecOrX: Vec2 | number, y: number = null): void { if(vecOrX instanceof Vec2){ @@ -120,10 +140,18 @@ export default class Viewport { } } + /** + * Sets the zoom level of the viewport + * @param zoom The zoom level + */ setZoomLevel(zoom: number): void { this.view.halfSize.scale(1/zoom); } + /** + * Gets the zoom level of the viewport + * @returns The zoom level + */ getZoomLevel(): number { return this.canvasSize.x/this.view.hw/2 } @@ -139,7 +167,7 @@ export default class Viewport { /** * Tells the viewport to focus on a point. Overidden by "following". - * @param focus + * @param focus The point the viewport should focus on */ setFocus(focus: Vec2): void { this.focus.copy(focus); @@ -147,7 +175,8 @@ export default class Viewport { /** * Returns true if the CanvasNode is inside of the viewport - * @param node + * @param node The node to check + * @returns True if the node is currently visible in the viewport, false if not */ includes(node: CanvasNode): boolean { let parallax = node.getLayer() instanceof ParallaxLayer || node.getLayer() instanceof UILayer ? (node.getLayer()).parallax : new Vec2(1, 1); @@ -162,10 +191,10 @@ export default class Viewport { // TODO: This should probably be done automatically, or should consider the aspect ratio or something /** * Sets the bounds of the viewport - * @param lowerX - * @param lowerY - * @param upperX - * @param upperY + * @param lowerX The left edge of the viewport + * @param lowerY The top edge of the viewport + * @param upperX The right edge of the viewport + * @param upperY The bottom edge of the viewport */ setBounds(lowerX: number, lowerY: number, upperX: number, upperY: number): void { let hwidth = (upperX - lowerX)/2; diff --git a/src/Sound/AudioManager.ts b/src/Sound/AudioManager.ts index f440bf8..c3af555 100644 --- a/src/Sound/AudioManager.ts +++ b/src/Sound/AudioManager.ts @@ -3,9 +3,18 @@ import Receiver from "../Events/Receiver"; import ResourceManager from "../ResourceManager/ResourceManager"; import { GameEventType } from "../Events/GameEventType"; +/** + * Manages any sounds or music needed for the game. + * Through the EventQueue, exposes interface to play sounds so GameNodes can activate sounds without + * needing direct references to the audio system + */ export default class AudioManager { private static instance: AudioManager; + + /** The event receiver of this AudioManager */ private receiver: Receiver; + + /** A Map of the names of currently playing (or paused) sounds to their AudioBuffers */ private currentSounds: Map; private audioCtx: AudioContext; @@ -19,6 +28,7 @@ export default class AudioManager { /** * Get the instance of the AudioManager class or create a new one if none exists + * @returns The AudioManager */ public static getInstance(): AudioManager { if(!this.instance){ @@ -42,15 +52,12 @@ export default class AudioManager { /** * Returns the current audio context + * @returns The AudioContext */ public getAudioContext(): AudioContext { return this.audioCtx; } - /** - * Creates a new sound from the key of a loaded audio file - * @param key The key of the loaded audio file to create a new sound for - */ /* According to the MDN, create a new sound for every call: @@ -61,6 +68,11 @@ export default class AudioManager { hold a reference to it. It will automatically be garbage-collected at an appropriate time, which won't be until sometime after the sound has finished playing. */ + /** + * Creates a new sound from the key of a loaded audio file + * @param key The key of the loaded audio file to create a new sound for + * @returns The newly created AudioBuffer + */ protected createSound(key: string): AudioBufferSourceNode { // Get audio buffer let buffer = ResourceManager.getInstance().getAudio(key); @@ -108,11 +120,7 @@ export default class AudioManager { this.currentSounds.delete(key); } } - - /** - * Updates the AudioManager - * @param deltaT - */ + update(deltaT: number): void { // Play each audio clip requested // TODO - Add logic to merge sounds if there are multiple of the same key diff --git a/src/Utils/ArrayUtils.ts b/src/Utils/ArrayUtils.ts index caa890f..9ee99d4 100644 --- a/src/Utils/ArrayUtils.ts +++ b/src/Utils/ArrayUtils.ts @@ -1,8 +1,10 @@ +/** A class containing some utility functions for Arrays */ export default class ArrayUtils { /** * Returns a 2d array of dim1 x dim2 filled with 1s - * @param dim1 - * @param dim2 + * @param dim1 The first dimension of the array to create + * @param dim2 The second dimension of the array to create + * @returns A dim1 x dim2 Array filled with 1s */ static ones2d(dim1: number, dim2: number): number[][] { let arr = new Array>(dim1); @@ -18,6 +20,13 @@ export default class ArrayUtils { return arr; } + /** + * Returns a 2d array of dim1 x dim2 filled with true or false + * @param dim1 The first dimension of the array to create + * @param dim2 The second dimension of the array to create + * @param flag The boolean to fill the array with + * @returns A dim1 x dim2 Array filled with flag + */ static bool2d(dim1: number, dim2: number, flag: boolean): boolean[][] { let arr = new Array>(dim1); diff --git a/src/Utils/Color.ts b/src/Utils/Color.ts index ec9789c..55096a5 100644 --- a/src/Utils/Color.ts +++ b/src/Utils/Color.ts @@ -1,12 +1,26 @@ import MathUtils from "./MathUtils"; // TODO: This should be moved to the datatypes folder +/** + * A Color util class that keeps track of colors like a vector, but can be converted into a string format + */ export default class Color { + /** The red value */ public r: number; + /** The green value */ public g: number; + /** The blue value */ public b: number; + /** The alpha value */ public a: number; + /** + * Creates a new color + * @param r Red + * @param g Green + * @param b Blue + * @param a Alpha + */ constructor(r: number = 0, g: number = 0, b: number = 0, a: number = 1){ this.r = r; this.g = g; @@ -14,45 +28,93 @@ export default class Color { this.a = a; } + /** + * Transparent color + * @returns rgba(0, 0, 0, 0) + */ static get TRANSPARENT(): Color { return new Color(0, 0, 0, 0); } + /** + * Red color + * @returns rgb(255, 0, 0) + */ static get RED(): Color { return new Color(255, 0, 0, 1); } + /** + * Green color + * @returns rgb(0, 255, 0) + */ static get GREEN(): Color { return new Color(0, 255, 0, 1); } + /** + * Blue color + * @returns rgb(0, 0, 255) + */ static get BLUE(): Color { return new Color(0, 0, 255, 1); } + /** + * Yellow color + * @returns rgb(255, 255, 0) + */ static get YELLOW(): Color { return new Color(255, 255, 0, 1); } + /** + * Purple color + * @returns rgb(255, 0, 255) + */ static get PURPLE(): Color { return new Color(255, 0, 255, 1); } + + /** + * Cyan color + * @returns rgb(0, 255, 255) + */ static get CYAN(): Color { return new Color(0, 255, 255, 1); } + /** + * White color + * @returns rgb(255, 255, 255) + */ static get WHITE(): Color { return new Color(255, 255, 255, 1); } + /** + * Black color + * @returns rgb(0, 0, 0) + */ static get BLACK(): Color { return new Color(0, 0, 0, 1); } + /** + * Orange color + * @returns rgb(255, 100, 0) + */ static get ORANGE(): Color { return new Color(255, 100, 0, 1); } + /** + * Sets the color to the values provided + * @param r Red + * @param g Green + * @param b Blue + * @param a Alpha + */ set(r: number, g: number, b: number, a: number = 1): void { this.r = r; this.g = g; @@ -62,6 +124,7 @@ export default class Color { /** * Returns a new color slightly lighter than the current color + * @returns A new lighter Color */ lighten(): Color { return new Color(MathUtils.clamp(this.r + 40, 0, 255), MathUtils.clamp(this.g + 40, 0, 255), MathUtils.clamp(this.b + 40, 0, 255), this.a); @@ -69,6 +132,7 @@ export default class Color { /** * Returns a new color slightly darker than the current color + * @returns A new darker Color */ darken(): Color { return new Color(MathUtils.clamp(this.r - 40, 0, 255), MathUtils.clamp(this.g - 40, 0, 255), MathUtils.clamp(this.b - 40, 0, 255), this.a); @@ -76,6 +140,7 @@ export default class Color { /** * Returns the color as a string of the form #RRGGBB + * @returns #RRGGBB */ toString(): string { return "#" + MathUtils.toHex(this.r, 2) + MathUtils.toHex(this.g, 2) + MathUtils.toHex(this.b, 2); @@ -83,6 +148,7 @@ export default class Color { /** * Returns the color as a string of the form rgb(r, g, b) + * @returns rgb(r, g, b) */ toStringRGB(): string { return "rgb(" + this.r.toString() + ", " + this.g.toString() + ", " + this.b.toString() + ")"; @@ -90,6 +156,7 @@ export default class Color { /** * Returns the color as a string of the form rgba(r, g, b, a) + * @returns rgba(r, g, b, a) */ toStringRGBA(): string { if(this.a === 0){ diff --git a/src/Utils/EaseFunctions.ts b/src/Utils/EaseFunctions.ts index d81f340..6633c3a 100644 --- a/src/Utils/EaseFunctions.ts +++ b/src/Utils/EaseFunctions.ts @@ -1,3 +1,5 @@ +// @ignorePage + export default class EaseFunctions { static easeInOutSine(x: number): number { diff --git a/src/Utils/GraphUtils.ts b/src/Utils/GraphUtils.ts index c8283bc..774e76d 100644 --- a/src/Utils/GraphUtils.ts +++ b/src/Utils/GraphUtils.ts @@ -1,7 +1,15 @@ -import Graph, { EdgeNode } from "../DataTypes/Graphs/Graph"; +import Graph from "../DataTypes/Graphs/Graph"; +import EdgeNode from "../DataTypes/Graphs/EdgeNode"; +/** A class to provides some utility functions for graphs */ export default class GraphUtils { + /** + * An implementation of Djikstra's shortest path algorithm based on the one described in The Algorithm Design Manual. + * @param g The graph + * @param start The number to start the shortest path from + * @returns An array containing the parent of each node of the Graph in the shortest path. + */ static djikstra(g: Graph, start: number): Array { let i: number; // Counter let p: EdgeNode; // Pointer to edgenode diff --git a/src/Utils/MathUtils.ts b/src/Utils/MathUtils.ts index 7ba954c..d763cc1 100644 --- a/src/Utils/MathUtils.ts +++ b/src/Utils/MathUtils.ts @@ -1,9 +1,11 @@ import Vec2 from "../DataTypes/Vec2"; +/** A class containing some utility functions for math operations */ export default class MathUtils { /** * Returns the sign of the value provided * @param x The value to extract the sign from + * @returns -1 if the number is less than 0, 1 otherwise */ static sign(x: number): number { return x < 0 ? -1 : 1; @@ -14,6 +16,7 @@ export default class MathUtils { * @param x The value to be clamped * @param min The min of the range * @param max The max of the range + * @returns x, if it is between min and max, or min/max if it exceeds their bounds */ static clamp(x: number, min: number, max: number): number { if(x < min) return min; @@ -24,6 +27,7 @@ export default class MathUtils { /** * Clamps the value x to the range between 0 and 1 * @param x The value to be clamped + * @returns x, if it is between 0 and 1, or 0/1 if it exceeds their bounds */ static clamp01(x: number): number { return MathUtils.clamp(x, 0, 1); @@ -42,11 +46,19 @@ export default class MathUtils { * @param a The first value for the interpolation bound * @param b The second value for the interpolation bound * @param t The time we are interpolating to + * @returns The value between a and b at time t */ static lerp(a: number, b: number, t: number): number { return a + t * (b - a); } + /** + * Inverse Linear Interpolation. Finds the time at which a value between a and b would occur + * @param a The first value for the interpolation bound + * @param b The second value for the interpolation bound + * @param value The current value + * @returns The time at which the current value occurs between a and b + */ static invLerp(a: number, b: number, value: number){ return (value - a)/(b - a); } @@ -55,6 +67,7 @@ export default class MathUtils { * Cuts off decimal points of a number after a specified place * @param num The number to floor * @param place The last decimal place of the new number + * @returns The floored number */ static floorToPlace(num: number, place: number): number { if(place === 0){ @@ -75,6 +88,7 @@ export default class MathUtils { * Returns the number as a hexadecimal * @param num The number to convert to hex * @param minLength The length of the returned hex string (adds zero padding if needed) + * @returns The hex representation of the number as a string */ static toHex(num: number, minLength: number = null): string { let factor = 1; @@ -99,8 +113,9 @@ export default class MathUtils { } /** - * Converts the number to hexadecimal - * @param num The number to convert to hexadecimal + * Converts a digit to hexadecimal. In this case, a digit is between 0 and 15 inclusive + * @param num The digit to convert to hexadecimal + * @returns The hex representation of the digit as a string */ static toHexDigit(num: number): string { if(num < 10){ diff --git a/src/Utils/PhysicsUtils.ts b/src/Utils/PhysicsUtils.ts deleted file mode 100644 index 2084d7b..0000000 --- a/src/Utils/PhysicsUtils.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Physical } from "../DataTypes/Interfaces/Descriptors"; - -export default class PhysicsUtils { - static sweepAndPrune(nodes: Array){ - // Sort - } -} \ No newline at end of file diff --git a/src/Utils/Rand/Perlin.ts b/src/Utils/Rand/Perlin.ts index 69bb95d..0327e5d 100644 --- a/src/Utils/Rand/Perlin.ts +++ b/src/Utils/Rand/Perlin.ts @@ -15,6 +15,9 @@ const permutation = [ 151,160,137,91,90,15, 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180 ]; +/** + * A noise generator + */ export default class Perlin { private p: Int16Array; @@ -30,11 +33,12 @@ export default class Perlin { /** * Returns a random perlin noise value - * @param x - * @param y - * @param z + * @param x An input value + * @param y An input value + * @param z An input value + * @returns A noise value */ - perlin(x: number, y: number, z: number = 0){ + perlin(x: number, y: number, z: number = 0): number { if(this.repeat > 0) { x = x%this.repeat; y = y%this.repeat; diff --git a/src/Utils/RandUtils.ts b/src/Utils/RandUtils.ts index 3c1bebb..42c104a 100644 --- a/src/Utils/RandUtils.ts +++ b/src/Utils/RandUtils.ts @@ -10,11 +10,13 @@ class Noise { } } +/** A class that has some random generator utils */ export default class RandUtils { /** * Generates a random integer in the specified range * @param min The min of the range (inclusive) * @param max The max of the range (exclusive) + * @returns A random int in the range [min, max) */ static randInt(min: number, max: number): number { return Math.floor(Math.random()*(max - min) + min); @@ -24,6 +26,7 @@ export default class RandUtils { * Generates a random hexadecimal number in the specified range * @param min The min of the range (inclusive) * @param max The max of the range (exclusive) + * @returns a random hex number in the range [min, max) as a string */ static randHex(min: number, max: number): string { return MathUtils.toHex(RandUtils.randInt(min, max)); @@ -31,6 +34,7 @@ export default class RandUtils { /** * Generates a random color + * @returns A random Color */ static randColor(): Color { let r = RandUtils.randInt(0, 256); @@ -39,6 +43,7 @@ export default class RandUtils { return new Color(r, g, b); } + /** A noise generator */ static noise: Noise = new Noise(); } \ No newline at end of file diff --git a/src/Utils/SortingUtils.ts b/src/Utils/SortingUtils.ts index a218deb..b5eec67 100644 --- a/src/Utils/SortingUtils.ts +++ b/src/Utils/SortingUtils.ts @@ -1,7 +1,14 @@ +/** Some utility functions for sorting arrays */ export default class SortingUtils { /** - * - * @param arr + * An implementation of insertion sort. + * In game engines, this is particularly useful to sort node positions because of temporal coherence - + * the idea that nodes are almost in the same place as last frame, and thus, in a frame-to-frame comparison, + * nodes essentially do not change position. + * This means we have a nearly sorted array of nodes if we keep track of this, + * so something like insertion sort actually becomes essentailly O(n), + * as it performs very well on nearly sorted arrays. + * @param arr The array to sort in place * @param comparator Compares element a and b in the array. Returns -1 if a < b, 0 if a = b, and 1 if a > b */ static insertionSort(arr: Array, comparator: (a: T, b: T) => number): void { @@ -16,7 +23,13 @@ export default class SortingUtils { } } - static swap(arr: Array, i: number, j: number){ + /** + * Swaps two elements in the provided array + * @param arr The array to perform the swap on in place + * @param i The first index + * @param j The second index + */ + static swap(arr: Array, i: number, j: number): void { let temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; diff --git a/src/Utils/StringUtils.ts b/src/Utils/StringUtils.ts index 1d5da49..672c050 100644 --- a/src/Utils/StringUtils.ts +++ b/src/Utils/StringUtils.ts @@ -1,7 +1,9 @@ +/** Some utility functions for dealing with strings */ export default class StringUtils { /** * Extracts the path from a filepath that includes the file - * @param filePath the filepath to extract the path form + * @param filePath the filepath to extract the path from + * @returns The path portion of the filepath provided */ static getPathFromFilePath(filePath: string): string { let splitPath = filePath.split("/");