diff options
author | Elizabeth Hunt <elizabeth.hunt@simponic.xyz> | 2023-08-12 13:49:16 -0600 |
---|---|---|
committer | Elizabeth Hunt <elizabeth.hunt@simponic.xyz> | 2023-08-12 13:49:16 -0600 |
commit | c6e9baa0009f7cce0f6ff156a3957ef04a8cb684 (patch) | |
tree | 9766a4a33ad1c86aa71a5f92daf8917f3e5f5eed /engine/systems | |
parent | b67ffb57c1bf6e9628339a3f43c71ccebdb46136 (diff) | |
download | jumpstorm-c6e9baa0009f7cce0f6ff156a3957ef04a8cb684.tar.gz jumpstorm-c6e9baa0009f7cce0f6ff156a3957ef04a8cb684.zip |
the great engine refactor
Diffstat (limited to 'engine/systems')
-rw-r--r-- | engine/systems/Collision.ts | 130 | ||||
-rw-r--r-- | engine/systems/FacingDirection.ts | 25 | ||||
-rw-r--r-- | engine/systems/Input.ts | 82 | ||||
-rw-r--r-- | engine/systems/Physics.ts | 34 | ||||
-rw-r--r-- | engine/systems/Render.ts | 57 | ||||
-rw-r--r-- | engine/systems/System.ts | 1 | ||||
-rw-r--r-- | engine/systems/WallBounds.ts | 27 |
7 files changed, 178 insertions, 178 deletions
diff --git a/engine/systems/Collision.ts b/engine/systems/Collision.ts index 846a95a..2bba03b 100644 --- a/engine/systems/Collision.ts +++ b/engine/systems/Collision.ts @@ -5,7 +5,7 @@ import { ComponentNames, Jump, Velocity, - Moment, + Forces, } from "../components"; import { Game } from "../Game"; import { PhysicsConstants } from "../config"; @@ -30,60 +30,64 @@ export class Collision extends System { { x: 0, y: 0 }, screenDimensions, Collision.QUADTREE_MAX_LEVELS, - Collision.QUADTREE_SPLIT_THRESHOLD + Collision.QUADTREE_SPLIT_THRESHOLD, ); } - public update(dt: number, game: Game) { + public update(_dt: number, game: Game) { // rebuild the quadtree this.quadTree.clear(); const entitiesToAddToQuadtree: Entity[] = []; Collision.COLLIDABLE_COMPONENT_NAMES.map((componentName) => - game.componentEntities.get(componentName) - ).forEach((entityIds?: Set<number>) => - entityIds?.forEach((id) => { - const entity = game.entities.get(id); - if (!entity.hasComponent(ComponentNames.BoundingBox)) { - return; - } - entitiesToAddToQuadtree.push(entity); - }) + game.componentEntities.get(componentName), + ).forEach( + (entityIds?: Set<number>) => + entityIds?.forEach((id) => { + const entity = game.entities.get(id); + if (!entity || !entity.hasComponent(ComponentNames.BoundingBox)) { + return; + } + entitiesToAddToQuadtree.push(entity); + }), ); entitiesToAddToQuadtree.forEach((entity) => { const boundingBox = entity.getComponent<BoundingBox>( - ComponentNames.BoundingBox + ComponentNames.BoundingBox, ); - this.quadTree.insert( - entity.id, - boundingBox.dimension, - boundingBox.center - ); + let dimension = { ...boundingBox.dimension }; + if (boundingBox.rotation != 0) { + dimension = boundingBox.getOutscribedBoxDims(); + } + + this.quadTree.insert(entity.id, dimension, boundingBox.center); }); // find colliding entities and perform collisions const collidingEntities = this.getCollidingEntities( entitiesToAddToQuadtree, - game.entities + game, ); collidingEntities.forEach(([entityAId, entityBId]) => { const [entityA, entityB] = [entityAId, entityBId].map((id) => - game.entities.get(id) + game.entities.get(id), ); - this.performCollision(entityA, entityB); + if (entityA && entityB) { + this.performCollision(entityA, entityB); + } }); } private performCollision(entityA: Entity, entityB: Entity) { const [entityABoundingBox, entityBBoundingBox] = [entityA, entityB].map( - (entity) => entity.getComponent<BoundingBox>(ComponentNames.BoundingBox) + (entity) => entity.getComponent<BoundingBox>(ComponentNames.BoundingBox), ); - let velocity: Velocity; + let velocity = new Velocity(); if (entityA.hasComponent(ComponentNames.Velocity)) { velocity = entityA.getComponent<Velocity>(ComponentNames.Velocity); } @@ -92,17 +96,16 @@ export class Collision extends System { entityA.hasComponent(ComponentNames.Collide) && entityB.hasComponent(ComponentNames.TopCollidable) && entityABoundingBox.center.y <= entityBBoundingBox.center.y && - velocity && velocity.dCartesian.dy >= 0 // don't apply "floor" logic when coming through the bottom ) { if (entityBBoundingBox.rotation != 0) { throw new Error( - `entity with id ${entityB.id} has TopCollidable component and a non-zero rotation. that is not (yet) supported.` + `entity with id ${entityB.id} has TopCollidable component and a non-zero rotation. that is not (yet) supported.`, ); } // remove previous velocity in the y axis - velocity.dCartesian.dy = 0; + if (velocity) velocity.dCartesian.dy = 0; // apply normal force if (entityA.hasComponent(ComponentNames.Gravity)) { @@ -110,7 +113,8 @@ export class Collision extends System { const F_n = -mass * PhysicsConstants.GRAVITY; entityA.getComponent<Forces>(ComponentNames.Forces).forces.push({ - fCartesian: { fy: F_n }, + fCartesian: { fy: F_n, fx: 0 }, + torque: 0, }); } @@ -128,31 +132,35 @@ export class Collision extends System { private getCollidingEntities( collidableEntities: Entity[], - entityMap: Map<number, Entity> + game: Game, ): [number, number][] { - const collidingEntityIds: [number, number] = []; + const collidingEntityIds: [number, number][] = []; for (const entity of collidableEntities) { const boundingBox = entity.getComponent<BoundingBox>( - ComponentNames.BoundingBox + ComponentNames.BoundingBox, ); - this.quadTree + const neighborIds = this.quadTree .getNeighborIds({ id: entity.id, dimension: boundingBox.dimension, center: boundingBox.center, }) - .filter((neighborId) => neighborId != entity.id) - .forEach((neighborId) => { - const neighborBoundingBox = entityMap - .get(neighborId) - .getComponent<BoundingBox>(ComponentNames.BoundingBox); - - if (boundingBox.isCollidingWith(neighborBoundingBox)) { - collidingEntityIds.push([entity.id, neighborId]); - } - }); + .filter((neighborId) => neighborId != entity.id); + + neighborIds.forEach((neighborId) => { + const neighbor = game.getEntity(neighborId); + if (!neighbor) return; + + const neighborBoundingBox = neighbor.getComponent<BoundingBox>( + ComponentNames.BoundingBox, + ); + + if (boundingBox.isCollidingWith(neighborBoundingBox)) { + collidingEntityIds.push([entity.id, neighborId]); + } + }); } return collidingEntityIds; @@ -161,55 +169,45 @@ export class Collision extends System { // ramblings: https://excalidraw.com/#json=z-xD86Za4a3duZuV2Oky0,KaGe-5iHJu1Si8inEo4GLQ private getDyToPushOutOfFloor( entityBoundingBox: BoundingBox, - floorBoundingBox: BoundingBox + floorBoundingBox: BoundingBox, ): number { const { - rotation, - center: { x, y }, dimension: { width, height }, + center: { x }, } = entityBoundingBox; - let rads = rotation * (Math.PI / 180); - if (rads >= Math.PI) { - rads -= Math.PI; // we have symmetry so we can skip two cases - } + const outScribedRectangle = entityBoundingBox.getOutscribedBoxDims(); - let boundedCollisionX = 0; // bounded x on the surface from width - let clippedX = 0; // x coordinate of the vertex below the surface - let outScribedRectangleHeight, dy, dx; - - if (rads <= Math.PI / 2) { - dx = (width * Math.cos(rads) - height * Math.sin(rads)) / 2; - outScribedRectangleHeight = - width * Math.sin(rads) + height * Math.cos(rads); - } else if (rads <= Math.PI) { + let rads = entityBoundingBox.getRotationInPiOfUnitCircle(); + let dx = (width * Math.cos(rads) - height * Math.sin(rads)) / 2; + + if (rads >= Math.PI / 2) { rads -= Math.PI / 2; dx = (height * Math.cos(rads) - width * Math.sin(rads)) / 2; - outScribedRectangleHeight = - width * Math.cos(rads) + height * Math.sin(rads); } + const clippedX = x + dx; // x coordinate of the vertex below the surface (if existant) + let boundedCollisionX = 0; // bounded x on the surface from width + if (x >= floorBoundingBox.center.x) { - clippedX = x + dx; boundedCollisionX = Math.min( floorBoundingBox.center.x + floorBoundingBox.dimension.width / 2, - clippedX + clippedX, ); return ( - outScribedRectangleHeight / 2 - + outScribedRectangle.height / 2 - Math.max((clippedX - boundedCollisionX) * Math.tan(rads), 0) ); } - clippedX = x - dx; boundedCollisionX = Math.max( floorBoundingBox.center.x - floorBoundingBox.dimension.width / 2, - clippedX + clippedX, ); return ( - outScribedRectangleHeight / 2 - - Math.max((boundedCollisionX - clippedX) * Math.tan(rads), 0) + outScribedRectangle.height / 2 - + Math.max((boundedCollisionX - clippedX) * Math.tan(Math.PI / 2 - rads), 0) ); } } diff --git a/engine/systems/FacingDirection.ts b/engine/systems/FacingDirection.ts index c6513ac..4426ab6 100644 --- a/engine/systems/FacingDirection.ts +++ b/engine/systems/FacingDirection.ts @@ -2,9 +2,9 @@ import { ComponentNames, Velocity, FacingDirection as FacingDirectionComponent, + Control, } from "../components"; import { Game } from "../Game"; -import type { Entity } from "../entities"; import { System, SystemNames } from "./"; export class FacingDirection extends System { @@ -13,24 +13,31 @@ export class FacingDirection extends System { } public update(_dt: number, game: Game) { - game.componentEntities - .get(ComponentNames.FacingDirection) - ?.forEach((entityId) => { - const entity = game.entities.get(entityId); + game.forEachEntityWithComponent( + ComponentNames.FacingDirection, + (entity) => { if (!entity.hasComponent(ComponentNames.Velocity)) { return; } + const totalVelocity: Velocity = new Velocity(); + const control = entity.getComponent<Control>(ComponentNames.Control); const velocity = entity.getComponent<Velocity>(ComponentNames.Velocity); + totalVelocity.add(velocity); + if (control) { + totalVelocity.add(control.controlVelocity); + } + const facingDirection = entity.getComponent<FacingDirectionComponent>( - ComponentNames.FacingDirection + ComponentNames.FacingDirection, ); - if (velocity.dCartesian.dx > 0) { + if (totalVelocity.dCartesian.dx > 0) { entity.addComponent(facingDirection.facingRightSprite); - } else if (velocity.dCartesian.dx < 0) { + } else if (totalVelocity.dCartesian.dx < 0) { entity.addComponent(facingDirection.facingLeftSprite); } - }); + }, + ); } } diff --git a/engine/systems/Input.ts b/engine/systems/Input.ts index 4272ec8..4aa9844 100644 --- a/engine/systems/Input.ts +++ b/engine/systems/Input.ts @@ -1,21 +1,16 @@ import { Jump, Forces, - Acceleration, ComponentNames, Velocity, Mass, + Control, } from "../components"; import { Game } from "../Game"; import { KeyConstants, PhysicsConstants } from "../config"; -import type { Entity } from "../entities"; import { Action } from "../interfaces"; import { System, SystemNames } from "./"; -/** - * TODO: Make velocities reset on each game loop (as similar to acceleration) - * - Then, we can add / remove velocity on update instead of just setting it and praying it's not modified externally - */ export class Input extends System { private keys: Set<string>; private actionTimeStamps: Map<Action, number>; @@ -23,7 +18,7 @@ export class Input extends System { constructor() { super(SystemNames.Input); - this.keys = new Set<number>(); + this.keys = new Set<string>(); this.actionTimeStamps = new Map<Action, number>(); } @@ -35,51 +30,52 @@ export class Input extends System { this.keys.delete(key); } - private hasSomeKey(keys: string[]): boolean { - return keys.some((key) => this.keys.has(key)); + private hasSomeKey(keys?: string[]): boolean { + if (keys) { + return keys.some((key) => this.keys.has(key)); + } + return false; } - public update(dt: number, game: Game) { - game.componentEntities.get(ComponentNames.Control)?.forEach((entityId) => { - const entity = game.entities.get(entityId); - if (!entity.hasComponent(ComponentNames.Velocity)) { - return; - } - - const velocity = entity.getComponent<Velocity>(ComponentNames.Velocity); + public update(_dt: number, game: Game) { + game.forEachEntityWithComponent(ComponentNames.Control, (entity) => { + const control = entity.getComponent<Control>(ComponentNames.Control); if (this.hasSomeKey(KeyConstants.ActionKeys.get(Action.MOVE_RIGHT))) { - velocity.dCartesian.dx = PhysicsConstants.PLAYER_MOVE_VEL; - } else if ( - this.hasSomeKey(KeyConstants.ActionKeys.get(Action.MOVE_LEFT)) - ) { - velocity.dCartesian.dx = -PhysicsConstants.PLAYER_MOVE_VEL; - } else { - velocity.dCartesian.dx = 0; + control.controlVelocity.dCartesian.dx += + PhysicsConstants.PLAYER_MOVE_VEL; } - }); - game.componentEntities.get(ComponentNames.Jump)?.forEach((entityId) => { - const entity = game.entities.get(entityId); - const jump = entity.getComponent<Jump>(ComponentNames.Jump); - const velocity = entity.getComponent<Velocity>(ComponentNames.Velocity); + if (this.hasSomeKey(KeyConstants.ActionKeys.get(Action.MOVE_LEFT))) { + control.controlVelocity.dCartesian.dx += + -PhysicsConstants.PLAYER_MOVE_VEL; + } - if (this.hasSomeKey(KeyConstants.ActionKeys.get(Action.JUMP))) { - if (jump.canJump) { - this.actionTimeStamps.set(Action.JUMP, performance.now()); + if (entity.hasComponent(ComponentNames.Jump)) { + const velocity = entity.getComponent<Velocity>(ComponentNames.Velocity); + const jump = entity.getComponent<Jump>(ComponentNames.Jump); - velocity.dCartesian.dy = PhysicsConstants.PLAYER_JUMP_INITIAL_VEL; - jump.canJump = false; - } + if (this.hasSomeKey(KeyConstants.ActionKeys.get(Action.JUMP))) { + if (jump.canJump) { + this.actionTimeStamps.set(Action.JUMP, performance.now()); + + velocity.dCartesian.dy += PhysicsConstants.PLAYER_JUMP_INITIAL_VEL; + jump.canJump = false; + } - if ( - performance.now() - this.actionTimeStamps.get(Action.JUMP) < - PhysicsConstants.MAX_JUMP_TIME_MS - ) { - const mass = entity.getComponent<Mass>(ComponentNames.Mass).mass; - entity.getComponent<Forces>(ComponentNames.Forces)?.forces.push({ - fCartesian: { fy: mass * PhysicsConstants.PLAYER_JUMP_ACC }, - }); + if ( + performance.now() - (this.actionTimeStamps.get(Action.JUMP) || 0) < + PhysicsConstants.MAX_JUMP_TIME_MS + ) { + const mass = entity.getComponent<Mass>(ComponentNames.Mass).mass; + entity.getComponent<Forces>(ComponentNames.Forces)?.forces.push({ + fCartesian: { + fy: mass * PhysicsConstants.PLAYER_JUMP_ACC, + fx: 0, + }, + torque: 0, + }); + } } } }); diff --git a/engine/systems/Physics.ts b/engine/systems/Physics.ts index 7edeb41..38962a6 100644 --- a/engine/systems/Physics.ts +++ b/engine/systems/Physics.ts @@ -1,6 +1,5 @@ import { System, SystemNames } from "."; import { - Acceleration, BoundingBox, ComponentNames, Forces, @@ -8,9 +7,10 @@ import { Velocity, Mass, Jump, + Moment, + Control, } from "../components"; import { PhysicsConstants } from "../config"; -import type { Entity } from "../entities"; import type { Force2D } from "../interfaces"; import { Game } from "../Game"; @@ -20,14 +20,12 @@ export class Physics extends System { } public update(dt: number, game: Game): void { - game.componentEntities.get(ComponentNames.Forces)?.forEach((entityId) => { - const entity = game.entities.get(entityId); - + game.forEachEntityWithComponent(ComponentNames.Forces, (entity) => { const mass = entity.getComponent<Mass>(ComponentNames.Mass).mass; const forces = entity.getComponent<Forces>(ComponentNames.Forces).forces; const velocity = entity.getComponent<Velocity>(ComponentNames.Velocity); const inertia = entity.getComponent<Moment>( - ComponentNames.Moment + ComponentNames.Moment, ).inertia; // F_g = mg, applied only until terminal velocity is reached @@ -37,7 +35,9 @@ export class Physics extends System { forces.push({ fCartesian: { fy: mass * PhysicsConstants.GRAVITY, + fx: 0, }, + torque: 0, }); } } @@ -51,7 +51,7 @@ export class Physics extends System { }, torque: accum.torque + (torque ?? 0), }), - { fCartesian: { fx: 0, fy: 0 }, torque: 0 } + { fCartesian: { fx: 0, fy: 0 }, torque: 0 }, ); // integrate accelerations @@ -62,6 +62,7 @@ export class Physics extends System { velocity.dCartesian.dx += ddx * dt; velocity.dCartesian.dy += ddy * dt; velocity.dTheta += (sumOfForces.torque * dt) / inertia; + // clear the forces entity.getComponent<Forces>(ComponentNames.Forces).forces = []; @@ -71,11 +72,17 @@ export class Physics extends System { } }); - game.componentEntities.get(ComponentNames.Velocity)?.forEach((entityId) => { - const entity = game.entities.get(entityId); - const velocity = entity.getComponent<Velocity>(ComponentNames.Velocity); + game.forEachEntityWithComponent(ComponentNames.Velocity, (entity) => { + const velocity: Velocity = new Velocity(); + const control = entity.getComponent<Control>(ComponentNames.Control); + + velocity.add(entity.getComponent<Velocity>(ComponentNames.Velocity)); + if (control) { + velocity.add(control.controlVelocity); + } + const boundingBox = entity.getComponent<BoundingBox>( - ComponentNames.BoundingBox + ComponentNames.BoundingBox, ); // integrate velocity @@ -86,6 +93,11 @@ export class Physics extends System { (boundingBox.rotation < 0 ? 360 + boundingBox.rotation : boundingBox.rotation) % 360; + + // clear the control velocity + if (control) { + control.controlVelocity = new Velocity(); + } }); } } diff --git a/engine/systems/Render.ts b/engine/systems/Render.ts index b5479e1..9bb4091 100644 --- a/engine/systems/Render.ts +++ b/engine/systems/Render.ts @@ -1,8 +1,6 @@ import { System, SystemNames } from "."; import { BoundingBox, ComponentNames, Sprite } from "../components"; -import type { Entity } from "../entities"; import { Game } from "../Game"; -import type { DrawArgs } from "../interfaces"; import { clamp } from "../utils"; export class Render extends System { @@ -16,39 +14,36 @@ export class Render extends System { public update(dt: number, game: Game) { this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); - game.componentEntities.get(ComponentNames.Sprite)?.forEach((entityId) => { - const entity = game.entities.get(entityId); + game.forEachEntityWithComponent(ComponentNames.Sprite, (entity) => { const sprite = entity.getComponent<Sprite>(ComponentNames.Sprite); sprite.update(dt); - let drawArgs: DrawArgs; - if (entity.hasComponent(ComponentNames.BoundingBox)) { - const boundingBox = entity.getComponent<BoundingBox>( - ComponentNames.BoundingBox - ); - - // don't render if we're outside the screen - if ( - clamp( - boundingBox.center.y, - -boundingBox.dimension.height / 2, - this.ctx.canvas.height + boundingBox.dimension.height / 2 - ) != boundingBox.center.y || - clamp( - boundingBox.center.x, - -boundingBox.dimension.width / 2, - this.ctx.canvas.width + boundingBox.dimension.width / 2 - ) != boundingBox.center.x - ) { - return; - } - - drawArgs = { - center: boundingBox.center, - dimension: boundingBox.dimension, - rotation: boundingBox.rotation, - }; + const boundingBox = entity.getComponent<BoundingBox>( + ComponentNames.BoundingBox, + ); + + // don't render if we're outside the screen + if ( + clamp( + boundingBox.center.y, + -boundingBox.dimension.height / 2, + this.ctx.canvas.height + boundingBox.dimension.height / 2, + ) != boundingBox.center.y || + clamp( + boundingBox.center.x, + -boundingBox.dimension.width / 2, + this.ctx.canvas.width + boundingBox.dimension.width / 2, + ) != boundingBox.center.x + ) { + return; } + + const drawArgs = { + center: boundingBox.center, + dimension: boundingBox.dimension, + rotation: boundingBox.rotation, + }; + sprite.draw(this.ctx, drawArgs); }); } diff --git a/engine/systems/System.ts b/engine/systems/System.ts index 1aad285..8b00dc5 100644 --- a/engine/systems/System.ts +++ b/engine/systems/System.ts @@ -1,4 +1,3 @@ -import { Entity } from "../entities"; import { Game } from "../Game"; export abstract class System { diff --git a/engine/systems/WallBounds.ts b/engine/systems/WallBounds.ts index 6ea2267..a0d4a9c 100644 --- a/engine/systems/WallBounds.ts +++ b/engine/systems/WallBounds.ts @@ -14,23 +14,16 @@ export class WallBounds extends System { } public update(_dt: number, game: Game) { - game.componentEntities - .get(ComponentNames.WallBounded) - ?.forEach((entityId) => { - const entity = game.entities.get(entityId); - if (!entity.hasComponent(ComponentNames.BoundingBox)) { - return; - } + game.forEachEntityWithComponent(ComponentNames.WallBounded, (entity) => { + const boundingBox = entity.getComponent<BoundingBox>( + ComponentNames.BoundingBox, + ); - const boundingBox = entity.getComponent<BoundingBox>( - ComponentNames.BoundingBox - ); - - boundingBox.center.x = clamp( - boundingBox.center.x, - boundingBox.dimension.width / 2, - this.screenWidth - boundingBox.dimension.width / 2 - ); - }); + boundingBox.center.x = clamp( + boundingBox.center.x, + boundingBox.dimension.width / 2, + this.screenWidth - boundingBox.dimension.width / 2, + ); + }); } } |