Skip to content

Commit

Permalink
feat: Add moving platforms and players with more levels and less gravity
Browse files Browse the repository at this point in the history
  • Loading branch information
johnedvard committed Sep 4, 2022
1 parent fc25def commit 7d9b86b
Show file tree
Hide file tree
Showing 15 changed files with 277 additions and 69 deletions.
44 changes: 44 additions & 0 deletions src/BounceBoard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Vector } from 'kontra';

import { acc } from './constants';
import { lineIntersection } from './utils';

export class BounceBoard {
p1;
p2;
level;

constructor({ p1, p2, level }) {
this.p1 = Vector(p1.x, p1.y);
this.p2 = Vector(p2.x, p2.y);
this.level = level;
}
update() {
this.handlePlayerCollision(this.level.player);
}

render(ctx) {
ctx.beginPath();
ctx.lineWidth = 4;
ctx.strokeStyle = acc;
// TODO (johnedvard) add bounce effect when hitting line
ctx.moveTo(this.p1.x, this.p1.y);
ctx.lineTo(this.p2.x, this.p2.y);

ctx.stroke();
ctx.restore();
}

handlePlayerCollision(player) {
const intersectionPoint = lineIntersection(
this.p1,
this.p2,
player.oldPos,
player.currPos
);
if (intersectionPoint) {
player.oldPos = Vector(player.oldPos.x - 100, player.oldPos.y + 100);
player.currPos = Vector(player.oldPos.x + 100, player.oldPos.y - 100);
}
}
}
24 changes: 22 additions & 2 deletions src/Brick.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import { getDirection, moveBehavior, setBehavior } from './behavior';

export class Brick {
x;
y;
level;
width = 32;
height = 32;
constructor(x, y, { level }) {
speed = 1;
constructor(x, y, { level, behavior, distance }) {
this.direction = getDirection(behavior, distance);
this.distance = Math.abs(distance);
this.behavior = behavior;
this.orgX = x;
this.orgY = y;
this.x = x;
this.y = y;
this.level = level;
Expand All @@ -18,7 +26,19 @@ export class Brick {
height: 12,
};
}
update() {}
update() {
const { axis, newDirection, multiplier } = moveBehavior({
behavior: this.behavior,
distance: this.distance,
direction: this.direction,
x: this.x,
y: this.y,
orgX: this.orgX,
orgY: this.orgY,
});
this.direction = newDirection;
this[axis] += this.speed * multiplier;
}

render(ctx) {
if (!ctx) return;
Expand Down
23 changes: 13 additions & 10 deletions src/Game.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export class Game {
},
render: function () {
if (!game.level) return;
context.save();
game.level.render(game.context);
},
});
Expand All @@ -39,10 +40,13 @@ export class Game {
this.listenForGameEvents();
}

loadLevel(levelId) {
this.level = new Level({ levelId, game: this });
loadLevel({ levelId, levelData }) {
if (levelId) {
this.level = new Level({ levelId, game: this });
} else if (levelData) {
this.level = new Level({ game: this, levelData: JSON.parse(levelData) });
}
}

addPointerListeners() {
onPointer('down', () => {
this.isDragging = true;
Expand All @@ -59,19 +63,18 @@ export class Game {
}
onStartNextLevel = () => {
this.level.destroy();
this.loadLevel(this.level.levelId + 1);
this.loadLevel({ levelId: this.level.levelId + 1 });
};
onStartLevel = ({ levelId, levelData }) => {
if (this.level) {
this.level.destroy();
}
if (levelId) {
this.loadLevel(levelId);
} else if (levelData) {
this.level = new Level({ game: this, levelData: JSON.parse(levelData) });
}
this.loadLevel({ levelId, levelData });
};
onReStartLevel = () => {
this.loadLevel(this.level.levelId);
this.loadLevel({
levelId: this.level.levelId,
levelData: this.level.levelData,
});
};
}
47 changes: 38 additions & 9 deletions src/Level.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import { BounceBoard } from './BounceBoard';
import { Brick } from './Brick';
import { Goal } from './Goal';
import { Heart } from './Heart';
import { Player } from './Player';
import { Saw } from './Saw';
import { lineIntersection } from './utils';
import { isBoxCollision } from './utils';

export class Level {
player;
saws = [];
goals = [];
hearts = [];
bricks = [];
bounceBoards = [];
isLevelLoaded = false;
levelId;
levelData;
constructor({ game, levelId, levelData }) {
this.levelId = levelId;
this.levelData = levelData;
this.game = game;
if (levelData) {
setTimeout(() => {
Expand All @@ -32,6 +38,7 @@ export class Level {
this.createGoals(levelData);
this.createHearts(levelData);
this.createBricks(levelData);
this.createBounceBoards(levelData);
this.isLevelLoaded = true;
}

Expand Down Expand Up @@ -61,6 +68,9 @@ export class Level {
this.bricks.forEach((brick) => {
brick.render(ctx);
});
this.bounceBoards.forEach((board) => {
board.render(ctx);
});
this.player.render(ctx);
this.goals.forEach((goal) => {
goal.render(ctx);
Expand All @@ -83,6 +93,9 @@ export class Level {
this.bricks.forEach((brick) => {
brick.update();
});
this.bounceBoards.forEach((board) => {
board.update();
});
}

createGoals(levelData) {
Expand All @@ -99,6 +112,7 @@ export class Level {
}

createSaws(levelData) {
if (!levelData.s) return;
levelData.s.forEach((saw) => {
// TODO (johnedvard) Add actual saw behaviour
this.saws.push(
Expand All @@ -108,14 +122,31 @@ export class Level {
}

createHearts(levelData) {
if (!levelData.h) return;
levelData.h.forEach((heart) => {
this.hearts.push(new Heart(heart.x, heart.y, { level: this }));
});
}

createBricks(levelData) {
if (!levelData.b) return;
levelData.b.forEach((brick) => {
this.bricks.push(new Brick(brick.x, brick.y, { level: this }));
this.bricks.push(
new Brick(brick.x, brick.y, {
behavior: brick.b,
distance: brick.d,
level: this,
})
);
});
}

createBounceBoards(levelData) {
if (!levelData.bb) return;
levelData.bb.forEach((board) => {
this.bounceBoards.push(
new BounceBoard({ p1: board.p1, p2: board.p2, level: this })
);
});
}

Expand All @@ -125,13 +156,11 @@ export class Level {
for (let i = 0; i < rope.length - 2; i++) {
this.saws.forEach((saw) => {
if (
// TODO (johnedvard) add to y-axis if saw is up down
lineIntersection(
{ x: saw.x - 5, y: saw.y - 5 },
{ x: saw.x + 5, y: saw.y + 5 },
{ x: rope.nodes[i].x, y: rope.nodes[i].y },
{ x: rope.nodes[i + 1].x, y: rope.nodes[i + 1].y }
)
isBoxCollision(rope.nodes[i], {
width: saw.width * saw.scale,
height: saw.height * saw.scale,
...saw,
})
) {
this.player.rope.cutRope(i);
}
Expand Down
47 changes: 43 additions & 4 deletions src/Player.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,42 @@
import skull from 'data-url:./assets/img/skull.png';

import { getPointer, Sprite, on } from 'kontra';
import { getPointer, Sprite, on, Vector } from 'kontra';
import { PlayerControls } from './PlayerControls';
import { fgc2 } from './constants';
import { ARCADIAN_HEAD_SELECTED, CUT_ROPE, GOAL_COLLISION } from './gameEvents';
import { Rope } from './Rope';
import { getSelectedArcadian } from './store';
import { createSprite } from './utils';
import { getDirection, moveBehavior, setBehavior } from './behavior';

export class Player {
game;
rope = []; // list of pointmasses
rope = [];
playerControls;
scale = 4;
sprite = { render: () => {}, x: 0, y: 0 }; // draw sprite on pointmass position
sprite = { render: () => {}, x: 0, y: 0 };
headSprite = { render: () => {}, x: 0, y: 0 }; // From Arcadian API
hasWon = false;
headImg;
headOffset = { x: 10, y: 38 };
isLeft = false;
isRopeCut = false;
anchorNodeSpeed = 1;
anchorNodeDirection;
anchorNodeOrgPos;

constructor({ game, levelData }) {
levelData.p = levelData.p || {};
this.anchorNodeDirection = getDirection(levelData.p.b, levelData.p.d);
this.distance = Math.abs(levelData.p.d);
this.behavior = levelData.p.b;
this.game = game;
const ropeLength = levelData.r;
const startX = levelData.p.x;
const startY = levelData.p.y;
this.anchorNodeOrgPos = Vector(startX, startY);
this.headImg = getSelectedArcadian().img || { width: 0, height: 0 };
this.createRope({ startX, startY, ropeLength });
this.createRope({ startX, startY, ropeLength, levelData });
createSprite({
x: startX,
y: startY,
Expand Down Expand Up @@ -139,10 +148,26 @@ export class Player {
this.rope.endNode.pos.y - this.headImg.height + +this.headOffset.y;

this.updateRope();
this.updateAnchorNode();
// this.dragRope(); // TODO (johnedvard) Only enable in local and beta env
this.playerControls.updateControls();
}

updateAnchorNode() {
const anchorNode = this.rope.anchorNode;
const { axis, newDirection, multiplier } = moveBehavior({
behavior: this.behavior,
distance: this.distance,
direction: this.anchorNodeDirection,
x: anchorNode.pos.x,
y: anchorNode.pos.y,
orgX: this.anchorNodeOrgPos.x,
orgY: this.anchorNodeOrgPos.y,
});
this.anchorNodeDirection = newDirection;
anchorNode.pos[axis] += this.anchorNodeSpeed * multiplier;
}

climbRope() {
this.rope.climbRope();
}
Expand Down Expand Up @@ -171,4 +196,18 @@ export class Player {
onCutRope = ({ rope }) => {
this.isRopeCut = true;
};

get currPos() {
return this.rope.endNode.pos;
}
get oldPos() {
return this.rope.endNode.oldPos;
}

set currPos(pos) {
this.rope.endNode.currPos = pos;
}
set oldPos(pos) {
this.rope.endNode.oldPos = pos;
}
}
8 changes: 7 additions & 1 deletion src/Rope.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { emit, Vector } from 'kontra';

import { fgc2, gravity, RESTING_DISTANCE } from './constants';
import { CUT_ROPE } from './gameEvents';
import { gameHeight, gameWidth } from './store';
Expand Down Expand Up @@ -48,7 +49,7 @@ export class Rope {
if (n === this.nodes[0]) n.pos = this.anchor;
const vxy = n.pos.subtract(n.oldPos);
n.oldPos = n.pos;
n.pos = n.pos.add(vxy).add(Vector(0, gravity));
n.pos = n.pos.add(vxy).add(Vector(0, gravity * n.mass));
});
}
constrainNodes() {
Expand Down Expand Up @@ -150,4 +151,9 @@ export class Rope {
get endNode() {
return this.nodes[this.nodes.length - 1];
}

get anchorNode() {
if (!this.nodes.length) return {};
return this.nodes[0];
}
}
Loading

0 comments on commit 7d9b86b

Please sign in to comment.