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