@@ -1,42 +1,78 @@ | |||
import Rect from './Rect.js'; | |||
import Vec2 from './Vec2.js'; | |||
import Rect from "./Rect.js"; | |||
export class Trait { | |||
constructor(entity, name) { | |||
constructor(entity, name, deps = []) { | |||
this.name = name; | |||
this.entity = entity; | |||
this.enabled = true; | |||
this.deps = deps; | |||
} | |||
_init() { | |||
let missing = []; | |||
this.deps.forEach(d => { | |||
if (!this.entity.has(d)) | |||
missing.push(d); | |||
}); | |||
if (missing.length > 0) | |||
throw new Error("Missing dependency traits: "+missing.join(", ")); | |||
this.init(); | |||
} | |||
init() {} | |||
update(dt) {} | |||
postUpdate(dt) {} | |||
} | |||
export default class Entity { | |||
constructor(level) { | |||
this.level = level; | |||
this.bounds = new Rect(); | |||
this.velocity = new Vec2(); | |||
this.t = {}; | |||
this.t = { | |||
physics: null, | |||
keyboardController: null, | |||
collider: null, | |||
platform: null, | |||
}; | |||
this.traits = []; | |||
this.updateTraits = []; | |||
this.postUpdateTraits = []; | |||
} | |||
init() { | |||
_init() { | |||
for (let trait of this.traits) { | |||
trait.init(); | |||
trait._init(); | |||
} | |||
this.init(); | |||
} | |||
update(dt) { | |||
for (let trait of this.traits) { | |||
_update(dt) { | |||
for (let trait of this.updateTraits) { | |||
if (trait.enabled) | |||
trait.update(dt); | |||
} | |||
this.bounds.pos.x += this.velocity.x * dt; | |||
this.bounds.pos.y += this.velocity.y * dt; | |||
this.update(dt); | |||
} | |||
_postUpdate(dt) { | |||
for (let trait of this.postUpdateTraits) { | |||
if (trait.enabled) | |||
trait.postUpdate(dt); | |||
} | |||
this.postUpdate(dt); | |||
} | |||
init() {} | |||
update(dt) {} | |||
postUpdate(dt) {} | |||
draw(ctx) {} | |||
has(name) { | |||
@@ -45,7 +81,15 @@ export default class Entity { | |||
} | |||
addTrait(t) { | |||
if (this.t[t.name] === undefined) | |||
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); | |||
} | |||
} |
@@ -1,35 +1,64 @@ | |||
import Player from './entities/Player.js'; | |||
import structures from './structures.js'; | |||
import structures from "./structures.js"; | |||
export default class Level { | |||
constructor(canvas) { | |||
this.step = 1 / 120; | |||
this.stepLimit = 2; | |||
this.canvas = canvas; | |||
this.ctx = canvas.getContext("2d"); | |||
this.lastTime = null; | |||
this.raf = null; | |||
this.timeAcc = 0; | |||
this.entities = []; | |||
this.colliders = []; | |||
this.evts = []; | |||
this.structures = []; | |||
this.structure = structures.floor(2, 2, 1); | |||
this.evts = []; | |||
} | |||
spawn(ent, x, y) { | |||
spawnEntity(ent, x, y) { | |||
ent.bounds.pos.set(x, y); | |||
this.entities.push(ent); | |||
ent.init(); | |||
if (ent.has("collider")) | |||
this.colliders.push(ent); | |||
ent._init(); | |||
} | |||
spawnStructure(structure, x, y) { | |||
structure.bounds.pos.set(x, y); | |||
this.structures.push(structure); | |||
structure.init(); | |||
} | |||
physics(dt) { | |||
this.entities.forEach(ent => | |||
ent.update(dt)); | |||
this.entities.forEach(ent => { | |||
ent.bounds.pos.x += ent.velocity.x * dt; | |||
ent.bounds.pos.y += ent.velocity.y * dt; | |||
ent._update(dt)); | |||
this.entities.forEach(ent => | |||
ent._postUpdate(dt)); | |||
// Collide with structures | |||
this.structures.forEach(s => { | |||
if (!s.attrs.collides) | |||
return; | |||
this.colliders.forEach(ent => { | |||
let bounds = s.collidesWith(ent.bounds); | |||
if (!bounds) | |||
return; | |||
ent.t.collider.collideStructure(bounds); | |||
}); | |||
}); | |||
// Collide with entities | |||
this.colliders.forEach(ent => { | |||
this.colliders.forEach(ent2 => { | |||
if (ent === ent2 || !ent.bounds.intersects(ent2.bounds)) | |||
return; | |||
ent.t.collider.collideEntity(ent2); | |||
}); | |||
}); | |||
} | |||
@@ -39,29 +68,14 @@ export default class Level { | |||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); | |||
this.entities.forEach(ent => ent.draw(this.ctx)); | |||
this.structure.draw(this.ctx); | |||
this.structures.forEach(struct => struct.draw(this.ctx)); | |||
} | |||
update(time) { | |||
if (this.lastTime != null) { | |||
let dt = (time - this.lastTime) / 1000; | |||
this.timeAcc += dt; | |||
if (this.timeAcc > this.stepLimit) { | |||
console.warn( | |||
"Attempt to simulate "+this.timeAcc.toFixed(2)+" "+ | |||
"seconds, which is over the limit ("+this.stepLimit+"s). "+ | |||
"Resetting accumulator."); | |||
this.timeAcc = dt; | |||
} | |||
let nticks = 0; | |||
while (this.timeAcc >= this.step) { | |||
nticks += 1; | |||
this.physics(this.step); | |||
this.timeAcc -= this.step; | |||
} | |||
this.physics(dt); | |||
this.draw(); | |||
} | |||
@@ -1,4 +1,4 @@ | |||
import Vec2 from './Vec2.js'; | |||
import Vec2 from "./Vec2.js"; | |||
export default class Rect { | |||
constructor(pos = new Vec2(), size = new Vec2()) { | |||
@@ -7,33 +7,91 @@ export default class Rect { | |||
} | |||
draw(ctx) { | |||
ctx.moveTo(this.left, this.top); | |||
ctx.lineTo(this.right, this.top); | |||
ctx.lineTo(this.right, this.bottom); | |||
ctx.lineTo(this.left, this.bottom); | |||
ctx.moveTo(this.pixelLeft, this.pixelTop); | |||
ctx.lineTo(this.pixelRight, this.pixelTop); | |||
ctx.lineTo(this.pixelRight, this.pixelBottom); | |||
ctx.lineTo(this.pixelLeft, this.pixelBottom); | |||
ctx.closePath(); | |||
ctx.stroke(); | |||
} | |||
intersects(other) { | |||
return ( | |||
(this.left <= other.right && this.right >= other.left) && | |||
(this.top <= other.bottom && this.bottom >= other.top)); | |||
} | |||
contains(other) { | |||
return ( | |||
(this.left <= other.left && this.right >= other.right) && | |||
(this.top <= other.top && this.bottom >= other.bottom)); | |||
} | |||
get top() { | |||
return this.pos.y; | |||
} | |||
set top(n) { | |||
this.pos.y = n; | |||
} | |||
get pixelTop() { | |||
return this.pos.pixelY; | |||
} | |||
set pixelTop(y) { | |||
this.pos.pixelY = y; | |||
} | |||
resizeTopTo(y) { | |||
let diff = y - this.top; | |||
this.top = y; | |||
this.size.y += diff; | |||
} | |||
pixelResizeTopTo(y) { | |||
let diff = y - this.pixelTop; | |||
this.pixelTop = y; | |||
this.size.pixelY += diff; | |||
} | |||
get bottom() { | |||
return this.pos.y + this.size.y; | |||
} | |||
set bottom(n) { | |||
this.pos.y = n - this.size.y; | |||
set bottom(y) { | |||
this.pos.y = y - this.size.y; | |||
} | |||
get pixelBottom() { | |||
return this.pos.pixelY + this.size.pixelY; | |||
} | |||
set pixelBottom(y) { | |||
this.pos.pixelY = y + this.size.pixelY; | |||
} | |||
resizeBottomTo(y) { | |||
let diff = y - this.bottom; | |||
this.size.y += diff; | |||
} | |||
pixelResizeBottomTo(y) { | |||
let diff = y - this.pixelBottom; | |||
this.size.pixelY += diff; | |||
} | |||
get left() { | |||
return this.pos.x; | |||
} | |||
set left(n) { | |||
this.pos.x = n; | |||
set left(x) { | |||
this.pos.x = x; | |||
} | |||
get pixelLeft() { | |||
return this.pos.pixelX; | |||
} | |||
set pixelLeft(x) { | |||
this.pos.pixelX = x; | |||
} | |||
resizeLeftTo(x) { | |||
let diff = x - this.left; | |||
this.left = x; | |||
this.size.x += diff; | |||
} | |||
pixelResizeLeftTo(x) { | |||
let diff = x - this.pixelLeft; | |||
this.pixelLeft = x; | |||
this.size.pixelX += diff; | |||
} | |||
get right() { | |||
@@ -42,4 +100,18 @@ export default class Rect { | |||
set right(n) { | |||
this.pos.x = n - this.size.x; | |||
} | |||
get pixelRight() { | |||
return this.pos.pixelX + this.size.pixelX; | |||
} | |||
set pixelRight(x) { | |||
this.pos.pixelX = x + this.size.pixelX; | |||
} | |||
resizeRightTo(x) { | |||
let diff = x - this.right; | |||
this.size.x += diff; | |||
} | |||
pixelResizeRightTo(x) { | |||
let diff = x - this.pixelRight; | |||
this.size.pixelX += diff; | |||
} | |||
} |
@@ -1,5 +1,5 @@ | |||
export default class SpriteSheet { | |||
constructor(url, tilew = 1, tileh = 1, scale = 1) { | |||
constructor(url, tilew, tileh, scale = 1) { | |||
this.url = url; | |||
this.tilew = tilew; | |||
this.tileh = tileh; |
@@ -1,52 +1,160 @@ | |||
import assets from './assets.js'; | |||
import Rect from './Rect.js'; | |||
import Vec2 from './Vec2.js'; | |||
import assets from "./assets.js"; | |||
import Rect from "./Rect.js"; | |||
import Vec2 from "./Vec2.js"; | |||
function findBounds(ctx, arr, bounds) { | |||
function findBounds(arr, bounds) { | |||
arr.forEach(e => { | |||
if (e instanceof Array) { | |||
drawArr(ctx, e); | |||
} else { | |||
let right = (e.x + 1) * assets.tiles.tileWidth; | |||
let bottom = (e.y + 1) * assets.tiles.tileHeight; | |||
if (bounds.size.x < right) | |||
bounds.size.x = right; | |||
if (bounds.size.y < bottom) | |||
bounds.size.y = bottom; | |||
} | |||
let right = (e.x + 1) * assets.tiles.tileWidth; | |||
let bottom = (e.y + 1) * assets.tiles.tileHeight; | |||
if (bounds.size.x < right) | |||
bounds.size.x = right; | |||
if (bounds.size.y < bottom) | |||
bounds.size.y = bottom; | |||
}); | |||
} | |||
function drawArr(ctx, arr) { | |||
function rightOrBelow(r1, r2) { | |||
// r2 right of r1 | |||
if ( | |||
(r1.top === r2.top && r1.bottom == r2.bottom) && | |||
(r1.right + 1 >= r2.left && r1.left <= r2.left)) | |||
return true; | |||
// r2 below r1 | |||
if ( | |||
(r1.right === r2.right && r1.left == r2.left) && | |||
(r1.bottom + 1 >= r2.top && r1.top <= r2.top)) | |||
return true; | |||
return false; | |||
} | |||
function continuous(r1, r2) { | |||
return rightOrBelow(r1, r2) || rightOrBelow(r2, r1); | |||
} | |||
function findGeometry(bounds, arr, geometry) { | |||
let allBounds = []; | |||
arr.forEach(e => { | |||
if (e instanceof Array) { | |||
drawArr(ctx, e); | |||
} else { | |||
assets.tiles.drawTile(ctx, e.tile, e.x, e.y); | |||
let eBounds = new Rect(bounds.pos.clone()); | |||
eBounds.pos.pixelX += e.x * assets.tiles.tileWidth; | |||
eBounds.pos.pixelY += e.y * assets.tiles.tileWidth; | |||
eBounds.size.pixelX = assets.tiles.tileWidth; | |||
eBounds.size.pixelY = assets.tiles.tileWidth; | |||
allBounds.push(eBounds); | |||
}); | |||
function runPass() { | |||
if (allBounds.lenght === 1) { | |||
geometry.push(allBounds[0]); | |||
allBounds.splice(0, 1); | |||
return; | |||
} | |||
// Find continuous bounds | |||
let changed = false; | |||
geometry.forEach((g, gi) => { | |||
allBounds.forEach((b, bi) => { | |||
if (continuous(g, b)) { | |||
if (b.left < g.left) | |||
g.pixelResizeLeftTo(b.pixelLeft); | |||
if (b.right > g.right) | |||
g.pixelResizeRightTo(b.pixelRight); | |||
if (b.top < g.top) | |||
g.pixelResizeTopTo(b.pixelTop); | |||
if (b.bottom > g.bottom) | |||
g.pixelResizeBottomTo(b.pixelBottom); | |||
allBounds.splice(bi, 1); | |||
changed = true; | |||
} | |||
}); | |||
}); | |||
// If no continuous blocks were found, we need a new rectangle | |||
if (!changed) { | |||
geometry.push(allBounds[0]); | |||
allBounds.splice(0, 1); | |||
} | |||
} | |||
while (allBounds.length > 0) { | |||
runPass(); | |||
} | |||
} | |||
function drawToCanvas(ctx, arr) { | |||
arr.forEach(e => { | |||
assets.tiles.drawTile(ctx, e.tile, e.x, e.y); | |||
}); | |||
} | |||
function flattened(arr, newArr = []) { | |||
arr.forEach(e => { | |||
if (e instanceof Array) | |||
flattened(e, newArr); | |||
else | |||
newArr.push(e); | |||
}); | |||
return newArr; | |||
} | |||
export default class Structure { | |||
constructor(x, y, arr) { | |||
console.log(arr); | |||
constructor(attrs, nestedArr) { | |||
this.attrs = { | |||
collides: false, | |||
} | |||
for (let a of attrs) | |||
this.setAttr(a); | |||
this.nestedArr = nestedArr; | |||
this.bounds = new Rect(); | |||
this.geometry = []; | |||
this.can = document.createElement("canvas"); | |||
this.bounds = new Rect(new Vec2( | |||
x * assets.tiles.tileWidth, y * assets.tiles.tileHeight)); | |||
} | |||
let ctx = this.can.getContext("2d"); | |||
findBounds(ctx, arr, this.bounds); | |||
init() { | |||
var arr = flattened(this.nestedArr); | |||
findBounds(arr, this.bounds); | |||
this.can.width = this.bounds.size.x; | |||
this.can.height = this.bounds.size.y; | |||
assets.tiles.whenReady(() => drawArr(ctx, arr)); | |||
console.log(this.bounds.pos, this.bounds.size); | |||
if (this.attrs.collides) { | |||
findGeometry(this.bounds, arr, this.geometry); | |||
} | |||
let ctx = this.can.getContext("2d"); | |||
assets.tiles.whenReady(() => drawToCanvas(ctx, arr)); | |||
} | |||
draw(ctx) { | |||
ctx.drawImage( | |||
this.can, this.bounds.pos.x, this.bounds.pos.y, | |||
this.can, this.bounds.pos.pixelX, this.bounds.pos.pixelY, | |||
this.bounds.size.x, this.bounds.size.y); | |||
} | |||
setAttr(attr) { | |||
if (this.attrs[attr] == null) | |||
throw new Error("Invalid attribute:", attr); | |||
this.attrs[attr] = true; | |||
return this; | |||
} | |||
collidesWith(rect) { | |||
if (!this.bounds.intersects(rect)) | |||
return null; | |||
for (let bounds of this.geometry) { | |||
if (bounds.intersects(rect)) | |||
return bounds; | |||
} | |||
return null; | |||
} | |||
} |
@@ -1,3 +1,5 @@ | |||
let meter = 32; | |||
export default class Vec2 { | |||
constructor(x = 0, y = 0) { | |||
this.x = x; | |||
@@ -8,4 +10,22 @@ export default class Vec2 { | |||
this.x = x; | |||
this.y = y; | |||
} | |||
clone() { | |||
return new Vec2(this.x, this.y); | |||
} | |||
get pixelX() { | |||
return Math.floor(this.x * meter); | |||
} | |||
set pixelX(x) { | |||
this.x = Math.floor(x) / meter; | |||
} | |||
get pixelY() { | |||
return Math.floor(this.y * meter); | |||
} | |||
set pixelY(y) { | |||
this.y = Math.floor(y) / meter; | |||
} | |||
} |
@@ -1,4 +1,4 @@ | |||
import SpriteSheet from './SpriteSheet.js'; | |||
import SpriteSheet from "./SpriteSheet.js"; | |||
export default { | |||
tiles: new SpriteSheet("assets/tiles.png", 32, 32, 2) |
@@ -0,0 +1,18 @@ | |||
import Entity from "../Entity.js"; | |||
import TPlatform from "../traits/TPlatform.js"; | |||
import TCollider from "../traits/TCollider.js"; | |||
export default class Platform extends Entity { | |||
constructor(level) { | |||
super(level); | |||
this.bounds.size.set(5, 1); | |||
this.addTrait(new TCollider(this)); | |||
this.addTrait(new TPlatform(this)); | |||
} | |||
draw(ctx) { | |||
this.bounds.draw(ctx); | |||
} | |||
} |
@@ -1,16 +1,17 @@ | |||
import Entity from '../Entity.js'; | |||
import Vec2 from '../Vec2.js'; | |||
import Entity from "../Entity.js"; | |||
import KeyboardControls from '../traits/KeyboardControls.js'; | |||
import Physics from '../traits/Physics.js'; | |||
import TKeyboardController from "../traits/TKeyboardController.js"; | |||
import TPhysics from "../traits/TPhysics.js"; | |||
import TCollider from "../traits/TCollider.js"; | |||
export default class Player extends Entity { | |||
constructor(level) { | |||
super(level); | |||
this.bounds.size.set(20, 20); | |||
this.addTrait(new KeyboardControls(this)); | |||
this.addTrait(new Physics(this)); | |||
this.bounds.size.set(1, 2); | |||
this.addTrait(new TCollider(this)); | |||
this.addTrait(new TKeyboardController(this)); | |||
this.addTrait(new TPhysics(this)); | |||
} | |||
draw(ctx) { |
@@ -1,12 +1,16 @@ | |||
import Level from './Level'; | |||
import Player from './entities/Player'; | |||
import Vec2 from './Vec2'; | |||
import Level from "./Level.js"; | |||
import Player from "./entities/Player.js"; | |||
import Platform from "./entities/Platform.js"; | |||
import structures from "./structures.js"; | |||
import Vec2 from "./Vec2.js"; | |||
let canvas = document.getElementById("canvas"); | |||
let level = new Level(canvas); | |||
level.spawn(new Player(level), 20, 20); | |||
level.spawnEntity(new Player(level), 10, 1); | |||
level.spawnEntity(new Platform(level), 16, 4); | |||
level.spawnStructure(structures.floor(4), 4, 4); | |||
level.start(); | |||
@@ -1,13 +1,14 @@ | |||
import Structure from './Structure.js'; | |||
import Structure from "./Structure.js"; | |||
export default { | |||
floor: (x, y, width) => { | |||
floor: (width) => { | |||
let attrs = [ "collides" ]; | |||
if (width <= 1) { | |||
return new Structure(x, y, [ { x: 0, y: 0, tile: "grass-lr" }]); | |||
return new Structure(attrs, [ { x: 0, y: 0, tile: "grass-lr" }]); | |||
} else { | |||
return new Structure(x, y, [ | |||
return new Structure(attrs, [ | |||
{ x: 0, y: 0, tile: "grass-l", }, | |||
Array.from({ length: width - 2 }, (x, i) => | |||
Array.from({ length: width - 2 }, (_, i) => | |||
({ x: i + 1, y: 0, tile: "grass" })), | |||
{ x: width - 1, y: 0, tile: "grass-r" }, | |||
]); |
@@ -1,34 +0,0 @@ | |||
import {Trait} from '../Entity.js'; | |||
export default class KeyboardControls extends Trait { | |||
constructor(entity) { | |||
super(entity, "keyboardControls"); | |||
this.speed = 500; | |||
this.map = { | |||
KeyA: 'left', | |||
KeyD: 'right', | |||
}; | |||
this.pressed = {}; | |||
} | |||
onkey(evt) { | |||
let name = this.map[evt.code]; | |||
if (name == null) return; | |||
evt.preventDefault(); | |||
this.pressed[name] = evt.type === 'keydown'; | |||
} | |||
init() { | |||
window.addEventListener("keydown", e => this.onkey(e)); | |||
window.addEventListener("keyup", e => this.onkey(e)); | |||
} | |||
update(dt) { | |||
if (this.pressed.left) | |||
this.entity.velocity.x -= this.speed * dt; | |||
if (this.pressed.right) | |||
this.entity.velocity.x += this.speed * dt; | |||
} | |||
} |
@@ -1,13 +0,0 @@ | |||
import {Trait} from '../Entity.js'; | |||
export default class Physics extends Trait { | |||
constructor(entity) { | |||
super(entity, "physics"); | |||
this.gravity = 9.81; | |||
} | |||
update(dt) { | |||
this.entity.velocity.y += this.gravity * dt; | |||
} | |||
} |
@@ -0,0 +1,27 @@ | |||
import {Trait} from "../Entity.js"; | |||
export default class Collider extends Trait { | |||
constructor(entity) { | |||
super(entity, "collider"); | |||
this.collides = false; | |||
this.cEntity = null; | |||
this.cStructure = null; | |||
} | |||
collideEntity(e) { | |||
this.cEntity = e; | |||
this.collides = true; | |||
} | |||
collideStructure(s) { | |||
this.cStructure = s; | |||
this.collides = true; | |||
} | |||
postUpdate() { | |||
this.cEntity = null; | |||
this.cStructure = null; | |||
this.collides = false; | |||
} | |||
} |
@@ -0,0 +1,62 @@ | |||
import {Trait} from "../Entity.js"; | |||
export default class TKeyboardController extends Trait { | |||
constructor(entity) { | |||
super(entity, "keyboardController", [ "physics" ]); | |||
this.speed = 50; | |||
this.speedAir = 20; | |||
this.jump = 8; | |||
this.jumpTimeMax = 0.4; | |||
this.updrift = 100; | |||
this.map = { | |||
KeyA: 'left', | |||
KeyD: 'right', | |||
Space: "jump", | |||
}; | |||
this.pressed = {}; | |||
this.jumpTime = 0; | |||
this.jumping = false; | |||
} | |||
onkey(evt) { | |||
let name = this.map[evt.code]; | |||
if (name == null) return; | |||
evt.preventDefault(); | |||
this.pressed[name] = evt.type === 'keydown'; | |||
} | |||
init() { | |||
window.addEventListener("keydown", e => this.onkey(e)); | |||
window.addEventListener("keyup", e => this.onkey(e)); | |||
} | |||
update(dt) { | |||
let phys = this.entity.t.physics; | |||
let speed = phys.onGround ? this.speed : this.speedAir; | |||
if (this.pressed.left) | |||
phys.relVelocity.x -= speed * dt; | |||
if (this.pressed.right) | |||
phys.relVelocity.x += speed * dt; | |||
if (phys.onGround && this.pressed.jump) { | |||
phys.relVelocity.y -= this.jump; | |||
this.jumpTime = this.jumpTimeMax; | |||
this.jumping = true; | |||
phys.onGround = false; | |||
} | |||
if (phys.onGround || !this.pressed.jump || this.jumpTime <= 0) | |||
this.jumping = false; | |||
if (this.jumping) { | |||
phys.relVelocity.y -= this.updrift * this.jumpTime * dt; | |||
this.jumpTime -= dt; | |||
} | |||
} | |||
} |
@@ -0,0 +1,84 @@ | |||
import {Trait} from "../Entity.js"; | |||
import Vec2 from "../Vec2.js"; | |||
export default class TPhysics extends Trait { | |||
constructor(entity) { | |||
super(entity, "physics"); | |||
this.gravity = 40; | |||
this.groundFriction = 6; | |||
this.airFriction = 2; | |||
this.velocity = new Vec2(); | |||
this.relVelocity = new Vec2(); | |||
this.onGround = false; | |||
this.prevRelativeTo = null; | |||
this.relativeTo = null; | |||
} | |||
update(dt) { | |||
let collider = this.entity.t.collider; | |||
this.prevRelativeTo = this.relativeTo; | |||
this.relativeTo = null; | |||
this.onGround = false; | |||
if ( | |||
this.entity.has("collider") && | |||
collider.collides && | |||
this.relVelocity.y >= 0) { | |||
// Structures are static; just teleport us to the top of them | |||
if (collider.cStructure) { | |||
this.relVelocity.y = 0; | |||
this.entity.bounds.bottom = collider.cStructure.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.relVelocity.y = 0; | |||
this.entity.bounds.bottom = collider.cEntity.bounds.top; | |||
this.onGround = true; | |||
if (collider.cEntity.has("physics")) { | |||
let cPhys = collider.cEntity.t.physics; | |||
this.relativeTo = cPhys; | |||
} | |||
} | |||
} | |||
this.relVelocity.y += this.gravity * dt; | |||
// If we just started riding something, adjust relative velocity | |||
if (!this.prevRelativeTo && this.relativeTo) { | |||
this.relVelocity.x = this.velocity.x - this.relativeTo.velocity.x; | |||
this.relVelocity.y = this.velocity.y - this.relativeTo.velocity.y; | |||
// If we just stopped riding something, adjust relative velocity | |||
} else if (this.prevRelativeTo && !this.relativeTo) { | |||
this.relVelocity.x = this.velocity.x + this.relativeTo.velocity.x; | |||
this.relVelocity.y = this.velocity.y + this.relativeTo.velocity.y; | |||
} | |||
// Apply friction | |||
var fric = this.onGround ? this.groundFriction : this.airFriction; | |||
var xRatio = 1 / (1 + (dt * fric)); | |||
this.relVelocity.x *= xRatio; | |||
// Update velocity | |||
if (this.relativeTo) { | |||
this.velocity.x = this.relVelocity.x + this.relativeTo.velocity.x; | |||
this.velocity.y = this.relVelocity.y + this.relativeTo.velocity.y; | |||
} else { | |||
this.velocity.x = this.relVelocity.x; | |||
this.velocity.y = this.relVelocity.y; | |||
} | |||
} | |||
postUpdate(dt) { | |||
this.entity.bounds.pos.x += this.velocity.x * dt; | |||
this.entity.bounds.pos.y += this.velocity.y * dt; | |||
} | |||
} |
@@ -0,0 +1,7 @@ | |||
import {Trait} from "../Entity.js"; | |||
export default class TPlatform extends Trait { | |||
constructor(entity) { | |||
super(entity, "platform", [ "collider" ]); | |||
} | |||
} |