| @@ -30,12 +30,14 @@ export default class Entity { | |||
| constructor(level) { | |||
| this.level = level; | |||
| this.bounds = new Rect(); | |||
| this.time = 0; | |||
| this.t = { | |||
| physics: null, | |||
| keyboardController: null, | |||
| collider: null, | |||
| platform: null, | |||
| wall: null, | |||
| }; | |||
| this.traits = []; | |||
| @@ -58,6 +60,7 @@ export default class Entity { | |||
| } | |||
| this.update(dt); | |||
| this.time += dt; | |||
| } | |||
| _postUpdate(dt) { | |||
| @@ -6,6 +6,7 @@ export default class Level { | |||
| this.ctx = canvas.getContext("2d"); | |||
| this.lastTime = null; | |||
| this.raf = null; | |||
| this.minFPS = 10; | |||
| this.entities = []; | |||
| this.colliders = []; | |||
| @@ -39,7 +40,7 @@ export default class Level { | |||
| // Collide with structures | |||
| this.structures.forEach(s => { | |||
| if (!s.attrs.collides) | |||
| if (!s.attrs.wall && !s.attrs.platform) | |||
| return; | |||
| this.colliders.forEach(ent => { | |||
| @@ -75,7 +76,14 @@ export default class Level { | |||
| if (this.lastTime != null) { | |||
| let dt = (time - this.lastTime) / 1000; | |||
| this.physics(dt); | |||
| if (1 / dt > this.minFPS) { | |||
| this.physics(dt); | |||
| } else { | |||
| console.log( | |||
| "Too long between updates ("+dt.toFixed(2)+"s, "+ | |||
| (1 / dt).toFixed(2)+"FPS). Skipping."); | |||
| } | |||
| this.draw(); | |||
| } | |||
| @@ -20,10 +20,16 @@ export default class Rect { | |||
| (this.left <= other.right && this.right >= other.left) && | |||
| (this.top <= other.bottom && this.bottom >= other.top)); | |||
| } | |||
| bottomIntersects(other) { | |||
| return ( | |||
| (this.left <= other.right && this.right >= other.left) && | |||
| (this.bottom <= other.bottom && this.bottom >= other.top)); | |||
| intersectSide(other) { | |||
| if (this.midY < other.top) | |||
| return "top"; | |||
| else if (this.midY > other.bottom) | |||
| return "bottom"; | |||
| else if (this.midX < other.left) | |||
| return "left"; | |||
| else | |||
| return "right"; | |||
| } | |||
| contains(other) { | |||
| @@ -32,6 +38,13 @@ export default class Rect { | |||
| (this.top <= other.top && this.bottom >= other.bottom)); | |||
| } | |||
| get midX() { | |||
| return this.pos.x + (this.size.x / 2); | |||
| } | |||
| get midY() { | |||
| return this.pos.y + (this.size.y / 2); | |||
| } | |||
| get top() { | |||
| return this.pos.y; | |||
| } | |||
| @@ -9,9 +9,9 @@ function findBounds(arr, bounds) { | |||
| let bottom = (e.y + 1) * assets.tiles.tileHeight; | |||
| if (right > bounds.size.x) | |||
| bounds.size.x = right; | |||
| bounds.size.pixelX = right; | |||
| if (bottom > bounds.size.y) | |||
| bounds.size.y = bottom; | |||
| bounds.size.pixelY = bottom; | |||
| }); | |||
| } | |||
| @@ -102,7 +102,8 @@ export default class Structure { | |||
| this.texture = new Texture(nestedArr); | |||
| this.attrs = { | |||
| collides: false, | |||
| wall: false, | |||
| platform: false, | |||
| } | |||
| for (let a of attrs) | |||
| @@ -118,7 +119,7 @@ export default class Structure { | |||
| findBounds(arr, this.bounds); | |||
| if (this.attrs.collides) { | |||
| if (this.attrs.wall || this.attrs.platform) { | |||
| findGeometry(this.bounds, arr, this.geometry); | |||
| } | |||
| } | |||
| @@ -12,11 +12,35 @@ export default class Platform extends Entity { | |||
| this.addTrait(new TCollider(this)); | |||
| this.addTrait(new TPlatform(this)); | |||
| this.addTrait(new TPhysics(this)); | |||
| this.targetSpeed = 3; | |||
| this.accel = 5; | |||
| this.dir = 1; | |||
| } | |||
| init() { | |||
| this.targetTop = this.bounds.pos.y; | |||
| this.targetBottom = this.targetTop + 5; | |||
| this.t.physics.gravity = 0; | |||
| this.t.physics.velocity.y = 0; | |||
| } | |||
| update(dt) { | |||
| let phys = this.t.physics; | |||
| phys.velocity.y -= phys.gravity * dt; | |||
| if (this.bounds.pos.y <= this.targetTop) | |||
| this.dir = 1; | |||
| else if (this.bounds.pos.y >= this.targetBottom) | |||
| this.dir = -1; | |||
| if (this.dir == 1) { | |||
| if (phys.velocity.y < this.targetSpeed) | |||
| phys.velocity.y += this.accel * dt; | |||
| } else { | |||
| if (phys.velocity.y > -this.targetSpeed) | |||
| phys.velocity.y -= this.accel * dt; | |||
| } | |||
| } | |||
| draw(ctx) { | |||
| @@ -3,6 +3,6 @@ import tiles from "./tiles.js"; | |||
| export default { | |||
| floor: width => new Structure( | |||
| [ "collides" ], | |||
| [ "wall" ], | |||
| tiles.fromLine(width, "grass")), | |||
| }; | |||
| @@ -5,26 +5,26 @@ export default class Collider extends Trait { | |||
| super(entity, "collider"); | |||
| this.collides = false; | |||
| this.cEntity = null; | |||
| this.cStructure = null; | |||
| this.cBounds = null; | |||
| this.entities = []; | |||
| this.structures = []; | |||
| this.structureBounds = []; | |||
| } | |||
| collideEntity(e) { | |||
| this.collides = true; | |||
| this.cEntity = e; | |||
| this.cBounds = e.bounds; | |||
| this.entities.push(e); | |||
| } | |||
| collideStructure(s, b) { | |||
| this.collides = true; | |||
| this.cStructure = s; | |||
| this.cBounds = b; | |||
| this.structures.push(s); | |||
| this.structureBounds.push(b); | |||
| } | |||
| postUpdate() { | |||
| this.cEntity = null; | |||
| this.cStructure = null; | |||
| this.entities.length = 0; | |||
| this.structures.length = 0; | |||
| this.structureBounds.length = 0; | |||
| this.collides = false; | |||
| } | |||
| } | |||
| @@ -10,6 +10,7 @@ export default class TKeyboardController extends Trait { | |||
| this.jump = 8; | |||
| this.jumpTimeMax = 0.4; | |||
| this.updrift = 100; | |||
| this.jumpLeeway = 0.4; | |||
| this.map = { | |||
| KeyA: 'left', | |||
| @@ -21,6 +22,7 @@ export default class TKeyboardController extends Trait { | |||
| this.jumpTime = 0; | |||
| this.jumping = false; | |||
| this.jumped = false | |||
| } | |||
| onkey(evt) { | |||
| @@ -37,6 +39,7 @@ export default class TKeyboardController extends Trait { | |||
| update(dt) { | |||
| let phys = this.entity.t.physics; | |||
| let onGround = phys.onGround; | |||
| let speed = phys.onGround ? this.speed : this.speedAir; | |||
| @@ -44,16 +47,24 @@ export default class TKeyboardController extends Trait { | |||
| phys.velocity.x -= speed * dt; | |||
| if (this.pressed.right) | |||
| phys.velocity.x += speed * dt; | |||
| if (phys.onGround && this.pressed.jump) { | |||
| let canJump = | |||
| !this.jumped && onGround && | |||
| this.entity.time -phys.timeLastOnGround < this.jumpLeeway; | |||
| if (this.pressed.jump && canJump) { | |||
| phys.velocity.y = -this.jump; | |||
| console.log("jumping to", phys.velocity.y); | |||
| this.entity.bounds.pos.y += phys.velocity.y * dt; | |||
| this.jumpTime = this.jumpTimeMax; | |||
| this.jumping = true; | |||
| phys.onGround = false; | |||
| this.jumped = true; | |||
| onGround = false; | |||
| } | |||
| if (phys.onGround || !this.pressed.jump || this.jumpTime <= 0) | |||
| if (onGround || !this.pressed.jump || this.jumpTime <= 0) | |||
| this.jumping = false; | |||
| if (onGround) | |||
| this.jumped = false; | |||
| if (this.jumping) { | |||
| phys.velocity.y -= this.updrift * this.jumpTime * dt; | |||
| @@ -1,6 +1,8 @@ | |||
| import {Trait} from "../Entity.js"; | |||
| import Vec2 from "../Vec2.js"; | |||
| let zeroVector = new Vec2(0, 0); | |||
| export default class TPhysics extends Trait { | |||
| constructor(entity) { | |||
| super(entity, "physics"); | |||
| @@ -13,57 +15,95 @@ export default class TPhysics extends Trait { | |||
| this.velocity = new Vec2(); | |||
| this.onGround = false; | |||
| this.prevRelativeTo = null; | |||
| this.relativeTo = null; | |||
| this.groundBounds = null; | |||
| this.groundVelocity = null; | |||
| this.oldGroundBounds = null; | |||
| this.timeLastOnGround = 0; | |||
| } | |||
| update(dt) { | |||
| let collider = this.entity.t.collider; | |||
| collideTop(bounds, absVelocity) { | |||
| if (this.onGround || bounds === this.oldGroundBounds) | |||
| return; | |||
| this.prevRelativeTo = this.relativeTo; | |||
| this.relativeTo = null; | |||
| this.onGround = false; | |||
| this.onGround = true; | |||
| this.groundBounds = bounds; | |||
| this.groundVelocity = absVelocity; | |||
| if ( | |||
| this.entity.has("collider") && | |||
| collider.collides && | |||
| this.velocity.y >= 0 && | |||
| this.entity.bounds.bottomIntersects(collider.cBounds)) { | |||
| // Structures are static; just teleport us to the top of them | |||
| if (collider.cStructure) { | |||
| this.velocity.y = 0; | |||
| this.entity.bounds.bottom = collider.cBounds.top; | |||
| this.onGround = true; | |||
| // If we're colliding with an entity, and that entity is | |||
| // a platform, teleport us to the top of them. | |||
| } else if (collider.cEntity.has("platform")) { | |||
| this.velocity.y = 0; | |||
| this.entity.bounds.bottom = collider.cBounds.top; | |||
| this.onGround = true; | |||
| if (collider.cEntity.has("physics")) { | |||
| let cPhys = collider.cEntity.t.physics; | |||
| this.relativeTo = cPhys; | |||
| } | |||
| } | |||
| } | |||
| this.velocity.x -= absVelocity.x; | |||
| this.velocity.y = 0; | |||
| this.entity.bounds.bottom = bounds.top; | |||
| } | |||
| collideWall(bounds, absVelocity) { | |||
| let side = this.entity.bounds.intersectSide(bounds); | |||
| if (side === "top") | |||
| return this.collideTop(bounds, absVelocity); | |||
| } | |||
| collidePlatform(bounds, absVelocity) { | |||
| let side = this.entity.bounds.intersectSide(bounds); | |||
| if (side === "top") | |||
| return this.collideTop(bounds, absVelocity); | |||
| } | |||
| update(dt) { | |||
| // Gravity | |||
| if (!this.onGround) | |||
| this.velocity.y += this.gravity * dt; | |||
| // If we just started riding something, adjust relative absVelocity | |||
| if (!this.prevRelativeTo && this.relativeTo) { | |||
| this.velocity.x = this.absVelocity.x - this.relativeTo.absVelocity.x; | |||
| this.velocity.y = this.absVelocity.y - this.relativeTo.absVelocity.y; | |||
| if (this.entity.has("collider")) { | |||
| // Check if we're still on the same ground | |||
| if (this.onGround) { | |||
| let stillOnGround = | |||
| (this.entity.bounds.intersects(this.groundBounds)) && | |||
| (this.entity.bounds.intersectSide(this.groundBounds) === "top"); | |||
| // If we just stopped riding something, adjust relative absVelocity | |||
| } else if (this.prevRelativeTo && !this.relativeTo) { | |||
| this.velocity.x += this.prevRelativeTo.absVelocity.x; | |||
| this.velocity.y += this.prevRelativeTo.absVelocity.y; | |||
| if (stillOnGround) { | |||
| this.entity.bounds.bottom = this.groundBounds.top; | |||
| } else { | |||
| this.velocity.x += this.groundVelocity.x; | |||
| this.velocity.y += this.groundVelocity.y; | |||
| this.onGround = false; | |||
| this.oldGroundBounds = this.groundBounds; | |||
| this.groundBounds = null; | |||
| this.groundVelocity = null; | |||
| } | |||
| } | |||
| // Collide with new stuff | |||
| let collider = this.entity.t.collider; | |||
| collider.entities.forEach(e => { | |||
| let vel; | |||
| if (e.has("physics")) | |||
| vel = e.t.physics.absVelocity; | |||
| else | |||
| vel = zeroVector; | |||
| if (e.has("wall")) | |||
| this.collideWall(e.bounds, vel); | |||
| else if (e.has("platform")) | |||
| this.collidePlatform(e.bounds, vel); | |||
| }); | |||
| collider.structures.forEach((s, i) => { | |||
| let bounds = collider.structureBounds[i]; | |||
| if (s.attrs.wall) | |||
| this.collideWall(bounds, zeroVector); | |||
| else if (s.attrs.platform) | |||
| this.collidePlatform(bounds, zeroVector); | |||
| }); | |||
| this.oldGroundBounds = null; | |||
| } | |||
| // Track the last time we were on the ground | |||
| if (this.onGround) | |||
| this.timeLastOnGround = this.entity.time; | |||
| // Apply friction | |||
| var fric = this.onGround ? this.groundFriction : this.airFriction; | |||
| var xRatio = 1 / (1 + (dt * fric)); | |||
| @@ -71,11 +111,12 @@ export default class TPhysics extends Trait { | |||
| } | |||
| postUpdate(dt) { | |||
| this.jumped = false; | |||
| // Update absVelocity | |||
| if (this.relativeTo) { | |||
| this.absVelocity.x = this.velocity.x + this.relativeTo.absVelocity.x; | |||
| this.absVelocity.y = this.velocity.y + this.relativeTo.absVelocity.y; | |||
| if (this.onGround) { | |||
| this.absVelocity.x = this.velocity.x + this.groundVelocity.x; | |||
| this.absVelocity.y = this.velocity.y + this.groundVelocity.y; | |||
| } else { | |||
| this.absVelocity.x = this.velocity.x; | |||
| this.absVelocity.y = this.velocity.y; | |||