@@ -27,17 +27,25 @@ export class Trait { | |||
} | |||
export default class Entity { | |||
constructor(level) { | |||
constructor(level, layerName) { | |||
this.layerName = layerName; | |||
this.level = level; | |||
this.bounds = new Rect(); | |||
this.time = 0; | |||
this.traitOrder = [ | |||
"collider", | |||
"platform", | |||
"wall", | |||
"keyboardController", | |||
"physics", | |||
]; | |||
this.t = { | |||
physics: null, | |||
keyboardController: null, | |||
collider: null, | |||
platform: null, | |||
wall: null, | |||
keyboardController: null, | |||
physics: null, | |||
}; | |||
this.traits = []; | |||
@@ -46,6 +54,18 @@ export default class Entity { | |||
} | |||
_init() { | |||
for (let traitName of this.traitOrder) { | |||
let t = this.t[traitName]; | |||
if (t == null) | |||
continue; | |||
this.traits.push(t); | |||
if (t.update !== Trait.prototype.update) | |||
this.updateTraits.push(t); | |||
if (t.postUpdate !== Trait.prototype.postUpdate) | |||
this.postUpdateTraits.push(t); | |||
} | |||
for (let trait of this.traits) { | |||
trait._init(); | |||
} | |||
@@ -88,11 +108,5 @@ export default class Entity { | |||
throw new Error("Invalid trait:", t); | |||
this.t[t.name] = t; | |||
this.traits.push(t); | |||
if (t.update !== Trait.prototype.update) | |||
this.updateTraits.push(t); | |||
if (t.postUpdate !== Trait.prototype.postUpdate) | |||
this.postUpdateTraits.push(t); | |||
} | |||
} |
@@ -10,6 +10,11 @@ export default class Level { | |||
this.minFPS = 5; | |||
this.backgroundColor = "#87CEFA"; | |||
this.layerOrder = { | |||
platform: 0, | |||
player: 1, | |||
}; | |||
this.layers = []; | |||
this.entities = []; | |||
this.colliders = []; | |||
@@ -21,7 +26,16 @@ export default class Level { | |||
spawnEntity(ent, x, y) { | |||
ent.bounds.pos.set(x, y); | |||
let layerIdx = this.layerOrder[ent.layerName]; | |||
console.log(ent.layerName, this.layerOrder, layerIdx); | |||
if (layerIdx == null) | |||
throw new Error("Unknown layer name: "+ent.layerName); | |||
if (this.layers[layerIdx] == null) | |||
this.layers[layerIdx] = []; | |||
this.entities.push(ent); | |||
this.layers[layerIdx].push(ent); | |||
if (ent.has("collider")) | |||
this.colliders.push(ent); | |||
@@ -34,12 +48,14 @@ export default class Level { | |||
structure.init(); | |||
} | |||
physics(dt) { | |||
this.entities.forEach(ent => | |||
physicsLayer(dt, layer) { | |||
layer.forEach(ent => | |||
ent._update(dt)); | |||
this.entities.forEach(ent => | |||
layer.forEach(ent => | |||
ent._postUpdate(dt)); | |||
} | |||
physics(dt) { | |||
// Collide with structures | |||
this.structures.forEach(s => { | |||
if (!s.attrs.wall && !s.attrs.platform) | |||
@@ -63,6 +79,8 @@ export default class Level { | |||
ent.t.collider.collideEntity(ent2); | |||
}); | |||
}); | |||
this.layers.forEach(l => this.physicsLayer(dt, l)); | |||
} | |||
draw() { | |||
@@ -70,7 +88,7 @@ export default class Level { | |||
this.ctx.beginPath(); | |||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); | |||
this.entities.forEach(ent => ent.draw(this.ctx)); | |||
this.layers.forEach(l => l.forEach(ent => ent.draw(this.ctx))); | |||
this.structures.forEach(struct => struct.draw(this.ctx)); | |||
} | |||
@@ -10,7 +10,7 @@ import TPhysics from "../traits/TPhysics.js"; | |||
export default class BrokenPlatform extends Entity { | |||
constructor(level, width = 5) { | |||
super(level); | |||
super(level, "platform"); | |||
this.bounds.size.set(width, 0.5); | |||
this.addTrait(new TCollider(this)); | |||
@@ -58,7 +58,11 @@ export default class BrokenPlatform extends Entity { | |||
update(dt) { | |||
if (!this.shaking && !this.falling) { | |||
this.t.collider.entities.forEach(e => { | |||
if (e.has("physics")) { | |||
let fall = | |||
e.has("physics") && | |||
e.bounds.intersectSide(this.bounds) === "top"; | |||
if (fall) { | |||
this.startShake(); | |||
} | |||
}); |
@@ -1,26 +0,0 @@ | |||
import Entity from "../Entity.js"; | |||
import Texture from "../Texture.js"; | |||
import Tile from "../Tile.js"; | |||
import assets from "../assets.js"; | |||
import TPlatform from "../traits/TPlatform.js"; | |||
import TCollider from "../traits/TCollider.js"; | |||
import TPhysics from "../traits/TPhysics.js"; | |||
export default class FallingPlatform extends Entity { | |||
constructor(level, width = 5) { | |||
super(level); | |||
this.bounds.size.set(width, 0.5); | |||
this.addTrait(new TCollider(this)); | |||
this.addTrait(new TPlatform(this)); | |||
this.addTrait(new TPhysics(this)); | |||
this.texture = new Texture(assets.tiles, | |||
Tile.createLine(this.bounds.size.x, "platform")); | |||
} | |||
draw(ctx) { | |||
this.texture.drawAt(ctx, this.bounds.pos); | |||
} | |||
} |
@@ -9,7 +9,7 @@ import TPhysics from "../traits/TPhysics.js"; | |||
export default class FloatingPlatform extends Entity { | |||
constructor(level, width = 5) { | |||
super(level); | |||
super(level, "platform"); | |||
this.bounds.size.set(width, 0.5); | |||
this.addTrait(new TCollider(this)); |
@@ -9,7 +9,7 @@ import TCollider from "../traits/TCollider.js"; | |||
export default class Player extends Entity { | |||
constructor(level) { | |||
super(level); | |||
super(level, "player"); | |||
this.bounds.size.set(1, 2); | |||
this.addTrait(new TCollider(this)); |
@@ -3,7 +3,6 @@ import Vec2 from "./Vec2.js"; | |||
import Player from "./entities/Player.js"; | |||
import FloatingPlatform from "./entities/FloatingPlatform.js"; | |||
import FallingPlatform from "./entities/FallingPlatform.js"; | |||
import BrokenPlatform from "./entities/BrokenPlatform.js"; | |||
import structures from "./structures.js"; | |||
@@ -13,10 +12,6 @@ let level = new Level(canvas); | |||
level.spawnEntity(new Player(level), 10, 1); | |||
level.spawnEntity(new FloatingPlatform(level), 16, 4); | |||
level.spawnEntity(new FallingPlatform(level), 20, 1); | |||
level.spawnEntity(new FallingPlatform(level), 20, 0); | |||
level.spawnEntity(new FallingPlatform(level), 20, -1); | |||
level.spawnEntity(new FallingPlatform(level), 20, -2); | |||
level.spawnStructure(structures.floor(8, 6), 4, 4); | |||
level.spawnEntity(new BrokenPlatform(level), 27, 4); |
@@ -53,7 +53,7 @@ export default class TKeyboardController extends Trait { | |||
this.entity.time -phys.timeLastOnGround < this.jumpLeeway; | |||
if (this.pressed.jump && canJump) { | |||
phys.velocity.y = -this.jump; | |||
phys.velocity.y -= this.jump; | |||
this.entity.bounds.pos.y += phys.velocity.y * dt; | |||
this.jumpTime = this.jumpTimeMax; | |||
this.jumping = true; |
@@ -7,109 +7,75 @@ export default class TPhysics extends Trait { | |||
constructor(entity) { | |||
super(entity, "physics"); | |||
this.velocity = new Vec2(); | |||
this.gravity = 40; | |||
this.groundFriction = 6; | |||
this.airFriction = 2; | |||
this.absVelocity = new Vec2(); | |||
this.velocity = new Vec2(); | |||
this.timeLastOnGround = 0; | |||
this.onGround = false; | |||
this.groundBounds = null; | |||
this.groundVelocity = null; | |||
this.oldGroundBounds = null; | |||
this.timeLastOnGround = 0; | |||
} | |||
collideTop(bounds, absVelocity) { | |||
if (this.onGround || bounds === this.oldGroundBounds) | |||
return; | |||
this.onGround = true; | |||
this.groundBounds = bounds; | |||
this.groundVelocity = absVelocity; | |||
this.velocity.x -= absVelocity.x; | |||
this.velocity.y = 0; | |||
this.entity.bounds.bottom = bounds.top; | |||
collideTop(bounds, velocity) { | |||
if (!this.groundBounds || bounds.top < this.groundBounds.top) { | |||
this.groundBounds = bounds; | |||
this.groundVelocity = velocity; | |||
} | |||
} | |||
collideWall(bounds, absVelocity) { | |||
collideWall(bounds, velocity) { | |||
let side = this.entity.bounds.intersectSide(bounds); | |||
if (side === "top") | |||
return this.collideTop(bounds, absVelocity); | |||
return this.collideTop(bounds, velocity); | |||
} | |||
collidePlatform(bounds, absVelocity) { | |||
collidePlatform(bounds, velocity) { | |||
let side = this.entity.bounds.intersectSide(bounds); | |||
if (side === "top") | |||
return this.collideTop(bounds, absVelocity); | |||
return this.collideTop(bounds, velocity); | |||
} | |||
update(dt) { | |||
// Gravity | |||
if (!this.onGround) | |||
this.velocity.y += this.gravity * dt; | |||
// Collide | |||
if (this.entity.has("collider")) { | |||
let jumping = | |||
this.groundVelocity && | |||
this.velocity.y < this.groundVelocity.y; | |||
// 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") && | |||
!jumping; | |||
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 | |||
if (!jumping) { | |||
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; | |||
let collider = this.entity.t.collider; | |||
this.groundBounds = null; | |||
this.groundVelocity = null; | |||
collider.entities.forEach(e => { | |||
let vel = zeroVector; | |||
if (e.has("physics")) | |||
vel = e.t.physics.velocity; | |||
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); | |||
}); | |||
} | |||
// Track the last time we were on the ground | |||
if (this.onGround) | |||
this.onGround = | |||
(this.groundVelocity && this.velocity.y >= this.groundVelocity.y); | |||
if (this.onGround) { | |||
this.timeLastOnGround = this.entity.time; | |||
this.velocity.y = this.groundVelocity.y; | |||
} | |||
if (!this.onGround) | |||
this.velocity.y += this.gravity * dt; | |||
// Apply friction | |||
var fric = this.onGround ? this.groundFriction : this.airFriction; | |||
@@ -118,19 +84,12 @@ export default class TPhysics extends Trait { | |||
} | |||
postUpdate(dt) { | |||
this.jumped = false; | |||
// Update absVelocity | |||
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; | |||
} | |||
// Move | |||
this.entity.bounds.pos.x += this.absVelocity.x * dt; | |||
this.entity.bounds.pos.y += this.absVelocity.y * dt; | |||
this.entity.bounds.pos.x += this.velocity.x * dt; | |||
this.entity.bounds.pos.y += this.velocity.y * dt; | |||
if (this.onGround) | |||
this.entity.bounds.bottom = this.groundBounds.top; | |||
} | |||
} |