@@ -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; |