Browse Source

lots of physics stuff

master
mortie 6 years ago
parent
commit
f10927e2d8
9 changed files with 169 additions and 68 deletions
  1. 3
    0
      js/Entity.js
  2. 10
    2
      js/Level.js
  3. 17
    4
      js/Rect.js
  4. 5
    4
      js/Structure.js
  5. 25
    1
      js/entities/Platform.js
  6. 1
    1
      js/structures.js
  7. 9
    9
      js/traits/TCollider.js
  8. 15
    4
      js/traits/TKeyboardController.js
  9. 84
    43
      js/traits/TPhysics.js

+ 3
- 0
js/Entity.js View File

constructor(level) { constructor(level) {
this.level = level; this.level = level;
this.bounds = new Rect(); this.bounds = new Rect();
this.time = 0;


this.t = { this.t = {
physics: null, physics: null,
keyboardController: null, keyboardController: null,
collider: null, collider: null,
platform: null, platform: null,
wall: null,
}; };


this.traits = []; this.traits = [];
} }


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


_postUpdate(dt) { _postUpdate(dt) {

+ 10
- 2
js/Level.js View File

this.ctx = canvas.getContext("2d"); this.ctx = canvas.getContext("2d");
this.lastTime = null; this.lastTime = null;
this.raf = null; this.raf = null;
this.minFPS = 10;


this.entities = []; this.entities = [];
this.colliders = []; this.colliders = [];


// Collide with structures // Collide with structures
this.structures.forEach(s => { this.structures.forEach(s => {
if (!s.attrs.collides)
if (!s.attrs.wall && !s.attrs.platform)
return; return;


this.colliders.forEach(ent => { this.colliders.forEach(ent => {
if (this.lastTime != null) { if (this.lastTime != null) {
let dt = (time - this.lastTime) / 1000; let dt = (time - this.lastTime) / 1000;


this.physics(dt);
if (1 / dt > this.minFPS) {
this.physics(dt);
} else {
console.log(
"Too long between updates ("+dt.toFixed(2)+"s, "+
(1 / dt).toFixed(2)+"FPS). Skipping.");
}

this.draw(); this.draw();
} }



+ 17
- 4
js/Rect.js View File

(this.left <= other.right && this.right >= other.left) && (this.left <= other.right && this.right >= other.left) &&
(this.top <= other.bottom && this.bottom >= other.top)); (this.top <= other.bottom && this.bottom >= other.top));
} }
bottomIntersects(other) {
return (
(this.left <= other.right && this.right >= other.left) &&
(this.bottom <= other.bottom && this.bottom >= other.top));

intersectSide(other) {
if (this.midY < other.top)
return "top";
else if (this.midY > other.bottom)
return "bottom";
else if (this.midX < other.left)
return "left";
else
return "right";
} }


contains(other) { contains(other) {
(this.top <= other.top && this.bottom >= other.bottom)); (this.top <= other.top && this.bottom >= other.bottom));
} }


get midX() {
return this.pos.x + (this.size.x / 2);
}
get midY() {
return this.pos.y + (this.size.y / 2);
}

get top() { get top() {
return this.pos.y; return this.pos.y;
} }

+ 5
- 4
js/Structure.js View File

let bottom = (e.y + 1) * assets.tiles.tileHeight; let bottom = (e.y + 1) * assets.tiles.tileHeight;


if (right > bounds.size.x) if (right > bounds.size.x)
bounds.size.x = right;
bounds.size.pixelX = right;
if (bottom > bounds.size.y) if (bottom > bounds.size.y)
bounds.size.y = bottom;
bounds.size.pixelY = bottom;
}); });
} }


this.texture = new Texture(nestedArr); this.texture = new Texture(nestedArr);


this.attrs = { this.attrs = {
collides: false,
wall: false,
platform: false,
} }


for (let a of attrs) for (let a of attrs)


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


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

+ 25
- 1
js/entities/Platform.js View File

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

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

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

this.t.physics.gravity = 0;
this.t.physics.velocity.y = 0;
} }


update(dt) { update(dt) {
let phys = this.t.physics; let phys = this.t.physics;
phys.velocity.y -= phys.gravity * dt;

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

if (this.dir == 1) {
if (phys.velocity.y < this.targetSpeed)
phys.velocity.y += this.accel * dt;
} else {
if (phys.velocity.y > -this.targetSpeed)
phys.velocity.y -= this.accel * dt;
}
} }


draw(ctx) { draw(ctx) {

+ 1
- 1
js/structures.js View File



export default { export default {
floor: width => new Structure( floor: width => new Structure(
[ "collides" ],
[ "wall" ],
tiles.fromLine(width, "grass")), tiles.fromLine(width, "grass")),
}; };

+ 9
- 9
js/traits/TCollider.js View File

super(entity, "collider"); super(entity, "collider");


this.collides = false; this.collides = false;
this.cEntity = null;
this.cStructure = null;
this.cBounds = null;
this.entities = [];
this.structures = [];
this.structureBounds = [];
} }


collideEntity(e) { collideEntity(e) {
this.collides = true; this.collides = true;
this.cEntity = e;
this.cBounds = e.bounds;
this.entities.push(e);
} }


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


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

+ 15
- 4
js/traits/TKeyboardController.js View File

this.jump = 8; this.jump = 8;
this.jumpTimeMax = 0.4; this.jumpTimeMax = 0.4;
this.updrift = 100; this.updrift = 100;
this.jumpLeeway = 0.4;


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


this.jumpTime = 0; this.jumpTime = 0;
this.jumping = false; this.jumping = false;
this.jumped = false
} }


onkey(evt) { onkey(evt) {


update(dt) { update(dt) {
let phys = this.entity.t.physics; let phys = this.entity.t.physics;
let onGround = phys.onGround;


let speed = phys.onGround ? this.speed : this.speedAir; let speed = phys.onGround ? this.speed : this.speedAir;


phys.velocity.x -= speed * dt; phys.velocity.x -= speed * dt;
if (this.pressed.right) if (this.pressed.right)
phys.velocity.x += speed * dt; phys.velocity.x += speed * dt;
if (phys.onGround && this.pressed.jump) {

let canJump =
!this.jumped && onGround &&
this.entity.time -phys.timeLastOnGround < this.jumpLeeway;

if (this.pressed.jump && canJump) {
phys.velocity.y = -this.jump; phys.velocity.y = -this.jump;
console.log("jumping to", phys.velocity.y);
this.entity.bounds.pos.y += phys.velocity.y * dt;
this.jumpTime = this.jumpTimeMax; this.jumpTime = this.jumpTimeMax;
this.jumping = true; this.jumping = true;
phys.onGround = false;
this.jumped = true;
onGround = false;
} }


if (phys.onGround || !this.pressed.jump || this.jumpTime <= 0)
if (onGround || !this.pressed.jump || this.jumpTime <= 0)
this.jumping = false; this.jumping = false;
if (onGround)
this.jumped = false;


if (this.jumping) { if (this.jumping) {
phys.velocity.y -= this.updrift * this.jumpTime * dt; phys.velocity.y -= this.updrift * this.jumpTime * dt;

+ 84
- 43
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.velocity = new Vec2(); this.velocity = new Vec2();


this.onGround = false; this.onGround = false;
this.prevRelativeTo = null;
this.relativeTo = null;
this.groundBounds = null;
this.groundVelocity = null;
this.oldGroundBounds = null;
this.timeLastOnGround = 0;
} }


update(dt) {
let collider = this.entity.t.collider;
collideTop(bounds, absVelocity) {
if (this.onGround || bounds === this.oldGroundBounds)
return;


this.prevRelativeTo = this.relativeTo;
this.relativeTo = null;
this.onGround = false;
this.onGround = true;
this.groundBounds = bounds;
this.groundVelocity = absVelocity;


if (
this.entity.has("collider") &&
collider.collides &&
this.velocity.y >= 0 &&
this.entity.bounds.bottomIntersects(collider.cBounds)) {

// Structures are static; just teleport us to the top of them
if (collider.cStructure) {
this.velocity.y = 0;
this.entity.bounds.bottom = collider.cBounds.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.velocity.y = 0;
this.entity.bounds.bottom = collider.cBounds.top;
this.onGround = true;

if (collider.cEntity.has("physics")) {
let cPhys = collider.cEntity.t.physics;
this.relativeTo = cPhys;
}
}
}
this.velocity.x -= absVelocity.x;
this.velocity.y = 0;
this.entity.bounds.bottom = bounds.top;
}

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

if (side === "top")
return this.collideTop(bounds, absVelocity);
}

collidePlatform(bounds, absVelocity) {
let side = this.entity.bounds.intersectSide(bounds);


if (side === "top")
return this.collideTop(bounds, absVelocity);
}

update(dt) {

// Gravity
if (!this.onGround) if (!this.onGround)
this.velocity.y += this.gravity * dt; this.velocity.y += this.gravity * dt;


// If we just started riding something, adjust relative absVelocity
if (!this.prevRelativeTo && this.relativeTo) {
this.velocity.x = this.absVelocity.x - this.relativeTo.absVelocity.x;
this.velocity.y = this.absVelocity.y - this.relativeTo.absVelocity.y;
if (this.entity.has("collider")) {

// 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");


// If we just stopped riding something, adjust relative absVelocity
} else if (this.prevRelativeTo && !this.relativeTo) {
this.velocity.x += this.prevRelativeTo.absVelocity.x;
this.velocity.y += this.prevRelativeTo.absVelocity.y;
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
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;
} }


// Track the last time we were on the ground
if (this.onGround)
this.timeLastOnGround = this.entity.time;

// Apply friction // Apply friction
var fric = this.onGround ? this.groundFriction : this.airFriction; var fric = this.onGround ? this.groundFriction : this.airFriction;
var xRatio = 1 / (1 + (dt * fric)); var xRatio = 1 / (1 + (dt * fric));
} }


postUpdate(dt) { postUpdate(dt) {
this.jumped = false;


// Update absVelocity // Update absVelocity
if (this.relativeTo) {
this.absVelocity.x = this.velocity.x + this.relativeTo.absVelocity.x;
this.absVelocity.y = this.velocity.y + this.relativeTo.absVelocity.y;
if (this.onGround) {
this.absVelocity.x = this.velocity.x + this.groundVelocity.x;
this.absVelocity.y = this.velocity.y + this.groundVelocity.y;
} else { } else {
this.absVelocity.x = this.velocity.x; this.absVelocity.x = this.velocity.x;
this.absVelocity.y = this.velocity.y; this.absVelocity.y = this.velocity.y;

Loading…
Cancel
Save