From e6d4d75cd5a284410f3d7f1c4bf56b71480be1c0 Mon Sep 17 00:00:00 2001 From: Joe Weaver Date: Mon, 17 Aug 2020 20:09:41 -0400 Subject: [PATCH] added basic physics collision checks --- src/GameState/Factories/CanvasNodeFactory.ts | 1 + src/GameState/Factories/PhysicsNodeFactory.ts | 23 +++++ src/GameState/Factories/TilemapFactory.ts | 1 + src/GameState/Scene.ts | 7 ++ src/Nodes/CanvasNode.ts | 5 -- src/Nodes/GameNode.ts | 10 +++ src/Nodes/Player.ts | 37 -------- src/Nodes/PlayerSprite.ts | 18 ++++ src/Nodes/Tilemap.ts | 4 +- src/Nodes/Tilemaps/OrthogonalTilemap.ts | 2 +- src/Physics/Colliders/AABB.ts | 29 ++++++ src/Physics/Colliders/Collider.ts | 18 ++++ src/Physics/PhysicsManager.ts | 89 +++++++++++++++++++ src/Physics/PhysicsNode.ts | 44 +++++++++ src/Player.ts | 44 +++++++++ src/main.ts | 11 +-- tsconfig.json | 25 +++++- 17 files changed, 313 insertions(+), 55 deletions(-) create mode 100644 src/GameState/Factories/PhysicsNodeFactory.ts delete mode 100644 src/Nodes/Player.ts create mode 100644 src/Nodes/PlayerSprite.ts create mode 100644 src/Physics/Colliders/AABB.ts create mode 100644 src/Physics/Colliders/Collider.ts create mode 100644 src/Physics/PhysicsManager.ts create mode 100644 src/Physics/PhysicsNode.ts create mode 100644 src/Player.ts diff --git a/src/GameState/Factories/CanvasNodeFactory.ts b/src/GameState/Factories/CanvasNodeFactory.ts index ec7136c..7d48409 100644 --- a/src/GameState/Factories/CanvasNodeFactory.ts +++ b/src/GameState/Factories/CanvasNodeFactory.ts @@ -1,6 +1,7 @@ import Scene from "../Scene"; import Viewport from "../../SceneGraph/Viewport"; import CanvasItem from "../../Nodes/CanvasNode" +import PlayerSprite from "../../Nodes/PlayerSprite"; export default class CanvasNodeFactory { private scene: Scene; diff --git a/src/GameState/Factories/PhysicsNodeFactory.ts b/src/GameState/Factories/PhysicsNodeFactory.ts new file mode 100644 index 0000000..67d913a --- /dev/null +++ b/src/GameState/Factories/PhysicsNodeFactory.ts @@ -0,0 +1,23 @@ +import Scene from "../Scene"; +import Viewport from "../../SceneGraph/Viewport"; +import PhysicsNode from "../../Physics/PhysicsNode"; +import PhysicsManager from "../../Physics/PhysicsManager"; + +export default class PhysicsNodeFactory { + private scene: Scene; + private physicsManager: PhysicsManager; + + constructor(scene: Scene, physicsManager: PhysicsManager){ + this.scene = scene; + this.physicsManager = physicsManager; + } + + add(constr: new (...a: any) => T, ...args: any): T { + let instance = new constr(...args); + instance.init(this.scene); + instance.addManager(this.physicsManager); + instance.create(); + this.physicsManager.add(instance); + return instance; + } +} \ No newline at end of file diff --git a/src/GameState/Factories/TilemapFactory.ts b/src/GameState/Factories/TilemapFactory.ts index 9843bc5..79edf69 100644 --- a/src/GameState/Factories/TilemapFactory.ts +++ b/src/GameState/Factories/TilemapFactory.ts @@ -20,6 +20,7 @@ export default class TilemapFactory { // For each of the layers in the tilemap, create a tilemap for(let layer of tilemapData.layers){ let tilemap = new constr(tilemapData, layer); + tilemap.init(this.scene); // Add to scene this.scene.addTilemap(tilemap); diff --git a/src/GameState/Scene.ts b/src/GameState/Scene.ts index 1267726..9d1a95f 100644 --- a/src/GameState/Scene.ts +++ b/src/GameState/Scene.ts @@ -7,12 +7,15 @@ import CanvasNodeFactory from "./Factories/CanvasNodeFactory"; import GameState from "./GameState"; import Tilemap from "../Nodes/Tilemap"; import TilemapFactory from "./Factories/TilemapFactory"; +import PhysicsManager from "../Physics/PhysicsManager"; +import PhysicsNodeFactory from "./Factories/PhysicsNodeFactory"; export default class Scene { private gameState: GameState; private viewport: Viewport private parallax: Vec2; sceneGraph: SceneGraph; + private physicsManager: PhysicsManager; private tilemaps: Array; private paused: boolean; private hidden: boolean; @@ -20,6 +23,7 @@ export default class Scene { // Factories public canvasNode: CanvasNodeFactory; public tilemap: TilemapFactory; + public physics: PhysicsNodeFactory; constructor(viewport: Viewport, gameState: GameState){ this.gameState = gameState; @@ -29,10 +33,12 @@ export default class Scene { this.tilemaps = new Array(); this.paused = false; this.hidden = false; + this.physicsManager = new PhysicsManager(); // Factories this.canvasNode = new CanvasNodeFactory(this, this.viewport); this.tilemap = new TilemapFactory(this, this.viewport); + this.physics = new PhysicsNodeFactory(this, this.physicsManager); } setPaused(pauseValue: boolean): void { @@ -84,6 +90,7 @@ export default class Scene { update(deltaT: number): void { if(!this.paused){ this.viewport.update(deltaT); + this.physicsManager.update(deltaT); this.sceneGraph.update(deltaT); } } diff --git a/src/Nodes/CanvasNode.ts b/src/Nodes/CanvasNode.ts index 019c88a..eff0be9 100644 --- a/src/Nodes/CanvasNode.ts +++ b/src/Nodes/CanvasNode.ts @@ -4,17 +4,12 @@ import Scene from "../GameState/Scene"; export default abstract class CanvasNode extends GameNode{ protected size: Vec2; - protected scene: Scene; constructor(){ super(); this.size = new Vec2(0, 0); } - init(scene: Scene){ - this.scene = scene; - } - getSize(): Vec2 { return this.size; } diff --git a/src/Nodes/GameNode.ts b/src/Nodes/GameNode.ts index 97592e6..6716c9c 100644 --- a/src/Nodes/GameNode.ts +++ b/src/Nodes/GameNode.ts @@ -4,18 +4,28 @@ import Vec2 from "../DataTypes/Vec2"; import Map from "../DataTypes/Map"; import Receiver from "../Events/Receiver"; import GameEvent from "../Events/GameEvent"; +import Scene from "../GameState/Scene"; export default abstract class GameNode{ private eventQueue: EventQueue; protected input: InputReceiver; protected position: Vec2; private receiver: Receiver; + protected scene: Scene; constructor(){ this.eventQueue = EventQueue.getInstance(); this.input = InputReceiver.getInstance(); this.position = new Vec2(0, 0); } + + init(scene: Scene){ + this.scene = scene; + } + + getScene(): Scene { + return this.scene; + } getPosition(): Vec2 { return this.position; diff --git a/src/Nodes/Player.ts b/src/Nodes/Player.ts deleted file mode 100644 index 23ae51b..0000000 --- a/src/Nodes/Player.ts +++ /dev/null @@ -1,37 +0,0 @@ -import CanvasNode from "./CanvasNode"; -import Vec2 from "../DataTypes/Vec2"; -import Debug from "../Debug/Debug"; - -export default class Player extends CanvasNode{ - velocity: Vec2; - speed: number; - debug: Debug; - - constructor(){ - super(); - this.velocity = new Vec2(0, 0); - this.speed = 300; - this.size = new Vec2(50, 50); - this.debug = Debug.getInstance(); - }; - - update(deltaT: number): void { - let dir = new Vec2(0, 0); - dir.x += this.input.isPressed('a') ? -1 : 0; - dir.x += this.input.isPressed('d') ? 1 : 0; - dir.y += this.input.isPressed('s') ? 1 : 0; - dir.y += this.input.isPressed('w') ? -1 : 0; - - dir.normalize(); - - this.velocity = dir.scale(this.speed); - this.position = this.position.add(this.velocity.scale(deltaT)); - - this.debug.log("player", "Player Pos: " + this.position.toFixed()); - } - - render(ctx: CanvasRenderingContext2D, origin: Vec2){ - ctx.fillStyle = "#FF0000"; - ctx.fillRect(this.position.x - origin.x, this.position.y - origin.y, this.size.x, this.size.y); - } -} \ No newline at end of file diff --git a/src/Nodes/PlayerSprite.ts b/src/Nodes/PlayerSprite.ts new file mode 100644 index 0000000..80f426b --- /dev/null +++ b/src/Nodes/PlayerSprite.ts @@ -0,0 +1,18 @@ +import CanvasNode from "./CanvasNode"; +import Vec2 from "../DataTypes/Vec2"; +import Debug from "../Debug/Debug"; + +export default class Player extends CanvasNode{ + debug: Debug; + + constructor(){ + super(); + }; + + update(deltaT: number): void {} + + render(ctx: CanvasRenderingContext2D, origin: Vec2){ + ctx.fillStyle = "#FF0000"; + ctx.fillRect(this.position.x - origin.x, this.position.y - origin.y, this.size.x, this.size.y); + } +} \ No newline at end of file diff --git a/src/Nodes/Tilemap.ts b/src/Nodes/Tilemap.ts index a150f48..e950c37 100644 --- a/src/Nodes/Tilemap.ts +++ b/src/Nodes/Tilemap.ts @@ -15,7 +15,7 @@ export default abstract class Tilemap extends GameNode { super(); this.tilesets = new Array(); this.worldSize = new Vec2(0, 0); - this.init(tilemapData, layerData); + this.parseTilemapData(tilemapData, layerData); } getTilesets(): Tileset[] { @@ -36,7 +36,7 @@ export default abstract class Tilemap extends GameNode { /** * Sets up the tileset using the data loaded from file */ - abstract init(tilemapData: TiledTilemapData, layerData: TiledLayerData): void; + abstract parseTilemapData(tilemapData: TiledTilemapData, layerData: TiledLayerData): void; abstract render(ctx: CanvasRenderingContext2D, origin: Vec2, viewportSize: Vec2): void; } \ No newline at end of file diff --git a/src/Nodes/Tilemaps/OrthogonalTilemap.ts b/src/Nodes/Tilemaps/OrthogonalTilemap.ts index 26cca19..4ec5f85 100644 --- a/src/Nodes/Tilemaps/OrthogonalTilemap.ts +++ b/src/Nodes/Tilemaps/OrthogonalTilemap.ts @@ -5,7 +5,7 @@ import Tileset from "../../DataTypes/Tilesets/Tileset"; export default class OrthogonalTilemap extends Tilemap { - init(tilemapData: TiledTilemapData, layer: TiledLayerData): void { + parseTilemapData(tilemapData: TiledTilemapData, layer: TiledLayerData): void { this.worldSize.set(tilemapData.width, tilemapData.height); this.data = layer.data; tilemapData.tilesets.forEach(tilesetData => this.tilesets.push(new Tileset(tilesetData))); diff --git a/src/Physics/Colliders/AABB.ts b/src/Physics/Colliders/AABB.ts new file mode 100644 index 0000000..d494264 --- /dev/null +++ b/src/Physics/Colliders/AABB.ts @@ -0,0 +1,29 @@ +import Collider from "./Collider"; +import Vec2 from "../../DataTypes/Vec2"; + +export default class AABB extends Collider { + + isCollidingWith(other: Collider): boolean { + if(other instanceof AABB){ + if(other.position.x > this.position.x && other.position.x < this.position.x + this.size.x){ + return other.position.y > this.position.y && other.position.y < this.position.y + this.size.y; + } + } + return false; + } + + willCollideWith(other: Collider, thisVel: Vec2, otherVel: Vec2): boolean { + if(other instanceof AABB){ + let thisPos = new Vec2(this.position.x + thisVel.x, this.position.y + thisVel.y); + let otherPos = new Vec2(other.position.x + otherVel.x, other.position.y + otherVel.y); + + if(otherPos.x > thisPos.x && otherPos.x < thisPos.x + this.size.x){ + return otherPos.y > thisPos.y && otherPos.y < thisPos.y + this.size.y; + } + } + return false; + } + + update(deltaT: number): void {} + +} \ No newline at end of file diff --git a/src/Physics/Colliders/Collider.ts b/src/Physics/Colliders/Collider.ts new file mode 100644 index 0000000..625da05 --- /dev/null +++ b/src/Physics/Colliders/Collider.ts @@ -0,0 +1,18 @@ +import GameNode from "../../Nodes/GameNode"; +import Vec2 from "../../DataTypes/Vec2"; + +export default abstract class Collider extends GameNode { + protected size: Vec2; + + getSize(): Vec2 { + return this.size; + } + + setSize(size: Vec2): void { + this.size = size; + } + + abstract isCollidingWith(other: Collider): boolean; + + abstract willCollideWith(other: Collider, thisVel: Vec2, otherVel: Vec2): boolean; +} \ No newline at end of file diff --git a/src/Physics/PhysicsManager.ts b/src/Physics/PhysicsManager.ts new file mode 100644 index 0000000..71c023f --- /dev/null +++ b/src/Physics/PhysicsManager.ts @@ -0,0 +1,89 @@ +import PhysicsNode from "./PhysicsNode"; +import Vec2 from "../DataTypes/Vec2"; + +export default class PhysicsManager { + + physicsNodes: Array; + movements: Array; + + constructor(){ + this.physicsNodes = new Array(); + this.movements = new Array(); + } + + add(node: PhysicsNode): void { + this.physicsNodes.push(node); + } + + addMovement(node: PhysicsNode, velocity: Vec2){ + this.movements.push(new MovementData(node, velocity)); + } + + update(deltaT: number): void { + for(let node of this.physicsNodes){ + node.update(deltaT); + } + + let staticSet = new Array(); + let dynamicSet = new Array(); + + // TODO: REALLY bad, the physics system has to be improved, but that isn't the focus for now + for(let node of this.physicsNodes){ + if(node.isMoving){ + dynamicSet.push(node); + node.isMoving = false; + } else { + staticSet.push(node); + } + } + + // For now, we will only have the moving player, don't bother checking for collisions with other moving things + for(let movingNode of dynamicSet){ + // Get velocity of node + let velocity = null; + for(let data of this.movements){ + if(data.node === movingNode){ + velocity = new Vec2(data.velocity.x, data.velocity.y); + } + } + + + for(let staticNode of staticSet){ + if(movingNode.getCollider().willCollideWith(staticNode.getCollider(), velocity, new Vec2(0, 0))){ + this.handleCollision(movingNode, staticNode, velocity); + } + } + + movingNode.finishMove(velocity); + } + + // Reset movements + this.movements = new Array(); + } + + handleCollision(movingNode: PhysicsNode, staticNode: PhysicsNode, velocity: Vec2){ + let ASize = movingNode.getCollider().getSize(); + let A = new Vec2(movingNode.getPosition().x + ASize.x, movingNode.getPosition().y + ASize.y); + let BSize = staticNode.getCollider().getSize(); + let B = new Vec2(staticNode.getPosition().x + BSize.x, staticNode.getPosition().y + BSize.y); + + let firstContact = new Vec2(0, 0); + firstContact.x = (B.x-(BSize.x/2) - (A.x + (ASize.x/2)))/(velocity.x - 0); + firstContact.y = (B.y-(BSize.y/2) - (A.y + (ASize.y/2)))/(velocity.y - 0); + + if(firstContact.x < 1 || firstContact.y < 1){ + // We collided + let firstCollisionTime = Math.min(firstContact.x, firstContact.y); + velocity.scale(firstCollisionTime); + } + } +} + +class MovementData{ + node: PhysicsNode; + velocity: Vec2; + constructor(node: PhysicsNode, velocity: Vec2){ + this.node = node; + this.velocity = velocity; + } +} \ No newline at end of file diff --git a/src/Physics/PhysicsNode.ts b/src/Physics/PhysicsNode.ts new file mode 100644 index 0000000..5c32fad --- /dev/null +++ b/src/Physics/PhysicsNode.ts @@ -0,0 +1,44 @@ +import Collider from "./Colliders/Collider"; +import GameNode from "../Nodes/GameNode"; +import PhysicsManager from "./PhysicsManager"; +import Vec2 from "../DataTypes/Vec2"; + +export default abstract class PhysicsNode extends GameNode { + + protected collider: Collider = null; + protected children: Array; + private manager: PhysicsManager; + isMoving: boolean; + + constructor(){ + super(); + this.children = new Array(); + this.isMoving = false; + } + + addManager(manager: PhysicsManager): void { + this.manager = manager; + } + + isCollidable(): boolean { + return this.collider !== null; + } + + getCollider(): Collider { + return this.collider; + } + + move(velocity: Vec2): void { + this.isMoving = true; + this.manager.addMovement(this, velocity); + } + + finishMove(velocity: Vec2): void { + this.position.add(velocity); + for(let child of this.children){ + child.getPosition().add(velocity); + } + } + + abstract create(): void; +} \ No newline at end of file diff --git a/src/Player.ts b/src/Player.ts new file mode 100644 index 0000000..fb17e7d --- /dev/null +++ b/src/Player.ts @@ -0,0 +1,44 @@ +import PhysicsNode from "./Physics/PhysicsNode"; +import Vec2 from "./DataTypes/Vec2"; +import Debug from "./Debug/Debug"; +import AABB from "./Physics/Colliders/AABB"; +import PlayerSprite from "./Nodes/PlayerSprite"; + +export default class Player extends PhysicsNode { + velocity: Vec2; + speed: number; + debug: Debug; + + constructor(){ + super(); + this.velocity = new Vec2(0, 0); + this.speed = 300; + this.collider = new AABB(); + this.collider.setSize(new Vec2(50, 50)); + this.position = new Vec2(0, 0); + this.debug = Debug.getInstance(); + } + + create(): void { + let sprite = this.scene.canvasNode.add(PlayerSprite); + sprite.setPosition(this.position); + sprite.setSize(50, 50); + this.children.push(sprite); + } + + update(deltaT: number): void { + let dir = new Vec2(0, 0); + dir.x += this.input.isPressed('a') ? -1 : 0; + dir.x += this.input.isPressed('d') ? 1 : 0; + dir.y += this.input.isPressed('s') ? 1 : 0; + dir.y += this.input.isPressed('w') ? -1 : 0; + + dir.normalize(); + + this.velocity = dir.scale(this.speed); + this.move(this.velocity.scale(deltaT)); + + this.debug.log("player", "Player Pos: " + this.position.toFixed()); + } + +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 44a6053..230e2eb 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,5 @@ import GameLoop from "./Loop/GameLoop"; -import Scene from "./GameState/Scene"; -import Player from "./Nodes/Player"; +import Player from "./Player"; import UIElement from "./Nodes/UIElement"; import ColoredCircle from "./Nodes/ColoredCircle"; import Color from "./Utils/Color"; @@ -24,7 +23,7 @@ function main(){ pauseMenu.setParallax(0, 0); // Initialize GameObjects - let player = mainScene.canvasNode.add(Player); + let player = mainScene.physics.add(Player); mainScene.getViewport().follow(player); let recordButton = uiLayer.canvasNode.add(Button); @@ -79,11 +78,7 @@ function main(){ pauseMenu.disable(); } - for(let i = 0; i < 10; i++){ - mainScene.canvasNode.add(ColoredCircle); - } - - backgroundScene.tilemap.add(OrthogonalTilemap, "assets/tilemaps/MultiLayer.json"); + mainScene.tilemap.add(OrthogonalTilemap, "assets/tilemaps/MultiLayer.json"); for(let i = 0; i < 30; i++){ let cc = foregroundLayer.canvasNode.add(ColoredCircle); diff --git a/tsconfig.json b/tsconfig.json index 5a86195..bc19cdc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,10 @@ { "files": [ "src/main.ts", + "src/Player.ts", + "src/DataTypes/Tilesets/TiledData", + "src/DataTypes/Tilesets/Tileset", "src/DataTypes/Collection.ts", "src/DataTypes/Map.ts", "src/DataTypes/Queue.ts", @@ -9,10 +12,15 @@ "src/DataTypes/Vec2.ts", "src/DataTypes/Vec4.ts", + "src/Debug/Debug.ts", + "src/Events/EventQueue.ts", "src/Events/GameEvent.ts", "src/Events/Receiver.ts", + "src/GameState/Factories/CanvasNodeFactory.ts", + "src/GameState/Factories/PhysicsNodeFactory.ts", + "src/GameState/Factories/TilemapFactory.ts", "src/GameState/GameState.ts", "src/GameState/Scene.ts", @@ -21,20 +29,33 @@ "src/Loop/GameLoop.ts", + "src/Nodes/Tilemaps/OrgthogonalTilemap.ts", + "src/Nodes/UIElements/Button.ts", + "src/Nodes/UIElements/Label.ts", + "src/Nodes/CanvasNode.ts", "src/Nodes/ColoredCircle.ts", "src/Nodes/GameNode.ts", - "src/Nodes/Player.ts", + "src/Nodes/PlayerSprite.ts", + "src/Nodes/Tilemap.ts", "src/Nodes/UIElement.ts", + "src/Physics/Colliders/AABB.ts", + "src/Physics/Colliders/Collider.ts", + "src/Physics/PhysicsManager.ts", + "src/Physics/PhysicsNode.ts", + "src/Playback/Recorder.ts", + "src/ResourceManager/ResourceManager.ts", + "src/SceneGraph/SceneGraph.ts", "src/SceneGraph/SceneGraphArray.ts", "src/SceneGraph/Viewport.ts", "src/Utils/Color.ts", "src/Utils/MathUtils.ts", - "src/Utils/RandUtils.ts" + "src/Utils/RandUtils.ts", + "src/Utils/StringUtils.ts" ], "compilerOptions": { "noImplicitAny": true,