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(){
|
||||
// Set the world size
|
||||
this.worldSize = new Vec2(800, 600);
|
||||
this.sceneGraph = new SceneGraphQuadTree(this.viewport, this);
|
||||
this.viewport.setBounds(0, 0, 800, 600)
|
||||
this.viewport.setCenter(400, 300);
|
||||
// this.worldSize = new Vec2(800, 600);
|
||||
// this.sceneGraph = new SceneGraphQuadTree(this.viewport, this);
|
||||
// this.viewport.setBounds(0, 0, 800, 600)
|
||||
// this.viewport.setCenter(400, 300);
|
||||
|
||||
let layer = this.addLayer();
|
||||
this.boids = new Array();
|
||||
// let layer = this.addLayer();
|
||||
// this.boids = new Array();
|
||||
|
||||
// Add the player
|
||||
let player = this.add.graphic(Player, layer, new Vec2(0, 0));
|
||||
player.addPhysics();
|
||||
let ai = new PlayerController(player, "topdown");
|
||||
player.update = (deltaT: number) => {ai.update(deltaT)}
|
||||
this.viewport.follow(player);
|
||||
this.viewport.enableZoom();
|
||||
// // Add the player
|
||||
// let player = this.add.graphic(Player, layer, new Vec2(0, 0));
|
||||
// player.addPhysics();
|
||||
// let ai = new PlayerController(player, "topdown");
|
||||
// player.update = (deltaT: number) => {ai.update(deltaT)}
|
||||
// this.viewport.follow(player);
|
||||
// this.viewport.enableZoom();
|
||||
|
||||
// Create a bunch of boids
|
||||
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()));
|
||||
boid.fb = new FlockBehavior(this, boid, this.boids, 75, 50);
|
||||
boid.size.set(5, 5);
|
||||
this.boids.push(boid);
|
||||
}
|
||||
// // Create a bunch of boids
|
||||
// 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()));
|
||||
// boid.fb = new FlockBehavior(this, boid, this.boids, 75, 50);
|
||||
// boid.size.set(5, 5);
|
||||
// this.boids.push(boid);
|
||||
// }
|
||||
}
|
||||
|
||||
updateScene(deltaT: number): void {
|
||||
|
|
|
@ -27,9 +27,22 @@ export default class Graph {
|
|||
addEdge(x: number, y: number, weight?: number){
|
||||
let edge = new EdgeNode(y, weight);
|
||||
|
||||
if(this.edges[x]){
|
||||
edge.next = this.edges[x];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -40,6 +53,34 @@ export default class Graph {
|
|||
getDegree(x: number): number {
|
||||
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 {
|
||||
|
|
|
@ -10,21 +10,34 @@ export default class PositionGraph extends Graph implements Debug_Renderable{
|
|||
this.positions = new Array(MAX_V);
|
||||
}
|
||||
|
||||
addPositionedNode(position: Vec2){
|
||||
this.positions[this.numVertices] = position;
|
||||
this.addNode();
|
||||
}
|
||||
|
||||
setNodePosition(index: number, position: Vec2): void {
|
||||
this.positions[index] = position;
|
||||
}
|
||||
|
||||
getNodePosition(index: number): Vec2 {
|
||||
return this.positions[index];
|
||||
}
|
||||
|
||||
addEdge(x: number, y: number): void {
|
||||
if(!this.positions[x] || !this.positions[y]){
|
||||
throw "Can't add edge to un-positioned node!";
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
protected nodeToString(index: number): string {
|
||||
return "Node " + index + " - " + this.positions[index].toString();
|
||||
}
|
||||
|
||||
debug_render(ctx: CanvasRenderingContext2D, origin: Vec2, zoom: number): void {
|
||||
for(let point of this.positions){
|
||||
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 Shape from "../Shapes/Shape";
|
||||
import Vec2 from "../Vec2";
|
||||
import NavigationPath from "../../Pathfinding/NavigationPath";
|
||||
import GameNode from "../../Nodes/GameNode";
|
||||
|
||||
export interface Unique {
|
||||
/** The unique id of this object. */
|
||||
|
@ -107,6 +109,37 @@ export interface Physical {
|
|||
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 {
|
||||
/** Updates this object. */
|
||||
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];
|
||||
}
|
||||
|
||||
/** Returns true if this stack is empty */
|
||||
isEmpty(): boolean {
|
||||
return this.head === -1;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.forEach((item, index) => delete this.stack[index]);
|
||||
this.head = -1;
|
||||
|
@ -62,8 +67,22 @@ export default class Stack<T> implements Collection {
|
|||
forEach(func: (item: T, index?: number) => void): void{
|
||||
let i = 0;
|
||||
while(i <= this.head){
|
||||
func(this.stack[i]);
|
||||
func(this.stack[i], i);
|
||||
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)
|
||||
*/
|
||||
|
@ -99,10 +101,11 @@ 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
|
||||
*/
|
||||
setToAngle(angle: number): Vec2 {
|
||||
this.x = Math.cos(angle);
|
||||
this.y = Math.sin(angle);
|
||||
setToAngle(angle: number, radius: number = 1): Vec2 {
|
||||
this.x = MathUtils.floorToPlace(Math.cos(angle)*radius, 5);
|
||||
this.y = MathUtils.floorToPlace(-Math.sin(angle)*radius, 5);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -114,6 +117,14 @@ export default class Vec2 {
|
|||
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
|
||||
* @param magnitude
|
||||
|
@ -245,6 +256,22 @@ export default class Vec2 {
|
|||
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
|
||||
*/
|
||||
|
@ -267,12 +294,23 @@ export default class Vec2 {
|
|||
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
|
||||
* @param other The vector to check against
|
||||
*/
|
||||
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 {
|
||||
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
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,17 +4,16 @@ import Receiver from "../Events/Receiver";
|
|||
import Emitter from "../Events/Emitter";
|
||||
import Scene from "../Scene/Scene";
|
||||
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 GameEvent from "../Events/GameEvent";
|
||||
import Map from "../DataTypes/Map";
|
||||
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
|
||||
*/
|
||||
export default abstract class GameNode implements Positioned, Unique, Updateable, Physical {
|
||||
export default abstract class GameNode implements Positioned, Unique, Updateable, Physical, Actor {
|
||||
/*---------- POSITIONED ----------*/
|
||||
private _position: Vec2;
|
||||
|
||||
|
@ -38,6 +37,13 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
|
|||
sweptRect: AABB;
|
||||
isPlayer: boolean;
|
||||
|
||||
/*---------- ACTOR ----------*/
|
||||
_ai: AI;
|
||||
aiActive: boolean;
|
||||
actorId: number;
|
||||
path: NavigationPath;
|
||||
pathfinding: boolean = false;
|
||||
|
||||
protected input: InputReceiver;
|
||||
protected receiver: Receiver;
|
||||
protected emitter: Emitter;
|
||||
|
@ -93,6 +99,9 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
|
|||
finishMove = (): void => {
|
||||
this.moving = false;
|
||||
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);
|
||||
};
|
||||
|
||||
/*---------- 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 ----------*/
|
||||
/**
|
||||
* 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;
|
||||
}
|
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 {}
|
||||
|
||||
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.fillRect(this.position.x - origin.x - this.size.x/2, this.position.y - origin.y - this.size.y/2,
|
||||
this.size.x, this.size.y);
|
||||
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*zoom, this.size.y*zoom);
|
||||
}
|
||||
|
||||
}
|
|
@ -34,8 +34,8 @@ export default class Rect extends Graphic {
|
|||
update(deltaT: number): void {}
|
||||
|
||||
render(ctx: CanvasRenderingContext2D): void {
|
||||
let origin = this.getViewportOriginWithParallax();
|
||||
let zoom = this.getViewportScale();
|
||||
let origin = this.scene.getViewTranslation(this);
|
||||
let zoom = this.scene.getViewScale();
|
||||
|
||||
if(this.color.a !== 0){
|
||||
ctx.fillStyle = this.color.toStringRGB();
|
||||
|
|
|
@ -29,8 +29,8 @@ export default class Sprite extends CanvasNode {
|
|||
|
||||
render(ctx: CanvasRenderingContext2D): void {
|
||||
let image = ResourceManager.getInstance().getImage(this.imageId);
|
||||
let origin = this.getViewportOriginWithParallax();
|
||||
let zoom = this.getViewportScale();
|
||||
let origin = this.scene.getViewTranslation(this);
|
||||
let zoom = this.scene.getViewScale();
|
||||
|
||||
ctx.drawImage(image,
|
||||
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;
|
||||
ctx.globalAlpha = this.getLayer().getAlpha();
|
||||
|
||||
let origin = this.getViewportOriginWithParallax();
|
||||
let zoom = this.getViewportScale();
|
||||
let origin = this.scene.getViewTranslation(this);
|
||||
let zoom = this.scene.getViewScale();
|
||||
|
||||
if(this.visible){
|
||||
for(let i = 0; i < this.data.length; i++){
|
||||
|
|
|
@ -31,8 +31,10 @@ export default class UIElement extends CanvasNode {
|
|||
protected isClicked: boolean;
|
||||
protected isEntered: boolean;
|
||||
|
||||
constructor(){
|
||||
constructor(position: Vec2){
|
||||
super();
|
||||
this.position = position;
|
||||
|
||||
this.textColor = new Color(0, 0, 0, 1);
|
||||
this.backgroundColor = 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){
|
||||
let data = {};
|
||||
console.log("Click event: " + this.onClickEventId)
|
||||
this.emitter.fireEvent(this.onClickEventId, data);
|
||||
}
|
||||
}
|
||||
|
@ -179,7 +182,7 @@ export default class UIElement extends CanvasNode {
|
|||
let previousAlpha = ctx.globalAlpha;
|
||||
ctx.globalAlpha = this.getLayer().getAlpha();
|
||||
|
||||
let origin = this.getViewportOriginWithParallax();
|
||||
let origin = this.scene.getViewTranslation(this);
|
||||
|
||||
ctx.font = this.fontSize + "px " + this.font;
|
||||
let offset = this.calculateOffset(ctx);
|
||||
|
|
|
@ -4,8 +4,10 @@ import Vec2 from "../../DataTypes/Vec2";
|
|||
|
||||
export default class Button extends UIElement{
|
||||
|
||||
constructor(){
|
||||
super();
|
||||
constructor(position: Vec2, text: string){
|
||||
super(position);
|
||||
this.text = text;
|
||||
|
||||
this.backgroundColor = new Color(150, 75, 203);
|
||||
this.borderColor = new Color(41, 46, 30);
|
||||
this.textColor = new Color(255, 255, 255);
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import Vec2 from "../../DataTypes/Vec2";
|
||||
import UIElement from "../UIElement";
|
||||
|
||||
export default class Label extends UIElement{
|
||||
constructor(text: string){
|
||||
super();
|
||||
constructor(position: Vec2, text: string){
|
||||
super(position);
|
||||
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 SceneGraph from "../../SceneGraph/SceneGraph";
|
||||
import UIElement from "../../Nodes/UIElement";
|
||||
import Layer from "../Layer";
|
||||
import Graphic from "../../Nodes/Graphic";
|
||||
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 {
|
||||
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
|
||||
* @param constr The constructor of the UIElement to be created
|
||||
* @param layer The layer to add the UIElement to
|
||||
* @param args Any additional arguments to feed to the constructor
|
||||
* @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
|
||||
*/
|
||||
addUIElement = <T extends UIElement>(constr: new (...a: any) => T, layer: Layer, ...args: any): T => {
|
||||
let instance = new constr(...args);
|
||||
addUIElement = (type: string | UIElementType, layerName: string, options?: Record<string, any>): UIElement => {
|
||||
// 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.id = this.scene.generateId();
|
||||
this.scene.getSceneGraph().addNode(instance);
|
||||
|
||||
// Add instance to layer
|
||||
layer.addNode(instance);
|
||||
layer.addNode(instance)
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
@ -35,9 +55,11 @@ export default class CanvasNodeFactory {
|
|||
/**
|
||||
* Adds a sprite to the current scene
|
||||
* @param key The key of the image the sprite will represent
|
||||
* @param layer The layer on which to add the sprite
|
||||
* @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);
|
||||
|
||||
// Add instance to scene
|
||||
|
@ -53,21 +75,90 @@ export default class CanvasNodeFactory {
|
|||
|
||||
/**
|
||||
* Adds a new graphic element to the current Scene
|
||||
* @param constr The constructor of the graphic element to add
|
||||
* @param layer The layer on which to add the graphic
|
||||
* @param args Any additional arguments to send to the graphic constructor
|
||||
* @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
|
||||
*/
|
||||
addGraphic = <T extends Graphic>(constr: new (...a: any) => T, layer: Layer, ...args: any): T => {
|
||||
let instance = new constr(...args);
|
||||
addGraphic = (type: GraphicType | string, layerName: string, options?: Record<string, any>): Graphic => {
|
||||
// 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
|
||||
instance.setScene(this.scene);
|
||||
instance.id = this.scene.generateId();
|
||||
|
||||
if(!(this.scene.isParallaxLayer(layerName) || this.scene.isUILayer(layerName))){
|
||||
this.scene.getSceneGraph().addNode(instance);
|
||||
}
|
||||
|
||||
// Add instance to layer
|
||||
layer.addNode(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 Tilemap from "../../Nodes/Tilemap";
|
||||
import PhysicsManager from "../../Physics/PhysicsManager";
|
||||
import ResourceManager from "../../ResourceManager/ResourceManager";
|
||||
import OrthogonalTilemap from "../../Nodes/Tilemaps/OrthogonalTilemap";
|
||||
import Layer from "../Layer";
|
||||
|
@ -8,6 +7,8 @@ import Tileset from "../../DataTypes/Tilesets/Tileset";
|
|||
import Vec2 from "../../DataTypes/Vec2";
|
||||
import { TiledCollectionTile } from "../../DataTypes/Tilesets/TiledData";
|
||||
import Sprite from "../../Nodes/Sprites/Sprite";
|
||||
import PositionGraph from "../../DataTypes/Graphs/PositionGraph";
|
||||
import Navmesh from "../../Pathfinding/Navmesh";
|
||||
|
||||
export default class TilemapFactory {
|
||||
private scene: Scene;
|
||||
|
@ -63,7 +64,7 @@ export default class TilemapFactory {
|
|||
// Loop over the layers of the tilemap and create tiledlayers or object layers
|
||||
for(let layer of tilemapData.layers){
|
||||
|
||||
let sceneLayer = this.scene.addLayer();
|
||||
let sceneLayer = this.scene.addLayer(layer.name);
|
||||
|
||||
if(layer.type === "tilelayer"){
|
||||
// Create a new tilemap object for the layer
|
||||
|
@ -82,17 +83,34 @@ export default class TilemapFactory {
|
|||
}
|
||||
} else {
|
||||
|
||||
let isNavmeshPoints = false
|
||||
let isNavmeshPoints = false;
|
||||
let navmeshName;
|
||||
let edges;
|
||||
if(layer.properties){
|
||||
for(let prop of layer.properties){
|
||||
if(prop.name === "NavmeshPoints"){
|
||||
isNavmeshPoints = true;
|
||||
} else if(prop.name === "name"){
|
||||
navmeshName = prop.value;
|
||||
} else if(prop.name === "edges"){
|
||||
edges = prop.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -126,7 +144,7 @@ export default class TilemapFactory {
|
|||
// The object is a tile from this set
|
||||
let imageKey = tileset.getImageKey();
|
||||
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();
|
||||
sprite.position.set((obj.x + size.x/2)*scale.x, (obj.y - size.y/2)*scale.y);
|
||||
sprite.setImageOffset(offset);
|
||||
|
@ -140,7 +158,7 @@ export default class TilemapFactory {
|
|||
for(let tile of collectionTiles){
|
||||
if(obj.gid === tile.id){
|
||||
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.scale.set(scale.x, scale.y);
|
||||
}
|
||||
|
|
|
@ -1,24 +1,39 @@
|
|||
import Vec2 from "../DataTypes/Vec2";
|
||||
import Scene from "./Scene";
|
||||
import MathUtils from "../Utils/MathUtils";
|
||||
import GameNode from "../Nodes/GameNode";
|
||||
|
||||
|
||||
/**
|
||||
* A layer in the scene. Has its own alpha value and parallax.
|
||||
*/
|
||||
export default class Layer {
|
||||
/** The scene this layer belongs to */
|
||||
protected scene: Scene;
|
||||
protected parallax: Vec2;
|
||||
|
||||
/** The name of this layer */
|
||||
protected name: string;
|
||||
|
||||
/** Whether this layer is paused or not */
|
||||
protected paused: boolean;
|
||||
|
||||
/** Whether this layer is hidden from being rendered or not */
|
||||
protected hidden: boolean;
|
||||
|
||||
/** The global alpha level of this layer */
|
||||
protected alpha: number;
|
||||
|
||||
/** An array of the GameNodes that belong to this layer */
|
||||
protected items: Array<GameNode>;
|
||||
|
||||
/** Whether or not this layer should be ysorted */
|
||||
protected ySort: boolean;
|
||||
|
||||
/** The depth of this layer compared to other layers */
|
||||
protected depth: number;
|
||||
|
||||
constructor(scene: Scene){
|
||||
constructor(scene: Scene, name: string){
|
||||
this.scene = scene;
|
||||
this.parallax = new Vec2(1, 1);
|
||||
this.name = name;
|
||||
this.paused = false;
|
||||
this.hidden = false;
|
||||
this.alpha = 1;
|
||||
|
@ -51,24 +66,18 @@ export default class Layer {
|
|||
return this.hidden;
|
||||
}
|
||||
|
||||
/** Pauses this scene and hides it */
|
||||
disable(): void {
|
||||
this.paused = true;
|
||||
this.hidden = true;
|
||||
}
|
||||
|
||||
/** Unpauses this layer and makes it visible */
|
||||
enable(): void {
|
||||
this.paused = false;
|
||||
this.hidden = false;
|
||||
}
|
||||
|
||||
setParallax(x: number, y: number): void {
|
||||
this.parallax.set(x, y);
|
||||
}
|
||||
|
||||
getParallax(): Vec2 {
|
||||
return this.parallax;
|
||||
}
|
||||
|
||||
setYSort(ySort: boolean): void {
|
||||
this.ySort = ySort;
|
||||
}
|
||||
|
@ -89,4 +98,8 @@ export default class Layer {
|
|||
this.items.push(node);
|
||||
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 Emitter from "../Events/Emitter";
|
||||
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 {
|
||||
/** The size of the game world. */
|
||||
|
@ -40,18 +46,33 @@ export default class Scene implements Updateable, Renderable {
|
|||
/** This list of tilemaps in this scene. */
|
||||
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*/
|
||||
protected sceneGraph: SceneGraph;
|
||||
|
||||
/** The physics manager of the Scene */
|
||||
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 */
|
||||
public add: FactoryManager;
|
||||
|
||||
/** An interface that allows the loading of different files for use in the scene */
|
||||
public load: ResourceManager;
|
||||
|
||||
protected navmeshes: Array<Navmesh>;
|
||||
|
||||
constructor(viewport: Viewport, sceneManager: SceneManager, game: GameLoop){
|
||||
this.worldSize = new Vec2(500, 500);
|
||||
this.viewport = viewport;
|
||||
|
@ -64,7 +85,14 @@ export default class Scene implements Updateable, Renderable {
|
|||
|
||||
this.tilemaps = new Array();
|
||||
this.sceneGraph = new SceneGraphArray(this.viewport, this);
|
||||
|
||||
this.layers = new Map();
|
||||
this.uiLayers = new Map();
|
||||
this.parallaxLayers = new Map();
|
||||
|
||||
this.physicsManager = new BasicPhysicsManager();
|
||||
this.navManager = new NavigationManager();
|
||||
this.aiManager = new AIManager();
|
||||
|
||||
this.add = new FactoryManager(this, this.tilemaps);
|
||||
|
||||
|
@ -89,6 +117,9 @@ export default class Scene implements Updateable, Renderable {
|
|||
update(deltaT: number): void {
|
||||
this.updateScene(deltaT);
|
||||
|
||||
// Do all AI updates
|
||||
this.aiManager.update(deltaT);
|
||||
|
||||
// Update all physics objects
|
||||
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.
|
||||
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
|
||||
this.sceneGraph.render(ctx);
|
||||
|
||||
|
@ -124,6 +174,9 @@ export default class Scene implements Updateable, Renderable {
|
|||
|
||||
// Debug render the physicsManager
|
||||
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 {
|
||||
|
@ -136,9 +189,112 @@ export default class Scene implements Updateable, Renderable {
|
|||
|
||||
/**
|
||||
* 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 {
|
||||
return this.sceneGraph.addLayer();
|
||||
addLayer(name: string, depth?: number): Layer {
|
||||
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 */
|
||||
|
@ -158,6 +314,14 @@ export default class Scene implements Updateable, Renderable {
|
|||
return this.physicsManager;
|
||||
}
|
||||
|
||||
getNavigationManager(): NavigationManager {
|
||||
return this.navManager;
|
||||
}
|
||||
|
||||
getAIManager(): AIManager {
|
||||
return this.aiManager;
|
||||
}
|
||||
|
||||
generateId(): number {
|
||||
return this.sceneManager.generateId();
|
||||
}
|
||||
|
|
|
@ -3,8 +3,6 @@ import CanvasNode from "../Nodes/CanvasNode";
|
|||
import Map from "../DataTypes/Map";
|
||||
import Vec2 from "../DataTypes/Vec2";
|
||||
import Scene from "../Scene/Scene";
|
||||
import Layer from "../Scene/Layer";
|
||||
import Stack from "../DataTypes/Stack";
|
||||
import AABB from "../DataTypes/Shapes/AABB";
|
||||
|
||||
/**
|
||||
|
@ -15,14 +13,12 @@ export default abstract class SceneGraph {
|
|||
protected nodeMap: Map<CanvasNode>;
|
||||
protected idCounter: number;
|
||||
protected scene: Scene;
|
||||
protected layers: Stack<Layer>;
|
||||
|
||||
constructor(viewport: Viewport, scene: Scene){
|
||||
this.viewport = viewport;
|
||||
this.scene = scene;
|
||||
this.nodeMap = new Map<CanvasNode>();
|
||||
this.idCounter = 0;
|
||||
this.layers = new Stack(10);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -94,18 +90,6 @@ export default abstract class SceneGraph {
|
|||
*/
|
||||
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 render(ctx: CanvasRenderingContext2D): 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;
|
||||
}
|
||||
}
|
|
@ -77,15 +77,6 @@ export default class SceneGraphQuadTree extends SceneGraph {
|
|||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -6,6 +6,8 @@ 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";
|
||||
|
||||
export default class Viewport {
|
||||
private view: AABB;
|
||||
|
@ -46,8 +48,9 @@ export default class Viewport {
|
|||
return this.view.center;
|
||||
}
|
||||
|
||||
/** Returns a new Vec2 with the origin of the viewport */
|
||||
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
|
||||
*/
|
||||
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();
|
||||
this.view.center.mult(parallax);
|
||||
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(this.following){
|
||||
// Update our list of previous positions
|
||||
|
@ -220,8 +221,6 @@ export default class Viewport {
|
|||
pos.x = Math.floor(pos.x);
|
||||
pos.y = Math.floor(pos.y);
|
||||
|
||||
Debug.log("vp", "Viewport pos: " + pos.toString())
|
||||
|
||||
this.view.center.copy(pos);
|
||||
} else {
|
||||
if(this.lastPositions.getSize() > this.smoothingFactor){
|
||||
|
@ -240,7 +239,6 @@ export default class Viewport {
|
|||
pos.x = Math.floor(pos.x);
|
||||
pos.y = Math.floor(pos.y);
|
||||
|
||||
Debug.log("vp", "Viewport pos: " + pos.toString())
|
||||
this.view.center.copy(pos);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,12 @@ import Graph, { EdgeNode } from "../DataTypes/Graphs/Graph";
|
|||
|
||||
export default class GraphUtils {
|
||||
|
||||
static djikstra(g: Graph, start: number): void {
|
||||
static djikstra(g: Graph, start: number): Array<number> {
|
||||
let i: number; // Counter
|
||||
let p: EdgeNode; // Pointer to edgenode
|
||||
let inTree: Array<boolean>
|
||||
let distance: number;
|
||||
let inTree: Array<boolean> = new Array(g.numVertices);
|
||||
let distance: Array<number> = new Array(g.numVertices);
|
||||
let parent: Array<number> = new Array(g.numVertices);
|
||||
let v: number; // Current vertex to process
|
||||
let w: number; // Candidate for next vertex
|
||||
let weight: number; // Edge weight
|
||||
|
@ -15,7 +16,41 @@ export default class GraphUtils {
|
|||
for(i = 0; i < g.numVertices; i++){
|
||||
inTree[i] = false;
|
||||
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
|
||||
* @param a The first 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){
|
||||
return a + x * (b - a);
|
||||
static lerp(a: number, b: number, t: number): number {
|
||||
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;
|
||||
velocity: Vec2 = Vec2.ZERO;
|
||||
|
||||
ai: BoidController;
|
||||
//ai: BoidController;
|
||||
fb: FlockBehavior;
|
||||
|
||||
constructor(position: Vec2){
|
||||
super();
|
||||
this.position = position;
|
||||
this.ai = new BoidController(this);
|
||||
//this.ai = new BoidController(this);
|
||||
}
|
||||
|
||||
update(deltaT: number){
|
||||
|
@ -22,8 +22,8 @@ export default class Boid extends Graphic {
|
|||
}
|
||||
|
||||
render(ctx: CanvasRenderingContext2D): void {
|
||||
let origin = this.getViewportOriginWithParallax();
|
||||
let zoom = this.getViewportScale();
|
||||
let origin = this.scene.getViewTranslation(this);
|
||||
let zoom = this.scene.getViewScale();
|
||||
|
||||
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));
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import State from "../../../DataTypes/State/State";
|
||||
import StateMachine from "../../../DataTypes/State/StateMachine";
|
||||
import Vec2 from "../../../DataTypes/Vec2";
|
||||
import Debug from "../../../Debug/Debug";
|
||||
import GameEvent from "../../../Events/GameEvent";
|
||||
import MathUtils from "../../../Utils/MathUtils";
|
||||
import { CustomGameEventType } from "../../CustomGameEventType";
|
||||
|
@ -68,17 +67,6 @@ export default class BoidBehavior extends State {
|
|||
this.actor.direction = this.actor.velocity.clone();
|
||||
speed = MathUtils.clamp(speed, BoidBehavior.MIN_SPEED, BoidBehavior.MAX_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
|
||||
|
|
|
@ -55,13 +55,5 @@ export default class GoombaController extends StateMachine {
|
|||
|
||||
update(deltaT: number): void {
|
||||
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 GoombaController from "../Enemies/GoombaController";
|
||||
import InputReceiver from "../../Input/InputReceiver";
|
||||
import { GraphicType } from "../../Nodes/Graphics/GraphicTypes";
|
||||
|
||||
export default class MarioClone extends Scene {
|
||||
|
||||
|
@ -15,24 +16,22 @@ export default class MarioClone extends Scene {
|
|||
}
|
||||
|
||||
startScene(): void {
|
||||
let layer = this.addLayer();
|
||||
this.addLayer("main");
|
||||
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.addPhysics();
|
||||
player.isPlayer = true;
|
||||
this.viewport.follow(player);
|
||||
this.viewport.setBounds(0, 0, 5120, 1280);
|
||||
|
||||
let ai = new PlayerController(player);
|
||||
ai.initialize(PlayerStates.IDLE);
|
||||
player.update = (deltaT: number) => {ai.update(deltaT)};
|
||||
player.ai = new PlayerController();
|
||||
|
||||
player.addTrigger("CoinBlock", "playerHitCoinBlock");
|
||||
|
||||
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);
|
||||
ai.initialize("idle");
|
||||
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 Rect from "../../Nodes/Graphics/Rect";
|
||||
import Vec2 from "../../DataTypes/Vec2";
|
||||
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 {
|
||||
|
||||
|
@ -12,15 +15,32 @@ export default class PathfindingScene extends Scene {
|
|||
startScene(){
|
||||
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();
|
||||
let ai = new PlayerController(player, "topdown");
|
||||
ai.speed = 400;
|
||||
player.update = (deltaT: number) => {ai.update(deltaT)}
|
||||
player.addAI(PlayerController, {playerType: "topdown", speed: 400});
|
||||
|
||||
// Set the viewport to follow the player
|
||||
this.viewport.setBounds(0, 0, 40*64, 40*64);
|
||||
this.viewport.follow(player);
|
||||
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){
|
||||
super(position, new Vec2(20, 20));
|
||||
|
||||
this.controller = new PlayerController(this, PlayerType.TOPDOWN);
|
||||
//this.controller = new PlayerController(this, PlayerType.TOPDOWN);
|
||||
}
|
||||
|
||||
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 Debug from "../../Debug/Debug";
|
||||
import GameNode from "../../Nodes/GameNode";
|
||||
|
@ -23,21 +23,18 @@ export enum PlayerStates {
|
|||
PREVIOUS = "previous"
|
||||
}
|
||||
|
||||
export default class PlayerController extends StateMachine {
|
||||
export default class PlayerController extends StateMachineAI {
|
||||
protected owner: GameNode;
|
||||
velocity: Vec2 = Vec2.ZERO;
|
||||
speed: number;
|
||||
speed: number = 400;
|
||||
MIN_SPEED: number = 400;
|
||||
MAX_SPEED: number = 1000;
|
||||
|
||||
|
||||
constructor(owner: GameNode, playerType: string){
|
||||
super();
|
||||
|
||||
initializeAI(owner: GameNode, config: Record<string, any>){
|
||||
this.owner = owner;
|
||||
|
||||
if(playerType === PlayerType.TOPDOWN){
|
||||
this.initializeTopDown();
|
||||
if(config.playerType === PlayerType.TOPDOWN){
|
||||
this.initializeTopDown(config.speed);
|
||||
} else {
|
||||
this.initializePlatformer();
|
||||
}
|
||||
|
@ -46,11 +43,11 @@ export default class PlayerController extends StateMachine {
|
|||
/**
|
||||
* Initializes the player controller for a top down player
|
||||
*/
|
||||
initializeTopDown(): void {
|
||||
initializeTopDown(speed: number): void {
|
||||
let idle = new IdleTopDown(this);
|
||||
let move = new MoveTopDown(this, this.owner);
|
||||
|
||||
this.speed = 150;
|
||||
this.speed = speed ? speed : 150;
|
||||
|
||||
this.addState(PlayerStates.IDLE, idle);
|
||||
this.addState(PlayerStates.MOVE, move);
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import StateMachine from "../../../../DataTypes/State/StateMachine";
|
||||
import Debug from "../../../../Debug/Debug";
|
||||
import Idle from "./Idle";
|
||||
import Jump from "./Jump";
|
||||
|
@ -6,6 +5,7 @@ import Walk from "./Walk";
|
|||
import Run from "./Run";
|
||||
import GameNode from "../../../../Nodes/GameNode";
|
||||
import Vec2 from "../../../../DataTypes/Vec2";
|
||||
import StateMachineAI from "../../../../AI/StateMachineAI";
|
||||
|
||||
export enum PlayerStates {
|
||||
WALK = "walk",
|
||||
|
@ -15,16 +15,14 @@ export enum PlayerStates {
|
|||
PREVIOUS = "previous"
|
||||
}
|
||||
|
||||
export default class PlayerController extends StateMachine {
|
||||
export default class PlayerController extends StateMachineAI {
|
||||
protected owner: GameNode;
|
||||
velocity: Vec2 = Vec2.ZERO;
|
||||
speed: number = 400;
|
||||
MIN_SPEED: number = 400;
|
||||
MAX_SPEED: number = 1000;
|
||||
|
||||
constructor(owner: GameNode){
|
||||
super();
|
||||
|
||||
initializeAI(owner: GameNode, config: Record<string, any>): void {
|
||||
this.owner = owner;
|
||||
|
||||
let idle = new Idle(this, owner);
|
||||
|
@ -35,6 +33,8 @@ export default class PlayerController extends StateMachine {
|
|||
this.addState(PlayerStates.RUN, run);
|
||||
let jump = new Jump(this, owner);
|
||||
this.addState(PlayerStates.JUMP, jump);
|
||||
|
||||
this.initialize(PlayerStates.IDLE);
|
||||
}
|
||||
|
||||
currentStateString: string = "";
|
||||
|
|
Loading…
Reference in New Issue
Block a user