constructor(level) { | constructor(level) { | ||||
this.level = level; | this.level = level; | ||||
this.bounds = new Rect(); | this.bounds = new Rect(); | ||||
this.time = 0; | |||||
this.t = { | this.t = { | ||||
physics: null, | physics: null, | ||||
keyboardController: null, | keyboardController: null, | ||||
collider: null, | collider: null, | ||||
platform: null, | platform: null, | ||||
wall: null, | |||||
}; | }; | ||||
this.traits = []; | this.traits = []; | ||||
} | } | ||||
this.update(dt); | this.update(dt); | ||||
this.time += dt; | |||||
} | } | ||||
_postUpdate(dt) { | _postUpdate(dt) { |
this.ctx = canvas.getContext("2d"); | this.ctx = canvas.getContext("2d"); | ||||
this.lastTime = null; | this.lastTime = null; | ||||
this.raf = null; | this.raf = null; | ||||
this.minFPS = 10; | |||||
this.entities = []; | this.entities = []; | ||||
this.colliders = []; | this.colliders = []; | ||||
// Collide with structures | // Collide with structures | ||||
this.structures.forEach(s => { | this.structures.forEach(s => { | ||||
if (!s.attrs.collides) | |||||
if (!s.attrs.wall && !s.attrs.platform) | |||||
return; | return; | ||||
this.colliders.forEach(ent => { | this.colliders.forEach(ent => { | ||||
if (this.lastTime != null) { | if (this.lastTime != null) { | ||||
let dt = (time - this.lastTime) / 1000; | 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(); | this.draw(); | ||||
} | } | ||||
(this.left <= other.right && this.right >= other.left) && | (this.left <= other.right && this.right >= other.left) && | ||||
(this.top <= other.bottom && this.bottom >= other.top)); | (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) { | contains(other) { | ||||
(this.top <= other.top && this.bottom >= other.bottom)); | (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() { | get top() { | ||||
return this.pos.y; | return this.pos.y; | ||||
} | } |
let bottom = (e.y + 1) * assets.tiles.tileHeight; | let bottom = (e.y + 1) * assets.tiles.tileHeight; | ||||
if (right > bounds.size.x) | if (right > bounds.size.x) | ||||
bounds.size.x = right; | |||||
bounds.size.pixelX = right; | |||||
if (bottom > bounds.size.y) | if (bottom > bounds.size.y) | ||||
bounds.size.y = bottom; | |||||
bounds.size.pixelY = bottom; | |||||
}); | }); | ||||
} | } | ||||
this.texture = new Texture(nestedArr); | this.texture = new Texture(nestedArr); | ||||
this.attrs = { | this.attrs = { | ||||
collides: false, | |||||
wall: false, | |||||
platform: false, | |||||
} | } | ||||
for (let a of attrs) | for (let a of attrs) | ||||
findBounds(arr, this.bounds); | findBounds(arr, this.bounds); | ||||
if (this.attrs.collides) { | |||||
if (this.attrs.wall || this.attrs.platform) { | |||||
findGeometry(this.bounds, arr, this.geometry); | findGeometry(this.bounds, arr, this.geometry); | ||||
} | } | ||||
} | } |
this.addTrait(new TCollider(this)); | this.addTrait(new TCollider(this)); | ||||
this.addTrait(new TPlatform(this)); | this.addTrait(new TPlatform(this)); | ||||
this.addTrait(new TPhysics(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) { | update(dt) { | ||||
let phys = this.t.physics; | 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) { | draw(ctx) { |
export default { | export default { | ||||
floor: width => new Structure( | floor: width => new Structure( | ||||
[ "collides" ], | |||||
[ "wall" ], | |||||
tiles.fromLine(width, "grass")), | tiles.fromLine(width, "grass")), | ||||
}; | }; |
super(entity, "collider"); | super(entity, "collider"); | ||||
this.collides = false; | this.collides = false; | ||||
this.cEntity = null; | |||||
this.cStructure = null; | |||||
this.cBounds = null; | |||||
this.entities = []; | |||||
this.structures = []; | |||||
this.structureBounds = []; | |||||
} | } | ||||
collideEntity(e) { | collideEntity(e) { | ||||
this.collides = true; | this.collides = true; | ||||
this.cEntity = e; | |||||
this.cBounds = e.bounds; | |||||
this.entities.push(e); | |||||
} | } | ||||
collideStructure(s, b) { | collideStructure(s, b) { | ||||
this.collides = true; | this.collides = true; | ||||
this.cStructure = s; | |||||
this.cBounds = b; | |||||
this.structures.push(s); | |||||
this.structureBounds.push(b); | |||||
} | } | ||||
postUpdate() { | postUpdate() { | ||||
this.cEntity = null; | |||||
this.cStructure = null; | |||||
this.entities.length = 0; | |||||
this.structures.length = 0; | |||||
this.structureBounds.length = 0; | |||||
this.collides = false; | this.collides = false; | ||||
} | } | ||||
} | } |
this.jump = 8; | this.jump = 8; | ||||
this.jumpTimeMax = 0.4; | this.jumpTimeMax = 0.4; | ||||
this.updrift = 100; | this.updrift = 100; | ||||
this.jumpLeeway = 0.4; | |||||
this.map = { | this.map = { | ||||
KeyA: 'left', | KeyA: 'left', | ||||
this.jumpTime = 0; | this.jumpTime = 0; | ||||
this.jumping = false; | this.jumping = false; | ||||
this.jumped = false | |||||
} | } | ||||
onkey(evt) { | onkey(evt) { | ||||
update(dt) { | update(dt) { | ||||
let phys = this.entity.t.physics; | let phys = this.entity.t.physics; | ||||
let onGround = phys.onGround; | |||||
let speed = phys.onGround ? this.speed : this.speedAir; | let speed = phys.onGround ? this.speed : this.speedAir; | ||||
phys.velocity.x -= speed * dt; | phys.velocity.x -= speed * dt; | ||||
if (this.pressed.right) | if (this.pressed.right) | ||||
phys.velocity.x += speed * dt; | 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; | 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.jumpTime = this.jumpTimeMax; | ||||
this.jumping = true; | 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; | this.jumping = false; | ||||
if (onGround) | |||||
this.jumped = false; | |||||
if (this.jumping) { | if (this.jumping) { | ||||
phys.velocity.y -= this.updrift * this.jumpTime * dt; | phys.velocity.y -= this.updrift * this.jumpTime * dt; |
import {Trait} from "../Entity.js"; | import {Trait} from "../Entity.js"; | ||||
import Vec2 from "../Vec2.js"; | import Vec2 from "../Vec2.js"; | ||||
let zeroVector = new Vec2(0, 0); | |||||
export default class TPhysics extends Trait { | export default class TPhysics extends Trait { | ||||
constructor(entity) { | constructor(entity) { | ||||
super(entity, "physics"); | super(entity, "physics"); | ||||
this.velocity = new Vec2(); | this.velocity = new Vec2(); | ||||
this.onGround = false; | 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) | if (!this.onGround) | ||||
this.velocity.y += this.gravity * dt; | 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 | // Apply friction | ||||
var fric = this.onGround ? this.groundFriction : this.airFriction; | var fric = this.onGround ? this.groundFriction : this.airFriction; | ||||
var xRatio = 1 / (1 + (dt * fric)); | var xRatio = 1 / (1 + (dt * fric)); | ||||
} | } | ||||
postUpdate(dt) { | postUpdate(dt) { | ||||
this.jumped = false; | |||||
// Update absVelocity | // 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 { | } else { | ||||
this.absVelocity.x = this.velocity.x; | this.absVelocity.x = this.velocity.x; | ||||
this.absVelocity.y = this.velocity.y; | this.absVelocity.y = this.velocity.y; |