From eeaf73bab4f214038f1ce474f01dfe5584db86a9 Mon Sep 17 00:00:00 2001 From: Joe Weaver Date: Fri, 5 Feb 2021 13:56:56 -0500 Subject: [PATCH] started platformer demo, added features back to physics system --- .gitignore | 12 +- .../images/platformer_background.png | Bin 0 -> 4787 bytes dist/demo_assets/images/wolfie2d_text.png | Bin 0 -> 701 bytes dist/demo_assets/sounds/jump.wav | Bin 0 -> 22413 bytes .../spritesheets/platformer/player.json | 27 ++ .../spritesheets/platformer/player.png | Bin 0 -> 268 bytes .../tilemaps/platformer/platformer.json | 453 ++++++++++++++++++ .../tilemaps/platformer/platformer.png | Bin 0 -> 1696 bytes src/Platformer.ts | 49 ++ src/PlatformerPlayerController.ts | 59 +++ src/Wolfie2D/AI/ControllerAI.ts | 18 + src/Wolfie2D/AI/StateMachineAI.ts | 1 + src/Wolfie2D/DataTypes/Interfaces/AI.ts | 5 + .../DataTypes/Physics/AreaCollision.ts | 25 +- src/Wolfie2D/DataTypes/Shapes/AABB.ts | 70 +++ src/Wolfie2D/DataTypes/State/StateMachine.ts | 18 +- src/Wolfie2D/Debug/Debug.ts | 5 + src/Wolfie2D/Loop/Game.ts | 8 +- src/Wolfie2D/Loop/GameOptions.ts | 8 +- src/Wolfie2D/Nodes/CanvasNode.ts | 3 +- src/Wolfie2D/Nodes/GameNode.ts | 47 +- src/Wolfie2D/Physics/BasicPhysicsManager.ts | 49 +- .../Rendering/Animations/AnimationManager.ts | 12 + .../Scene/Factories/TilemapFactory.ts | 25 +- src/Wolfie2D/Scene/Scene.ts | 2 +- src/Wolfie2D/SceneGraph/Viewport.ts | 17 +- src/Wolfie2D/Utils/MathUtils.ts | 16 + src/Wolfie2D/_Demos/readme.md | 2 + src/default_scene.ts | 3 +- src/main.ts | 16 +- 30 files changed, 881 insertions(+), 69 deletions(-) create mode 100644 dist/demo_assets/images/platformer_background.png create mode 100644 dist/demo_assets/images/wolfie2d_text.png create mode 100644 dist/demo_assets/sounds/jump.wav create mode 100644 dist/demo_assets/spritesheets/platformer/player.json create mode 100644 dist/demo_assets/spritesheets/platformer/player.png create mode 100644 dist/demo_assets/tilemaps/platformer/platformer.json create mode 100644 dist/demo_assets/tilemaps/platformer/platformer.png create mode 100644 src/Platformer.ts create mode 100644 src/PlatformerPlayerController.ts create mode 100644 src/Wolfie2D/AI/ControllerAI.ts create mode 100644 src/Wolfie2D/_Demos/readme.md diff --git a/.gitignore b/.gitignore index d26c746..c1721ba 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,12 @@ +# Exclude node modules node_modules -dist/ \ No newline at end of file + +# Exclude the compiled project +dist/* + +# Include the demo_assets folder +!dist/demo_assets/ + +### IF YOU ARE MAKING A PROJECT, YOU MAY WANT TO UNCOMMENT THIS LINE ### +# !dist/assets/ + diff --git a/dist/demo_assets/images/platformer_background.png b/dist/demo_assets/images/platformer_background.png new file mode 100644 index 0000000000000000000000000000000000000000..2f221a95b2b01bfa991f5a7b4f24f894a19a3ec1 GIT binary patch literal 4787 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 KAn^Ymf&T@|j!QiN literal 0 HcmV?d00001 diff --git a/dist/demo_assets/spritesheets/platformer/player.json b/dist/demo_assets/spritesheets/platformer/player.json new file mode 100644 index 0000000..c9eab64 --- /dev/null +++ b/dist/demo_assets/spritesheets/platformer/player.json @@ -0,0 +1,27 @@ +{ + "name": "PlatformerPlayer", + "spriteSheetImage": "player.png", + "spriteWidth": 16, + "spriteHeight": 16, + "columns": 5, + "rows": 1, + "durationType": "time", + "animations": [ + { + "name": "IDLE", + "frames": [ {"index": 0, "duration": 1} ] + }, + { + "name": "WALK", + "frames": [ {"index": 0, "duration": 16}, {"index": 1, "duration": 16}, {"index": 2, "duration": 16}, {"index": 3, "duration": 16} ] + }, + { + "name": "JUMP", + "frames":[ {"index": 4, "duration": 32}] + }, + { + "name": "FALL", + "frames":[ {"index": 4, "duration": 32}] + } + ] +} \ No newline at end of file diff --git a/dist/demo_assets/spritesheets/platformer/player.png b/dist/demo_assets/spritesheets/platformer/player.png new file mode 100644 index 0000000000000000000000000000000000000000..4db71ac37e1a3ba3c701a7228ed56639e3ca091a GIT binary patch literal 268 zcmV+n0rUQeP)Px##z{m$R9J=Wm%$RlAP7W5r~m)U^inzmSB02NIPx*T1iAfRCt{2oy~FFJPd}Zoh`&aE!ekQl2VcezEZvhl2VdOZgvZ{(}{A(aEHq! z#2L_pp*hQr#}rlqP*AzP=vlb_U$Fx)fWV!oWenGHAkF00RI*M2jl@7 znLY-fg1{Il1xR7hXV%cWy@dR?=zAHkB9HiUGJj%aT0D2zh{WN-09^t}*%kSgJkThdrw50l<8)-f#B>9h~;Qt-3F) zL@EZ*E{a*!bzQkd36ez(mTLfe$h8&V=JoIrO5PaS0{I0y+~fhM_5W~xN>M^tN*?aV z;M*ZN3(%mnXvB(~iUC^NYhgwKD63-4XH&HF-vXEh9vmpZ!MRKWYfV+yMq(N`T3|R! z9^fVh*ir_LPt1KmB^5}P6octj8 z9jW9+O4}`T9`Hduz`-$75=Qw@?Z|vio}2&SOD$oBOTdy(zc+)UDc5v)08i+zQhyhy*Y)=D zxsvi6Bjf={?Tb6tOH(a;37TkI02AhCR3(_99zeYVqoe)*7W}{LX~j$;XjDFb`E>dG z>*w#Q?U)|&_aCnny}bpg;DZANI5>y_m>>q|#J8{inVK1V|M7qFi{9Jk$8s(vU4XU0 zcg|*?BrvG}o}7kj%V)r34B($OD+A!oaLNrJ2Ixra|1{>=HGOPpUjy<0KpfK+U`D9f zglF$y6EQ#m+!$c1eTtg_JCWNhVnzF3k1F@Q1lc6+8RKu=kXxg_1r> z-G@IVAcYRLg@gvB(?Sc z7U1AO0Xj0mECvd|q{R1IF!dBr04BMoyyxIcSQsdPaeBKED1cG+avM+pCVLDNmV*t$ zsTatxhjKvyI$=5T4}k&*vFDs$@>);;Ccl3E&OYs>OUChWy8ydc6mbOC)*u zl=T23)3PeK$CFM01rWqP@il4S)Uhdr=oMqY1u&s1cxwkNXE_dA6l$HC7@(w}lX}-M qbsZFh3<#w#T1&@>0Ti*2Ecy%M@${2|+`(V~0000): void { + this.owner = owner; + this.jumpSoundKey = options.jumpSoundKey; + this.emitter = new Emitter(); + } + + handleEvent(event: GameEvent): void { + // Do nothing for now + } + + update(deltaT: number): void { + // Get the direction from key presses + const x = (Input.isPressed("left") ? -1 : 0) + (Input.isPressed("right") ? 1 : 0); + + // Get last velocity and override x + const velocity = this.owner.getLastVelocity(); + velocity.x = x * 100 * deltaT; + + // Check for jump condition + if(this.owner.onGround && Input.isJustPressed("jump")){ + // We are jumping + velocity.y = -250*deltaT; + + // Loop our jump animation + this.owner.animation.play("JUMP", true); + + // Play the jump sound + this.emitter.fireEvent(GameEventType.PLAY_SOUND, {key: this.jumpSoundKey, loop: false}); + } else { + velocity.y += 10*deltaT; + } + + if(this.owner.onGround && !Input.isJustPressed("jump")){ + // If we're on the ground, but aren't jumping, show walk animation + if(velocity.x === 0){ + this.owner.animation.playIfNotAlready("IDLE", true); + } else { + this.owner.animation.playIfNotAlready("WALK", true); + } + } + + // If we're walking left, flip the sprite + this.owner.invertX = velocity.x < 0; + + this.owner.move(velocity); + } +} \ No newline at end of file diff --git a/src/Wolfie2D/AI/ControllerAI.ts b/src/Wolfie2D/AI/ControllerAI.ts new file mode 100644 index 0000000..8eb3c62 --- /dev/null +++ b/src/Wolfie2D/AI/ControllerAI.ts @@ -0,0 +1,18 @@ +import AI from "../DataTypes/Interfaces/AI"; +import GameEvent from "../Events/GameEvent"; +import GameNode from "../Nodes/GameNode"; + +/** + * A very basic AI class that just runs a function every update + */ +export default class ControllerAI implements AI { + protected owner: GameNode; + + initializeAI(owner: GameNode, options: Record): void { + this.owner = owner; + } + + handleEvent(event: GameEvent): void {} + + update(deltaT: number): void {} +} \ No newline at end of file diff --git a/src/Wolfie2D/AI/StateMachineAI.ts b/src/Wolfie2D/AI/StateMachineAI.ts index 7322544..1ee0ddf 100644 --- a/src/Wolfie2D/AI/StateMachineAI.ts +++ b/src/Wolfie2D/AI/StateMachineAI.ts @@ -1,5 +1,6 @@ import AI from "../DataTypes/Interfaces/AI"; import StateMachine from "../DataTypes/State/StateMachine"; +import GameEvent from "../Events/GameEvent"; import GameNode from "../Nodes/GameNode"; /** diff --git a/src/Wolfie2D/DataTypes/Interfaces/AI.ts b/src/Wolfie2D/DataTypes/Interfaces/AI.ts index 3e544fc..a7572e9 100644 --- a/src/Wolfie2D/DataTypes/Interfaces/AI.ts +++ b/src/Wolfie2D/DataTypes/Interfaces/AI.ts @@ -1,4 +1,6 @@ +import GameEvent from "../../Events/GameEvent"; import GameNode from "../../Nodes/GameNode"; +import Actor from "./Actor"; import Updateable from "./Updateable"; /** @@ -7,4 +9,7 @@ import Updateable from "./Updateable"; export default interface AI extends Updateable { /** Initializes the AI with the actor and any additional config */ initializeAI(owner: GameNode, options: Record): void; + + /** Handles events from the Actor */ + handleEvent(event: GameEvent): void; } \ No newline at end of file diff --git a/src/Wolfie2D/DataTypes/Physics/AreaCollision.ts b/src/Wolfie2D/DataTypes/Physics/AreaCollision.ts index 260f247..1d2b94e 100644 --- a/src/Wolfie2D/DataTypes/Physics/AreaCollision.ts +++ b/src/Wolfie2D/DataTypes/Physics/AreaCollision.ts @@ -1,4 +1,7 @@ +import Physical from "../Interfaces/Physical"; import AABB from "../Shapes/AABB"; +import Vec2 from "../Vec2"; +import Hit from "./Hit"; /** * A class that contains the area of overlap of two colliding objects to allow for sorting by the physics system. @@ -6,16 +9,32 @@ import AABB from "../Shapes/AABB"; export default class AreaCollision { /** The area of the overlap for the colliding objects */ area: number; + /** The AABB of the other collider in this collision */ collider: AABB; - + + /** Type of the collision */ + type: string; + + /** Ther other object in the collision */ + other: Physical; + + /** The tile, if this was a tilemap collision */ + tile: Vec2; + + /** The physics hit for this object */ + hit: Hit; + /** * Creates a new AreaCollision object * @param area The area of the collision * @param collider The other collider */ - constructor(area: number, collider: AABB){ + constructor(area: number, collider: AABB, other: Physical, type: string, tile: Vec2){ this.area = area; - this.collider = collider; + this.collider = collider; + this.other = other; + this.type = type; + this.tile = tile; } } \ No newline at end of file diff --git a/src/Wolfie2D/DataTypes/Shapes/AABB.ts b/src/Wolfie2D/DataTypes/Shapes/AABB.ts index a0a4cce..ad6fb42 100644 --- a/src/Wolfie2D/DataTypes/Shapes/AABB.ts +++ b/src/Wolfie2D/DataTypes/Shapes/AABB.ts @@ -146,7 +146,13 @@ export default class AABB extends Shape { // We hit on the left or right size hit.normal.x = -signX; hit.normal.y = 0; + } else if(Math.abs(tnearx - tneary) < 0.0001){ + // We hit on the corner + hit.normal.x = -signX; + hit.normal.y = -signY; + hit.normal.normalize(); } else { + // We hit on the top or bottom hit.normal.x = 0; hit.normal.y = -signY; } @@ -190,6 +196,70 @@ export default class AABB extends Shape { return true; } + /** + * Determines whether these AABBs are JUST touching - not overlapping. + * Vec2.x is -1 if the other is to the left, 1 if to the right. + * Likewise, Vec2.y is -1 if the other is on top, 1 if on bottom. + * @param other The other AABB to check + * @returns The collision sides stored in a Vec2 if the AABBs are touching, null otherwise + */ + touchesAABB(other: AABB): Vec2 { + let dx = other.x - this.x; + let px = this.hw + other.hw - Math.abs(dx); + + let dy = other.y - this.y; + let py = this.hh + other.hh - Math.abs(dy); + + // If one axis is just touching and the other is overlapping, true + if((px === 0 && py >= 0) || (py === 0 && px >= 0)){ + let ret = new Vec2(); + + if(px === 0){ + ret.x = other.x < this.x ? -1 : 1; + } + + if(py === 0){ + ret.y = other.y < this.y ? -1 : 1; + } + + return ret; + } else { + return null; + } + } + + /** + * Determines whether these AABBs are JUST touching - not overlapping. + * Also, if they are only touching corners, they are considered not touching. + * Vec2.x is -1 if the other is to the left, 1 if to the right. + * Likewise, Vec2.y is -1 if the other is on top, 1 if on bottom. + * @param other The other AABB to check + * @returns The side of the touch, stored as a Vec2, or null if there is no touch + */ + touchesAABBWithoutCorners(other: AABB): Vec2 { + let dx = other.x - this.x; + let px = this.hw + other.hw - Math.abs(dx); + + let dy = other.y - this.y; + let py = this.hh + other.hh - Math.abs(dy); + + // If one axis is touching, and the other is strictly overlapping + if((px === 0 && py > 0) || (py === 0 && px > 0)){ + let ret = new Vec2(); + + if(px === 0){ + ret.x = other.x < this.x ? -1 : 1; + } else { + ret.y = other.y < this.y ? -1 : 1; + } + + return ret; + + } else { + return null; + } + } + /** * Calculates the area of the overlap between this AABB and another * @param other The other AABB diff --git a/src/Wolfie2D/DataTypes/State/StateMachine.ts b/src/Wolfie2D/DataTypes/State/StateMachine.ts index bd6fd5d..3215c62 100644 --- a/src/Wolfie2D/DataTypes/State/StateMachine.ts +++ b/src/Wolfie2D/DataTypes/State/StateMachine.ts @@ -116,24 +116,14 @@ export default class StateMachine implements Updateable { * Handles input. This happens at the very beginning of this state machine's update cycle. * @param event The game event to process */ - handleInput(event: GameEvent): void { - this.currentState.handleInput(event); + handleEvent(event: GameEvent): void { + if(this.active){ + this.currentState.handleInput(event); + } } // @implemented update(deltaT: number): void { - // If the state machine isn't currently active, ignore all events and don't update - if(!this.active){ - this.receiver.ignoreEvents(); - return; - } - - // Handle input from all events - while(this.receiver.hasNextEvent()){ - let event = this.receiver.getNextEvent(); - this.handleInput(event); - } - // Delegate the update to the current state this.currentState.update(deltaT); } diff --git a/src/Wolfie2D/Debug/Debug.ts b/src/Wolfie2D/Debug/Debug.ts index a4cefd3..7cb2090 100644 --- a/src/Wolfie2D/Debug/Debug.ts +++ b/src/Wolfie2D/Debug/Debug.ts @@ -62,6 +62,9 @@ export default class Debug { * @param color The color of the box to draw */ static drawBox(center: Vec2, halfSize: Vec2, filled: boolean, color: Color): void { + let alpha = this.debugRenderingContext.globalAlpha; + this.debugRenderingContext.globalAlpha = color.a; + if(filled){ this.debugRenderingContext.fillStyle = color.toString(); this.debugRenderingContext.fillRect(center.x - halfSize.x, center.y - halfSize.y, halfSize.x*2, halfSize.y*2); @@ -71,6 +74,8 @@ export default class Debug { this.debugRenderingContext.strokeStyle = color.toString(); this.debugRenderingContext.strokeRect(center.x - halfSize.x, center.y - halfSize.y, halfSize.x*2, halfSize.y*2); } + + this.debugRenderingContext.globalAlpha = alpha; } /** diff --git a/src/Wolfie2D/Loop/Game.ts b/src/Wolfie2D/Loop/Game.ts index db3036e..b5bc04b 100644 --- a/src/Wolfie2D/Loop/Game.ts +++ b/src/Wolfie2D/Loop/Game.ts @@ -70,8 +70,8 @@ export default class Game { this.DEBUG_CANVAS = document.getElementById("debug-canvas"); // Give the canvas a size and get the rendering context - this.WIDTH = this.gameOptions.viewportSize.x; - this.HEIGHT = this.gameOptions.viewportSize.y; + this.WIDTH = this.gameOptions.canvasSize.x; + this.HEIGHT = this.gameOptions.canvasSize.y; // For now, just hard code a canvas renderer. We can do this with options later this.renderingManager = new CanvasRenderer(); @@ -89,8 +89,8 @@ export default class Game { } // Size the viewport to the game canvas - const viewportSize = new Vec2(this.WIDTH, this.HEIGHT); - this.viewport = new Viewport(viewportSize.scaled(0.5), viewportSize); + const canvasSize = new Vec2(this.WIDTH, this.HEIGHT); + this.viewport = new Viewport(canvasSize, this.gameOptions.zoomLevel); // Initialize all necessary game subsystems this.eventQueue = EventQueue.getInstance(); diff --git a/src/Wolfie2D/Loop/GameOptions.ts b/src/Wolfie2D/Loop/GameOptions.ts index f6bb9e8..50bd213 100644 --- a/src/Wolfie2D/Loop/GameOptions.ts +++ b/src/Wolfie2D/Loop/GameOptions.ts @@ -3,7 +3,10 @@ /** The options for initializing the @reference[GameLoop] */ export default class GameOptions { /** The size of the viewport */ - viewportSize: {x: number, y: number}; + canvasSize: {x: number, y: number}; + + /* The default level of zoom */ + zoomLevel: number; /** The color to clear the canvas to each frame */ clearColor: {r: number, g: number, b: number} @@ -25,7 +28,8 @@ export default class GameOptions { static parse(options: Record): GameOptions { let gOpt = new GameOptions(); - gOpt.viewportSize = options.viewportSize ? options.viewportSize : {x: 800, y: 600}; + gOpt.canvasSize = options.canvasSize ? options.canvasSize : {x: 800, y: 600}; + gOpt.zoomLevel = options.zoomLevel ? options.zoomLevel : 1; gOpt.clearColor = options.clearColor ? options.clearColor : {r: 255, g: 255, b: 255}; gOpt.inputs = options.inputs ? options.inputs : []; gOpt.showDebug = !!options.showDebug; diff --git a/src/Wolfie2D/Nodes/CanvasNode.ts b/src/Wolfie2D/Nodes/CanvasNode.ts index b059462..b4d4f30 100644 --- a/src/Wolfie2D/Nodes/CanvasNode.ts +++ b/src/Wolfie2D/Nodes/CanvasNode.ts @@ -101,8 +101,7 @@ export default abstract class CanvasNode extends GameNode implements Region { // @implemented debugRender(): void { + Debug.drawBox(this.relativePosition, this.sizeWithZoom, false, Color.BLUE); super.debugRender(); - let color = this.isColliding ? Color.RED : Color.GREEN; - Debug.drawBox(this.relativePosition, this.sizeWithZoom, false, color); } } \ No newline at end of file diff --git a/src/Wolfie2D/Nodes/GameNode.ts b/src/Wolfie2D/Nodes/GameNode.ts index 25cb554..51854b7 100644 --- a/src/Wolfie2D/Nodes/GameNode.ts +++ b/src/Wolfie2D/Nodes/GameNode.ts @@ -31,12 +31,12 @@ export default abstract class GameNode implements Positioned, Unique, Updateable private _id: number; /*---------- PHYSICAL ----------*/ - hasPhysics: boolean; - moving: boolean; - onGround: boolean; - onWall: boolean; - onCeiling: boolean; - active: boolean; + hasPhysics: boolean = false; + moving: boolean = false; + onGround: boolean = false; + onWall: boolean = false; + onCeiling: boolean = false; + active: boolean = false; collisionShape: Shape; colliderOffset: Vec2; isStatic: boolean; @@ -97,10 +97,19 @@ export default abstract class GameNode implements Positioned, Unique, Updateable } get relativePosition(): Vec2 { + return this.inRelativeCoordinates(this.position); + } + + /** + * Converts a point to coordinates relative to the zoom and origin of this node + * @param point The point to conver + * @returns A new Vec2 representing the point in relative coordinates + */ + inRelativeCoordinates(point: Vec2): Vec2 { let origin = this.scene.getViewTranslation(this); let zoom = this.scene.getViewScale(); - return this.position.clone().sub(origin).scale(zoom); + return point.clone().sub(origin).scale(zoom); } /*---------- UNIQUE ----------*/ @@ -132,7 +141,6 @@ export default abstract class GameNode implements Positioned, Unique, Updateable * @param velocity The velocity with which the object will move. */ finishMove(): void { - console.log("finish"); this.moving = false; this.position.add(this._velocity); if(this.pathfinding){ @@ -307,22 +315,35 @@ export default abstract class GameNode implements Positioned, Unique, Updateable * @param deltaT The timestep of the update. */ update(deltaT: number): void { + // Defer event handling to AI. + while(this.receiver.hasNextEvent()){ + this._ai.handleEvent(this.receiver.getNextEvent()); + } + + // Update our tweens this.tweens.update(deltaT); } // @implemented debugRender(): void { - let color = this.isColliding ? Color.RED : Color.GREEN; - Debug.drawPoint(this.relativePosition, color); + // Draw the position of this GameNode + Debug.drawPoint(this.relativePosition, Color.BLUE); // If velocity is not zero, draw a vector for it if(this._velocity && !this._velocity.isZero()){ - Debug.drawRay(this.relativePosition, this._velocity.clone().scaleTo(20).add(this.relativePosition), color); + Debug.drawRay(this.relativePosition, this._velocity.clone().scaleTo(20).add(this.relativePosition), Color.BLUE); } // If this has a collider, draw it - if(this.isCollidable && this.collisionShape){ - Debug.drawBox(this.collisionShape.center, this.collisionShape.halfSize, false, Color.RED); + if(this.hasPhysics && this.collisionShape){ + let color = this.isColliding ? Color.RED : Color.GREEN; + + if(this.isTrigger){ + color = Color.PURPLE; + } + + color.a = 0.2; + Debug.drawBox(this.inRelativeCoordinates(this.collisionShape.center), this.collisionShape.halfSize.scaled(this.scene.getViewScale()), true, color); } } } diff --git a/src/Wolfie2D/Physics/BasicPhysicsManager.ts b/src/Wolfie2D/Physics/BasicPhysicsManager.ts index 8aec56b..5144f2b 100644 --- a/src/Wolfie2D/Physics/BasicPhysicsManager.ts +++ b/src/Wolfie2D/Physics/BasicPhysicsManager.ts @@ -89,6 +89,7 @@ export default class BasicPhysicsManager extends PhysicsManager { node.onCeiling = false; node.onWall = false; node.collidedWithTilemap = false; + node.isColliding = false; // Update the swept shapes of each node if(node.moving){ @@ -110,7 +111,7 @@ export default class BasicPhysicsManager extends PhysicsManager { let area = node.sweptRect.overlapArea(collider); if(area > 0){ // We had a collision - overlaps.push(new AreaCollision(area, collider)); + overlaps.push(new AreaCollision(area, collider, other, "GameNode", null)); } } @@ -120,7 +121,7 @@ export default class BasicPhysicsManager extends PhysicsManager { let area = node.sweptRect.overlapArea(collider); if(area > 0){ // We had a collision - overlaps.push(new AreaCollision(area, collider)); + overlaps.push(new AreaCollision(area, collider, other, "GameNode", null)); } } @@ -135,21 +136,27 @@ export default class BasicPhysicsManager extends PhysicsManager { // Sort the overlaps by area overlaps = overlaps.sort((a, b) => b.area - a.area); + // Keep track of hits to use later + let hits = []; /*---------- RESOLUTION PHASE ----------*/ // For every overlap, determine if we need to collide with it and when - for(let other of overlaps){ + for(let overlap of overlaps){ // Do a swept line test on the static AABB with this AABB size as padding (this is basically using a minkowski sum!) // Start the sweep at the position of this node with a delta of _velocity const point = node.collisionShape.center; const delta = node._velocity; const padding = node.collisionShape.halfSize; - const otherAABB = other.collider; + const otherAABB = overlap.collider; const hit = otherAABB.intersectSegment(node.collisionShape.center, node._velocity, node.collisionShape.halfSize); + overlap.hit = hit; + if(hit !== null){ + hits.push(hit); + // We got a hit, resolve with the time inside of the hit let tnearx = hit.nearTimes.x; let tneary = hit.nearTimes.y; @@ -164,11 +171,39 @@ export default class BasicPhysicsManager extends PhysicsManager { if(hit.nearTimes.x >= 0 && hit.nearTimes.x < 1){ - node._velocity.x = node._velocity.x * tnearx; + // Any tilemap objects that made it here are collidable + if(overlap.type === "Tilemap" || overlap.other.isCollidable){ + node._velocity.x = node._velocity.x * tnearx; + node.isColliding = true; + } } if(hit.nearTimes.y >= 0 && hit.nearTimes.y < 1){ - node._velocity.y = node._velocity.y * tneary; + // Any tilemap objects that made it here are collidable + if(overlap.type === "Tilemap" || overlap.other.isCollidable){ + node._velocity.y = node._velocity.y * tneary; + node.isColliding = true; + } + } + } + } + + // Check if we ended up on the ground, ceiling or wall + for(let overlap of overlaps){ + let collisionSide = overlap.collider.touchesAABBWithoutCorners(node.collisionShape.getBoundingRect()); + if(collisionSide !== null){ + // If we touch, not including corner cases, check the collision normal + if(overlap.hit !== null){ + if(collisionSide.y === -1){ + // Node is on top of overlap, so onGround + node.onGround = true; + } else if(collisionSide.y === 1){ + // Node is on bottom of overlap, so onCeiling + node.onCeiling = true; + } else { + // Node wasn't touching on y, so it is touching on x + node.onWall = true; + } } } } @@ -209,7 +244,7 @@ export default class BasicPhysicsManager extends PhysicsManager { let area = node.sweptRect.overlapArea(collider); if(area > 0){ // We had a collision - overlaps.push(new AreaCollision(area, collider)); + overlaps.push(new AreaCollision(area, collider, tilemap, "Tilemap", new Vec2(col, row))); } } } diff --git a/src/Wolfie2D/Rendering/Animations/AnimationManager.ts b/src/Wolfie2D/Rendering/Animations/AnimationManager.ts index e4cc0c5..05dfc6b 100644 --- a/src/Wolfie2D/Rendering/Animations/AnimationManager.ts +++ b/src/Wolfie2D/Rendering/Animations/AnimationManager.ts @@ -141,6 +141,18 @@ export default class AnimationManager { } } + /** + * Plays the specified animation. Does not restart it if it is already playing + * @param animation The name of the animation to play + * @param loop Whether or not to loop the animation. False by default + * @param onEnd The name of an event to send when this animation naturally stops playing. This only matters if loop is false. + */ + playIfNotAlready(animation: string, loop: boolean = false, onEnd?: string): void { + if(this.currentAnimation !== animation){ + this.play(animation, loop, onEnd); + } + } + /** * Plays the specified animation * @param animation The name of the animation to play diff --git a/src/Wolfie2D/Scene/Factories/TilemapFactory.ts b/src/Wolfie2D/Scene/Factories/TilemapFactory.ts index eafea11..6eb1d7b 100644 --- a/src/Wolfie2D/Scene/Factories/TilemapFactory.ts +++ b/src/Wolfie2D/Scene/Factories/TilemapFactory.ts @@ -77,20 +77,23 @@ export default class TilemapFactory { let sceneLayer; let isParallaxLayer = false; + let depth = 0; if(layer.properties){ for(let prop of layer.properties){ if(prop.name === "Parallax"){ isParallaxLayer = prop.value; + } else if(prop.name === "Depth") { + depth = prop.value; } } } if(isParallaxLayer){ console.log("Adding parallax layer: " + layer.name) - sceneLayer = this.scene.addParallaxLayer(layer.name, new Vec2(1, 1)); + sceneLayer = this.scene.addParallaxLayer(layer.name, new Vec2(1, 1), depth); } else { - sceneLayer = this.scene.addLayer(layer.name); + sceneLayer = this.scene.addLayer(layer.name, depth); } if(layer.type === "tilelayer"){ @@ -144,19 +147,19 @@ export default class TilemapFactory { // 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 isCollidable = false; let hasPhysics = false; - let isStatic = true; + let isCollidable = false; + let isTrigger = false; let group = ""; if(obj.properties){ for(let prop of obj.properties){ - if(prop.name === "Collidable"){ - isCollidable = prop.value; - } else if(prop.name === "Static"){ - isStatic = prop.value; - } else if(prop.name === "hasPhysics"){ + if(prop.name === "HasPhysics"){ hasPhysics = prop.value; + } else if(prop.name === "Collidable"){ + isCollidable = prop.value; + } else if(prop.name === "IsTrigger"){ + isTrigger = prop.value; } else if(prop.name === "Group"){ group = prop.value; } @@ -194,8 +197,10 @@ export default class TilemapFactory { // Now we have sprite. Associate it with our physics object if there is one if(hasPhysics){ - sprite.addPhysics(sprite.boundary.clone(), Vec2.ZERO, isCollidable, isStatic); + // Make the sprite a static physics object + sprite.addPhysics(sprite.boundary.clone(), Vec2.ZERO, isCollidable, true); sprite.group = group; + sprite.isTrigger = isTrigger; } } } diff --git a/src/Wolfie2D/Scene/Scene.ts b/src/Wolfie2D/Scene/Scene.ts index 9669840..8dcbcde 100644 --- a/src/Wolfie2D/Scene/Scene.ts +++ b/src/Wolfie2D/Scene/Scene.ts @@ -180,7 +180,7 @@ export default class Scene implements Updateable { this.renderingManager.render(visibleSet, this.tilemaps, this.uiLayers); let nodes = this.sceneGraph.getAllNodes(); - this.tilemaps.forEach(tilemap => tilemap.visible && tilemap.debugRender()); + this.tilemaps.forEach(tilemap => tilemap.visible ? nodes.push(tilemap) : 0); Debug.setNodes(nodes); } diff --git a/src/Wolfie2D/SceneGraph/Viewport.ts b/src/Wolfie2D/SceneGraph/Viewport.ts index c09e679..320d469 100644 --- a/src/Wolfie2D/SceneGraph/Viewport.ts +++ b/src/Wolfie2D/SceneGraph/Viewport.ts @@ -37,7 +37,7 @@ export default class Viewport { /** The size of the canvas */ private canvasSize: Vec2; - constructor(initialPosition: Vec2, canvasSize: Vec2){ + constructor(canvasSize: Vec2, zoomLevel: number){ this.view = new AABB(Vec2.ZERO, Vec2.ZERO); this.boundary = new AABB(Vec2.ZERO, Vec2.ZERO); this.lastPositions = new Queue(); @@ -46,13 +46,20 @@ export default class Viewport { this.canvasSize = Vec2.ZERO; this.focus = Vec2.ZERO; - // Set the center (and make the viewport stay there) - this.setCenter(initialPosition); - this.setFocus(initialPosition); + // Set the size of the canvas + this.setCanvasSize(canvasSize); + + console.log(canvasSize, zoomLevel); // Set the size of the viewport this.setSize(canvasSize); - this.setCanvasSize(canvasSize); + this.setZoomLevel(zoomLevel); + + console.log(this.getHalfSize().toString()); + + // Set the center (and make the viewport stay there) + this.setCenter(this.view.halfSize.clone()); + this.setFocus(this.view.halfSize.clone()); } /** Enables the viewport to zoom in and out */ diff --git a/src/Wolfie2D/Utils/MathUtils.ts b/src/Wolfie2D/Utils/MathUtils.ts index d763cc1..a910b18 100644 --- a/src/Wolfie2D/Utils/MathUtils.ts +++ b/src/Wolfie2D/Utils/MathUtils.ts @@ -11,6 +11,22 @@ export default class MathUtils { return x < 0 ? -1 : 1; } + /** + * Returns whether or not x is between a and b + * @param a The min bound + * @param b The max bound + * @param x The value to check + * @param exclusive Whether or not a and b are exclusive bounds + * @returns True if x is between a and b, false otherwise + */ + static between(a: number, b: number, x: number, exclusive?: boolean): boolean { + if(exclusive){ + return (a < x) && (x < b); + } else { + return (a <= x) && (x <= b); + } + } + /** * Clamps the value x to the range [min, max], rounding up or down if needed * @param x The value to be clamped diff --git a/src/Wolfie2D/_Demos/readme.md b/src/Wolfie2D/_Demos/readme.md new file mode 100644 index 0000000..6086b2f --- /dev/null +++ b/src/Wolfie2D/_Demos/readme.md @@ -0,0 +1,2 @@ +# Demos +This folder contains the demo projects created in the guides section of the Wolfie2D documentation, as well as any extra demos created for Wolfie2D. \ No newline at end of file diff --git a/src/default_scene.ts b/src/default_scene.ts index 51303a8..8166edb 100644 --- a/src/default_scene.ts +++ b/src/default_scene.ts @@ -1,7 +1,6 @@ /* #################### IMPORTS #################### */ // Import from Wolfie2D or your own files here import Vec2 from "./Wolfie2D/DataTypes/Vec2"; -import Debug from "./Wolfie2D/Debug/Debug"; import Input from "./Wolfie2D/Input/Input"; import Graphic from "./Wolfie2D/Nodes/Graphic"; import { GraphicType } from "./Wolfie2D/Nodes/Graphics/GraphicTypes"; @@ -32,7 +31,7 @@ export default class default_scene extends Scene { // The first argument is the key of the sprite (you get to decide what it is). // The second argument is the path to the actual image. // Paths start in the "dist/" folder, so start building your path from there - this.load.image("logo", "assets/wolfie2d_text.png"); + this.load.image("logo", "demo_assets/wolfie2d_text.png"); } // startScene() is where you should build any game objects you wish to have in your scene, diff --git a/src/main.ts b/src/main.ts index c863d82..d5c2cc3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,13 +1,19 @@ import Game from "./Wolfie2D/Loop/Game"; -import default_scene from "./default_scene"; +import Platformer from "./Platformer"; // The main function is your entrypoint into Wolfie2D. Specify your first scene and any options here. (function main(){ // These are options for initializing the game - // Here, we'll simply set the size of the viewport, and make the background of the game black + // Here, we'll set the size of the viewport, color the background, and set up key bindings. let options = { - viewportSize: {x: 800, y: 600}, - clearColor: {r: 0, g: 0, b: 0}, + canvasSize: {x: 800, y: 600}, + zoomLevel: 4, + clearColor: {r: 34, g: 32, b: 52}, + inputs: [ + { name: "left", keys: ["a"] }, + { name: "right", keys: ["d"] }, + { name: "jump", keys: ["space", "w"]} + ] } // Create our game. This will create all of the systems. @@ -20,5 +26,5 @@ import default_scene from "./default_scene"; let sceneOptions = {}; // Add our first scene. This will load this scene into the game world. - demoGame.getSceneManager().addScene(default_scene, sceneOptions); + demoGame.getSceneManager().addScene(Platformer, sceneOptions); })(); \ No newline at end of file