From 1047e31c709fc271d18d09db4b7622cb10ff8cca Mon Sep 17 00:00:00 2001 From: Joe Weaver Date: Wed, 28 Oct 2020 14:55:32 -0400 Subject: [PATCH] added extra demo classes and implemented pathfinding groundwork --- src/BoidDemo.ts | 5 +- src/DataTypes/Graphs/Graph.ts | 55 ++++++++++++++++ src/DataTypes/Graphs/PositionGraph.ts | 33 ++++++++++ src/DataTypes/Interfaces/Descriptors.ts | 4 +- src/DataTypes/Navmesh.ts | 27 ++++++++ src/DataTypes/Shapes/Shape.ts | 1 + src/DataTypes/Tilesets/Tileset.ts | 4 +- src/Nodes/GameNode.ts | 4 +- src/Nodes/Tilemaps/OrthogonalTilemap.ts | 3 +- src/Physics/BasicPhysicsManager.ts | 33 +++++++--- .../BroadPhaseAlgorithms/SweepAndPrune.ts | 4 +- src/Scene/Factories/FactoryManager.ts | 5 +- src/Scene/Factories/TilemapFactory.ts | 37 ++++++++--- src/Scene/Scene.ts | 5 +- src/Utils/GraphUtils.ts | 21 +++++++ src/Utils/MathUtils.ts | 22 ++++++- src/_DemoClasses/Enemies/Afraid.ts | 31 ++++++++++ src/_DemoClasses/Enemies/GoombaController.ts | 9 ++- src/_DemoClasses/Enemies/GoombaState.ts | 11 +++- src/_DemoClasses/Enemies/Idle.ts | 1 - src/_DemoClasses/Enemies/Jump.ts | 2 - src/_DemoClasses/Enemies/OnGround.ts | 2 + src/_DemoClasses/MarioClone/MarioClone.ts | 5 +- .../Pathfinding/PathfindingScene.ts | 26 ++++++++ src/_DemoClasses/Player/PlayerController.ts | 62 +++++++++++++++++-- .../Player/PlayerStates/MoveTopDown.ts | 12 ++-- src/main.ts | 5 +- 27 files changed, 379 insertions(+), 50 deletions(-) create mode 100644 src/DataTypes/Graphs/Graph.ts create mode 100644 src/DataTypes/Graphs/PositionGraph.ts create mode 100644 src/DataTypes/Navmesh.ts create mode 100644 src/Utils/GraphUtils.ts create mode 100644 src/_DemoClasses/Enemies/Afraid.ts create mode 100644 src/_DemoClasses/Pathfinding/PathfindingScene.ts diff --git a/src/BoidDemo.ts b/src/BoidDemo.ts index 22d57e3..4878d9d 100644 --- a/src/BoidDemo.ts +++ b/src/BoidDemo.ts @@ -5,6 +5,7 @@ import Color from "./Utils/Color"; import Boid from "./_DemoClasses/Boids/Boid"; import FlockBehavior from "./_DemoClasses/Boids/FlockBehavior"; import Player from "./_DemoClasses/Player/Player"; +import PlayerController from "./_DemoClasses/Player/PlayerController"; /** * This demo emphasizes an ai system for the game engine with component architecture @@ -27,7 +28,9 @@ export default class BoidDemo extends Scene { // 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(); diff --git a/src/DataTypes/Graphs/Graph.ts b/src/DataTypes/Graphs/Graph.ts new file mode 100644 index 0000000..9105b7e --- /dev/null +++ b/src/DataTypes/Graphs/Graph.ts @@ -0,0 +1,55 @@ +export const MAX_V = 100; + +export default class Graph { + edges: Array; + degree: Array; + numVertices: number; + numEdges: number; + directed: boolean; + weighted: boolean; + + constructor(directed: boolean = false){ + this.directed = directed; + this.weighted = false; + + this.numVertices = 0; + this.numEdges = 0; + + this.edges = new Array(MAX_V); + this.degree = new Array(MAX_V); + } + + addNode(): number { + this.numVertices++; + return this.numVertices; + } + + addEdge(x: number, y: number, weight?: number){ + let edge = new EdgeNode(y, weight); + + edge.next = this.edges[x]; + + this.edges[x] = edge; + this.numEdges += 1; + } + + getEdges(x: number): EdgeNode { + return this.edges[x]; + } + + getDegree(x: number): number { + return this.degree[x]; + } +} + +export class EdgeNode { + y: number; + next: EdgeNode; + weight: number; + + constructor(index: number, weight?: number){ + this.y = index; + this.next = null; + this.weight = weight ? weight : 1; + } +} \ No newline at end of file diff --git a/src/DataTypes/Graphs/PositionGraph.ts b/src/DataTypes/Graphs/PositionGraph.ts new file mode 100644 index 0000000..bc73695 --- /dev/null +++ b/src/DataTypes/Graphs/PositionGraph.ts @@ -0,0 +1,33 @@ +import Graph, { MAX_V } from "./Graph"; +import Vec2 from "../Vec2"; +import { Debug_Renderable } from "../Interfaces/Descriptors"; + +export default class PositionGraph extends Graph implements Debug_Renderable{ + positions: Array; + + constructor(directed: boolean = false){ + super(directed); + this.positions = new Array(MAX_V); + } + + setNodePosition(index: number, position: Vec2): void { + this.positions[index] = position; + } + + 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]); + + super.addEdge(x, y, weight); + } + + 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); + } + } +} \ No newline at end of file diff --git a/src/DataTypes/Interfaces/Descriptors.ts b/src/DataTypes/Interfaces/Descriptors.ts index 4a5f119..7ad3394 100644 --- a/src/DataTypes/Interfaces/Descriptors.ts +++ b/src/DataTypes/Interfaces/Descriptors.ts @@ -75,6 +75,8 @@ export interface Physical { /** The rectangle swept by the movement of this object, if dynamic */ sweptRect: AABB; + isPlayer: boolean; + /*---------- FUNCTIONS ----------*/ /** @@ -117,5 +119,5 @@ export interface Renderable { export interface Debug_Renderable { /** Renders the debugging infor for this object. */ - debug_render: (ctx: CanvasRenderingContext2D) => void; + debug_render: (ctx: CanvasRenderingContext2D, origin: Vec2, zoom: number) => void; } diff --git a/src/DataTypes/Navmesh.ts b/src/DataTypes/Navmesh.ts new file mode 100644 index 0000000..b748970 --- /dev/null +++ b/src/DataTypes/Navmesh.ts @@ -0,0 +1,27 @@ +import PositionGraph from "./Graphs/PositionGraph" +import Vec2 from "./Vec2"; + +export default class Navmesh { + protected graph: PositionGraph; + + getNavigationPath(fromPosition: Vec2, toPosition: Vec2): Array { + 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; + } +} \ No newline at end of file diff --git a/src/DataTypes/Shapes/Shape.ts b/src/DataTypes/Shapes/Shape.ts index ec2ecf8..b6fb91e 100644 --- a/src/DataTypes/Shapes/Shape.ts +++ b/src/DataTypes/Shapes/Shape.ts @@ -30,6 +30,7 @@ export default abstract class Shape { private static getTimeOfCollision_AABB_AABB(A: AABB, velA: Vec2, B: Shape, velB: Vec2): [Vec2, Vec2, boolean, boolean] { let posSmaller = A.center; let posLarger = B.center; + let sizeSmaller = A.halfSize; let sizeLarger = B.halfSize; diff --git a/src/DataTypes/Tilesets/Tileset.ts b/src/DataTypes/Tilesets/Tileset.ts index 14bd41c..1120acf 100644 --- a/src/DataTypes/Tilesets/Tileset.ts +++ b/src/DataTypes/Tilesets/Tileset.ts @@ -87,7 +87,7 @@ export default class Tileset { * @param origin The viewport origin in the current layer * @param scale The scale of the tilemap */ - renderTile(ctx: CanvasRenderingContext2D, tileIndex: number, dataIndex: number, worldSize: Vec2, origin: Vec2, scale: Vec2): void { + renderTile(ctx: CanvasRenderingContext2D, tileIndex: number, dataIndex: number, worldSize: Vec2, origin: Vec2, scale: Vec2, zoom: number): void { let image = ResourceManager.getInstance().getImage(this.imageKey); // Get the true index @@ -104,6 +104,6 @@ export default class Tileset { // Calculate the position in the world to render the tile let x = Math.floor((dataIndex % worldSize.x) * width * scale.x); let y = Math.floor(Math.floor(dataIndex / worldSize.x) * height * scale.y); - ctx.drawImage(image, left, top, width, height, x - origin.x, y - origin.y, width * scale.x, height * scale.y); + ctx.drawImage(image, left, top, width, height, Math.floor((x - origin.x)*zoom), Math.floor((y - origin.y)*zoom), Math.ceil(width * scale.x * zoom), Math.ceil(height * scale.y * zoom)); } } \ No newline at end of file diff --git a/src/Nodes/GameNode.ts b/src/Nodes/GameNode.ts index f8e4d11..f27709e 100644 --- a/src/Nodes/GameNode.ts +++ b/src/Nodes/GameNode.ts @@ -9,6 +9,7 @@ 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"; /** * The representation of an object in the game world @@ -35,6 +36,7 @@ export default abstract class GameNode implements Positioned, Unique, Updateable triggers: Map; _velocity: Vec2; sweptRect: AABB; + isPlayer: boolean; protected input: InputReceiver; protected receiver: Receiver; @@ -104,7 +106,7 @@ export default abstract class GameNode implements Positioned, Unique, Updateable this.moving = false; this.onGround = false; this.onWall = false; - this.onCeiling= false; + this.onCeiling = false; this.active = true; this.isCollidable = isCollidable; this.isStatic = isStatic; diff --git a/src/Nodes/Tilemaps/OrthogonalTilemap.ts b/src/Nodes/Tilemaps/OrthogonalTilemap.ts index 59f75b1..44911c6 100644 --- a/src/Nodes/Tilemaps/OrthogonalTilemap.ts +++ b/src/Nodes/Tilemaps/OrthogonalTilemap.ts @@ -86,6 +86,7 @@ export default class OrthogonalTilemap extends Tilemap { ctx.globalAlpha = this.getLayer().getAlpha(); let origin = this.getViewportOriginWithParallax(); + let zoom = this.getViewportScale(); if(this.visible){ for(let i = 0; i < this.data.length; i++){ @@ -93,7 +94,7 @@ export default class OrthogonalTilemap extends Tilemap { for(let tileset of this.tilesets){ if(tileset.hasTile(tileIndex)){ - tileset.renderTile(ctx, tileIndex, i, this.size, origin, this.scale); + tileset.renderTile(ctx, tileIndex, i, this.size, origin, this.scale, zoom); } } } diff --git a/src/Physics/BasicPhysicsManager.ts b/src/Physics/BasicPhysicsManager.ts index b7fe7b2..df346dd 100644 --- a/src/Physics/BasicPhysicsManager.ts +++ b/src/Physics/BasicPhysicsManager.ts @@ -75,14 +75,16 @@ export default class BasicPhysicsManager extends PhysicsManager { // TODO - This is problematic if a collision happens, but it is later learned that another collision happens before it if(node1.triggers.has(group2)){ // Node1 should send an event + console.log("Trigger") let eventType = node1.triggers.get(group2); - this.emitter.fireEvent(eventType, {node: node1, other: node2}); + this.emitter.fireEvent(eventType, {node: node1, other: node2, collision: {firstContact: firstContact}}); } if(node2.triggers.has(group1)){ // Node2 should send an event + console.log("Trigger") let eventType = node2.triggers.get(group1); - this.emitter.fireEvent(eventType, {node: node2, other: node1}); + this.emitter.fireEvent(eventType, {node: node2, other: node1, collision: {firstContact: firstContact}}); } if(collidingX && collidingY){ @@ -93,6 +95,9 @@ export default class BasicPhysicsManager extends PhysicsManager { // Get the amount to scale x and y based on their initial collision times let xScale = MathUtils.clamp(firstContact.x, 0, 1); let yScale = MathUtils.clamp(firstContact.y, 0, 1); + + MathUtils.floorToPlace(xScale, 4); + MathUtils.floorToPlace(yScale, 4); // Handle special case of stickiness on perfect corner to corner collisions if(xScale === yScale){ @@ -111,8 +116,8 @@ export default class BasicPhysicsManager extends PhysicsManager { } if(!node2.isStatic && node2.moving){ - node1.onGround = !node1onTop; - node1.onCeiling = node1onTop; + node2.onGround = !node1onTop; + node2.onCeiling = node1onTop; } } @@ -129,13 +134,9 @@ export default class BasicPhysicsManager extends PhysicsManager { } // Scale velocity for either node if it is moving - if(!node1.isStatic && node1.moving){ - node1._velocity.scale(xScale, yScale); - } + node1._velocity.scale(xScale, yScale); - if(!node2.isStatic && node2.moving){ - node2._velocity.scale(xScale, yScale); - } + node2._velocity.scale(xScale, yScale); } } } @@ -274,6 +275,18 @@ export default class BasicPhysicsManager extends PhysicsManager { // Get Collision (which may or may not happen) let [firstContact, lastContact, collidingX, collidingY] = Shape.getTimeOfCollision(node1.collisionShape, node1._velocity, node2.collisionShape, node2._velocity); + if(collidingX && collidingY){ + console.log("overlapping") + } + + if(node1.isPlayer){ + if(firstContact.x !== Infinity || firstContact.y !== Infinity) + Debug.log("playercol", "First Contact: " + firstContact.toFixed(4)) + } else if(node2.isPlayer) { + if(firstContact.x !== Infinity || firstContact.y !== Infinity) + Debug.log("playercol", "First Contact: " + firstContact.toFixed(4)) + } + this.resolveCollision(node1, node2, firstContact, lastContact, collidingX, collidingY); } diff --git a/src/Physics/BroadPhaseAlgorithms/SweepAndPrune.ts b/src/Physics/BroadPhaseAlgorithms/SweepAndPrune.ts index 24fe704..085444c 100644 --- a/src/Physics/BroadPhaseAlgorithms/SweepAndPrune.ts +++ b/src/Physics/BroadPhaseAlgorithms/SweepAndPrune.ts @@ -29,7 +29,7 @@ export default class SweepAndPrune extends BroadPhase { let node = this.xList[i]; let index = 1; - while(i + index < this.xList.length && node.sweptRect.right > this.xList[i + index].sweptRect.left){ + while(i + index < this.xList.length && node.sweptRect.right >= this.xList[i + index].sweptRect.left){ // Colliding pair in x-axis xCollisions.push([node, this.xList[i + index]]); index++; @@ -44,7 +44,7 @@ export default class SweepAndPrune extends BroadPhase { let node = this.yList[i]; let index = 1; - while(i + index < this.yList.length && node.sweptRect.bottom > this.yList[i + index].sweptRect.top){ + while(i + index < this.yList.length && node.sweptRect.bottom >= this.yList[i + index].sweptRect.top){ // Colliding pair in y-axis yCollisions.push([node, this.yList[i + index]]); index++; diff --git a/src/Scene/Factories/FactoryManager.ts b/src/Scene/Factories/FactoryManager.ts index 212cdbd..53afa49 100644 --- a/src/Scene/Factories/FactoryManager.ts +++ b/src/Scene/Factories/FactoryManager.ts @@ -1,7 +1,6 @@ import Scene from "../Scene"; import CanvasNodeFactory from "./CanvasNodeFactory"; import TilemapFactory from "./TilemapFactory"; -import PhysicsManager from "../../Physics/PhysicsManager"; import Tilemap from "../../Nodes/Tilemap"; export default class FactoryManager { @@ -10,9 +9,9 @@ export default class FactoryManager { private canvasNodeFactory: CanvasNodeFactory = new CanvasNodeFactory(); private tilemapFactory: TilemapFactory = new TilemapFactory(); - constructor(scene: Scene, physicsManager: PhysicsManager, tilemaps: Array){ + constructor(scene: Scene, tilemaps: Array){ this.canvasNodeFactory.init(scene); - this.tilemapFactory.init(scene, tilemaps, physicsManager); + this.tilemapFactory.init(scene, tilemaps); } // Expose all of the factories through the factory manager diff --git a/src/Scene/Factories/TilemapFactory.ts b/src/Scene/Factories/TilemapFactory.ts index 71243c6..27ec0de 100644 --- a/src/Scene/Factories/TilemapFactory.ts +++ b/src/Scene/Factories/TilemapFactory.ts @@ -12,13 +12,11 @@ import Sprite from "../../Nodes/Sprites/Sprite"; export default class TilemapFactory { private scene: Scene; private tilemaps: Array; - private physicsManager: PhysicsManager; private resourceManager: ResourceManager; - init(scene: Scene, tilemaps: Array, physicsManager: PhysicsManager): void { + init(scene: Scene, tilemaps: Array): void { this.scene = scene; this.tilemaps = tilemaps; - this.physicsManager = physicsManager; this.resourceManager = ResourceManager.getInstance(); } @@ -83,15 +81,39 @@ export default class TilemapFactory { tilemap.addPhysics(); } } else { + + let isNavmeshPoints = false + if(layer.properties){ + for(let prop of layer.properties){ + if(prop.name === "NavmeshPoints"){ + isNavmeshPoints = true; + } + } + } + + if(isNavmeshPoints){ + console.log("Parsing NavmeshPoints") + continue; + } + // Layer is an object layer, so add each object as a sprite to a new layer for(let obj of layer.objects){ // Check if obj is collidable - let collidable = false; + let isCollidable = false; + let hasPhysics = false; + let isStatic = true; + let group = ""; if(obj.properties){ for(let prop of obj.properties){ if(prop.name === "Collidable"){ - collidable = prop.value; + isCollidable = prop.value; + } else if(prop.name === "Static"){ + isStatic = prop.value; + } else if(prop.name === "hasPhysics"){ + hasPhysics = prop.value; + } else if(prop.name === "Group"){ + group = prop.value; } } } @@ -126,8 +148,9 @@ export default class TilemapFactory { } // Now we have sprite. Associate it with our physics object if there is one - if(collidable){ - sprite.addPhysics(); + if(hasPhysics){ + sprite.addPhysics(sprite.boundary.clone(), isCollidable, isStatic); + sprite.group = group; } } } diff --git a/src/Scene/Scene.ts b/src/Scene/Scene.ts index 0728adb..27fd4ef 100644 --- a/src/Scene/Scene.ts +++ b/src/Scene/Scene.ts @@ -13,6 +13,7 @@ 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"; export default class Scene implements Updateable, Renderable { /** The size of the game world. */ @@ -49,6 +50,8 @@ export default class Scene implements Updateable, Renderable { /** An interface that allows the loading of different files for use in the scene */ public load: ResourceManager; + protected navmeshes: Array; + constructor(viewport: Viewport, sceneManager: SceneManager, game: GameLoop){ this.worldSize = new Vec2(500, 500); this.viewport = viewport; @@ -63,7 +66,7 @@ export default class Scene implements Updateable, Renderable { this.sceneGraph = new SceneGraphArray(this.viewport, this); this.physicsManager = new BasicPhysicsManager(); - this.add = new FactoryManager(this, this.physicsManager, this.tilemaps); + this.add = new FactoryManager(this, this.tilemaps); this.load = ResourceManager.getInstance(); } diff --git a/src/Utils/GraphUtils.ts b/src/Utils/GraphUtils.ts new file mode 100644 index 0000000..cf87f56 --- /dev/null +++ b/src/Utils/GraphUtils.ts @@ -0,0 +1,21 @@ +import Graph, { EdgeNode } from "../DataTypes/Graphs/Graph"; + +export default class GraphUtils { + + static djikstra(g: Graph, start: number): void { + let i: number; // Counter + let p: EdgeNode; // Pointer to edgenode + let inTree: Array + let distance: number; + let v: number; // Current vertex to process + let w: number; // Candidate for next vertex + let weight: number; // Edge weight + let dist; // Best current distance from start + + for(i = 0; i < g.numVertices; i++){ + inTree[i] = false; + distance[i] = Infinity; + // parent[i] = -1; + } + } +} \ No newline at end of file diff --git a/src/Utils/MathUtils.ts b/src/Utils/MathUtils.ts index 95c1c96..be2bf83 100644 --- a/src/Utils/MathUtils.ts +++ b/src/Utils/MathUtils.ts @@ -45,7 +45,27 @@ export default class MathUtils { */ static lerp(a: number, b: number, x: number){ return a + x * (b - a); - } + } + + /** + * Cuts off decimal points of a number after a specified place + * @param num The number to floor + * @param place The last decimal place of the new number + */ + static floorToPlace(num: number, place: number): number { + if(place === 0){ + return Math.floor(num); + } + + let factor = 10; + while(place > 1){ + factor != 10; + place--; + } + + return Math.floor(num*factor)/factor; + + } /** * Returns the number as a hexadecimal diff --git a/src/_DemoClasses/Enemies/Afraid.ts b/src/_DemoClasses/Enemies/Afraid.ts new file mode 100644 index 0000000..ca68dd2 --- /dev/null +++ b/src/_DemoClasses/Enemies/Afraid.ts @@ -0,0 +1,31 @@ +import GameEvent from "../../Events/GameEvent"; +import MathUtils from "../../Utils/MathUtils"; +import { CustomGameEventType } from "../CustomGameEventType"; +import { GoombaStates } from "./GoombaController"; +import OnGround from "./OnGround"; + +export default class Afraid extends OnGround { + + onEnter(): void {} + + handleInput(event: GameEvent): void { + if(event.type === CustomGameEventType.PLAYER_MOVE){ + let pos = event.data.get("position"); + this.parent.direction.x = MathUtils.sign(this.owner.position.x - pos.x); + } else if(event.type === "playerHitCoinBlock") { + if(event.data.get("collision").firstContact.y < 1 && event.data.get("node").collisionShape.center.y > event.data.get("other").collisionShape.center.y){ + this.finished("previous"); + } + } + } + + update(deltaT: number): void { + super.update(deltaT); + + this.parent.velocity.x = this.parent.direction.x * this.parent.speed * 1.2; + + this.owner.move(this.parent.velocity.scaled(deltaT)); + } + + onExit(): void {} +} \ No newline at end of file diff --git a/src/_DemoClasses/Enemies/GoombaController.ts b/src/_DemoClasses/Enemies/GoombaController.ts index 12bf25d..398c108 100644 --- a/src/_DemoClasses/Enemies/GoombaController.ts +++ b/src/_DemoClasses/Enemies/GoombaController.ts @@ -3,6 +3,7 @@ import { CustomGameEventType } from "../CustomGameEventType"; import Idle from "../Enemies/Idle"; import Jump from "../Enemies/Jump"; import Walk from "../Enemies/Walk"; +import Afraid from "../Enemies/Afraid"; import Debug from "../../Debug/Debug"; import GameNode from "../../Nodes/GameNode"; import Vec2 from "../../DataTypes/Vec2"; @@ -11,7 +12,8 @@ export enum GoombaStates { IDLE = "idle", WALK = "walk", JUMP = "jump", - PREVIOUS = "previous" + PREVIOUS = "previous", + AFRAID = "afraid" } export default class GoombaController extends StateMachine { @@ -28,6 +30,7 @@ export default class GoombaController extends StateMachine { this.jumpy = jumpy; this.receiver.subscribe(CustomGameEventType.PLAYER_MOVE); + this.receiver.subscribe("playerHitCoinBlock"); if(this.jumpy){ this.receiver.subscribe(CustomGameEventType.PLAYER_JUMP); } @@ -38,11 +41,13 @@ export default class GoombaController extends StateMachine { this.addState(GoombaStates.WALK, walk); let jump = new Jump(this, owner); this.addState(GoombaStates.JUMP, jump); + let afraid = new Afraid(this, owner); + this.addState(GoombaStates.AFRAID, afraid); } changeState(stateName: string): void { - if(stateName === GoombaStates.JUMP){ + if(stateName === GoombaStates.JUMP || stateName === GoombaStates.AFRAID){ this.stack.push(this.stateMap.get(stateName)); } super.changeState(stateName); diff --git a/src/_DemoClasses/Enemies/GoombaState.ts b/src/_DemoClasses/Enemies/GoombaState.ts index 5144acc..ea1b135 100644 --- a/src/_DemoClasses/Enemies/GoombaState.ts +++ b/src/_DemoClasses/Enemies/GoombaState.ts @@ -1,7 +1,8 @@ import State from "../../DataTypes/State/State"; import StateMachine from "../../DataTypes/State/StateMachine"; +import GameEvent from "../../Events/GameEvent"; import GameNode from "../../Nodes/GameNode"; -import GoombaController from "./GoombaController"; +import GoombaController, { GoombaStates } from "./GoombaController"; export default abstract class GoombaState extends State { owner: GameNode; @@ -14,6 +15,14 @@ export default abstract class GoombaState extends State { this.owner = owner; } + handleInput(event: GameEvent): void { + if(event.type === "playerHitCoinBlock") { + if(event.data.get("collision").firstContact.y < 1 && event.data.get("node").collisionShape.center.y > event.data.get("other").collisionShape.center.y){ + this.finished(GoombaStates.AFRAID); + } + } + } + update(deltaT: number): void { // Do gravity this.parent.velocity.y += this.gravity*deltaT; diff --git a/src/_DemoClasses/Enemies/Idle.ts b/src/_DemoClasses/Enemies/Idle.ts index a3172f6..6542870 100644 --- a/src/_DemoClasses/Enemies/Idle.ts +++ b/src/_DemoClasses/Enemies/Idle.ts @@ -16,7 +16,6 @@ export default class Idle extends OnGround { this.finished(GoombaStates.WALK); } } - super.handleInput(event); } update(deltaT: number): void { diff --git a/src/_DemoClasses/Enemies/Jump.ts b/src/_DemoClasses/Enemies/Jump.ts index dfae729..8000eed 100644 --- a/src/_DemoClasses/Enemies/Jump.ts +++ b/src/_DemoClasses/Enemies/Jump.ts @@ -6,8 +6,6 @@ export default class Jump extends GoombaState { onEnter(): void {} - handleInput(event: GameEvent): void {} - update(deltaT: number): void { super.update(deltaT); diff --git a/src/_DemoClasses/Enemies/OnGround.ts b/src/_DemoClasses/Enemies/OnGround.ts index f529a77..c86a9fd 100644 --- a/src/_DemoClasses/Enemies/OnGround.ts +++ b/src/_DemoClasses/Enemies/OnGround.ts @@ -11,6 +11,8 @@ export default class OnGround extends GoombaState { this.finished(GoombaStates.JUMP); this.parent.velocity.y = -2000; } + + super.handleInput(event); } update(deltaT: number): void { diff --git a/src/_DemoClasses/MarioClone/MarioClone.ts b/src/_DemoClasses/MarioClone/MarioClone.ts index 5fa9e44..2d6530e 100644 --- a/src/_DemoClasses/MarioClone/MarioClone.ts +++ b/src/_DemoClasses/MarioClone/MarioClone.ts @@ -5,6 +5,7 @@ import Color from "../../Utils/Color"; import PlayerController from "../Player/PlayerStates/Platformer/PlayerController"; import { PlayerStates } from "../Player/PlayerStates/Platformer/PlayerController"; import GoombaController from "../Enemies/GoombaController"; +import InputReceiver from "../../Input/InputReceiver"; export default class MarioClone extends Scene { @@ -20,7 +21,7 @@ export default class MarioClone extends Scene { let player = this.add.graphic(Rect, layer, new Vec2(0, 0), new Vec2(64, 64)); player.setColor(Color.BLUE); player.addPhysics(); - + player.isPlayer = true; this.viewport.follow(player); this.viewport.setBounds(0, 0, 5120, 1280); @@ -28,6 +29,8 @@ export default class MarioClone extends Scene { ai.initialize(PlayerStates.IDLE); player.update = (deltaT: number) => {ai.update(deltaT)}; + 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 ai = new GoombaController(goomba, false); diff --git a/src/_DemoClasses/Pathfinding/PathfindingScene.ts b/src/_DemoClasses/Pathfinding/PathfindingScene.ts new file mode 100644 index 0000000..e89f0b9 --- /dev/null +++ b/src/_DemoClasses/Pathfinding/PathfindingScene.ts @@ -0,0 +1,26 @@ +import Scene from "../../Scene/Scene"; +import Rect from "../../Nodes/Graphics/Rect"; +import Vec2 from "../../DataTypes/Vec2"; +import PlayerController from "../Player/PlayerController"; + +export default class PathfindingScene extends Scene { + + loadScene(){ + this.load.tilemap("interior", "/assets/tilemaps/Interior.json"); + } + + startScene(){ + this.add.tilemap("interior"); + + let layer = this.addLayer(); + + let player = this.add.graphic(Rect, layer, new Vec2(500, 500), new Vec2(64, 64)); + player.addPhysics(); + let ai = new PlayerController(player, "topdown"); + ai.speed = 400; + player.update = (deltaT: number) => {ai.update(deltaT)} + this.viewport.setBounds(0, 0, 40*64, 40*64); + this.viewport.follow(player); + this.viewport.enableZoom(); + } +} \ No newline at end of file diff --git a/src/_DemoClasses/Player/PlayerController.ts b/src/_DemoClasses/Player/PlayerController.ts index 46d46b5..20560ca 100644 --- a/src/_DemoClasses/Player/PlayerController.ts +++ b/src/_DemoClasses/Player/PlayerController.ts @@ -1,7 +1,13 @@ import StateMachine from "../../DataTypes/State/StateMachine"; -import CanvasNode from "../../Nodes/CanvasNode"; +import Vec2 from "../../DataTypes/Vec2"; +import Debug from "../../Debug/Debug"; +import GameNode from "../../Nodes/GameNode"; import IdleTopDown from "./PlayerStates/IdleTopDown"; import MoveTopDown from "./PlayerStates/MoveTopDown"; +import Idle from "./PlayerStates/Platformer/Idle"; +import Jump from "./PlayerStates/Platformer/Jump"; +import Run from "./PlayerStates/Platformer/Run"; +import Walk from "./PlayerStates/Platformer/Walk"; export enum PlayerType { PLATFORMER = "platformer", @@ -9,20 +15,31 @@ export enum PlayerType { } export enum PlayerStates { + IDLE = "idle", MOVE = "move", - IDLE = "idle" + WALK = "walk", + RUN = "run", + JUMP = "jump", + PREVIOUS = "previous" } export default class PlayerController extends StateMachine { - protected owner: CanvasNode; + protected owner: GameNode; + velocity: Vec2 = Vec2.ZERO; + speed: number; + MIN_SPEED: number = 400; + MAX_SPEED: number = 1000; + - constructor(owner: CanvasNode, playerType: string){ + constructor(owner: GameNode, playerType: string){ super(); this.owner = owner; if(playerType === PlayerType.TOPDOWN){ this.initializeTopDown(); + } else { + this.initializePlatformer(); } } @@ -33,13 +50,32 @@ export default class PlayerController extends StateMachine { let idle = new IdleTopDown(this); let move = new MoveTopDown(this, this.owner); + this.speed = 150; + this.addState(PlayerStates.IDLE, idle); this.addState(PlayerStates.MOVE, move); this.initialize(PlayerStates.IDLE); } + initializePlatformer(): void { + this.speed = 400; + + let idle = new Idle(this, this.owner); + this.addState(PlayerStates.IDLE, idle); + let walk = new Walk(this, this.owner); + this.addState(PlayerStates.WALK, walk); + let run = new Run(this, this.owner); + this.addState(PlayerStates.RUN, run); + let jump = new Jump(this, this.owner); + this.addState(PlayerStates.JUMP, jump); + } + changeState(stateName: string): void { + if(stateName === PlayerStates.JUMP){ + this.stack.push(this.stateMap.get(stateName)); + } + if(stateName === PlayerStates.MOVE){ // If move, push to the stack this.stack.push(this.stateMap.get(stateName)); @@ -47,4 +83,22 @@ export default class PlayerController extends StateMachine { super.changeState(stateName); } + + update(deltaT: number): void { + super.update(deltaT); + + if(this.currentState instanceof Jump){ + Debug.log("playerstate", "Player State: Jump"); + } else if (this.currentState instanceof Walk){ + Debug.log("playerstate", "Player State: Walk"); + } else if (this.currentState instanceof Run){ + Debug.log("playerstate", "Player State: Run"); + } else if (this.currentState instanceof Idle){ + Debug.log("playerstate", "Player State: Idle"); + } else if (this.currentState instanceof IdleTopDown){ + Debug.log("playerstate", "Player State: Idle"); + } else if (this.currentState instanceof MoveTopDown){ + Debug.log("playerstate", "Player State: Move"); + } + } } \ No newline at end of file diff --git a/src/_DemoClasses/Player/PlayerStates/MoveTopDown.ts b/src/_DemoClasses/Player/PlayerStates/MoveTopDown.ts index 72a3156..eb9b621 100644 --- a/src/_DemoClasses/Player/PlayerStates/MoveTopDown.ts +++ b/src/_DemoClasses/Player/PlayerStates/MoveTopDown.ts @@ -3,16 +3,17 @@ import StateMachine from "../../../DataTypes/State/StateMachine"; import Vec2 from "../../../DataTypes/Vec2"; import GameEvent from "../../../Events/GameEvent"; import InputReceiver from "../../../Input/InputReceiver"; -import CanvasNode from "../../../Nodes/CanvasNode"; +import GameNode from "../../../Nodes/GameNode"; import { CustomGameEventType } from "../../CustomGameEventType"; +import PlayerController from "../PlayerController"; export default class MoveTopDown extends State { direction: Vec2 = Vec2.ZERO; - speed: number = 0; input: InputReceiver = InputReceiver.getInstance(); - owner: CanvasNode; + owner: GameNode; + parent: PlayerController - constructor(parent: StateMachine, owner: CanvasNode) { + constructor(parent: StateMachine, owner: GameNode) { super(parent); this.owner = owner; } @@ -20,7 +21,6 @@ export default class MoveTopDown extends State { onEnter(): void { // Initialize or reset the direction and speed this.direction.zero(); - this.speed = 100; } handleInput(event: GameEvent): void { @@ -38,7 +38,7 @@ export default class MoveTopDown extends State { } // Otherwise, we are still moving, so update position - let velocity = this.direction.normalize().scale(this.speed); + let velocity = this.direction.normalize().scale(this.parent.speed); this.owner.move(velocity.scale(deltaT)); // Emit an event to tell the world we are moving diff --git a/src/main.ts b/src/main.ts index 7a97e93..1ca22d0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,16 +1,15 @@ import GameLoop from "./Loop/GameLoop"; import {} from "./index"; -import MainScene from "./MainScene" -import QuadTreeScene from "./QuadTreeScene"; import BoidDemo from "./BoidDemo"; import MarioClone from "./_DemoClasses/MarioClone/MarioClone"; +import PathfindingScene from "./_DemoClasses/Pathfinding/PathfindingScene"; function main(){ // Create the game object let game = new GameLoop({canvasSize: {x: 800, y: 600}}); game.start(); let sm = game.getSceneManager(); - sm.addScene(MarioClone); + sm.addScene(PathfindingScene); } CanvasRenderingContext2D.prototype.roundedRect = function(x: number, y: number, w: number, h: number, r: number): void {