Browse Source

some stuff

master
mortie 6 years ago
parent
commit
d4ccb930a9

+ 5
- 7
js/Entity.js View File

import Rect from "./Rect.js"; import Rect from "./Rect.js";
import Vec2 from "./Vec2.js";


export class Trait { export class Trait {
constructor(entity, name, deps = []) { constructor(entity, name, deps = []) {
constructor(level, layerName) { constructor(level, layerName) {
this.layerName = layerName; this.layerName = layerName;
this.level = level; this.level = level;
this.bounds = new Rect();
this.pos = new Vec2();
this.bounds = new Rect(this.pos);
this.time = 0; this.time = 0;


this.traitOrder = [ this.traitOrder = [
"platform", "platform",
"wall", "wall",
"keyboardController", "keyboardController",
"behavior",
"physics", "physics",
]; ];
this.t = { this.t = {
platform: null, platform: null,
wall: null, wall: null,
keyboardController: null, keyboardController: null,
behavior: null,
physics: null, physics: null,
}; };


trait.update(dt); trait.update(dt);
} }


this.update(dt);
this.time += dt; this.time += dt;
} }


if (trait.enabled) if (trait.enabled)
trait.postUpdate(dt); trait.postUpdate(dt);
} }

this.postUpdate(dt);
} }


init() {} init() {}
update(dt) {}
postUpdate(dt) {}

draw(ctx) {} draw(ctx) {}


has(name) { has(name) {

+ 4
- 6
js/Level.js View File

} }


spawnEntity(ent, x, y) { spawnEntity(ent, x, y) {
ent.bounds.pos.set(x, y);
ent.pos.set(x, y);


let layerIdx = this.layerOrder[ent.layerName]; let layerIdx = this.layerOrder[ent.layerName];
console.log(ent.layerName, this.layerOrder, layerIdx);
if (layerIdx == null) if (layerIdx == null)
throw new Error("Unknown layer name: "+ent.layerName); throw new Error("Unknown layer name: "+ent.layerName);


} }


spawnStructure(structure, x, y) { spawnStructure(structure, x, y) {
structure.bounds.pos.set(x, y);
structure.pos.set(x, y);
this.structures.push(structure); this.structures.push(structure);
structure.init(); structure.init();
} }
return; return;


this.colliders.forEach(ent => { this.colliders.forEach(ent => {
let bounds = s.collidesWith(ent.bounds);
if (!bounds)
if (!s.bounds.intersects(ent.bounds))
return; return;


ent.t.collider.collideStructure(s, bounds);
ent.t.collider.collideStructure(s);
}); });
}); });



+ 3
- 71
js/Structure.js View File

return rightOrBelow(r1, r2) || rightOrBelow(r2, r1); return rightOrBelow(r1, r2) || rightOrBelow(r2, r1);
} }


function findGeometry(bounds, arr, geometry) {
let allBounds = [];
arr.forEach(e => {
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 flattened(arr, newArr = []) { function flattened(arr, newArr = []) {
arr.forEach(e => { arr.forEach(e => {
if (e instanceof Array) if (e instanceof Array)
this.setAttr(a); this.setAttr(a);


this.nestedArr = nestedArr; this.nestedArr = nestedArr;
this.bounds = new Rect();
this.geometry = [];
this.pos = new Vec2();
this.bounds = new Rect(this.pos);
} }


init() { init() {
var arr = flattened(this.nestedArr); var arr = flattened(this.nestedArr);


findBounds(arr, this.bounds); findBounds(arr, this.bounds);

if (this.attrs.wall || this.attrs.platform) {
findGeometry(this.bounds, arr, this.geometry);
}
} }


draw(ctx) { draw(ctx) {
this.texture.draw(ctx, this.bounds.pos.pixelX, this.bounds.pos.pixelY);
this.texture.draw(ctx, this.pos.pixelX, this.pos.pixelY);
} }


setAttr(attr) { setAttr(attr) {
this.attrs[attr] = true; this.attrs[attr] = true;
return this; 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;
}
} }

+ 90
- 24
js/Tile.js View File

} }
} }


Tile.createLine = function(width, name, x = 0, y = 0) {
if (width <= 1) {
return [ new Tile(x, y, name+"-lr") ];
function realNames(base, override = {}) {
return {
topLeft: override.topLeft || base+"-top-l",
top: override.top || base+"-top",
topRight: override.topRight || base+"-top-r",
topBoth: override.topBoth || base+"-top-lr",

left: override.left || base+"-l",
mid: override.mid || base,
right: override.right || base+"-r",
both: override.both || base+"-lr",

bottomLeft: override.bottomLeft || base+"-bottom-l",
bottom: override.bottom || base+"-bottom",
bottomRight: override.bottomRight || base+"-bottom-r",
bottomBoth: override.bottomBoth || base+"-bottom-lr",
};
}

function getName(x, y, size, names) {
let name = null;

if (size.x <= 1) {
if (y === 0)
name = names.topBoth;
else if (y === size.y - 1)
name = names.bottomBoth;
else
name = names.both;
} else { } else {
return [
new Tile(x, y, name+"-l"),
Array.from({ length: width - 2 }, (_, i) =>
new Tile(x + i + 1, y, name)),
new Tile(x + width - 1, y, name+"-r"),
];
if (y === 0) {
if (x === 0)
name = names.topLeft;
else if (x === size.x - 1)
name = names.topRight;
else
name = names.top;
} else if (y === size.y - 1) {
if (x === 0)
name = names.bottomLeft;
else if (x === size.x - 1)
name = names.bottomRight;
else
name = names.bottom;
} else {
if (x === 0)
name = names.left;
else if (x === size.x - 1)
name = names.right;
else
name = names.mid;
}
} }

if (!name)
name = "--invalid-"+x+"-"+y+"--";

return name;
} }


Tile.createBox = function(width, height, nameTop, nameMid, nameBottom) {
if (height <= 1) {
return Tile.createLine(width, nameTop);
} else if (height <= 2) {
return [
Tile.createLine(width, nameTop),
Tile.createLine(width, nameBottom),
];
} else {
return [
Tile.createLine(width, nameTop),
Array.from({ length: height - 2 }, (_, i) =>
Tile.createLine(width, nameMid, 0, i + 1)),
Tile.createLine(width, nameBottom, 0, height - 1),
];
Tile.createBox = function(pos, size, base, names) {
names = realNames(base, names);
let arr = [];

for (let x = 0; x < size.x; ++x) {
for (let y = 0; y < size.y; ++y) {
arr.push(new Tile(
pos.x + x, pos.y + y, getName(x, y, size, names)));
}
} }

return arr;
}

function getNameLine(x, width, names) {
if (width === 1)
return names.both;
else if (x === 0)
return names.left;
else if (x === width - 1)
return names.right;
else
return names.mid;
}

Tile.createLine = function(pos, width, base, names) {
names = realNames(base, names);
let arr = [];

for (let x = 0; x < width; ++x) {
arr.push(new Tile(
pos.x + x, pos.y, getNameLine(x, width, names)));
}

return arr;
} }

+ 2
- 0
js/Vec2.js View File

this.y = Math.floor(y) / meter; this.y = Math.floor(y) / meter;
} }
} }

Vec2.zero = new Vec2();

+ 1
- 1
js/assets.js View File

.defineTile("broken-platform-lr", 3, 3), .defineTile("broken-platform-lr", 3, 3),


entities: new SpriteSheet("assets/entities.png", 19, 32, 2) entities: new SpriteSheet("assets/entities.png", 19, 32, 2)
.defineTile("player-head", 0, 0),
.defineTile("player", 0, 0),
} }

+ 56
- 40
js/entities/BrokenPlatform.js View File

import Entity from "../Entity.js"; import Entity from "../Entity.js";
import {Trait} from "../Entity.js";
import Texture from "../Texture.js"; import Texture from "../Texture.js";
import Tile from "../Tile.js"; import Tile from "../Tile.js";
import Shaker from "../Shaker.js"; import Shaker from "../Shaker.js";
import Vec2 from "../Vec2.js";
import assets from "../assets.js"; import assets from "../assets.js";


import TPlatform from "../traits/TPlatform.js"; import TPlatform from "../traits/TPlatform.js";
import TCollider from "../traits/TCollider.js"; import TCollider from "../traits/TCollider.js";
import TPhysics from "../traits/TPhysics.js"; import TPhysics from "../traits/TPhysics.js";


export default class BrokenPlatform extends Entity {
constructor(level, width = 5) {
super(level, "platform");

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, "broken-platform"));
class Behavior extends Trait {
constructor(entity) {
super(entity, "behavior");


this.shaker = new Shaker(); this.shaker = new Shaker();
this.shake = 0; this.shake = 0;
this.shakeAccel = 14; this.shakeAccel = 14;
this.shaking = false;
this.floating = true;
this.reliableTime = 1; this.reliableTime = 1;
this.resetTime = 4; this.resetTime = 4;

this.state = 0;
this.timer = 0;
} }


init() { init() {
this.initialPos = this.bounds.pos.clone();
this.initialPos = this.entity.pos.clone();
} }


reset() { reset() {
this.shake = 0; this.shake = 0;
this.shaking = false;
this.floating = true;
this.t.physics.velocity.set(0, 0);
this.bounds.pos.set(this.initialPos.x, this.initialPos.y);
}

startShake() {
this.shake = 0;
this.shaking = true

setTimeout(() => {
this.floating = false;
this.shaking = false;
this.entity.t.physics.velocity.set(0, 0);
this.entity.pos.set(this.initialPos.x, this.initialPos.y);
this.entity.t.physics.moved = true;


setTimeout(() => {
this.reset();
}, this.resetTime * 1000);
}, this.reliableTime * 1000);
this.state = 0;
this.timer = 0;
} }


update(dt) { update(dt) {
if (!this.shaking && !this.falling) {
this.t.collider.entities.forEach(e => {
if (this.state === 0) {
this.entity.t.collider.entities.forEach(e => {
let fall = let fall =
e.has("physics") && e.has("physics") &&
e.bounds.intersectSide(this.bounds) === "top";
e.bounds.intersectSide(this.entity.bounds) === "top";


if (fall) { if (fall) {
this.startShake();
this.timer = this.reliableTime;
this.state = 1;
} }
}); });
} }


if (this.floating)
this.t.physics.velocity.y -= this.t.physics.gravity * dt;
if (this.shaking) {
if (this.state !== 2)
this.entity.t.physics.velocity.y -=
this.entity.t.physics.gravity * dt;

if (this.state === 1) {
this.shaker.shake(this.shake); this.shaker.shake(this.shake);
this.shake += this.shakeAccel * dt; this.shake += this.shakeAccel * dt;
this.timer -= dt;

if (this.timer <= 0) {
this.state = 2;
this.timer = this.resetTime;
}
} else if (this.state === 2) {
this.timer -= dt;

if (this.timer <= 0) {
this.reset();
}
} }


this.shaker.update(dt); this.shaker.update(dt);
} }
}

export default class BrokenPlatform extends Entity {
constructor(level, width = 5) {
super(level, "platform");

this.bounds.size.set(width, 0.5);
this.addTrait(new TCollider(this));
this.addTrait(new TPlatform(this));
this.addTrait(new TPhysics(this));
this.addTrait(new Behavior(this));

this.texture = new Texture(assets.tiles, Tile.createLine(
Vec2.zero, this.bounds.size.x,
"broken-platform"));
}


draw(ctx) { draw(ctx) {
let shaker = this.t.behavior.shaker;

this.texture.draw( this.texture.draw(
ctx, ctx,
this.bounds.pos.pixelX + this.shaker.vec.pixelX,
this.bounds.pos.pixelY + this.shaker.vec.pixelY);
this.pos.pixelX + shaker.vec.pixelX,
this.pos.pixelY + shaker.vec.pixelY);
} }
} }

+ 5
- 5
js/entities/FloatingPlatform.js View File

this.addTrait(new TPhysics(this)); this.addTrait(new TPhysics(this));


this.texture = new Texture(assets.tiles, this.texture = new Texture(assets.tiles,
Tile.createLine(this.bounds.size.x, "platform"));
Tile.createSidedLine(this.bounds.size.x, "platform"));


this.targetSpeed = 3; this.targetSpeed = 3;
this.accel = 5; this.accel = 5;
} }


init() { init() {
this.targetTop = this.bounds.pos.y;
this.targetTop = this.pos.y;
this.targetBottom = this.targetTop + this.distance; this.targetBottom = this.targetTop + this.distance;


this.t.physics.gravity = 0; this.t.physics.gravity = 0;
update(dt) { update(dt) {
let phys = this.t.physics; let phys = this.t.physics;


if (this.bounds.pos.y <= this.targetTop)
if (this.pos.y <= this.targetTop)
this.dir = 1; this.dir = 1;
else if (this.bounds.pos.y >= this.targetBottom)
else if (this.pos.y >= this.targetBottom)
this.dir = -1; this.dir = -1;


if (this.dir == 1) { if (this.dir == 1) {
} }


draw(ctx) { draw(ctx) {
this.texture.drawAt(ctx, this.bounds.pos);
this.texture.drawAt(ctx, this.pos);
} }
} }

+ 3
- 3
js/entities/Player.js View File

this.addTrait(new TPhysics(this)); this.addTrait(new TPhysics(this));


this.texture = new Texture(assets.entities, [ this.texture = new Texture(assets.entities, [
new Tile(0, 0, "player-head"),
new Tile(0, 0, "player"),
]); ]);
} }


draw(ctx) { draw(ctx) {
let x = let x =
this.bounds.pos.pixelX -
this.pos.pixelX -
assets.entities.scale * 1.5 - assets.entities.scale * 1.5 -
this.bounds.size.pixelX / 2 this.bounds.size.pixelX / 2


this.texture.draw(ctx, x, this.bounds.pos.pixelY);
this.texture.draw(ctx, x, this.pos.pixelY);
} }
} }

+ 5
- 3
js/main.js View File

let level = new Level(canvas); let level = new Level(canvas);


level.spawnEntity(new Player(level), 10, 1); level.spawnEntity(new Player(level), 10, 1);
level.spawnEntity(new FloatingPlatform(level), 16, 4);
level.spawnStructure(structures.floor(8, 6), 4, 4);


level.spawnEntity(new BrokenPlatform(level), 27, 4);
level.spawnStructure(structures.ground(new Vec2(22, 6)), 8, 8);
level.spawnStructure(structures.groundPillar(new Vec2(2, 3)), 14, 6);
level.spawnStructure(structures.groundPillar(new Vec2(1, 5)), 20, 4);

level.spawnEntity(new BrokenPlatform(level), 22, 6);


level.start(); level.start();



+ 19
- 2
js/structures.js View File

import Structure from "./Structure.js"; import Structure from "./Structure.js";
import Vec2 from "./Vec2.js";
import Tile from "./Tile.js"; import Tile from "./Tile.js";


export default { export default {
floor: (width, height = 1)=> new Structure(
ground: size => new Structure(
[ "wall" ], [ "wall" ],
Tile.createBox(width, height, "ground-top", "ground", "ground")),
Tile.createBox(
Vec2.zero, size, "ground", {
bottomLeft: "ground-l",
bottom: "ground",
bottomRight: "ground-r",
bottomBoth: "ground",
})),

groundPillar: size => new Structure(
[ "wall" ],
Tile.createBox(
Vec2.zero, size, "ground", {
bottomLeft: "ground",
bottom: "ground",
bottomRight: "ground",
bottomBoth: "ground",
})),
}; };

+ 1
- 3
js/traits/TCollider.js View File

this.entities.push(e); this.entities.push(e);
} }


collideStructure(s, b) {
collideStructure(s) {
this.collides = true; this.collides = true;
this.structures.push(s); this.structures.push(s);
this.structureBounds.push(b);
} }


postUpdate() { postUpdate() {
this.entities.length = 0; this.entities.length = 0;
this.structures.length = 0; this.structures.length = 0;
this.structureBounds.length = 0;
this.collides = false; this.collides = false;
} }
} }

+ 3
- 3
js/traits/TKeyboardController.js View File



this.jump = 8; this.jump = 8;
this.jumpTimeMax = 0.4; this.jumpTimeMax = 0.4;
this.updrift = 100;
this.jumpLeeway = 0.2;
this.updrift = 87;
this.jumpLeeway = 0.1;


this.map = { this.map = {
KeyA: 'left', KeyA: 'left',


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.pos.y += phys.velocity.y * dt;
this.jumpTime = this.jumpTimeMax; this.jumpTime = this.jumpTimeMax;
this.jumping = true; this.jumping = true;
this.jumped = true; this.jumped = true;

+ 22
- 15
js/traits/TPhysics.js View File

import {Trait} from "../Entity.js"; import {Trait} from "../Entity.js";
import Vec2 from "../Vec2.js"; import Vec2 from "../Vec2.js";


let zeroVector = new Vec2(0, 0);

export default class TPhysics extends Trait { export default class TPhysics extends Trait {
constructor(entity) { constructor(entity) {
super(entity, "physics"); super(entity, "physics");
this.onGround = false; this.onGround = false;
this.groundBounds = null; this.groundBounds = null;
this.groundVelocity = null; this.groundVelocity = null;
this.moved = false;
} }


collideTop(bounds, velocity) { collideTop(bounds, velocity) {
collideWall(bounds, velocity) { collideWall(bounds, velocity) {
let side = this.entity.bounds.intersectSide(bounds); let side = this.entity.bounds.intersectSide(bounds);


if (side === "top")
return this.collideTop(bounds, velocity);
if (side === "top") {
this.collideTop(bounds, velocity);
} else if (side === "left" && this.velocity.x > velocity.x) {
this.velocity.x = velocity.x;
this.entity.bounds.right = bounds.left;
} else if (side === "right" && this.velocity.x < velocity.x) {
this.velocity.x = velocity.x;
this.entity.bounds.left = bounds.right;
}
} }


collidePlatform(bounds, velocity) { collidePlatform(bounds, velocity) {
update(dt) { update(dt) {


// Collide // Collide
if (this.entity.has("collider")) {
if (this.entity.has("collider") && !this.moved) {
let collider = this.entity.t.collider; let collider = this.entity.t.collider;
this.groundBounds = null; this.groundBounds = null;
this.groundVelocity = null; this.groundVelocity = null;


collider.entities.forEach(e => { collider.entities.forEach(e => {
let vel = zeroVector;
let vel = Vec2.zero;
if (e.has("physics")) if (e.has("physics"))
vel = e.t.physics.velocity; vel = e.t.physics.velocity;


else if (e.has("platform")) else if (e.has("platform"))
this.collidePlatform(e.bounds, vel); this.collidePlatform(e.bounds, vel);
}); });
collider.structures.forEach((s, i) => {
let bounds = collider.structureBounds[i];
collider.structures.forEach((s) => {
if (s.attrs.wall) if (s.attrs.wall)
this.collideWall(bounds, zeroVector);
this.collideWall(s.bounds, Vec2.zero);
else if (s.attrs.platform) else if (s.attrs.platform)
this.collidePlatform(bounds, zeroVector);
this.collidePlatform(s.bounds, Vec2.zero);
}); });
} }


this.onGround = this.onGround =
(this.groundVelocity && this.velocity.y >= this.groundVelocity.y);
this.groundVelocity && this.velocity.y >= this.groundVelocity.y;


if (this.onGround) {
if (this.onGround && !this.moved) {
this.timeLastOnGround = this.entity.time; this.timeLastOnGround = this.entity.time;
this.velocity.y = this.groundVelocity.y; this.velocity.y = this.groundVelocity.y;
} }
postUpdate(dt) { postUpdate(dt) {


// Move // Move
this.entity.bounds.pos.x += this.velocity.x * dt;
this.entity.bounds.pos.y += this.velocity.y * dt;
this.entity.pos.x += this.velocity.x * dt;
this.entity.pos.y += this.velocity.y * dt;


if (this.onGround)
if (this.onGround && !this.moved)
this.entity.bounds.bottom = this.groundBounds.top; this.entity.bounds.bottom = this.groundBounds.top;

this.moved = false;
} }
} }

Loading…
Cancel
Save