import Rect from './Rect.js'; | |||||
import Vec2 from './Vec2.js'; | |||||
import Rect from "./Rect.js"; | |||||
export class Trait { | export class Trait { | ||||
constructor(entity, name) { | |||||
constructor(entity, name, deps = []) { | |||||
this.name = name; | this.name = name; | ||||
this.entity = entity; | this.entity = entity; | ||||
this.enabled = true; | 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() {} | init() {} | ||||
update(dt) {} | update(dt) {} | ||||
postUpdate(dt) {} | |||||
} | } | ||||
export default class Entity { | export default class Entity { | ||||
constructor(level) { | constructor(level) { | ||||
this.level = level; | this.level = level; | ||||
this.bounds = new Rect(); | this.bounds = new Rect(); | ||||
this.velocity = new Vec2(); | |||||
this.t = {}; | |||||
this.t = { | |||||
physics: null, | |||||
keyboardController: null, | |||||
collider: null, | |||||
platform: null, | |||||
}; | |||||
this.traits = []; | this.traits = []; | ||||
this.updateTraits = []; | |||||
this.postUpdateTraits = []; | |||||
} | } | ||||
init() { | |||||
_init() { | |||||
for (let trait of this.traits) { | 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) | if (trait.enabled) | ||||
trait.update(dt); | 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) {} | draw(ctx) {} | ||||
has(name) { | has(name) { | ||||
} | } | ||||
addTrait(t) { | addTrait(t) { | ||||
if (this.t[t.name] === undefined) | |||||
throw new Error("Invalid trait:", t); | |||||
this.t[t.name] = t; | this.t[t.name] = t; | ||||
this.traits.push(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); | |||||
} | } | ||||
} | } |
import Player from './entities/Player.js'; | |||||
import structures from './structures.js'; | |||||
import structures from "./structures.js"; | |||||
export default class Level { | export default class Level { | ||||
constructor(canvas) { | constructor(canvas) { | ||||
this.step = 1 / 120; | |||||
this.stepLimit = 2; | |||||
this.canvas = canvas; | this.canvas = canvas; | ||||
this.ctx = canvas.getContext("2d"); | this.ctx = canvas.getContext("2d"); | ||||
this.lastTime = null; | this.lastTime = null; | ||||
this.raf = null; | this.raf = null; | ||||
this.timeAcc = 0; | |||||
this.entities = []; | 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); | ent.bounds.pos.set(x, y); | ||||
this.entities.push(ent); | 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) { | physics(dt) { | ||||
this.entities.forEach(ent => | 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); | |||||
}); | |||||
}); | }); | ||||
} | } | ||||
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.entities.forEach(ent => ent.draw(this.ctx)); | ||||
this.structure.draw(this.ctx); | |||||
this.structures.forEach(struct => struct.draw(this.ctx)); | |||||
} | } | ||||
update(time) { | update(time) { | ||||
if (this.lastTime != null) { | if (this.lastTime != null) { | ||||
let dt = (time - this.lastTime) / 1000; | 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(); | this.draw(); | ||||
} | } | ||||
import Vec2 from './Vec2.js'; | |||||
import Vec2 from "./Vec2.js"; | |||||
export default class Rect { | export default class Rect { | ||||
constructor(pos = new Vec2(), size = new Vec2()) { | constructor(pos = new Vec2(), size = new Vec2()) { | ||||
} | } | ||||
draw(ctx) { | 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.closePath(); | ||||
ctx.stroke(); | 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() { | get top() { | ||||
return this.pos.y; | return this.pos.y; | ||||
} | } | ||||
set top(n) { | set top(n) { | ||||
this.pos.y = 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() { | get bottom() { | ||||
return this.pos.y + this.size.y; | 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() { | get left() { | ||||
return this.pos.x; | 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() { | get right() { | ||||
set right(n) { | set right(n) { | ||||
this.pos.x = n - this.size.x; | 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; | |||||
} | |||||
} | } |
export default class SpriteSheet { | export default class SpriteSheet { | ||||
constructor(url, tilew = 1, tileh = 1, scale = 1) { | |||||
constructor(url, tilew, tileh, scale = 1) { | |||||
this.url = url; | this.url = url; | ||||
this.tilew = tilew; | this.tilew = tilew; | ||||
this.tileh = tileh; | this.tileh = tileh; |
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 => { | 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 => { | 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 { | 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.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.width = this.bounds.size.x; | ||||
this.can.height = this.bounds.size.y; | 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) { | draw(ctx) { | ||||
ctx.drawImage( | 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); | 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; | |||||
} | |||||
} | } |
let meter = 32; | |||||
export default class Vec2 { | export default class Vec2 { | ||||
constructor(x = 0, y = 0) { | constructor(x = 0, y = 0) { | ||||
this.x = x; | this.x = x; | ||||
this.x = x; | this.x = x; | ||||
this.y = y; | 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; | |||||
} | |||||
} | } |
import SpriteSheet from './SpriteSheet.js'; | |||||
import SpriteSheet from "./SpriteSheet.js"; | |||||
export default { | export default { | ||||
tiles: new SpriteSheet("assets/tiles.png", 32, 32, 2) | tiles: new SpriteSheet("assets/tiles.png", 32, 32, 2) |
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); | |||||
} | |||||
} |
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 { | export default class Player extends Entity { | ||||
constructor(level) { | constructor(level) { | ||||
super(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) { | draw(ctx) { |
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 canvas = document.getElementById("canvas"); | ||||
let level = new Level(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(); | level.start(); | ||||
import Structure from './Structure.js'; | |||||
import Structure from "./Structure.js"; | |||||
export default { | export default { | ||||
floor: (x, y, width) => { | |||||
floor: (width) => { | |||||
let attrs = [ "collides" ]; | |||||
if (width <= 1) { | 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 { | } else { | ||||
return new Structure(x, y, [ | |||||
return new Structure(attrs, [ | |||||
{ x: 0, y: 0, tile: "grass-l", }, | { 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: i + 1, y: 0, tile: "grass" })), | ||||
{ x: width - 1, y: 0, tile: "grass-r" }, | { x: width - 1, y: 0, tile: "grass-r" }, | ||||
]); | ]); |
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; | |||||
} | |||||
} |
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; | |||||
} | |||||
} |
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; | |||||
} | |||||
} |
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; | |||||
} | |||||
} | |||||
} |
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; | |||||
} | |||||
} |
import {Trait} from "../Entity.js"; | |||||
export default class TPlatform extends Trait { | |||||
constructor(entity) { | |||||
super(entity, "platform", [ "collider" ]); | |||||
} | |||||
} |