} | } | ||||
export default class Entity { | export default class Entity { | ||||
constructor(level) { | |||||
constructor(level, layerName) { | |||||
this.layerName = layerName; | |||||
this.level = level; | this.level = level; | ||||
this.bounds = new Rect(); | this.bounds = new Rect(); | ||||
this.time = 0; | this.time = 0; | ||||
this.traitOrder = [ | |||||
"collider", | |||||
"platform", | |||||
"wall", | |||||
"keyboardController", | |||||
"physics", | |||||
]; | |||||
this.t = { | this.t = { | ||||
physics: null, | |||||
keyboardController: null, | |||||
collider: null, | collider: null, | ||||
platform: null, | platform: null, | ||||
wall: null, | wall: null, | ||||
keyboardController: null, | |||||
physics: null, | |||||
}; | }; | ||||
this.traits = []; | this.traits = []; | ||||
} | } | ||||
_init() { | _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) { | for (let trait of this.traits) { | ||||
trait._init(); | trait._init(); | ||||
} | } | ||||
throw new Error("Invalid trait:", t); | throw new Error("Invalid trait:", t); | ||||
this.t[t.name] = 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); | |||||
} | } | ||||
} | } |
this.minFPS = 5; | this.minFPS = 5; | ||||
this.backgroundColor = "#87CEFA"; | this.backgroundColor = "#87CEFA"; | ||||
this.layerOrder = { | |||||
platform: 0, | |||||
player: 1, | |||||
}; | |||||
this.layers = []; | |||||
this.entities = []; | this.entities = []; | ||||
this.colliders = []; | this.colliders = []; | ||||
spawnEntity(ent, x, y) { | spawnEntity(ent, x, y) { | ||||
ent.bounds.pos.set(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.entities.push(ent); | ||||
this.layers[layerIdx].push(ent); | |||||
if (ent.has("collider")) | if (ent.has("collider")) | ||||
this.colliders.push(ent); | this.colliders.push(ent); | ||||
structure.init(); | structure.init(); | ||||
} | } | ||||
physics(dt) { | |||||
this.entities.forEach(ent => | |||||
physicsLayer(dt, layer) { | |||||
layer.forEach(ent => | |||||
ent._update(dt)); | ent._update(dt)); | ||||
this.entities.forEach(ent => | |||||
layer.forEach(ent => | |||||
ent._postUpdate(dt)); | ent._postUpdate(dt)); | ||||
} | |||||
physics(dt) { | |||||
// Collide with structures | // Collide with structures | ||||
this.structures.forEach(s => { | this.structures.forEach(s => { | ||||
if (!s.attrs.wall && !s.attrs.platform) | if (!s.attrs.wall && !s.attrs.platform) | ||||
ent.t.collider.collideEntity(ent2); | ent.t.collider.collideEntity(ent2); | ||||
}); | }); | ||||
}); | }); | ||||
this.layers.forEach(l => this.physicsLayer(dt, l)); | |||||
} | } | ||||
draw() { | draw() { | ||||
this.ctx.beginPath(); | this.ctx.beginPath(); | ||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); | 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)); | this.structures.forEach(struct => struct.draw(this.ctx)); | ||||
} | } | ||||
export default class BrokenPlatform extends Entity { | export default class BrokenPlatform extends Entity { | ||||
constructor(level, width = 5) { | constructor(level, width = 5) { | ||||
super(level); | |||||
super(level, "platform"); | |||||
this.bounds.size.set(width, 0.5); | this.bounds.size.set(width, 0.5); | ||||
this.addTrait(new TCollider(this)); | this.addTrait(new TCollider(this)); | ||||
update(dt) { | update(dt) { | ||||
if (!this.shaking && !this.falling) { | if (!this.shaking && !this.falling) { | ||||
this.t.collider.entities.forEach(e => { | 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(); | this.startShake(); | ||||
} | } | ||||
}); | }); |
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); | |||||
} | |||||
} |
export default class FloatingPlatform extends Entity { | export default class FloatingPlatform extends Entity { | ||||
constructor(level, width = 5) { | constructor(level, width = 5) { | ||||
super(level); | |||||
super(level, "platform"); | |||||
this.bounds.size.set(width, 0.5); | this.bounds.size.set(width, 0.5); | ||||
this.addTrait(new TCollider(this)); | this.addTrait(new TCollider(this)); |
export default class Player extends Entity { | export default class Player extends Entity { | ||||
constructor(level) { | constructor(level) { | ||||
super(level); | |||||
super(level, "player"); | |||||
this.bounds.size.set(1, 2); | this.bounds.size.set(1, 2); | ||||
this.addTrait(new TCollider(this)); | this.addTrait(new TCollider(this)); |
import Player from "./entities/Player.js"; | import Player from "./entities/Player.js"; | ||||
import FloatingPlatform from "./entities/FloatingPlatform.js"; | import FloatingPlatform from "./entities/FloatingPlatform.js"; | ||||
import FallingPlatform from "./entities/FallingPlatform.js"; | |||||
import BrokenPlatform from "./entities/BrokenPlatform.js"; | import BrokenPlatform from "./entities/BrokenPlatform.js"; | ||||
import structures from "./structures.js"; | import structures from "./structures.js"; | ||||
level.spawnEntity(new Player(level), 10, 1); | level.spawnEntity(new Player(level), 10, 1); | ||||
level.spawnEntity(new FloatingPlatform(level), 16, 4); | 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.spawnStructure(structures.floor(8, 6), 4, 4); | ||||
level.spawnEntity(new BrokenPlatform(level), 27, 4); | level.spawnEntity(new BrokenPlatform(level), 27, 4); |
this.entity.time -phys.timeLastOnGround < this.jumpLeeway; | this.entity.time -phys.timeLastOnGround < this.jumpLeeway; | ||||
if (this.pressed.jump && canJump) { | 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.entity.bounds.pos.y += phys.velocity.y * dt; | ||||
this.jumpTime = this.jumpTimeMax; | this.jumpTime = this.jumpTimeMax; | ||||
this.jumping = true; | this.jumping = true; |
constructor(entity) { | constructor(entity) { | ||||
super(entity, "physics"); | super(entity, "physics"); | ||||
this.velocity = new Vec2(); | |||||
this.gravity = 40; | this.gravity = 40; | ||||
this.groundFriction = 6; | this.groundFriction = 6; | ||||
this.airFriction = 2; | this.airFriction = 2; | ||||
this.absVelocity = new Vec2(); | |||||
this.velocity = new Vec2(); | |||||
this.timeLastOnGround = 0; | |||||
this.onGround = false; | this.onGround = false; | ||||
this.groundBounds = null; | this.groundBounds = null; | ||||
this.groundVelocity = 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); | let side = this.entity.bounds.intersectSide(bounds); | ||||
if (side === "top") | 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); | let side = this.entity.bounds.intersectSide(bounds); | ||||
if (side === "top") | if (side === "top") | ||||
return this.collideTop(bounds, absVelocity); | |||||
return this.collideTop(bounds, velocity); | |||||
} | } | ||||
update(dt) { | update(dt) { | ||||
// Gravity | |||||
if (!this.onGround) | |||||
this.velocity.y += this.gravity * dt; | |||||
// Collide | |||||
if (this.entity.has("collider")) { | 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.timeLastOnGround = this.entity.time; | ||||
this.velocity.y = this.groundVelocity.y; | |||||
} | |||||
if (!this.onGround) | |||||
this.velocity.y += this.gravity * dt; | |||||
// Apply friction | // Apply friction | ||||
var fric = this.onGround ? this.groundFriction : this.airFriction; | var fric = this.onGround ? this.groundFriction : this.airFriction; | ||||
} | } | ||||
postUpdate(dt) { | 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 | // 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; | |||||
} | } | ||||
} | } |