added pathfinding and ai factories, split layers, and made fixes to other factories
This commit is contained in:
parent
1047e31c70
commit
ff5a2896fe
39
src/AI/AIManager.ts
Normal file
39
src/AI/AIManager.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import { Actor, AI, Updateable } from "../DataTypes/Interfaces/Descriptors";
|
||||||
|
import Map from "../DataTypes/Map";
|
||||||
|
|
||||||
|
export default class AIManager implements Updateable {
|
||||||
|
actors: Array<Actor>;
|
||||||
|
|
||||||
|
registeredAI: Map<new <T extends AI>() => T>;
|
||||||
|
|
||||||
|
constructor(){
|
||||||
|
this.actors = new Array();
|
||||||
|
this.registeredAI = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers an actor with the AIManager
|
||||||
|
* @param actor The actor to register
|
||||||
|
*/
|
||||||
|
registerActor(actor: Actor): void {
|
||||||
|
actor.actorId = this.actors.length;
|
||||||
|
this.actors.push(actor);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerAI(name: string, constr: new <T extends AI>() => T ): void {
|
||||||
|
this.registeredAI.add(name, constr);
|
||||||
|
}
|
||||||
|
|
||||||
|
generateAI(name: string): AI {
|
||||||
|
if(this.registeredAI.has(name)){
|
||||||
|
return new (this.registeredAI.get(name))();
|
||||||
|
} else {
|
||||||
|
throw `Cannot create AI with name ${name}, no AI with that name is registered`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update(deltaT: number): void {
|
||||||
|
// Run the ai for every active actor
|
||||||
|
this.actors.forEach(actor => { if(actor.aiActive) actor.ai.update(deltaT) });
|
||||||
|
}
|
||||||
|
}
|
9
src/AI/StateMachineAI.ts
Normal file
9
src/AI/StateMachineAI.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { AI } from "../DataTypes/Interfaces/Descriptors";
|
||||||
|
import StateMachine from "../DataTypes/State/StateMachine";
|
||||||
|
import GameNode from "../Nodes/GameNode";
|
||||||
|
|
||||||
|
export default class StateMachineAI extends StateMachine implements AI {
|
||||||
|
protected owner: GameNode;
|
||||||
|
|
||||||
|
initializeAI(owner: GameNode, config: Record<string, any>): void {}
|
||||||
|
}
|
|
@ -18,29 +18,29 @@ export default class BoidDemo extends Scene {
|
||||||
|
|
||||||
startScene(){
|
startScene(){
|
||||||
// Set the world size
|
// Set the world size
|
||||||
this.worldSize = new Vec2(800, 600);
|
// this.worldSize = new Vec2(800, 600);
|
||||||
this.sceneGraph = new SceneGraphQuadTree(this.viewport, this);
|
// this.sceneGraph = new SceneGraphQuadTree(this.viewport, this);
|
||||||
this.viewport.setBounds(0, 0, 800, 600)
|
// this.viewport.setBounds(0, 0, 800, 600)
|
||||||
this.viewport.setCenter(400, 300);
|
// this.viewport.setCenter(400, 300);
|
||||||
|
|
||||||
let layer = this.addLayer();
|
// let layer = this.addLayer();
|
||||||
this.boids = new Array();
|
// this.boids = new Array();
|
||||||
|
|
||||||
// Add the player
|
// // Add the player
|
||||||
let player = this.add.graphic(Player, layer, new Vec2(0, 0));
|
// let player = this.add.graphic(Player, layer, new Vec2(0, 0));
|
||||||
player.addPhysics();
|
// player.addPhysics();
|
||||||
let ai = new PlayerController(player, "topdown");
|
// let ai = new PlayerController(player, "topdown");
|
||||||
player.update = (deltaT: number) => {ai.update(deltaT)}
|
// player.update = (deltaT: number) => {ai.update(deltaT)}
|
||||||
this.viewport.follow(player);
|
// this.viewport.follow(player);
|
||||||
this.viewport.enableZoom();
|
// this.viewport.enableZoom();
|
||||||
|
|
||||||
// Create a bunch of boids
|
// // Create a bunch of boids
|
||||||
for(let i = 0; i < 150; i++){
|
// for(let i = 0; i < 150; i++){
|
||||||
let boid = this.add.graphic(Boid, layer, new Vec2(this.worldSize.x*Math.random(), this.worldSize.y*Math.random()));
|
// let boid = this.add.graphic(Boid, layer, new Vec2(this.worldSize.x*Math.random(), this.worldSize.y*Math.random()));
|
||||||
boid.fb = new FlockBehavior(this, boid, this.boids, 75, 50);
|
// boid.fb = new FlockBehavior(this, boid, this.boids, 75, 50);
|
||||||
boid.size.set(5, 5);
|
// boid.size.set(5, 5);
|
||||||
this.boids.push(boid);
|
// this.boids.push(boid);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
updateScene(deltaT: number): void {
|
updateScene(deltaT: number): void {
|
||||||
|
|
|
@ -27,9 +27,22 @@ export default class Graph {
|
||||||
addEdge(x: number, y: number, weight?: number){
|
addEdge(x: number, y: number, weight?: number){
|
||||||
let edge = new EdgeNode(y, weight);
|
let edge = new EdgeNode(y, weight);
|
||||||
|
|
||||||
edge.next = this.edges[x];
|
if(this.edges[x]){
|
||||||
|
edge.next = this.edges[x];
|
||||||
|
}
|
||||||
|
|
||||||
this.edges[x] = edge;
|
this.edges[x] = edge;
|
||||||
|
|
||||||
|
if(!this.directed){
|
||||||
|
edge = new EdgeNode(x, weight);
|
||||||
|
|
||||||
|
if(this.edges[y]){
|
||||||
|
edge.next = this.edges[y];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.edges[y] = edge;
|
||||||
|
}
|
||||||
|
|
||||||
this.numEdges += 1;
|
this.numEdges += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,6 +53,34 @@ export default class Graph {
|
||||||
getDegree(x: number): number {
|
getDegree(x: number): number {
|
||||||
return this.degree[x];
|
return this.degree[x];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected nodeToString(index: number): string {
|
||||||
|
return "Node " + index;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
let retval = "";
|
||||||
|
|
||||||
|
for(let i = 0; i < this.numVertices; i++){
|
||||||
|
let edge = this.edges[i];
|
||||||
|
let edgeStr = "";
|
||||||
|
while(edge !== null){
|
||||||
|
edgeStr += edge.y.toString();
|
||||||
|
if(this.weighted){
|
||||||
|
edgeStr += " (" + edge.weight + ")";
|
||||||
|
}
|
||||||
|
if(edge.next !== null){
|
||||||
|
edgeStr += ", ";
|
||||||
|
}
|
||||||
|
|
||||||
|
edge = edge.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
retval += this.nodeToString(i) + ": " + edgeStr + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EdgeNode {
|
export class EdgeNode {
|
||||||
|
|
|
@ -10,21 +10,34 @@ export default class PositionGraph extends Graph implements Debug_Renderable{
|
||||||
this.positions = new Array(MAX_V);
|
this.positions = new Array(MAX_V);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addPositionedNode(position: Vec2){
|
||||||
|
this.positions[this.numVertices] = position;
|
||||||
|
this.addNode();
|
||||||
|
}
|
||||||
|
|
||||||
setNodePosition(index: number, position: Vec2): void {
|
setNodePosition(index: number, position: Vec2): void {
|
||||||
this.positions[index] = position;
|
this.positions[index] = position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getNodePosition(index: number): Vec2 {
|
||||||
|
return this.positions[index];
|
||||||
|
}
|
||||||
|
|
||||||
addEdge(x: number, y: number): void {
|
addEdge(x: number, y: number): void {
|
||||||
if(!this.positions[x] || !this.positions[y]){
|
if(!this.positions[x] || !this.positions[y]){
|
||||||
throw "Can't add edge to un-positioned node!";
|
throw "Can't add edge to un-positioned node!";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Weight is the distance between the nodes
|
// Weight is the distance between the nodes
|
||||||
let weight = this.positions[x].distanceSqTo(this.positions[y]);
|
let weight = this.positions[x].distanceTo(this.positions[y]);
|
||||||
|
|
||||||
super.addEdge(x, y, weight);
|
super.addEdge(x, y, weight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected nodeToString(index: number): string {
|
||||||
|
return "Node " + index + " - " + this.positions[index].toString();
|
||||||
|
}
|
||||||
|
|
||||||
debug_render(ctx: CanvasRenderingContext2D, origin: Vec2, zoom: number): void {
|
debug_render(ctx: CanvasRenderingContext2D, origin: Vec2, zoom: number): void {
|
||||||
for(let point of this.positions){
|
for(let point of this.positions){
|
||||||
ctx.fillRect((point.x - origin.x - 4)*zoom, (point.y - origin.y - 4)*zoom, 8, 8);
|
ctx.fillRect((point.x - origin.x - 4)*zoom, (point.y - origin.y - 4)*zoom, 8, 8);
|
||||||
|
|
|
@ -3,6 +3,8 @@ import Map from "../Map";
|
||||||
import AABB from "../Shapes/AABB";
|
import AABB from "../Shapes/AABB";
|
||||||
import Shape from "../Shapes/Shape";
|
import Shape from "../Shapes/Shape";
|
||||||
import Vec2 from "../Vec2";
|
import Vec2 from "../Vec2";
|
||||||
|
import NavigationPath from "../../Pathfinding/NavigationPath";
|
||||||
|
import GameNode from "../../Nodes/GameNode";
|
||||||
|
|
||||||
export interface Unique {
|
export interface Unique {
|
||||||
/** The unique id of this object. */
|
/** The unique id of this object. */
|
||||||
|
@ -107,6 +109,37 @@ export interface Physical {
|
||||||
addTrigger: (group: string, eventType: string) => void;
|
addTrigger: (group: string, eventType: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a controller for a bot or a human. Must be able to update
|
||||||
|
*/
|
||||||
|
export interface AI extends Updateable {
|
||||||
|
/** Initializes the AI with the actor and any additional config */
|
||||||
|
initializeAI: (owner: GameNode, config: Record<string, any>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Actor {
|
||||||
|
/** The AI of the actor */
|
||||||
|
ai: AI;
|
||||||
|
|
||||||
|
/** The activity status of the actor */
|
||||||
|
aiActive: boolean;
|
||||||
|
|
||||||
|
/** The id of the actor according to the AIManager */
|
||||||
|
actorId: number;
|
||||||
|
|
||||||
|
path: NavigationPath;
|
||||||
|
|
||||||
|
pathfinding: boolean;
|
||||||
|
|
||||||
|
addAI: <T extends AI>(ai: string | (new () => T), options: Record<string, any>) => void;
|
||||||
|
|
||||||
|
setAIActive: (active: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Navigable {
|
||||||
|
getNavigationPath: (fromPosition: Vec2, toPosition: Vec2) => NavigationPath;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Updateable {
|
export interface Updateable {
|
||||||
/** Updates this object. */
|
/** Updates this object. */
|
||||||
update: (deltaT: number) => void;
|
update: (deltaT: number) => void;
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
import PositionGraph from "./Graphs/PositionGraph"
|
|
||||||
import Vec2 from "./Vec2";
|
|
||||||
|
|
||||||
export default class Navmesh {
|
|
||||||
protected graph: PositionGraph;
|
|
||||||
|
|
||||||
getNavigationPath(fromPosition: Vec2, toPosition: Vec2): Array<number> {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
getClosestNode(position: Vec2): number {
|
|
||||||
let n = this.graph.numVertices;
|
|
||||||
let i = 1;
|
|
||||||
let index = 0;
|
|
||||||
let dist = position.distanceSqTo(this.graph.positions[0]);
|
|
||||||
while(i < n){
|
|
||||||
let d = position.distanceSqTo(this.graph.positions[i]);
|
|
||||||
if(d < dist){
|
|
||||||
dist = d;
|
|
||||||
index = i;
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -47,6 +47,11 @@ export default class Stack<T> implements Collection {
|
||||||
return this.stack[this.head];
|
return this.stack[this.head];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns true if this stack is empty */
|
||||||
|
isEmpty(): boolean {
|
||||||
|
return this.head === -1;
|
||||||
|
}
|
||||||
|
|
||||||
clear(): void {
|
clear(): void {
|
||||||
this.forEach((item, index) => delete this.stack[index]);
|
this.forEach((item, index) => delete this.stack[index]);
|
||||||
this.head = -1;
|
this.head = -1;
|
||||||
|
@ -62,8 +67,22 @@ export default class Stack<T> implements Collection {
|
||||||
forEach(func: (item: T, index?: number) => void): void{
|
forEach(func: (item: T, index?: number) => void): void{
|
||||||
let i = 0;
|
let i = 0;
|
||||||
while(i <= this.head){
|
while(i <= this.head){
|
||||||
func(this.stack[i]);
|
func(this.stack[i], i);
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
let retval = "";
|
||||||
|
|
||||||
|
this.forEach( (item, index) => {
|
||||||
|
let str = item.toString()
|
||||||
|
if(index !== 0){
|
||||||
|
str += " -> "
|
||||||
|
}
|
||||||
|
retval = str + retval;
|
||||||
|
});
|
||||||
|
|
||||||
|
return "Top -> " + retval;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
import MathUtils from "../Utils/MathUtils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A two-dimensional vector (x, y)
|
* A two-dimensional vector (x, y)
|
||||||
*/
|
*/
|
||||||
|
@ -99,10 +101,11 @@ export default class Vec2 {
|
||||||
/**
|
/**
|
||||||
* Sets the vector's x and y based on the angle provided. Goes counter clockwise.
|
* Sets the vector's x and y based on the angle provided. Goes counter clockwise.
|
||||||
* @param angle The angle in radians
|
* @param angle The angle in radians
|
||||||
|
* @param radius The magnitude of the vector at the specified angle
|
||||||
*/
|
*/
|
||||||
setToAngle(angle: number): Vec2 {
|
setToAngle(angle: number, radius: number = 1): Vec2 {
|
||||||
this.x = Math.cos(angle);
|
this.x = MathUtils.floorToPlace(Math.cos(angle)*radius, 5);
|
||||||
this.y = Math.sin(angle);
|
this.y = MathUtils.floorToPlace(-Math.sin(angle)*radius, 5);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,6 +116,14 @@ export default class Vec2 {
|
||||||
vecTo(other: Vec2): Vec2 {
|
vecTo(other: Vec2): Vec2 {
|
||||||
return new Vec2(other.x - this.x, other.y - this.y);
|
return new Vec2(other.x - this.x, other.y - this.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a vector containing the direction from this vector to another
|
||||||
|
* @param other
|
||||||
|
*/
|
||||||
|
dirTo(other: Vec2): Vec2 {
|
||||||
|
return this.vecTo(other).normalize();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keeps the vector's direction, but sets its magnitude to be the provided magnitude
|
* Keeps the vector's direction, but sets its magnitude to be the provided magnitude
|
||||||
|
@ -245,6 +256,22 @@ export default class Vec2 {
|
||||||
return this.x*other.x + this.y*other.y;
|
return this.x*other.x + this.y*other.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the angle counter-clockwise in radians from this vector to another vector
|
||||||
|
* @param other
|
||||||
|
*/
|
||||||
|
angleToCCW(other: Vec2): number {
|
||||||
|
let dot = this.dot(other);
|
||||||
|
let det = this.x*other.y - this.y*other.x;
|
||||||
|
let angle = -Math.atan2(det, dot);
|
||||||
|
|
||||||
|
if(angle < 0){
|
||||||
|
angle += 2*Math.PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
return angle;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a string representation of this vector rounded to 1 decimal point
|
* Returns a string representation of this vector rounded to 1 decimal point
|
||||||
*/
|
*/
|
||||||
|
@ -267,12 +294,23 @@ export default class Vec2 {
|
||||||
return new Vec2(this.x, this.y);
|
return new Vec2(this.x, this.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
strictEquals(other: Vec2): boolean {
|
||||||
|
return this.x === other.x && this.y === other.y;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if this vector and other have the same x and y
|
* Returns true if this vector and other have the same x and y
|
||||||
* @param other The vector to check against
|
* @param other The vector to check against
|
||||||
*/
|
*/
|
||||||
equals(other: Vec2): boolean {
|
equals(other: Vec2): boolean {
|
||||||
return this.x === other.x && this.y === other.y;
|
let xEq = Math.abs(this.x - other.x) < 0.00000001;
|
||||||
|
let yEq = Math.abs(this.y - other.y) < 0.00000001;
|
||||||
|
|
||||||
|
return xEq && yEq;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -293,4 +331,8 @@ export default class Vec2 {
|
||||||
getOnChange(): string {
|
getOnChange(): string {
|
||||||
return this.onChange.toString();
|
return this.onChange.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -5,7 +5,11 @@ export default class Debug {
|
||||||
// A map of log messages to display on the screen
|
// A map of log messages to display on the screen
|
||||||
private static logMessages: Map<string> = new Map();
|
private static logMessages: Map<string> = new Map();
|
||||||
|
|
||||||
static log(id: string, message: string): void {
|
static log(id: string, ...messages: any): void {
|
||||||
|
let message = "";
|
||||||
|
for(let i = 0; i < messages.length; i++){
|
||||||
|
message += messages[i].toString();
|
||||||
|
}
|
||||||
this.logMessages.add(id, message);
|
this.logMessages.add(id, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,17 +4,16 @@ import Receiver from "../Events/Receiver";
|
||||||
import Emitter from "../Events/Emitter";
|
import Emitter from "../Events/Emitter";
|
||||||
import Scene from "../Scene/Scene";
|
import Scene from "../Scene/Scene";
|
||||||
import Layer from "../Scene/Layer";
|
import Layer from "../Scene/Layer";
|
||||||
import { Physical, Positioned, isRegion, Unique, Updateable, Region } from "../DataTypes/Interfaces/Descriptors"
|
import { Physical, Positioned, isRegion, Unique, Updateable, Actor, AI } from "../DataTypes/Interfaces/Descriptors"
|
||||||
import Shape from "../DataTypes/Shapes/Shape";
|
import Shape from "../DataTypes/Shapes/Shape";
|
||||||
import GameEvent from "../Events/GameEvent";
|
|
||||||
import Map from "../DataTypes/Map";
|
import Map from "../DataTypes/Map";
|
||||||
import AABB from "../DataTypes/Shapes/AABB";
|
import AABB from "../DataTypes/Shapes/AABB";
|
||||||
import Debug from "../Debug/Debug";
|
import NavigationPath from "../Pathfinding/NavigationPath";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The representation of an object in the game world
|
* The representation of an object in the game world
|
||||||
*/
|
*/
|
||||||
export default abstract class GameNode implements Positioned, Unique, Updateable, Physical {
|
export default abstract class GameNode implements Positioned, Unique, Updateable, Physical, Actor {
|
||||||
/*---------- POSITIONED ----------*/
|
/*---------- POSITIONED ----------*/
|
||||||
private _position: Vec2;
|
private _position: Vec2;
|
||||||
|
|
||||||
|
@ -38,6 +37,13 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
|
||||||
sweptRect: AABB;
|
sweptRect: AABB;
|
||||||
isPlayer: boolean;
|
isPlayer: boolean;
|
||||||
|
|
||||||
|
/*---------- ACTOR ----------*/
|
||||||
|
_ai: AI;
|
||||||
|
aiActive: boolean;
|
||||||
|
actorId: number;
|
||||||
|
path: NavigationPath;
|
||||||
|
pathfinding: boolean = false;
|
||||||
|
|
||||||
protected input: InputReceiver;
|
protected input: InputReceiver;
|
||||||
protected receiver: Receiver;
|
protected receiver: Receiver;
|
||||||
protected emitter: Emitter;
|
protected emitter: Emitter;
|
||||||
|
@ -93,6 +99,9 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
|
||||||
finishMove = (): void => {
|
finishMove = (): void => {
|
||||||
this.moving = false;
|
this.moving = false;
|
||||||
this.position.add(this._velocity);
|
this.position.add(this._velocity);
|
||||||
|
if(this.pathfinding){
|
||||||
|
this.path.handlePathProgress(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -138,6 +147,41 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
|
||||||
this.triggers.add(group, eventType);
|
this.triggers.add(group, eventType);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*---------- ACTOR ----------*/
|
||||||
|
get ai(): AI {
|
||||||
|
return this._ai;
|
||||||
|
}
|
||||||
|
|
||||||
|
set ai(ai: AI) {
|
||||||
|
if(!this._ai){
|
||||||
|
// If we haven't been previously had an ai, register us with the ai manager
|
||||||
|
this.scene.getAIManager().registerActor(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._ai = ai;
|
||||||
|
this.aiActive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
addAI<T extends AI>(ai: string | (new () => T), options?: Record<string, any>): void {
|
||||||
|
if(!this._ai){
|
||||||
|
this.scene.getAIManager().registerActor(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(typeof ai === "string"){
|
||||||
|
this._ai = this.scene.getAIManager().generateAI(ai);
|
||||||
|
} else {
|
||||||
|
this._ai = new ai();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._ai.initializeAI(this, options);
|
||||||
|
|
||||||
|
this.aiActive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
setAIActive(active: boolean): void {
|
||||||
|
this.aiActive = active;
|
||||||
|
}
|
||||||
|
|
||||||
/*---------- GAME NODE ----------*/
|
/*---------- GAME NODE ----------*/
|
||||||
/**
|
/**
|
||||||
* Sets the scene for this object.
|
* Sets the scene for this object.
|
||||||
|
@ -175,14 +219,5 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO - This doesn't seem ideal. Is there a better way to do this?
|
|
||||||
getViewportOriginWithParallax(): Vec2 {
|
|
||||||
return this.scene.getViewport().getOrigin().mult(this.layer.getParallax());
|
|
||||||
}
|
|
||||||
|
|
||||||
getViewportScale(): number {
|
|
||||||
return this.scene.getViewport().getZoomLevel();
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract update(deltaT: number): void;
|
abstract update(deltaT: number): void;
|
||||||
}
|
}
|
4
src/Nodes/Graphics/GraphicTypes.ts
Normal file
4
src/Nodes/Graphics/GraphicTypes.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export enum GraphicType {
|
||||||
|
POINT = "POINT",
|
||||||
|
RECT = "RECT",
|
||||||
|
}
|
|
@ -12,11 +12,12 @@ export default class Point extends Graphic {
|
||||||
update(deltaT: number): void {}
|
update(deltaT: number): void {}
|
||||||
|
|
||||||
render(ctx: CanvasRenderingContext2D): void {
|
render(ctx: CanvasRenderingContext2D): void {
|
||||||
let origin = this.getViewportOriginWithParallax();
|
let origin = this.scene.getViewTranslation(this);
|
||||||
|
let zoom = this.scene.getViewScale();
|
||||||
|
|
||||||
ctx.fillStyle = this.color.toStringRGBA();
|
ctx.fillStyle = this.color.toStringRGBA();
|
||||||
ctx.fillRect(this.position.x - origin.x - this.size.x/2, this.position.y - origin.y - this.size.y/2,
|
ctx.fillRect((this.position.x - origin.x - this.size.x/2)*zoom, (this.position.y - origin.y - this.size.y/2)*zoom,
|
||||||
this.size.x, this.size.y);
|
this.size.x*zoom, this.size.y*zoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -34,8 +34,8 @@ export default class Rect extends Graphic {
|
||||||
update(deltaT: number): void {}
|
update(deltaT: number): void {}
|
||||||
|
|
||||||
render(ctx: CanvasRenderingContext2D): void {
|
render(ctx: CanvasRenderingContext2D): void {
|
||||||
let origin = this.getViewportOriginWithParallax();
|
let origin = this.scene.getViewTranslation(this);
|
||||||
let zoom = this.getViewportScale();
|
let zoom = this.scene.getViewScale();
|
||||||
|
|
||||||
if(this.color.a !== 0){
|
if(this.color.a !== 0){
|
||||||
ctx.fillStyle = this.color.toStringRGB();
|
ctx.fillStyle = this.color.toStringRGB();
|
||||||
|
|
|
@ -29,8 +29,8 @@ export default class Sprite extends CanvasNode {
|
||||||
|
|
||||||
render(ctx: CanvasRenderingContext2D): void {
|
render(ctx: CanvasRenderingContext2D): void {
|
||||||
let image = ResourceManager.getInstance().getImage(this.imageId);
|
let image = ResourceManager.getInstance().getImage(this.imageId);
|
||||||
let origin = this.getViewportOriginWithParallax();
|
let origin = this.scene.getViewTranslation(this);
|
||||||
let zoom = this.getViewportScale();
|
let zoom = this.scene.getViewScale();
|
||||||
|
|
||||||
ctx.drawImage(image,
|
ctx.drawImage(image,
|
||||||
this.imageOffset.x, this.imageOffset.y, this.size.x, this.size.y,
|
this.imageOffset.x, this.imageOffset.y, this.size.x, this.size.y,
|
||||||
|
|
|
@ -85,8 +85,8 @@ export default class OrthogonalTilemap extends Tilemap {
|
||||||
let previousAlpha = ctx.globalAlpha;
|
let previousAlpha = ctx.globalAlpha;
|
||||||
ctx.globalAlpha = this.getLayer().getAlpha();
|
ctx.globalAlpha = this.getLayer().getAlpha();
|
||||||
|
|
||||||
let origin = this.getViewportOriginWithParallax();
|
let origin = this.scene.getViewTranslation(this);
|
||||||
let zoom = this.getViewportScale();
|
let zoom = this.scene.getViewScale();
|
||||||
|
|
||||||
if(this.visible){
|
if(this.visible){
|
||||||
for(let i = 0; i < this.data.length; i++){
|
for(let i = 0; i < this.data.length; i++){
|
||||||
|
|
|
@ -31,8 +31,10 @@ export default class UIElement extends CanvasNode {
|
||||||
protected isClicked: boolean;
|
protected isClicked: boolean;
|
||||||
protected isEntered: boolean;
|
protected isEntered: boolean;
|
||||||
|
|
||||||
constructor(){
|
constructor(position: Vec2){
|
||||||
super();
|
super();
|
||||||
|
this.position = position;
|
||||||
|
|
||||||
this.textColor = new Color(0, 0, 0, 1);
|
this.textColor = new Color(0, 0, 0, 1);
|
||||||
this.backgroundColor = new Color(0, 0, 0, 0);
|
this.backgroundColor = new Color(0, 0, 0, 0);
|
||||||
this.borderColor = new Color(0, 0, 0, 0);
|
this.borderColor = new Color(0, 0, 0, 0);
|
||||||
|
@ -82,6 +84,7 @@ export default class UIElement extends CanvasNode {
|
||||||
}
|
}
|
||||||
if(this.onClickEventId !== null){
|
if(this.onClickEventId !== null){
|
||||||
let data = {};
|
let data = {};
|
||||||
|
console.log("Click event: " + this.onClickEventId)
|
||||||
this.emitter.fireEvent(this.onClickEventId, data);
|
this.emitter.fireEvent(this.onClickEventId, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -179,7 +182,7 @@ export default class UIElement extends CanvasNode {
|
||||||
let previousAlpha = ctx.globalAlpha;
|
let previousAlpha = ctx.globalAlpha;
|
||||||
ctx.globalAlpha = this.getLayer().getAlpha();
|
ctx.globalAlpha = this.getLayer().getAlpha();
|
||||||
|
|
||||||
let origin = this.getViewportOriginWithParallax();
|
let origin = this.scene.getViewTranslation(this);
|
||||||
|
|
||||||
ctx.font = this.fontSize + "px " + this.font;
|
ctx.font = this.fontSize + "px " + this.font;
|
||||||
let offset = this.calculateOffset(ctx);
|
let offset = this.calculateOffset(ctx);
|
||||||
|
|
|
@ -4,8 +4,10 @@ import Vec2 from "../../DataTypes/Vec2";
|
||||||
|
|
||||||
export default class Button extends UIElement{
|
export default class Button extends UIElement{
|
||||||
|
|
||||||
constructor(){
|
constructor(position: Vec2, text: string){
|
||||||
super();
|
super(position);
|
||||||
|
this.text = text;
|
||||||
|
|
||||||
this.backgroundColor = new Color(150, 75, 203);
|
this.backgroundColor = new Color(150, 75, 203);
|
||||||
this.borderColor = new Color(41, 46, 30);
|
this.borderColor = new Color(41, 46, 30);
|
||||||
this.textColor = new Color(255, 255, 255);
|
this.textColor = new Color(255, 255, 255);
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
import Vec2 from "../../DataTypes/Vec2";
|
||||||
import UIElement from "../UIElement";
|
import UIElement from "../UIElement";
|
||||||
|
|
||||||
export default class Label extends UIElement{
|
export default class Label extends UIElement{
|
||||||
constructor(text: string){
|
constructor(position: Vec2, text: string){
|
||||||
super();
|
super(position);
|
||||||
this.text = text;
|
this.text = text;
|
||||||
}
|
}
|
||||||
}
|
}
|
4
src/Nodes/UIElements/UIElementTypes.ts
Normal file
4
src/Nodes/UIElements/UIElementTypes.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export enum UIElementType {
|
||||||
|
BUTTON = "BUTTON",
|
||||||
|
LABEL = "LABEL",
|
||||||
|
}
|
22
src/Pathfinding/NavigationManager.ts
Normal file
22
src/Pathfinding/NavigationManager.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { Navigable } from "../DataTypes/Interfaces/Descriptors"
|
||||||
|
import Map from "../DataTypes/Map";
|
||||||
|
import Vec2 from "../DataTypes/Vec2";
|
||||||
|
import NavigationPath from "./NavigationPath";
|
||||||
|
|
||||||
|
export default class NavigationManager {
|
||||||
|
|
||||||
|
protected navigableEntities: Map<Navigable>;
|
||||||
|
|
||||||
|
constructor(){
|
||||||
|
this.navigableEntities = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
addNavigableEntity(navName: string, nav: Navigable){
|
||||||
|
this.navigableEntities.add(navName, nav);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPath(navName: string, fromPosition: Vec2, toPosition: Vec2): NavigationPath {
|
||||||
|
let nav = this.navigableEntities.get(navName);
|
||||||
|
return nav.getNavigationPath(fromPosition.clone(), toPosition.clone());
|
||||||
|
}
|
||||||
|
}
|
35
src/Pathfinding/NavigationPath.ts
Normal file
35
src/Pathfinding/NavigationPath.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import Stack from "../DataTypes/Stack";
|
||||||
|
import Vec2 from "../DataTypes/Vec2";
|
||||||
|
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<Vec2>
|
||||||
|
protected currentMoveDirection: Vec2;
|
||||||
|
protected distanceThreshold: number;
|
||||||
|
|
||||||
|
constructor(path: Stack<Vec2>){
|
||||||
|
this.path = path;
|
||||||
|
console.log(path.toString())
|
||||||
|
this.currentMoveDirection = Vec2.ZERO;
|
||||||
|
this.distanceThreshold = 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
isDone(): boolean {
|
||||||
|
return this.path.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
getMoveDirection(node: GameNode): Vec2 {
|
||||||
|
// Return direction to next point in the nav
|
||||||
|
return node.position.dirTo(this.path.peek());
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
this.path.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
src/Pathfinding/Navmesh.ts
Normal file
53
src/Pathfinding/Navmesh.ts
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import PositionGraph from "../DataTypes/Graphs/PositionGraph";
|
||||||
|
import { Navigable } from "../DataTypes/Interfaces/Descriptors";
|
||||||
|
import Stack from "../DataTypes/Stack";
|
||||||
|
import Vec2 from "../DataTypes/Vec2";
|
||||||
|
import GraphUtils from "../Utils/GraphUtils";
|
||||||
|
import NavigationPath from "./NavigationPath";
|
||||||
|
|
||||||
|
export default class Navmesh implements Navigable {
|
||||||
|
protected graph: PositionGraph;
|
||||||
|
|
||||||
|
constructor(graph: PositionGraph){
|
||||||
|
this.graph = graph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getNavigationPath(fromPosition: Vec2, toPosition: Vec2): NavigationPath {
|
||||||
|
let start = this.getClosestNode(fromPosition);
|
||||||
|
let end = this.getClosestNode(toPosition);
|
||||||
|
|
||||||
|
let parent = GraphUtils.djikstra(this.graph, start);
|
||||||
|
|
||||||
|
let pathStack = new Stack<Vec2>(this.graph.numVertices);
|
||||||
|
|
||||||
|
// Push the final position and the final position in the graph
|
||||||
|
pathStack.push(toPosition.clone());
|
||||||
|
pathStack.push(this.graph.positions[end]);
|
||||||
|
|
||||||
|
// Add all parents along the path
|
||||||
|
let i = end;
|
||||||
|
while(parent[i] !== -1){
|
||||||
|
pathStack.push(this.graph.positions[parent[i]]);
|
||||||
|
i = parent[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new NavigationPath(pathStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
getClosestNode(position: Vec2): number {
|
||||||
|
let n = this.graph.numVertices;
|
||||||
|
let i = 1;
|
||||||
|
let index = 0;
|
||||||
|
let dist = position.distanceSqTo(this.graph.positions[0]);
|
||||||
|
while(i < n){
|
||||||
|
let d = position.distanceSqTo(this.graph.positions[i]);
|
||||||
|
if(d < dist){
|
||||||
|
dist = d;
|
||||||
|
index = i;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
12
src/Physics/Collisions.ts
Normal file
12
src/Physics/Collisions.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { Physical } from "../DataTypes/Interfaces/Descriptors";
|
||||||
|
import AABB from "../DataTypes/Shapes/AABB";
|
||||||
|
import Vec2 from "../DataTypes/Vec2";
|
||||||
|
|
||||||
|
export class Collision {
|
||||||
|
firstContact: Vec2;
|
||||||
|
lastContact: Vec2;
|
||||||
|
collidingX: boolean;
|
||||||
|
collidingY: boolean;
|
||||||
|
node1: Physical;
|
||||||
|
node2: Physical;
|
||||||
|
}
|
|
@ -1,9 +1,16 @@
|
||||||
import Scene from "../Scene";
|
import Scene from "../Scene";
|
||||||
import SceneGraph from "../../SceneGraph/SceneGraph";
|
|
||||||
import UIElement from "../../Nodes/UIElement";
|
import UIElement from "../../Nodes/UIElement";
|
||||||
import Layer from "../Layer";
|
import Layer from "../Layer";
|
||||||
import Graphic from "../../Nodes/Graphic";
|
import Graphic from "../../Nodes/Graphic";
|
||||||
import Sprite from "../../Nodes/Sprites/Sprite";
|
import Sprite from "../../Nodes/Sprites/Sprite";
|
||||||
|
import { GraphicType } from "../../Nodes/Graphics/GraphicTypes";
|
||||||
|
import { UIElementType } from "../../Nodes/UIElements/UIElementTypes";
|
||||||
|
import Point from "../../Nodes/Graphics/Point";
|
||||||
|
import Vec2 from "../../DataTypes/Vec2";
|
||||||
|
import Shape from "../../DataTypes/Shapes/Shape";
|
||||||
|
import Button from "../../Nodes/UIElements/Button";
|
||||||
|
import Label from "../../Nodes/UIElements/Label";
|
||||||
|
import Rect from "../../Nodes/Graphics/Rect";
|
||||||
|
|
||||||
export default class CanvasNodeFactory {
|
export default class CanvasNodeFactory {
|
||||||
private scene: Scene;
|
private scene: Scene;
|
||||||
|
@ -14,20 +21,33 @@ export default class CanvasNodeFactory {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an instance of a UIElement to the current scene - i.e. any class that extends UIElement
|
* Adds an instance of a UIElement to the current scene - i.e. any class that extends UIElement
|
||||||
* @param constr The constructor of the UIElement to be created
|
* @param type The type of UIElement to add
|
||||||
* @param layer The layer to add the UIElement to
|
* @param layerName The layer to add the UIElement to
|
||||||
* @param args Any additional arguments to feed to the constructor
|
* @param options Any additional arguments to feed to the constructor
|
||||||
*/
|
*/
|
||||||
addUIElement = <T extends UIElement>(constr: new (...a: any) => T, layer: Layer, ...args: any): T => {
|
addUIElement = (type: string | UIElementType, layerName: string, options?: Record<string, any>): UIElement => {
|
||||||
let instance = new constr(...args);
|
// Get the layer
|
||||||
|
let layer = this.scene.getLayer(layerName);
|
||||||
|
|
||||||
|
let instance: UIElement;
|
||||||
|
|
||||||
|
switch(type){
|
||||||
|
case UIElementType.BUTTON:
|
||||||
|
instance = this.buildButton(options);
|
||||||
|
break;
|
||||||
|
case UIElementType.LABEL:
|
||||||
|
instance = this.buildLabel(options);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw `UIElementType '${type}' does not exist, or is registered incorrectly.`
|
||||||
|
}
|
||||||
|
|
||||||
// Add instance to scene
|
|
||||||
instance.setScene(this.scene);
|
instance.setScene(this.scene);
|
||||||
instance.id = this.scene.generateId();
|
instance.id = this.scene.generateId();
|
||||||
this.scene.getSceneGraph().addNode(instance);
|
this.scene.getSceneGraph().addNode(instance);
|
||||||
|
|
||||||
// Add instance to layer
|
// Add instance to layer
|
||||||
layer.addNode(instance);
|
layer.addNode(instance)
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
@ -35,9 +55,11 @@ export default class CanvasNodeFactory {
|
||||||
/**
|
/**
|
||||||
* Adds a sprite to the current scene
|
* Adds a sprite to the current scene
|
||||||
* @param key The key of the image the sprite will represent
|
* @param key The key of the image the sprite will represent
|
||||||
* @param layer The layer on which to add the sprite
|
* @param layerName The layer on which to add the sprite
|
||||||
*/
|
*/
|
||||||
addSprite = (key: string, layer: Layer): Sprite => {
|
addSprite = (key: string, layerName: string): Sprite => {
|
||||||
|
let layer = this.scene.getLayer(layerName);
|
||||||
|
|
||||||
let instance = new Sprite(key);
|
let instance = new Sprite(key);
|
||||||
|
|
||||||
// Add instance to scene
|
// Add instance to scene
|
||||||
|
@ -53,21 +75,90 @@ export default class CanvasNodeFactory {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a new graphic element to the current Scene
|
* Adds a new graphic element to the current Scene
|
||||||
* @param constr The constructor of the graphic element to add
|
* @param type The type of graphic to add
|
||||||
* @param layer The layer on which to add the graphic
|
* @param layerName The layer on which to add the graphic
|
||||||
* @param args Any additional arguments to send to the graphic constructor
|
* @param options Any additional arguments to send to the graphic constructor
|
||||||
*/
|
*/
|
||||||
addGraphic = <T extends Graphic>(constr: new (...a: any) => T, layer: Layer, ...args: any): T => {
|
addGraphic = (type: GraphicType | string, layerName: string, options?: Record<string, any>): Graphic => {
|
||||||
let instance = new constr(...args);
|
// Get the layer
|
||||||
|
let layer = this.scene.getLayer(layerName);
|
||||||
|
|
||||||
|
let instance: Graphic;
|
||||||
|
|
||||||
|
switch(type){
|
||||||
|
case GraphicType.POINT:
|
||||||
|
instance = this.buildPoint(options);
|
||||||
|
break;
|
||||||
|
case GraphicType.RECT:
|
||||||
|
instance = this.buildRect(options);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw `GraphicType '${type}' does not exist, or is registered incorrectly.`
|
||||||
|
}
|
||||||
|
|
||||||
// Add instance to scene
|
// Add instance to scene
|
||||||
instance.setScene(this.scene);
|
instance.setScene(this.scene);
|
||||||
instance.id = this.scene.generateId();
|
instance.id = this.scene.generateId();
|
||||||
this.scene.getSceneGraph().addNode(instance);
|
|
||||||
|
if(!(this.scene.isParallaxLayer(layerName) || this.scene.isUILayer(layerName))){
|
||||||
|
this.scene.getSceneGraph().addNode(instance);
|
||||||
|
}
|
||||||
|
|
||||||
// Add instance to layer
|
// Add instance to layer
|
||||||
layer.addNode(instance);
|
layer.addNode(instance);
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- BUILDERS ---------- */
|
||||||
|
|
||||||
|
buildButton(options?: Record<string, any>): Button {
|
||||||
|
this.checkIfPropExists("Button", options, "position", Vec2, "Vec2");
|
||||||
|
this.checkIfPropExists("Button", options, "text", "string");
|
||||||
|
|
||||||
|
return new Button(options.position, options.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
buildLabel(options?: Record<string, any>): Label {
|
||||||
|
this.checkIfPropExists("Label", options, "position", Vec2, "Vec2");
|
||||||
|
this.checkIfPropExists("Label", options, "text", "string");
|
||||||
|
|
||||||
|
return new Label(options.position, options.text)
|
||||||
|
}
|
||||||
|
|
||||||
|
buildPoint(options?: Record<string, any>): Point {
|
||||||
|
this.checkIfPropExists("Point", options, "position", Vec2, "Vec2");
|
||||||
|
|
||||||
|
return new Point(options.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
buildRect(options?: Record<string, any>): Rect {
|
||||||
|
this.checkIfPropExists("Rect", options, "position", Vec2, "Vec2");
|
||||||
|
this.checkIfPropExists("Rect", options, "size", Vec2, "Vec2");
|
||||||
|
|
||||||
|
return new Rect(options.position, options.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- ERROR HANDLING ---------- */
|
||||||
|
|
||||||
|
checkIfPropExists<T>(objectName: string, options: Record<string, any>, prop: string, type: (new (...args: any) => T) | string, typeName?: string){
|
||||||
|
if(!options || !options[prop]){
|
||||||
|
// Check that the options object has the property
|
||||||
|
throw `${objectName} object requires argument ${prop} of type ${typeName}, but none was provided.`;
|
||||||
|
} else {
|
||||||
|
// Check that the property has the correct type
|
||||||
|
if((typeof type) === "string"){
|
||||||
|
if(!(typeof options[prop] === type)){
|
||||||
|
throw `${objectName} object requires argument ${prop} of type ${type}, but provided ${prop} was not of type ${type}.`;
|
||||||
|
}
|
||||||
|
} else if(type instanceof Function){
|
||||||
|
// If type is a constructor, check against that
|
||||||
|
if(!(options[prop] instanceof type)){
|
||||||
|
throw `${objectName} object requires argument ${prop} of type ${typeName}, but provided ${prop} was not of type ${typeName}.`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw `${objectName} object requires argument ${prop} of type ${typeName}, but provided ${prop} was not of type ${typeName}.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
import Scene from "../Scene";
|
import Scene from "../Scene";
|
||||||
import Tilemap from "../../Nodes/Tilemap";
|
import Tilemap from "../../Nodes/Tilemap";
|
||||||
import PhysicsManager from "../../Physics/PhysicsManager";
|
|
||||||
import ResourceManager from "../../ResourceManager/ResourceManager";
|
import ResourceManager from "../../ResourceManager/ResourceManager";
|
||||||
import OrthogonalTilemap from "../../Nodes/Tilemaps/OrthogonalTilemap";
|
import OrthogonalTilemap from "../../Nodes/Tilemaps/OrthogonalTilemap";
|
||||||
import Layer from "../Layer";
|
import Layer from "../Layer";
|
||||||
|
@ -8,6 +7,8 @@ import Tileset from "../../DataTypes/Tilesets/Tileset";
|
||||||
import Vec2 from "../../DataTypes/Vec2";
|
import Vec2 from "../../DataTypes/Vec2";
|
||||||
import { TiledCollectionTile } from "../../DataTypes/Tilesets/TiledData";
|
import { TiledCollectionTile } from "../../DataTypes/Tilesets/TiledData";
|
||||||
import Sprite from "../../Nodes/Sprites/Sprite";
|
import Sprite from "../../Nodes/Sprites/Sprite";
|
||||||
|
import PositionGraph from "../../DataTypes/Graphs/PositionGraph";
|
||||||
|
import Navmesh from "../../Pathfinding/Navmesh";
|
||||||
|
|
||||||
export default class TilemapFactory {
|
export default class TilemapFactory {
|
||||||
private scene: Scene;
|
private scene: Scene;
|
||||||
|
@ -63,7 +64,7 @@ export default class TilemapFactory {
|
||||||
// Loop over the layers of the tilemap and create tiledlayers or object layers
|
// Loop over the layers of the tilemap and create tiledlayers or object layers
|
||||||
for(let layer of tilemapData.layers){
|
for(let layer of tilemapData.layers){
|
||||||
|
|
||||||
let sceneLayer = this.scene.addLayer();
|
let sceneLayer = this.scene.addLayer(layer.name);
|
||||||
|
|
||||||
if(layer.type === "tilelayer"){
|
if(layer.type === "tilelayer"){
|
||||||
// Create a new tilemap object for the layer
|
// Create a new tilemap object for the layer
|
||||||
|
@ -82,17 +83,34 @@ export default class TilemapFactory {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
let isNavmeshPoints = false
|
let isNavmeshPoints = false;
|
||||||
|
let navmeshName;
|
||||||
|
let edges;
|
||||||
if(layer.properties){
|
if(layer.properties){
|
||||||
for(let prop of layer.properties){
|
for(let prop of layer.properties){
|
||||||
if(prop.name === "NavmeshPoints"){
|
if(prop.name === "NavmeshPoints"){
|
||||||
isNavmeshPoints = true;
|
isNavmeshPoints = true;
|
||||||
|
} else if(prop.name === "name"){
|
||||||
|
navmeshName = prop.value;
|
||||||
|
} else if(prop.name === "edges"){
|
||||||
|
edges = prop.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isNavmeshPoints){
|
if(isNavmeshPoints){
|
||||||
console.log("Parsing NavmeshPoints")
|
let g = new PositionGraph();
|
||||||
|
|
||||||
|
for(let obj of layer.objects){
|
||||||
|
g.addPositionedNode(new Vec2(obj.x, obj.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let edge of edges){
|
||||||
|
g.addEdge(edge.from, edge.to);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scene.getNavigationManager().addNavigableEntity(navmeshName, new Navmesh(g));
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +144,7 @@ export default class TilemapFactory {
|
||||||
// The object is a tile from this set
|
// The object is a tile from this set
|
||||||
let imageKey = tileset.getImageKey();
|
let imageKey = tileset.getImageKey();
|
||||||
let offset = tileset.getImageOffsetForTile(obj.gid);
|
let offset = tileset.getImageOffsetForTile(obj.gid);
|
||||||
sprite = this.scene.add.sprite(imageKey, sceneLayer);
|
sprite = this.scene.add.sprite(imageKey, layer.name);
|
||||||
let size = tileset.getTileSize().clone();
|
let size = tileset.getTileSize().clone();
|
||||||
sprite.position.set((obj.x + size.x/2)*scale.x, (obj.y - size.y/2)*scale.y);
|
sprite.position.set((obj.x + size.x/2)*scale.x, (obj.y - size.y/2)*scale.y);
|
||||||
sprite.setImageOffset(offset);
|
sprite.setImageOffset(offset);
|
||||||
|
@ -140,7 +158,7 @@ export default class TilemapFactory {
|
||||||
for(let tile of collectionTiles){
|
for(let tile of collectionTiles){
|
||||||
if(obj.gid === tile.id){
|
if(obj.gid === tile.id){
|
||||||
let imageKey = tile.image;
|
let imageKey = tile.image;
|
||||||
sprite = this.scene.add.sprite(imageKey, sceneLayer);
|
sprite = this.scene.add.sprite(imageKey, layer.name);
|
||||||
sprite.position.set((obj.x + tile.imagewidth/2)*scale.x, (obj.y - tile.imageheight/2)*scale.y);
|
sprite.position.set((obj.x + tile.imagewidth/2)*scale.x, (obj.y - tile.imageheight/2)*scale.y);
|
||||||
sprite.scale.set(scale.x, scale.y);
|
sprite.scale.set(scale.x, scale.y);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,39 @@
|
||||||
import Vec2 from "../DataTypes/Vec2";
|
|
||||||
import Scene from "./Scene";
|
import Scene from "./Scene";
|
||||||
import MathUtils from "../Utils/MathUtils";
|
import MathUtils from "../Utils/MathUtils";
|
||||||
import GameNode from "../Nodes/GameNode";
|
import GameNode from "../Nodes/GameNode";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A layer in the scene. Has its own alpha value and parallax.
|
* A layer in the scene. Has its own alpha value and parallax.
|
||||||
*/
|
*/
|
||||||
export default class Layer {
|
export default class Layer {
|
||||||
|
/** The scene this layer belongs to */
|
||||||
protected scene: Scene;
|
protected scene: Scene;
|
||||||
protected parallax: Vec2;
|
|
||||||
|
/** The name of this layer */
|
||||||
|
protected name: string;
|
||||||
|
|
||||||
|
/** Whether this layer is paused or not */
|
||||||
protected paused: boolean;
|
protected paused: boolean;
|
||||||
|
|
||||||
|
/** Whether this layer is hidden from being rendered or not */
|
||||||
protected hidden: boolean;
|
protected hidden: boolean;
|
||||||
|
|
||||||
|
/** The global alpha level of this layer */
|
||||||
protected alpha: number;
|
protected alpha: number;
|
||||||
|
|
||||||
|
/** An array of the GameNodes that belong to this layer */
|
||||||
protected items: Array<GameNode>;
|
protected items: Array<GameNode>;
|
||||||
|
|
||||||
|
/** Whether or not this layer should be ysorted */
|
||||||
protected ySort: boolean;
|
protected ySort: boolean;
|
||||||
|
|
||||||
|
/** The depth of this layer compared to other layers */
|
||||||
protected depth: number;
|
protected depth: number;
|
||||||
|
|
||||||
constructor(scene: Scene){
|
constructor(scene: Scene, name: string){
|
||||||
this.scene = scene;
|
this.scene = scene;
|
||||||
this.parallax = new Vec2(1, 1);
|
this.name = name;
|
||||||
this.paused = false;
|
this.paused = false;
|
||||||
this.hidden = false;
|
this.hidden = false;
|
||||||
this.alpha = 1;
|
this.alpha = 1;
|
||||||
|
@ -51,24 +66,18 @@ export default class Layer {
|
||||||
return this.hidden;
|
return this.hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Pauses this scene and hides it */
|
||||||
disable(): void {
|
disable(): void {
|
||||||
this.paused = true;
|
this.paused = true;
|
||||||
this.hidden = true;
|
this.hidden = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Unpauses this layer and makes it visible */
|
||||||
enable(): void {
|
enable(): void {
|
||||||
this.paused = false;
|
this.paused = false;
|
||||||
this.hidden = false;
|
this.hidden = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
setParallax(x: number, y: number): void {
|
|
||||||
this.parallax.set(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
getParallax(): Vec2 {
|
|
||||||
return this.parallax;
|
|
||||||
}
|
|
||||||
|
|
||||||
setYSort(ySort: boolean): void {
|
setYSort(ySort: boolean): void {
|
||||||
this.ySort = ySort;
|
this.ySort = ySort;
|
||||||
}
|
}
|
||||||
|
@ -89,4 +98,8 @@ export default class Layer {
|
||||||
this.items.push(node);
|
this.items.push(node);
|
||||||
node.setLayer(this);
|
node.setLayer(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getItems(): Array<GameNode> {
|
||||||
|
return this.items;
|
||||||
|
}
|
||||||
}
|
}
|
12
src/Scene/Layers/ParallaxLayer.ts
Normal file
12
src/Scene/Layers/ParallaxLayer.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import Layer from "../Layer";
|
||||||
|
import Vec2 from "../../DataTypes/Vec2";
|
||||||
|
import Scene from "../Scene";
|
||||||
|
|
||||||
|
export default class ParallaxLayer extends Layer {
|
||||||
|
parallax: Vec2;
|
||||||
|
|
||||||
|
constructor(scene: Scene, name: string, parallax: Vec2){
|
||||||
|
super(scene, name);
|
||||||
|
this.parallax = parallax;
|
||||||
|
}
|
||||||
|
}
|
9
src/Scene/Layers/UILayer.ts
Normal file
9
src/Scene/Layers/UILayer.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import Vec2 from "../../DataTypes/Vec2";
|
||||||
|
import Scene from "../Scene";
|
||||||
|
import ParallaxLayer from "./ParallaxLayer";
|
||||||
|
|
||||||
|
export default class UILayer extends ParallaxLayer {
|
||||||
|
constructor(scene: Scene, name: string){
|
||||||
|
super(scene, name, Vec2.ZERO);
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,13 @@ import SceneManager from "./SceneManager";
|
||||||
import Receiver from "../Events/Receiver";
|
import Receiver from "../Events/Receiver";
|
||||||
import Emitter from "../Events/Emitter";
|
import Emitter from "../Events/Emitter";
|
||||||
import { Renderable, Updateable } from "../DataTypes/Interfaces/Descriptors";
|
import { Renderable, Updateable } from "../DataTypes/Interfaces/Descriptors";
|
||||||
import Navmesh from "../DataTypes/Navmesh";
|
import NavigationManager from "../Pathfinding/NavigationManager";
|
||||||
|
import AIManager from "../AI/AIManager";
|
||||||
|
import Map from "../DataTypes/Map";
|
||||||
|
import ParallaxLayer from "./Layers/ParallaxLayer";
|
||||||
|
import UILayer from "./Layers/UILayer";
|
||||||
|
import CanvasNode from "../Nodes/CanvasNode";
|
||||||
|
import GameNode from "../Nodes/GameNode";
|
||||||
|
|
||||||
export default class Scene implements Updateable, Renderable {
|
export default class Scene implements Updateable, Renderable {
|
||||||
/** The size of the game world. */
|
/** The size of the game world. */
|
||||||
|
@ -40,18 +46,33 @@ export default class Scene implements Updateable, Renderable {
|
||||||
/** This list of tilemaps in this scene. */
|
/** This list of tilemaps in this scene. */
|
||||||
protected tilemaps: Array<Tilemap>;
|
protected tilemaps: Array<Tilemap>;
|
||||||
|
|
||||||
|
/** A map from layer names to the layers themselves */
|
||||||
|
protected layers: Map<Layer>;
|
||||||
|
|
||||||
|
/** A map from parallax layer names to the parallax layers themselves */
|
||||||
|
protected parallaxLayers: Map<ParallaxLayer>;
|
||||||
|
|
||||||
|
/** A map from uiLayer names to the uiLayers themselves */
|
||||||
|
protected uiLayers: Map<UILayer>;
|
||||||
|
|
||||||
/** The scene graph of the Scene*/
|
/** The scene graph of the Scene*/
|
||||||
protected sceneGraph: SceneGraph;
|
protected sceneGraph: SceneGraph;
|
||||||
|
|
||||||
|
/** The physics manager of the Scene */
|
||||||
protected physicsManager: PhysicsManager;
|
protected physicsManager: PhysicsManager;
|
||||||
|
|
||||||
|
/** The navigation manager of the Scene */
|
||||||
|
protected navManager: NavigationManager;
|
||||||
|
|
||||||
|
/** The AI manager of the Scene */
|
||||||
|
protected aiManager: AIManager;
|
||||||
|
|
||||||
/** An interface that allows the adding of different nodes to the scene */
|
/** An interface that allows the adding of different nodes to the scene */
|
||||||
public add: FactoryManager;
|
public add: FactoryManager;
|
||||||
|
|
||||||
/** An interface that allows the loading of different files for use in the scene */
|
/** An interface that allows the loading of different files for use in the scene */
|
||||||
public load: ResourceManager;
|
public load: ResourceManager;
|
||||||
|
|
||||||
protected navmeshes: Array<Navmesh>;
|
|
||||||
|
|
||||||
constructor(viewport: Viewport, sceneManager: SceneManager, game: GameLoop){
|
constructor(viewport: Viewport, sceneManager: SceneManager, game: GameLoop){
|
||||||
this.worldSize = new Vec2(500, 500);
|
this.worldSize = new Vec2(500, 500);
|
||||||
this.viewport = viewport;
|
this.viewport = viewport;
|
||||||
|
@ -64,7 +85,14 @@ export default class Scene implements Updateable, Renderable {
|
||||||
|
|
||||||
this.tilemaps = new Array();
|
this.tilemaps = new Array();
|
||||||
this.sceneGraph = new SceneGraphArray(this.viewport, this);
|
this.sceneGraph = new SceneGraphArray(this.viewport, this);
|
||||||
|
|
||||||
|
this.layers = new Map();
|
||||||
|
this.uiLayers = new Map();
|
||||||
|
this.parallaxLayers = new Map();
|
||||||
|
|
||||||
this.physicsManager = new BasicPhysicsManager();
|
this.physicsManager = new BasicPhysicsManager();
|
||||||
|
this.navManager = new NavigationManager();
|
||||||
|
this.aiManager = new AIManager();
|
||||||
|
|
||||||
this.add = new FactoryManager(this, this.tilemaps);
|
this.add = new FactoryManager(this, this.tilemaps);
|
||||||
|
|
||||||
|
@ -89,6 +117,9 @@ export default class Scene implements Updateable, Renderable {
|
||||||
update(deltaT: number): void {
|
update(deltaT: number): void {
|
||||||
this.updateScene(deltaT);
|
this.updateScene(deltaT);
|
||||||
|
|
||||||
|
// Do all AI updates
|
||||||
|
this.aiManager.update(deltaT);
|
||||||
|
|
||||||
// Update all physics objects
|
// Update all physics objects
|
||||||
this.physicsManager.update(deltaT);
|
this.physicsManager.update(deltaT);
|
||||||
|
|
||||||
|
@ -111,6 +142,25 @@ export default class Scene implements Updateable, Renderable {
|
||||||
// We need to keep track of the order of things.
|
// We need to keep track of the order of things.
|
||||||
let visibleSet = this.sceneGraph.getVisibleSet();
|
let visibleSet = this.sceneGraph.getVisibleSet();
|
||||||
|
|
||||||
|
// Add parallax layer items to the visible set (we're rendering them all for now)
|
||||||
|
this.parallaxLayers.forEach(key => {
|
||||||
|
let pLayer = this.parallaxLayers.get(key);
|
||||||
|
for(let node of pLayer.getItems()){
|
||||||
|
if(node instanceof CanvasNode){
|
||||||
|
visibleSet.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sort by depth, then by visible set by y-value
|
||||||
|
visibleSet.sort((a, b) => {
|
||||||
|
if(a.getLayer().getDepth() === b.getLayer().getDepth()){
|
||||||
|
return (a.boundary.bottom) - (b.boundary.bottom);
|
||||||
|
} else {
|
||||||
|
return a.getLayer().getDepth() - b.getLayer().getDepth();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Render scene graph for demo
|
// Render scene graph for demo
|
||||||
this.sceneGraph.render(ctx);
|
this.sceneGraph.render(ctx);
|
||||||
|
|
||||||
|
@ -124,6 +174,9 @@ export default class Scene implements Updateable, Renderable {
|
||||||
|
|
||||||
// Debug render the physicsManager
|
// Debug render the physicsManager
|
||||||
this.physicsManager.debug_render(ctx);
|
this.physicsManager.debug_render(ctx);
|
||||||
|
|
||||||
|
// Render the uiLayers
|
||||||
|
this.uiLayers.forEach(key => this.uiLayers.get(key).getItems().forEach(node => (<CanvasNode>node).render(ctx)));
|
||||||
}
|
}
|
||||||
|
|
||||||
setRunning(running: boolean): void {
|
setRunning(running: boolean): void {
|
||||||
|
@ -136,11 +189,114 @@ export default class Scene implements Updateable, Renderable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a new layer to the scene and returns it
|
* 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
|
||||||
*/
|
*/
|
||||||
addLayer(): Layer {
|
addLayer(name: string, depth?: number): Layer {
|
||||||
return this.sceneGraph.addLayer();
|
if(this.layers.has(name) || this.uiLayers.has(name)){
|
||||||
|
throw `Layer with name ${name} already exists`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let layer = new Layer(this, name);
|
||||||
|
|
||||||
|
this.layers.add(name, layer);
|
||||||
|
|
||||||
|
if(depth){
|
||||||
|
layer.setDepth(depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
return layer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new parallax layer to this scene and returns it
|
||||||
|
* @param name The name of the parallax layer
|
||||||
|
* @param parallax The parallax level
|
||||||
|
* @param depth The depth of the layer
|
||||||
|
*/
|
||||||
|
addParallaxLayer(name: string, parallax: Vec2, depth?: number): ParallaxLayer {
|
||||||
|
if(this.layers.has(name) || this.uiLayers.has(name)){
|
||||||
|
throw `Layer with name ${name} already exists`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let layer = new ParallaxLayer(this, name, parallax);
|
||||||
|
|
||||||
|
this.layers.add(name, layer);
|
||||||
|
|
||||||
|
if(depth){
|
||||||
|
layer.setDepth(depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
return layer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new UILayer to the scene
|
||||||
|
* @param name The name of the new UIlayer
|
||||||
|
*/
|
||||||
|
addUILayer(name: string): UILayer {
|
||||||
|
if(this.layers.has(name) || this.uiLayers.has(name)){
|
||||||
|
throw `Layer with name ${name} already exists`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let layer = new UILayer(this, name);
|
||||||
|
|
||||||
|
this.uiLayers.add(name, layer);
|
||||||
|
|
||||||
|
return layer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a layer from the scene by name if it exists
|
||||||
|
* @param name The name of the layer
|
||||||
|
*/
|
||||||
|
getLayer(name: string): Layer {
|
||||||
|
if(this.layers.has(name)){
|
||||||
|
return this.layers.get(name);
|
||||||
|
} else if(this.parallaxLayers.has(name)){
|
||||||
|
return this.parallaxLayers.get(name);
|
||||||
|
} else if(this.uiLayers.has(name)){
|
||||||
|
return this.uiLayers.get(name);
|
||||||
|
} else {
|
||||||
|
throw `Requested layer ${name} does not exist.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this layer is a ParallaxLayer
|
||||||
|
* @param name
|
||||||
|
*/
|
||||||
|
isParallaxLayer(name: string): boolean {
|
||||||
|
return this.parallaxLayers.has(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this layer is a UILayer
|
||||||
|
* @param name
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
getViewTranslation(node: GameNode): Vec2 {
|
||||||
|
let layer = node.getLayer();
|
||||||
|
|
||||||
|
if(layer instanceof ParallaxLayer || layer instanceof UILayer){
|
||||||
|
return this.viewport.getOrigin().mult(layer.parallax);
|
||||||
|
} else {
|
||||||
|
return this.viewport.getOrigin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the scale level of the view */
|
||||||
|
getViewScale(): number {
|
||||||
|
return this.viewport.getZoomLevel();
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns the viewport associated with this scene */
|
/** Returns the viewport associated with this scene */
|
||||||
getViewport(): Viewport {
|
getViewport(): Viewport {
|
||||||
return this.viewport;
|
return this.viewport;
|
||||||
|
@ -158,6 +314,14 @@ export default class Scene implements Updateable, Renderable {
|
||||||
return this.physicsManager;
|
return this.physicsManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getNavigationManager(): NavigationManager {
|
||||||
|
return this.navManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAIManager(): AIManager {
|
||||||
|
return this.aiManager;
|
||||||
|
}
|
||||||
|
|
||||||
generateId(): number {
|
generateId(): number {
|
||||||
return this.sceneManager.generateId();
|
return this.sceneManager.generateId();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,6 @@ import CanvasNode from "../Nodes/CanvasNode";
|
||||||
import Map from "../DataTypes/Map";
|
import Map from "../DataTypes/Map";
|
||||||
import Vec2 from "../DataTypes/Vec2";
|
import Vec2 from "../DataTypes/Vec2";
|
||||||
import Scene from "../Scene/Scene";
|
import Scene from "../Scene/Scene";
|
||||||
import Layer from "../Scene/Layer";
|
|
||||||
import Stack from "../DataTypes/Stack";
|
|
||||||
import AABB from "../DataTypes/Shapes/AABB";
|
import AABB from "../DataTypes/Shapes/AABB";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,14 +13,12 @@ export default abstract class SceneGraph {
|
||||||
protected nodeMap: Map<CanvasNode>;
|
protected nodeMap: Map<CanvasNode>;
|
||||||
protected idCounter: number;
|
protected idCounter: number;
|
||||||
protected scene: Scene;
|
protected scene: Scene;
|
||||||
protected layers: Stack<Layer>;
|
|
||||||
|
|
||||||
constructor(viewport: Viewport, scene: Scene){
|
constructor(viewport: Viewport, scene: Scene){
|
||||||
this.viewport = viewport;
|
this.viewport = viewport;
|
||||||
this.scene = scene;
|
this.scene = scene;
|
||||||
this.nodeMap = new Map<CanvasNode>();
|
this.nodeMap = new Map<CanvasNode>();
|
||||||
this.idCounter = 0;
|
this.idCounter = 0;
|
||||||
this.layers = new Stack(10);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -93,18 +89,6 @@ export default abstract class SceneGraph {
|
||||||
* @param y
|
* @param y
|
||||||
*/
|
*/
|
||||||
protected abstract getNodesAtCoords(x: number, y: number): Array<CanvasNode>;
|
protected abstract getNodesAtCoords(x: number, y: number): Array<CanvasNode>;
|
||||||
|
|
||||||
addLayer(): Layer {
|
|
||||||
let layer = new Layer(this.scene);
|
|
||||||
let depth = this.layers.size();
|
|
||||||
layer.setDepth(depth);
|
|
||||||
this.layers.push(layer);
|
|
||||||
return layer;
|
|
||||||
}
|
|
||||||
|
|
||||||
getLayers(): Stack<Layer> {
|
|
||||||
return this.layers;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract update(deltaT: number): void;
|
abstract update(deltaT: number): void;
|
||||||
|
|
||||||
|
|
|
@ -91,15 +91,6 @@ export default class SceneGraphArray extends SceneGraph{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by depth, then by visible set by y-value
|
|
||||||
visibleSet.sort((a, b) => {
|
|
||||||
if(a.getLayer().getDepth() === b.getLayer().getDepth()){
|
|
||||||
return (a.boundary.bottom) - (b.boundary.bottom);
|
|
||||||
} else {
|
|
||||||
return a.getLayer().getDepth() - b.getLayer().getDepth();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return visibleSet;
|
return visibleSet;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -77,15 +77,6 @@ export default class SceneGraphQuadTree extends SceneGraph {
|
||||||
|
|
||||||
visibleSet = visibleSet.filter(node => !node.getLayer().isHidden());
|
visibleSet = visibleSet.filter(node => !node.getLayer().isHidden());
|
||||||
|
|
||||||
// Sort by depth, then by visible set by y-value
|
|
||||||
visibleSet.sort((a, b) => {
|
|
||||||
if(a.getLayer().getDepth() === b.getLayer().getDepth()){
|
|
||||||
return (a.boundary.bottom) - (b.boundary.bottom);
|
|
||||||
} else {
|
|
||||||
return a.getLayer().getDepth() - b.getLayer().getDepth();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return visibleSet;
|
return visibleSet;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -6,6 +6,8 @@ import Queue from "../DataTypes/Queue";
|
||||||
import AABB from "../DataTypes/Shapes/AABB";
|
import AABB from "../DataTypes/Shapes/AABB";
|
||||||
import Debug from "../Debug/Debug";
|
import Debug from "../Debug/Debug";
|
||||||
import InputReceiver from "../Input/InputReceiver";
|
import InputReceiver from "../Input/InputReceiver";
|
||||||
|
import ParallaxLayer from "../Scene/Layers/ParallaxLayer";
|
||||||
|
import UILayer from "../Scene/Layers/UILayer";
|
||||||
|
|
||||||
export default class Viewport {
|
export default class Viewport {
|
||||||
private view: AABB;
|
private view: AABB;
|
||||||
|
@ -46,8 +48,9 @@ export default class Viewport {
|
||||||
return this.view.center;
|
return this.view.center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns a new Vec2 with the origin of the viewport */
|
||||||
getOrigin(): Vec2 {
|
getOrigin(): Vec2 {
|
||||||
return this.view.center.clone().sub(this.view.halfSize)
|
return new Vec2(this.view.left, this.view.top);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -133,7 +136,7 @@ export default class Viewport {
|
||||||
* @param node
|
* @param node
|
||||||
*/
|
*/
|
||||||
includes(node: CanvasNode): boolean {
|
includes(node: CanvasNode): boolean {
|
||||||
let parallax = node.getLayer().getParallax();
|
let parallax = node.getLayer() instanceof ParallaxLayer || node.getLayer() instanceof UILayer ? (<ParallaxLayer>node.getLayer()).parallax : new Vec2(1, 1);
|
||||||
let center = this.view.center.clone();
|
let center = this.view.center.clone();
|
||||||
this.view.center.mult(parallax);
|
this.view.center.mult(parallax);
|
||||||
let overlaps = this.view.overlaps(node.boundary);
|
let overlaps = this.view.overlaps(node.boundary);
|
||||||
|
@ -197,8 +200,6 @@ export default class Viewport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug.log("vpzoom", "View size: " + this.view.getHalfSize());
|
|
||||||
|
|
||||||
// If viewport is following an object
|
// If viewport is following an object
|
||||||
if(this.following){
|
if(this.following){
|
||||||
// Update our list of previous positions
|
// Update our list of previous positions
|
||||||
|
@ -219,8 +220,6 @@ export default class Viewport {
|
||||||
// Assure there are no lines in the tilemap
|
// Assure there are no lines in the tilemap
|
||||||
pos.x = Math.floor(pos.x);
|
pos.x = Math.floor(pos.x);
|
||||||
pos.y = Math.floor(pos.y);
|
pos.y = Math.floor(pos.y);
|
||||||
|
|
||||||
Debug.log("vp", "Viewport pos: " + pos.toString())
|
|
||||||
|
|
||||||
this.view.center.copy(pos);
|
this.view.center.copy(pos);
|
||||||
} else {
|
} else {
|
||||||
|
@ -240,7 +239,6 @@ export default class Viewport {
|
||||||
pos.x = Math.floor(pos.x);
|
pos.x = Math.floor(pos.x);
|
||||||
pos.y = Math.floor(pos.y);
|
pos.y = Math.floor(pos.y);
|
||||||
|
|
||||||
Debug.log("vp", "Viewport pos: " + pos.toString())
|
|
||||||
this.view.center.copy(pos);
|
this.view.center.copy(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,12 @@ import Graph, { EdgeNode } from "../DataTypes/Graphs/Graph";
|
||||||
|
|
||||||
export default class GraphUtils {
|
export default class GraphUtils {
|
||||||
|
|
||||||
static djikstra(g: Graph, start: number): void {
|
static djikstra(g: Graph, start: number): Array<number> {
|
||||||
let i: number; // Counter
|
let i: number; // Counter
|
||||||
let p: EdgeNode; // Pointer to edgenode
|
let p: EdgeNode; // Pointer to edgenode
|
||||||
let inTree: Array<boolean>
|
let inTree: Array<boolean> = new Array(g.numVertices);
|
||||||
let distance: number;
|
let distance: Array<number> = new Array(g.numVertices);
|
||||||
|
let parent: Array<number> = new Array(g.numVertices);
|
||||||
let v: number; // Current vertex to process
|
let v: number; // Current vertex to process
|
||||||
let w: number; // Candidate for next vertex
|
let w: number; // Candidate for next vertex
|
||||||
let weight: number; // Edge weight
|
let weight: number; // Edge weight
|
||||||
|
@ -15,7 +16,41 @@ export default class GraphUtils {
|
||||||
for(i = 0; i < g.numVertices; i++){
|
for(i = 0; i < g.numVertices; i++){
|
||||||
inTree[i] = false;
|
inTree[i] = false;
|
||||||
distance[i] = Infinity;
|
distance[i] = Infinity;
|
||||||
// parent[i] = -1;
|
parent[i] = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
distance[start] = 0;
|
||||||
|
v = start;
|
||||||
|
|
||||||
|
while(!inTree[v]){
|
||||||
|
inTree[v] = true;
|
||||||
|
p = g.edges[v];
|
||||||
|
|
||||||
|
while(p !== null){
|
||||||
|
w = p.y;
|
||||||
|
weight = p.weight;
|
||||||
|
|
||||||
|
if(distance[w] > distance[v] + weight){
|
||||||
|
distance[w] = distance[v] + weight;
|
||||||
|
parent[w] = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
p = p.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
v = 0;
|
||||||
|
|
||||||
|
dist = Infinity;
|
||||||
|
|
||||||
|
for(i = 0; i <= g.numVertices; i++){
|
||||||
|
if(!inTree[i] && dist > distance[i]){
|
||||||
|
dist = distance;
|
||||||
|
v = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -41,10 +41,14 @@ export default class MathUtils {
|
||||||
* Linear Interpolation
|
* Linear Interpolation
|
||||||
* @param a The first value for the interpolation bound
|
* @param a The first value for the interpolation bound
|
||||||
* @param b The second value for the interpolation bound
|
* @param b The second value for the interpolation bound
|
||||||
* @param x The value we are interpolating
|
* @param t The time we are interpolating to
|
||||||
*/
|
*/
|
||||||
static lerp(a: number, b: number, x: number){
|
static lerp(a: number, b: number, t: number): number {
|
||||||
return a + x * (b - a);
|
return a + t * (b - a);
|
||||||
|
}
|
||||||
|
|
||||||
|
static invLerp(a: number, b: number, value: number){
|
||||||
|
return (value - a)/(b - a);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -8,13 +8,13 @@ export default class Boid extends Graphic {
|
||||||
acceleration: Vec2 = Vec2.ZERO;
|
acceleration: Vec2 = Vec2.ZERO;
|
||||||
velocity: Vec2 = Vec2.ZERO;
|
velocity: Vec2 = Vec2.ZERO;
|
||||||
|
|
||||||
ai: BoidController;
|
//ai: BoidController;
|
||||||
fb: FlockBehavior;
|
fb: FlockBehavior;
|
||||||
|
|
||||||
constructor(position: Vec2){
|
constructor(position: Vec2){
|
||||||
super();
|
super();
|
||||||
this.position = position;
|
this.position = position;
|
||||||
this.ai = new BoidController(this);
|
//this.ai = new BoidController(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
update(deltaT: number){
|
update(deltaT: number){
|
||||||
|
@ -22,8 +22,8 @@ export default class Boid extends Graphic {
|
||||||
}
|
}
|
||||||
|
|
||||||
render(ctx: CanvasRenderingContext2D): void {
|
render(ctx: CanvasRenderingContext2D): void {
|
||||||
let origin = this.getViewportOriginWithParallax();
|
let origin = this.scene.getViewTranslation(this);
|
||||||
let zoom = this.getViewportScale();
|
let zoom = this.scene.getViewScale();
|
||||||
|
|
||||||
let dirVec = this.direction.scaled(this.size.x, this.size.y);
|
let dirVec = this.direction.scaled(this.size.x, this.size.y);
|
||||||
let finVec1 = this.direction.clone().rotateCCW(Math.PI/2).scale(this.size.x/2, this.size.y/2).sub(this.direction.scaled(this.size.x/1.5, this.size.y/1.5));
|
let finVec1 = this.direction.clone().rotateCCW(Math.PI/2).scale(this.size.x/2, this.size.y/2).sub(this.direction.scaled(this.size.x/1.5, this.size.y/1.5));
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import State from "../../../DataTypes/State/State";
|
import State from "../../../DataTypes/State/State";
|
||||||
import StateMachine from "../../../DataTypes/State/StateMachine";
|
import StateMachine from "../../../DataTypes/State/StateMachine";
|
||||||
import Vec2 from "../../../DataTypes/Vec2";
|
import Vec2 from "../../../DataTypes/Vec2";
|
||||||
import Debug from "../../../Debug/Debug";
|
|
||||||
import GameEvent from "../../../Events/GameEvent";
|
import GameEvent from "../../../Events/GameEvent";
|
||||||
import MathUtils from "../../../Utils/MathUtils";
|
import MathUtils from "../../../Utils/MathUtils";
|
||||||
import { CustomGameEventType } from "../../CustomGameEventType";
|
import { CustomGameEventType } from "../../CustomGameEventType";
|
||||||
|
@ -68,17 +67,6 @@ export default class BoidBehavior extends State {
|
||||||
this.actor.direction = this.actor.velocity.clone();
|
this.actor.direction = this.actor.velocity.clone();
|
||||||
speed = MathUtils.clamp(speed, BoidBehavior.MIN_SPEED, BoidBehavior.MAX_SPEED);
|
speed = MathUtils.clamp(speed, BoidBehavior.MIN_SPEED, BoidBehavior.MAX_SPEED);
|
||||||
this.actor.velocity.scale(speed);
|
this.actor.velocity.scale(speed);
|
||||||
|
|
||||||
if(this.actor.id < 1){
|
|
||||||
Debug.log("BoidSep", "Separation: " + separationForce.toString());
|
|
||||||
Debug.log("BoidAl", "Alignment: " + alignmentForce.toString());
|
|
||||||
Debug.log("BoidCo", "Cohesion: " + cohesionForce.toString());
|
|
||||||
Debug.log("BoidSpd", "Speed: " + speed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.actor.id < 1){
|
|
||||||
Debug.log("BoidDir", "Velocity: " + this.actor.velocity.toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the position
|
// Update the position
|
||||||
|
|
|
@ -55,13 +55,5 @@ export default class GoombaController extends StateMachine {
|
||||||
|
|
||||||
update(deltaT: number): void {
|
update(deltaT: number): void {
|
||||||
super.update(deltaT);
|
super.update(deltaT);
|
||||||
|
|
||||||
if(this.currentState instanceof Jump){
|
|
||||||
Debug.log("goombastate", "GoombaState: Jump");
|
|
||||||
} else if (this.currentState instanceof Walk){
|
|
||||||
Debug.log("goombastate", "GoombaState: Walk");
|
|
||||||
} else {
|
|
||||||
Debug.log("goombastate", "GoombaState: Idle");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -6,6 +6,7 @@ import PlayerController from "../Player/PlayerStates/Platformer/PlayerController
|
||||||
import { PlayerStates } from "../Player/PlayerStates/Platformer/PlayerController";
|
import { PlayerStates } from "../Player/PlayerStates/Platformer/PlayerController";
|
||||||
import GoombaController from "../Enemies/GoombaController";
|
import GoombaController from "../Enemies/GoombaController";
|
||||||
import InputReceiver from "../../Input/InputReceiver";
|
import InputReceiver from "../../Input/InputReceiver";
|
||||||
|
import { GraphicType } from "../../Nodes/Graphics/GraphicTypes";
|
||||||
|
|
||||||
export default class MarioClone extends Scene {
|
export default class MarioClone extends Scene {
|
||||||
|
|
||||||
|
@ -15,24 +16,22 @@ export default class MarioClone extends Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
startScene(): void {
|
startScene(): void {
|
||||||
let layer = this.addLayer();
|
this.addLayer("main");
|
||||||
this.add.tilemap("level", new Vec2(2, 2));
|
this.add.tilemap("level", new Vec2(2, 2));
|
||||||
|
|
||||||
let player = this.add.graphic(Rect, layer, new Vec2(0, 0), new Vec2(64, 64));
|
let player = this.add.graphic(GraphicType.RECT, "main", {position: new Vec2(0, 0), size: new Vec2(64, 64)});
|
||||||
player.setColor(Color.BLUE);
|
player.setColor(Color.BLUE);
|
||||||
player.addPhysics();
|
player.addPhysics();
|
||||||
player.isPlayer = true;
|
player.isPlayer = true;
|
||||||
this.viewport.follow(player);
|
this.viewport.follow(player);
|
||||||
this.viewport.setBounds(0, 0, 5120, 1280);
|
this.viewport.setBounds(0, 0, 5120, 1280);
|
||||||
|
|
||||||
let ai = new PlayerController(player);
|
player.ai = new PlayerController();
|
||||||
ai.initialize(PlayerStates.IDLE);
|
|
||||||
player.update = (deltaT: number) => {ai.update(deltaT)};
|
|
||||||
|
|
||||||
player.addTrigger("CoinBlock", "playerHitCoinBlock");
|
player.addTrigger("CoinBlock", "playerHitCoinBlock");
|
||||||
|
|
||||||
for(let xPos of [14, 20, 25, 30, 33, 37, 49, 55, 58, 70, 74]){
|
for(let xPos of [14, 20, 25, 30, 33, 37, 49, 55, 58, 70, 74]){
|
||||||
let goomba = this.add.sprite("goomba", layer);
|
let goomba = this.add.sprite("goomba", "main");
|
||||||
let ai = new GoombaController(goomba, false);
|
let ai = new GoombaController(goomba, false);
|
||||||
ai.initialize("idle");
|
ai.initialize("idle");
|
||||||
goomba.update = (deltaT: number) => {ai.update(deltaT)};
|
goomba.update = (deltaT: number) => {ai.update(deltaT)};
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import StateMachineAI from "../../../AI/StateMachineAI";
|
||||||
|
import GameNode from "../../../Nodes/GameNode";
|
||||||
|
import PathfinderIdle from "./PathfinderIdle";
|
||||||
|
import PathfinderNav from "./PathfinderNav";
|
||||||
|
|
||||||
|
export default class PathfinderController extends StateMachineAI {
|
||||||
|
protected owner: GameNode;
|
||||||
|
|
||||||
|
initializeAI(owner: GameNode, config: Record<string, any>): void {
|
||||||
|
this.owner = owner;
|
||||||
|
|
||||||
|
let idle = new PathfinderIdle(this);
|
||||||
|
this.addState("idle", idle);
|
||||||
|
|
||||||
|
let nav = new PathfinderNav(this, owner, config.player);
|
||||||
|
this.addState("nav", nav);
|
||||||
|
|
||||||
|
this.receiver.subscribe("navigate");
|
||||||
|
|
||||||
|
this.initialize("idle");
|
||||||
|
}
|
||||||
|
}
|
17
src/_DemoClasses/Pathfinding/Pathfinder/PathfinderIdle.ts
Normal file
17
src/_DemoClasses/Pathfinding/Pathfinder/PathfinderIdle.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import State from "../../../DataTypes/State/State";
|
||||||
|
import GameEvent from "../../../Events/GameEvent";
|
||||||
|
|
||||||
|
export default class PathfinderIdle extends State {
|
||||||
|
onEnter(): void {}
|
||||||
|
|
||||||
|
handleInput(event: GameEvent): void {
|
||||||
|
if(event.type === "navigate"){
|
||||||
|
this.finished("nav");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update(deltaT: number): void {}
|
||||||
|
|
||||||
|
onExit(): void {}
|
||||||
|
|
||||||
|
}
|
41
src/_DemoClasses/Pathfinding/Pathfinder/PathfinderNav.ts
Normal file
41
src/_DemoClasses/Pathfinding/Pathfinder/PathfinderNav.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import State from "../../../DataTypes/State/State";
|
||||||
|
import GameEvent from "../../../Events/GameEvent";
|
||||||
|
import GameNode from "../../../Nodes/GameNode";
|
||||||
|
import NavigationPath from "../../../Pathfinding/NavigationPath";
|
||||||
|
import PathfinderController from "./PathfinderController";
|
||||||
|
|
||||||
|
export default class PathfinderNav extends State {
|
||||||
|
parent: PathfinderController;
|
||||||
|
owner: GameNode;
|
||||||
|
player: GameNode;
|
||||||
|
|
||||||
|
constructor(parent: PathfinderController, owner: GameNode, player: GameNode){
|
||||||
|
super(parent);
|
||||||
|
this.owner = owner;
|
||||||
|
this.player = player;
|
||||||
|
}
|
||||||
|
|
||||||
|
onEnter(): void {
|
||||||
|
// Request a path
|
||||||
|
this.owner.path = this.owner.getScene().getNavigationManager().getPath("main", this.owner.position, this.player.position);
|
||||||
|
this.owner.pathfinding = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInput(event: GameEvent): void {}
|
||||||
|
|
||||||
|
update(deltaT: number): void {
|
||||||
|
if(this.owner.path.isDone()){
|
||||||
|
this.finished("idle");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dir = this.owner.path.getMoveDirection(this.owner);
|
||||||
|
|
||||||
|
this.owner.move(dir.scale(200 * deltaT));
|
||||||
|
}
|
||||||
|
|
||||||
|
onExit(): void {
|
||||||
|
this.owner.pathfinding = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,7 +1,10 @@
|
||||||
import Scene from "../../Scene/Scene";
|
import Scene from "../../Scene/Scene";
|
||||||
import Rect from "../../Nodes/Graphics/Rect";
|
|
||||||
import Vec2 from "../../DataTypes/Vec2";
|
import Vec2 from "../../DataTypes/Vec2";
|
||||||
import PlayerController from "../Player/PlayerController";
|
import PlayerController from "../Player/PlayerController";
|
||||||
|
import { GraphicType } from "../../Nodes/Graphics/GraphicTypes";
|
||||||
|
import { UIElementType } from "../../Nodes/UIElements/UIElementTypes";
|
||||||
|
import Color from "../../Utils/Color";
|
||||||
|
import PathfinderController from "./Pathfinder/PathfinderController";
|
||||||
|
|
||||||
export default class PathfindingScene extends Scene {
|
export default class PathfindingScene extends Scene {
|
||||||
|
|
||||||
|
@ -12,15 +15,32 @@ export default class PathfindingScene extends Scene {
|
||||||
startScene(){
|
startScene(){
|
||||||
this.add.tilemap("interior");
|
this.add.tilemap("interior");
|
||||||
|
|
||||||
let layer = this.addLayer();
|
// Add a layer for the game objects
|
||||||
|
this.addLayer("main");
|
||||||
|
|
||||||
let player = this.add.graphic(Rect, layer, new Vec2(500, 500), new Vec2(64, 64));
|
// Add the player
|
||||||
|
let player = this.add.graphic(GraphicType.RECT, "main", {position: new Vec2(500, 500), size: new Vec2(64, 64)});
|
||||||
player.addPhysics();
|
player.addPhysics();
|
||||||
let ai = new PlayerController(player, "topdown");
|
player.addAI(PlayerController, {playerType: "topdown", speed: 400});
|
||||||
ai.speed = 400;
|
|
||||||
player.update = (deltaT: number) => {ai.update(deltaT)}
|
// Set the viewport to follow the player
|
||||||
this.viewport.setBounds(0, 0, 40*64, 40*64);
|
this.viewport.setBounds(0, 0, 40*64, 40*64);
|
||||||
this.viewport.follow(player);
|
this.viewport.follow(player);
|
||||||
this.viewport.enableZoom();
|
this.viewport.enableZoom();
|
||||||
|
|
||||||
|
// Add a navigator
|
||||||
|
let nav = this.add.graphic(GraphicType.RECT, "main", {position: new Vec2(700, 400), size: new Vec2(64, 64)});
|
||||||
|
nav.setColor(Color.BLUE);
|
||||||
|
nav.addPhysics();
|
||||||
|
nav.addAI(PathfinderController, {player: player});
|
||||||
|
|
||||||
|
// Add a layer for the ui
|
||||||
|
this.addUILayer("uiLayer");
|
||||||
|
|
||||||
|
// Add a button that triggers the navigator
|
||||||
|
let btn = this.add.uiElement(UIElementType.BUTTON, "uiLayer", {position: new Vec2(400, 20), text: "Navigate"});
|
||||||
|
btn.size = new Vec2(120, 35);
|
||||||
|
btn.setBackgroundColor(Color.BLUE);
|
||||||
|
btn.onClickEventId = "navigate";
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -8,10 +8,10 @@ export default class Player extends Rect {
|
||||||
constructor(position: Vec2){
|
constructor(position: Vec2){
|
||||||
super(position, new Vec2(20, 20));
|
super(position, new Vec2(20, 20));
|
||||||
|
|
||||||
this.controller = new PlayerController(this, PlayerType.TOPDOWN);
|
//this.controller = new PlayerController(this, PlayerType.TOPDOWN);
|
||||||
}
|
}
|
||||||
|
|
||||||
update(deltaT: number): void {
|
update(deltaT: number): void {
|
||||||
this.controller.update(deltaT);
|
//this.controller.update(deltaT);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import StateMachine from "../../DataTypes/State/StateMachine";
|
import StateMachineAI from "../../AI/StateMachineAI";
|
||||||
import Vec2 from "../../DataTypes/Vec2";
|
import Vec2 from "../../DataTypes/Vec2";
|
||||||
import Debug from "../../Debug/Debug";
|
import Debug from "../../Debug/Debug";
|
||||||
import GameNode from "../../Nodes/GameNode";
|
import GameNode from "../../Nodes/GameNode";
|
||||||
|
@ -23,21 +23,18 @@ export enum PlayerStates {
|
||||||
PREVIOUS = "previous"
|
PREVIOUS = "previous"
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class PlayerController extends StateMachine {
|
export default class PlayerController extends StateMachineAI {
|
||||||
protected owner: GameNode;
|
protected owner: GameNode;
|
||||||
velocity: Vec2 = Vec2.ZERO;
|
velocity: Vec2 = Vec2.ZERO;
|
||||||
speed: number;
|
speed: number = 400;
|
||||||
MIN_SPEED: number = 400;
|
MIN_SPEED: number = 400;
|
||||||
MAX_SPEED: number = 1000;
|
MAX_SPEED: number = 1000;
|
||||||
|
|
||||||
|
|
||||||
constructor(owner: GameNode, playerType: string){
|
initializeAI(owner: GameNode, config: Record<string, any>){
|
||||||
super();
|
|
||||||
|
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
|
|
||||||
if(playerType === PlayerType.TOPDOWN){
|
if(config.playerType === PlayerType.TOPDOWN){
|
||||||
this.initializeTopDown();
|
this.initializeTopDown(config.speed);
|
||||||
} else {
|
} else {
|
||||||
this.initializePlatformer();
|
this.initializePlatformer();
|
||||||
}
|
}
|
||||||
|
@ -46,11 +43,11 @@ export default class PlayerController extends StateMachine {
|
||||||
/**
|
/**
|
||||||
* Initializes the player controller for a top down player
|
* Initializes the player controller for a top down player
|
||||||
*/
|
*/
|
||||||
initializeTopDown(): void {
|
initializeTopDown(speed: number): void {
|
||||||
let idle = new IdleTopDown(this);
|
let idle = new IdleTopDown(this);
|
||||||
let move = new MoveTopDown(this, this.owner);
|
let move = new MoveTopDown(this, this.owner);
|
||||||
|
|
||||||
this.speed = 150;
|
this.speed = speed ? speed : 150;
|
||||||
|
|
||||||
this.addState(PlayerStates.IDLE, idle);
|
this.addState(PlayerStates.IDLE, idle);
|
||||||
this.addState(PlayerStates.MOVE, move);
|
this.addState(PlayerStates.MOVE, move);
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import StateMachine from "../../../../DataTypes/State/StateMachine";
|
|
||||||
import Debug from "../../../../Debug/Debug";
|
import Debug from "../../../../Debug/Debug";
|
||||||
import Idle from "./Idle";
|
import Idle from "./Idle";
|
||||||
import Jump from "./Jump";
|
import Jump from "./Jump";
|
||||||
|
@ -6,6 +5,7 @@ import Walk from "./Walk";
|
||||||
import Run from "./Run";
|
import Run from "./Run";
|
||||||
import GameNode from "../../../../Nodes/GameNode";
|
import GameNode from "../../../../Nodes/GameNode";
|
||||||
import Vec2 from "../../../../DataTypes/Vec2";
|
import Vec2 from "../../../../DataTypes/Vec2";
|
||||||
|
import StateMachineAI from "../../../../AI/StateMachineAI";
|
||||||
|
|
||||||
export enum PlayerStates {
|
export enum PlayerStates {
|
||||||
WALK = "walk",
|
WALK = "walk",
|
||||||
|
@ -15,16 +15,14 @@ export enum PlayerStates {
|
||||||
PREVIOUS = "previous"
|
PREVIOUS = "previous"
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class PlayerController extends StateMachine {
|
export default class PlayerController extends StateMachineAI {
|
||||||
protected owner: GameNode;
|
protected owner: GameNode;
|
||||||
velocity: Vec2 = Vec2.ZERO;
|
velocity: Vec2 = Vec2.ZERO;
|
||||||
speed: number = 400;
|
speed: number = 400;
|
||||||
MIN_SPEED: number = 400;
|
MIN_SPEED: number = 400;
|
||||||
MAX_SPEED: number = 1000;
|
MAX_SPEED: number = 1000;
|
||||||
|
|
||||||
constructor(owner: GameNode){
|
initializeAI(owner: GameNode, config: Record<string, any>): void {
|
||||||
super();
|
|
||||||
|
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
|
|
||||||
let idle = new Idle(this, owner);
|
let idle = new Idle(this, owner);
|
||||||
|
@ -35,6 +33,8 @@ export default class PlayerController extends StateMachine {
|
||||||
this.addState(PlayerStates.RUN, run);
|
this.addState(PlayerStates.RUN, run);
|
||||||
let jump = new Jump(this, owner);
|
let jump = new Jump(this, owner);
|
||||||
this.addState(PlayerStates.JUMP, jump);
|
this.addState(PlayerStates.JUMP, jump);
|
||||||
|
|
||||||
|
this.initialize(PlayerStates.IDLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentStateString: string = "";
|
currentStateString: string = "";
|
||||||
|
|
Loading…
Reference in New Issue
Block a user