Skip to content

Instantly share code, notes, and snippets.

@slacktracer
Created November 13, 2021 19:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save slacktracer/ec686b749a41e2bef0f1dc454175cc86 to your computer and use it in GitHub Desktop.
Save slacktracer/ec686b749a41e2bef0f1dc454175cc86 to your computer and use it in GitHub Desktop.
side scroller v0.6 (matter.js)
<div id="keysleft">
move: WASD
<br>hold/drop: E
<br>pause: F
<br>gravity: R
</div>
<div id="keysright">
zoom: + -
<br>fire: click
<br>testing: T
</div>
<canvas id="canvas"></canvas>
<!-- <svg class='background'>
<defs>
<style type="text/css"><![CDATA[
.keyrect {
stroke: #333;
fill: "#555";
rx: 5;
}
]]></style>
</defs>
<g id = 'background'>
<circle cx="500" cy="-300" r="100" fill="#d67" opacity='1' stroke-width="0"/>
<rect x="-600" y="200" width="400" height="400" fill='#021' opacity="0.1" stroke-width="0"/>
</g>
</svg> -->
<!-- <svg class='foreground'>
<defs>
<linearGradient id="vert_shadow" x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stop-color="#000" stop-opacity="0.3"/>
<stop offset="100%" stop-color="#000" stop-opacity="0.1"/>
</linearGradient>\
<linearGradient id="sunset" x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stop-color="#f00" stop-opacity="0.3"/>
<stop offset="100%" stop-color="#000" stop-opacity="0"/>
</linearGradient>
<radialGradient id="darkcircle">
<stop offset="0%" stop-color="#000" stop-opacity="0.6"/>
<stop offset="100%" stop-color="#000" stop-opacity="0"/>
</radialGradient>
<radialGradient id="sunsetRad">
<stop offset="0%" stop-color="#f00" stop-opacity="0.4"/>
<stop offset="100%" stop-color="#000" stop-opacity="0"/>
</radialGradient>
<style type="text/css"><![CDATA[
.keyrect {
stroke: #333;
fill: "#555";
rx: 5;
}
]]></style>
</defs>
<g id = 'foreground'>
<circle cx="500" cy="-300" r="1200" fill='url(#sunsetRad)' stroke-width="0"/>
<rect x="450" y="664" width="500" height="237" fill='#000' opacity="0.1" stroke-width="0"/>
<rect x="-600" y="794" width="400" height="106" fill='url(#vert_shadow)' stroke-width="0"/>
</g>
</svg> -->
<svg class='intro' onclick="runPlatformer(this)" viewBox="0 0 800 800">
<defs>
<style type="text/css"><![CDATA[
.keyrect {
stroke: #333;
fill: "#555";
rx: 5;
}
.letter {
fill: #ccc;
font-family: sans-serif;
font-weight: bold;
font-size: 40px;
}
.instructions {
fill: #555;
font-family: sans-serif;
font-size: 40px;
}
.info {
fill: #777;
font-family: sans-serif;
font-size: 15px;
}
.title {
fill: #333;
font-family: sans-serif;
font-weight: bold;
font-size: 100px;
}
]]></style>
</defs>
<g id = "controls">
<text id = "title" class = "title" text-anchor="middle" x="400" y="200">side scroller</text>
<text id = "instruct" class = "instructions" text-anchor="middle" x="400" y="650">click to begin</text>
<!-- <text class = "info" x="5" y="695">move: WASD</text>
<text class = "info" x="5" y="715">fire: click</text>
<text class = "info" x="5" y="735">pause: F</text>
<text class = "info" x="5" y="755">grab: E</text>
<text class = "info" x="5" y="775">zoom: +-</text>
<text class = "info" x="5" y="795">testing: T</text> -->
<g transform = "translate(130,380)">
<!-- <rect class = "keyrect" id="q" x="0" y="0" width="60" height="60" fill='#99a' stroke-width="0"/>
<text class = "letter" text-anchor="middle" x="30" y="45">Q</text> -->
<rect class = "keyrect" id="e" x="140" y="0" width="60" height="60" fill='#99a' stroke-width="0"/>
<text class = "letter" text-anchor="middle" x="170" y="45">E</text>
<rect class = "keyrect" id="e" x="210" y="0" width="60" height="60" fill='#99a' stroke-width="0"/>
<text class = "letter" text-anchor="middle" x="240" y="45">R</text>
<rect class = "keyrect" id="w" x="70" y="0" width="60" height="60" fill='#555' stroke-width="0"/>
<text class = "letter" text-anchor="middle" x="100" y="45">W</text>
<rect class = "keyrect" id="s" x="70" y="70" width="60" height="60" fill='#555' stroke-width="0"/>
<text class = "letter" text-anchor="middle" x="100" y="113">S</text>
<rect class = "keyrect" id="d" x="140" y="70" width="60" height="60" fill='#555' stroke-width="0"/>
<text class = "letter" text-anchor="middle" x="170" y="113">D</text>
<rect class = "keyrect" id="d" x="210" y="70" width="60" height="60" fill='#99a' stroke-width="0"/>
<text class = "letter" text-anchor="middle" x="240" y="113">F</text>
<rect class = "keyrect" id="a" x="0" y="70" width="60" height="60" fill='#555' stroke-width="0"/>
<text class = "letter" text-anchor="middle" x="30" y="113">A</text>
</g>
<g transform = "translate(480,350) scale(0.35)">
<path fill="#555" d="M284.5,182c0,15.74-12.76,28.5-28.5,28.5l0,0c-15.74,0-28.5-12.76-28.5-28.5v-49
c0-15.74,12.76-28.5,28.5-28.5l0,0c15.74,0,28.5,12.76,28.5,28.5V182z"/>
<g>
<path fill="#555" d="M274,88.035c15.071,6.217,25.557,19.987,25.557,35.988v67.953c0,16.001-10.485,29.771-25.557,35.988V264.5
h147V141.806C421,72.325,362.797,16,291,16h-17V88.035z"/>
<path fill="#555" d="M91,275v95.193C91,439.675,149.203,496,221,496h70c71.797,0,130-56.325,130-125.807V275H91z"/>
<path fill="#222" d="M238,264.5v-36.535c-15.071-6.217-25.557-19.987-25.557-35.988v-67.953
c0-16.001,10.485-29.771,25.557-35.988V16h-17C149.203,16,91,72.325,91,141.806V264.5H238z"/>
</g>
</g>
</g>
</svg>
"use strict";
/* TODO: ******************************************* by lilgreenland
make a graphics that looks like the player has a tail
(loose wires / a tail / a rope)
to indicate player motion
draw images on top of bodies
make an svg in html, export to png, add to canvas
add a background layer
add a foreground layer for shadows, lights, stuff in front of player
add more methods of player interaction
portals
need to find a way to fire the portals at locations
use raycasting in matter.js
they could only interact with statics
gun
you'd have to add bad guys too of course...
flipping rotating gravity
also rotate the canvas around
game mechanics
mechanics that support the physics engine
add rope/constraint
store/spawn bodies in player (like starfall)
get ideas from game: limbo / inside
environmental hazards
laser
lava
button / switch
door
fizzler
moving platform
map zones
water
low friction ground
bouncy ground
give each foot a sensor to check for ground collisions
feet with not go into the ground even on slanted ground
this might be not worth it, but it might look really cool
track foot positions with velocity better as the player walks/crouch/runs
track what body the player is standing on
get id from jump sensor collisions, find the body with the id.
when player jumps/moves apply an opposite force on that body
leg animation should be relative to the velocity of the body player is on
brief forced crouch after landing at a high speed
you could just set the crouch keys[] to true for a few cycles if velocity.y is large
give grab a method of interaction with bullets, while paused
physics puzzle mechanics
move a block over and jump on it to get over a wall
drop some weight on a suspended nonrotating block to lower it and remove a blockage
knock over a very tall plank to clear a wide gap
FIX************************************************************
holding a body with a constraint pushes on other bodies too easily
mostly fixed by capping the mass of what player can hold
on testing mode read out mouse postion isn't accurate when zoomed
not sure how to fix this, need to figure out the math for size changes when zoomed
try drawing a picture
*/
//set up canvas
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext("2d");
function setupCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx.font = "15px Arial";
ctx.lineJoin = 'round';
ctx.lineCap = "round";
}
setupCanvas();
window.onresize = function() {
setupCanvas();
};
//mouse move input
window.onmousemove = function(e) {
mech.getMousePos(e.clientX, e.clientY);
};
//mouse click input
//keyboard input
const keys = [];
document.body.addEventListener("keyup", function(e) {
keys[e.keyCode] = false;
});
document.body.addEventListener("keydown", function(e) {
keys[e.keyCode] = true;
if (keys[84]) { //t = testing mode
if (game.testing) {
game.testing = false;
} else {
game.testing = true;
}
}
});
const stats = new Stats(); //setup stats library to show FPS
stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: custom
//stats.domElement.style.opacity = '0.5'
// game Object Prototype *********************************************
//*********************************************************************
const gameProto = function() {
this.testing = false; //testing mode: shows wireframe and some variables
//time related vars and methods
this.cycle = 0; //total cycles, 60 per second
this.cyclePaused = 0;
this.lastTimeStamp = 0; //tracks time stamps for measuing delta
this.delta = 0; //measures how slow the engine is running compared to 60fps
this.buttonCD = 0
this.gravityDir = 0;
this.gravityFlip = function() {
if (keys[82] && this.buttonCD < this.cycle) {
this.buttonCD = this.cycle + 30;
//engine.world.gravity.scale = -engine.world.gravity.scale
if (this.gravityDir) {
this.gravityDir = 0;
} else {
this.gravityDir = 1;
}
Matter.Body.setPosition(player, {
x: player.position.x,
y: -player.position.y
})
Matter.Body.setVelocity(player, {
x: player.velocity.x,
y: -player.velocity.y
})
mech.testingMoveLook();
mech.Sy = mech.y
for (let i = 0; i < bullet.length; i++) {
Matter.Body.setPosition(bullet[i], {
x: bullet[i].position.x,
y: -bullet[i].position.y
})
Matter.Body.setVelocity(bullet[i], {
x: bullet[i].velocity.x,
y: -bullet[i].velocity.y
})
}
for (let i = 0; i < body.length; i++) {
Matter.Body.setPosition(body[i], {
x: body[i].position.x,
y: -body[i].position.y
})
Matter.Body.setVelocity(body[i], {
x: body[i].velocity.x,
y: -body[i].velocity.y
})
}
for (let i = 0; i < map.length; i++) {
Matter.Body.setPosition(map[i], {
x: map[i].position.x,
y: -map[i].position.y
})
//Matter.Body.rotate(map[i], Math.PI)
}
for (let i = 0; i < cons.length; i++) {
cons[i].pointA = {
x: cons[i].pointA.x,
y: -cons[i].pointA.y
}
}
//ctx.rotate(this.gravityDir);
//engine.world.gravity.scale = -engine.world.gravity.scale
//this.gravityDir = (this.gravityDir + Math.PI)%(Math.PI*2);
//Matter.Body.setAngle(player, this.gravityDir)
//Matter.Body.rotate(player, Math.PI);
}
};
this.timing = function() {
this.cycle++; //tracks game cycles
//delta is used to adjust forces on game slow down;
this.delta = (engine.timing.timestamp - this.lastTimeStamp) / 16.666666666666;
this.lastTimeStamp = engine.timing.timestamp; //track last engine timestamp
}
this.zoom = 1 / 300;
this.scaleZoom = function() {
if (this.zoom != 1) {
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.scale(this.zoom, this.zoom);
ctx.translate(-canvas.width / 2, -canvas.height / 2);
}
}
this.keyZoom = function() {
if (keys[187]) { //plus
this.zoom *= 1.01;
} else if (keys[189]) { //minus
this.zoom *= 0.99;
} else if (keys[48]) {
this.zoom = 1;
}
}
this.wipe = function() {
if (this.isPaused) {
ctx.fillStyle = "rgba(255,255,255,0.1)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
} else {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
}
this.isPaused = false;
this.pause = function() {
if (keys[70] && mech.buttonCD < this.cycle) {
mech.buttonCD = this.cycle + 20;
if (!this.isPaused) {
this.cyclePaused = this.cycle;
this.isPaused = true;
for (let i = 0; i < body.length; i++) {
body[i].pausedVelocity = body[i].velocity; //sleep wipes velocity, so we need to keep track
body[i].pausedVelocityA = body[i].angularVelocity; //sleep wipes velocity, so we need to keep track
Matter.Sleeping.set(body[i], true);
}
for (let i = 0; i < bullet.length; i++) {
bullet[i].pausedVelocity = bullet[i].velocity; //sleep wipes velocity, so we need to keep track
bullet[i].pausedVelocityA = bullet[i].angularVelocity; //sleep wipes velocity, so we need to keep track
Matter.Sleeping.set(bullet[i], true);
}
} else {
this.isPaused = false;
for (let i = 0; i < body.length; i++) {
Matter.Sleeping.set(body[i], false);
Matter.Body.setVelocity(body[i], body[i].pausedVelocity); //return old velocity before pause
Matter.Body.setAngularVelocity(body[i], body[i].angularVelocity)
}
for (let i = 0; i < bullet.length; i++) {
bullet[i].birthCycle += this.cycle - this.cyclePaused; //extends the lifespan of a bullet
Matter.Sleeping.set(bullet[i], false);
if (bullet[i].pausedVelocity) {
Matter.Body.setVelocity(bullet[i], bullet[i].pausedVelocity); //return old velocity before pause
Matter.Body.setAngularVelocity(bullet[i], bullet[i].angularVelocity)
}
}
}
}
}
}
const game = new gameProto();
// player Object Prototype *********************************************
//**********************************************************************
//**********************************************************************
//**********************************************************************
const mechProto = function() {
this.width = 50;
this.radius = 30;
this.stroke = "#333";
this.fill = "#eee";
this.canFire = true;
this.height = 42;
this.yOffWhen = {
crouch: 22,
stand: 49,
jump: 70
}
this.yOff = 70;
this.yOffGoal = 70;
this.onGround = false; //checks if on ground or in air
this.onBody = {
id: 0,
index: 0,
type: "map",
action: ''
};
this.numTouching = 0;
this.crouch = false;
this.isHeadClear = true;
this.spawnPos = {
x: 0,
y: 0
};
this.spawnVel = {
x: 0,
y: 0
};
this.x = this.spawnPos.x;
this.y = this.spawnPos.y;
this.setPosToSpawn = function(){
this.x = this.spawnPos.x;
this.y = this.spawnPos.y;
this.Vx = this.spawnVel.x;
this.Vy = this.spawnVel.y;
Matter.Body.setPosition(player, this.spawnPos);
Matter.Body.setVelocity(player, this.spawnVel);
}
this.Sy = this.y; //adds a smoothing effect to vertical only
this.Vx = 0;
this.Vy = 0;
this.VxMax = 7;
this.mass = 5;
this.Fx = 0.004 * this.mass; //run Force on ground
this.FxAir = 0.0006 * this.mass; //run Force in Air
this.Fy = -0.04 * this.mass; //jump Force
this.angle = 0;
this.walk_cycle = 0;
this.stepSize = 0;
this.flipLegs = -1;
this.hip = {
x: 12,
y: 24,
};
this.knee = {
x: 0,
y: 0,
x2: 0,
y2: 0
};
this.foot = {
x: 0,
y: 0
};
this.legLength1 = 55;
this.legLength2 = 45;
this.canvasX = canvas.width / 2;
this.canvasY = canvas.height / 2;
this.transX = this.canvasX - this.x;
this.transY = this.canvasX - this.x;
this.mouse = {
x: canvas.width / 3,
y: canvas.height
};
this.getMousePos = function(x, y) {
this.mouse.x = x;
this.mouse.y = y;
};
this.testingMoveLook = function() {
//move
this.x = player.position.x;
this.y = playerBody.position.y - this.yOff;
this.Vx = player.velocity.x;
this.Vy = player.velocity.y;
//look
this.canvasX = canvas.width / 2
this.canvasY = canvas.height / 2
this.transX = this.canvasX - this.x;
this.transY = this.canvasY - this.y;
this.angle = Math.atan2(this.mouse.y - this.canvasY, this.mouse.x - this.canvasX);
}
this.move = function() {
this.x = player.position.x;
//looking at player body, to ignore the other parts of the player composite
this.y = playerBody.position.y - this.yOff;
this.Vx = player.velocity.x;
this.Vy = player.velocity.y;
};
this.look = function() {
//set a max on mouse look
let mX = this.mouse.x;
if (mX > canvas.width * 0.8) {
mX = canvas.width * 0.8;
} else if (mX < canvas.width * 0.2) {
mX = canvas.width * 0.2;
}
let mY = this.mouse.y;
if (mY > canvas.height * 0.8) {
mY = canvas.height * 0.8;
} else if (mY < canvas.height * 0.2) {
mY = canvas.height * 0.2;
}
//set mouse look
this.canvasX = this.canvasX * 0.94 + (canvas.width - mX) * 0.06;
this.canvasY = this.canvasY * 0.94 + (canvas.height - mY) * 0.06;
//set translate values
this.transX = this.canvasX - this.x;
this.Sy = 0.99 * this.Sy + 0.01 * (this.y);
//hard caps how behind y position tracking can get.
if (this.Sy - this.y > canvas.height / 2) {
this.Sy = this.y + canvas.height / 2
} else if (this.Sy - this.y < -canvas.height / 2) {
this.Sy = this.y - canvas.height / 2
}
this.transY = this.canvasY - this.Sy;
//make player head angled towards mouse
this.angle = Math.atan2(this.mouse.y - this.canvasY, this.mouse.x - this.canvasX);
};
this.doCrouch = function() {
if (!this.crouch) {
this.crouch = true;
this.yOffGoal = this.yOffWhen.crouch;
Matter.Body.translate(playerHead, {
x: 0,
y: 40
})
}
}
this.undoCrouch = function() {
this.crouch = false;
this.yOffGoal = this.yOffWhen.stand;
Matter.Body.translate(playerHead, {
x: 0,
y: -40
})
}
this.enterAir = function() {
this.onGround = false;
player.frictionAir = 0.001;
if (this.isHeadClear) {
if (this.crouch) {
this.undoCrouch();
}
this.yOffGoal = this.yOffWhen.jump;
};
}
this.enterLand = function() {
this.onGround = true;
if (this.crouch) {
if (this.isHeadClear) {
this.undoCrouch();
player.frictionAir = 0.12;
} else {
this.yOffGoal = this.yOffWhen.crouch;
player.frictionAir = 0.5;
}
} else {
this.yOffGoal = this.yOffWhen.stand;
player.frictionAir = 0.12;
}
};
this.buttonCD_jump = 0; //cooldown for player buttons
this.keyMove = function() {
if (this.onGround) { //on ground **********************
if (this.crouch) { //crouch
if (!(keys[40] || keys[83]) && this.isHeadClear) { //not pressing crouch anymore
this.undoCrouch();
player.frictionAir = 0.12;
}
} else if (keys[40] || keys[83]) { //on ground && not crouched and pressing s or down
this.doCrouch();
player.frictionAir = 0.5;
} else if ((keys[32] || keys[38] || keys[87]) && this.buttonCD_jump + 20 < game.cycle) { //jump
this.buttonCD_jump = game.cycle; //can't jump until 20 cycles pass
Matter.Body.setVelocity(player, { //zero player velocity for consistant jumps
x: player.velocity.x,
y: 0
});
player.force.y = this.Fy / game.delta; //jump force / delta so that force is the same on game slowdowns
}
//horizontal move on ground
if (keys[37] || keys[65]) { //left or a
if (player.velocity.x > -this.VxMax) {
player.force.x = -this.Fx / game.delta;
}
} else if (keys[39] || keys[68]) { //right or d
if (player.velocity.x < this.VxMax) {
player.force.x = this.Fx / game.delta;
}
}
} else { // in air **********************************
//check for short jumps
if (this.buttonCD_jump + 60 > game.cycle && //just pressed jump
!(keys[32] || keys[38] || keys[87]) && //but not pressing jump key
this.Vy < 0) { // and velocity is up
Matter.Body.setVelocity(player, { //reduce player velocity every cycle until not true
x: player.velocity.x,
y: player.velocity.y * 0.94
});
}
if (keys[37] || keys[65]) { // move player left / a
if (player.velocity.x > -this.VxMax + 2) {
player.force.x = -this.FxAir / game.delta;
}
} else if (keys[39] || keys[68]) { //move player right / d
if (player.velocity.x < this.VxMax - 2) {
player.force.x = this.FxAir / game.delta;
}
}
}
//smoothly move height towards height goal ************
this.yOff = this.yOff * 0.85 + this.yOffGoal * 0.15
};
this.death = function() {
Matter.Body.setPosition(player, this.spawnPos);
Matter.Body.setVelocity(player, this.spawnVel);
this.dropBody();
//this.testingMoveLook(); //updates mech position
//this.Sy = mech.y //moves camera to new position quickly
}
this.deathCheck = function() {
if (this.y > 4000) { // if player is 4000px deep reset to spawn Position and Velocity
this.death();
}
};
this.holdKeyDown = 0;
this.buttonCD = 0; //cooldown for player buttons
this.keyHold = function() { //checks for holding/dropping/picking up bodies
if (this.isHolding) {
//give the constaint more length and less stiffness if it is pulled out of position
const Dx = body[this.holdingBody].position.x - holdConstraint.pointA.x;
const Dy = body[this.holdingBody].position.y - holdConstraint.pointA.y;
holdConstraint.length = Math.sqrt(Dx * Dx + Dy * Dy) * 0.95;
holdConstraint.stiffness = -0.01 * holdConstraint.length + 1;
if (holdConstraint.length > 90) this.dropBody(); //drop it if the constraint gets too long
holdConstraint.pointA = { //set constraint position
x: this.x + 50 * Math.cos(this.angle), //just in front of player nose
y: this.y + 50 * Math.sin(this.angle)
};
if (keys[81]) { // q = rotate the body
body[this.holdingBody].torque = 0.05 * body[this.holdingBody].mass;
}
//look for dropping held body
if (this.buttonCD < game.cycle) {
if (keys[69]) { //if holding e drops
this.holdKeyDown++;
} else if (this.holdKeyDown && !keys[69]) {
this.dropBody(); //if you hold down e long enough the body is thrown
this.throwBody();
}
}
} else if (keys[69]) { //when not holding e = pick up body
this.findClosestBody();
if (this.closest.dist2 < 10000) { //pick up if distance closer then 100*100
this.isHolding = true;
this.holdKeyDown = 0;
this.buttonCD = game.cycle + 20;
body[this.holdingBody].collisionFilter.group = 2; //force old holdingBody to collide with player
this.holdingBody = this.closest.index; //set new body to be the holdingBody
//body[this.closest.index].isSensor = true; //sensor seems a bit inconsistant
body[this.holdingBody].collisionFilter.group = -2; //don't collide with player
body[this.holdingBody].frictionAir = 0.1; //makes the holding body less jittery
holdConstraint.bodyB = body[this.holdingBody];
holdConstraint.length = 0;
holdConstraint.pointA = {
x: this.x + 50 * Math.cos(this.angle),
y: this.y + 50 * Math.sin(this.angle)
};
}
}
};
this.dropBody = function() {
let timer; //reset player collision
function resetPlayerCollision() {
timer = setTimeout(function() {
const dx = mech.x - body[mech.holdingBody].position.x
const dy = mech.y - body[mech.holdingBody].position.y
if (dx * dx + dy * dy > 15000) {
body[mech.holdingBody].collisionFilter.group = 2; //can collide with player
} else {
resetPlayerCollision();
}
}, 100);
}
resetPlayerCollision();
this.isHolding = false;
body[this.holdingBody].frictionAir = 0.01;
holdConstraint.bodyB = jumpSensor; //set on sensor to get the constaint on somethign else
};
this.throwMax = 150;
this.throwBody = function() {
let throwMag = 0;
if (this.holdKeyDown > 20) {
if (this.holdKeyDown > this.throwMax) this.holdKeyDown = this.throwMax;
//scale fire with mass and with holdKeyDown time
throwMag = body[this.holdingBody].mass * this.holdKeyDown * 0.001;
}
body[this.holdingBody].force.x = throwMag * Math.cos(this.angle);
body[this.holdingBody].force.y = throwMag * Math.sin(this.angle);
};
this.isHolding = false;
this.holdingBody = 0;
this.closest = {
dist2: 1000000,
index: 0
};
this.findClosestBody = function() {
this.closest.dist2 = 100000;
for (let i = 0; i < body.length; i++) {
const Px = body[i].position.x - (this.x + 50 * Math.cos(this.angle));
const Py = body[i].position.y - (this.y + 50 * Math.sin(this.angle));
if (body[i].mass < player.mass && Px * Px + Py * Py < this.closest.dist2) {
this.closest.dist2 = Px * Px + Py * Py;
this.closest.index = i;
}
}
};
this.standingOnActions = function() {
if (this.onBody.type === 'map') {
var that = this; //brings the this ness into the deeper object methods
var actions = {
'death': function() {
that.death();
},
'slow': function() {
Matter.Body.setVelocity(player, { //reduce player velocity every cycle until not true
x: player.velocity.x * 0.5,
y: player.velocity.y * 0.5
});
},
'launch': function() {
//that.dropBody();
Matter.Body.setVelocity(player, { //zero player velocity for consistant jumps
x: player.velocity.x,
y: 0
});
player.force.y = -0.1 * that.mass / game.delta;
},
'default': function() {}
};
(actions[map[this.onBody.index].action] || actions['default'])();
}
}
/* this.forcePoke = function() {
for (var i = 0; i < body.length; i++) {
var Dx = body[i].position.x - (this.mouse.x - this.transX);
var Dy = body[i].position.y - (this.mouse.y - this.transY);
var accel = 0.2 / Math.sqrt(Dx * Dx + Dy * Dy);
if (accel > 0.01) accel = 0.01; //cap accel
accel = accel * body[i].mass //scale with mass
var angle = Math.atan2(Dy, Dx);
body[i].force.x -= accel * Math.cos(angle);
body[i].force.y -= accel * Math.sin(angle);
}
}; */
this.drawLeg = function(stroke) {
ctx.save();
ctx.scale(this.flipLegs, 1); //leg lines
ctx.strokeStyle = stroke;
ctx.lineWidth = 7;
ctx.beginPath();
ctx.moveTo(this.hip.x, this.hip.y);
ctx.lineTo(this.knee.x, this.knee.y);
ctx.lineTo(this.foot.x, this.foot.y);
ctx.stroke();
//toe lines
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(this.foot.x, this.foot.y);
ctx.lineTo(this.foot.x - 15, this.foot.y + 5);
ctx.moveTo(this.foot.x, this.foot.y);
ctx.lineTo(this.foot.x + 15, this.foot.y + 5);
ctx.stroke();
//hip joint
ctx.strokeStyle = this.stroke;
ctx.fillStyle = this.fill;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(this.hip.x, this.hip.y, 11, 0, 2 * Math.PI);
ctx.fill();
ctx.stroke();
//knee joint
ctx.beginPath();
ctx.arc(this.knee.x, this.knee.y, 7, 0, 2 * Math.PI);
ctx.fill();
ctx.stroke();
//foot joint
ctx.beginPath();
ctx.arc(this.foot.x, this.foot.y, 6, 0, 2 * Math.PI);
ctx.fill();
ctx.stroke();
ctx.restore();
};
this.calcLeg = function(cycle_offset, offset) {
this.hip.x = 12 + offset;
this.hip.y = 24 + offset;
//stepSize goes to zero if Vx is zero or not on ground (make this transition cleaner)
//changes to stepsize are smoothed by adding only a percent of the new value each cycle
this.stepSize = 0.9 * this.stepSize + 0.1 * (8 * Math.sqrt(Math.abs(this.Vx)) * this.onGround);
const stepAngle = 0.037 * this.walk_cycle + cycle_offset;
this.foot.x = 2 * this.stepSize * Math.cos(stepAngle) + offset;
this.foot.y = offset + this.stepSize * Math.sin(stepAngle) + this.yOff + this.height;
const Ymax = this.yOff + this.height;
if (this.foot.y > Ymax) this.foot.y = Ymax;
//calculate knee position as intersection of circle from hip and foot
const d = Math.sqrt((this.hip.x - this.foot.x) * (this.hip.x - this.foot.x) +
(this.hip.y - this.foot.y) * (this.hip.y - this.foot.y));
const l = (this.legLength1 * this.legLength1 - this.legLength2 * this.legLength2 + d * d) / (2 * d);
const h = Math.sqrt(this.legLength1 * this.legLength1 - l * l);
this.knee.x = l / d * (this.foot.x - this.hip.x) - h / d * (this.foot.y - this.hip.y) + this.hip.x + offset;
this.knee.y = l / d * (this.foot.y - this.hip.y) + h / d * (this.foot.x - this.hip.x) + this.hip.y;
};
this.draw = function() {
ctx.fillStyle = this.fill;
if (this.mouse.x > canvas.width / 2) {
this.flipLegs = 1;
} else {
this.flipLegs = -1;
}
this.walk_cycle += this.flipLegs * this.Vx;
//draw body
ctx.save();
// if(game.gravityDir){
// ctx.scale(1, -1);
// //ctx.translate(0,-1.5*canvas.height)
// ctx.translate(this.x, -this.y-this.yOff-this.height);
// } else{
// ctx.translate(this.x, this.y);
// }
ctx.translate(this.x, this.y);
this.calcLeg(Math.PI, -3);
this.drawLeg('#444');
this.calcLeg(0, 0);
this.drawLeg('#333');
ctx.rotate(this.angle);
ctx.strokeStyle = this.stroke;
ctx.lineWidth = 2;
//ctx.fillStyle = this.fill;
let grd = ctx.createLinearGradient(-30, 0, 30, 0);
grd.addColorStop(0, "#bbb");
grd.addColorStop(1, "#fff");
ctx.fillStyle = grd;
ctx.beginPath();
//ctx.moveTo(0, 0);
ctx.arc(0, 0, 30, 0, 2 * Math.PI);
ctx.arc(15, 0, 4, 0, 2 * Math.PI);
ctx.fill();
ctx.stroke();
ctx.restore();
//draw holding graphics
if (this.isHolding) {
if (this.holdKeyDown > 20) {
if (this.holdKeyDown > this.throwMax) {
ctx.strokeStyle = 'rgba(255, 0, 255, 0.8)';
} else {
ctx.strokeStyle = 'rgba(255, 0, 255, ' + (0.2 + 0.4 * this.holdKeyDown / this.throwMax) + ')';
}
} else {
ctx.strokeStyle = 'rgba(0, 255, 255, 0.2)';
}
ctx.lineWidth = 10;
ctx.beginPath();
ctx.moveTo(holdConstraint.bodyB.position.x + Math.random() * 2,
holdConstraint.bodyB.position.y + Math.random() * 2);
ctx.lineTo(this.x + 15 * Math.cos(this.angle), this.y + 15 * Math.sin(this.angle));
//ctx.lineTo(holdConstraint.pointA.x,holdConstraint.pointA.y);
ctx.stroke();
}
};
this.info = function() {
let line = 80;
ctx.fillStyle = "#000";
ctx.fillText("Press T to exit testing mode", 5, line);
line += 30;
ctx.fillText("cycle: " + game.cycle, 5, line);
line += 20;
ctx.fillText("delta: " + game.delta.toFixed(6), 5, line);
line += 20;
ctx.fillText("mX: " + (this.mouse.x - this.transX*game.zoom).toFixed(2), 5, line);
line += 20;
ctx.fillText("mY: " + (this.mouse.y - this.transY*game.zoom).toFixed(2), 5, line);
line += 20;
ctx.fillText("x: " + this.x.toFixed(0), 5, line);
line += 20;
ctx.fillText("y: " + this.y.toFixed(0), 5, line);
line += 20;
ctx.fillText("Vx: " + this.Vx.toFixed(2), 5, line);
line += 20;
ctx.fillText("Vy: " + this.Vy.toFixed(2), 5, line);
line += 20;
ctx.fillText("Fx: " + player.force.x.toFixed(3), 5, line);
line += 20;
ctx.fillText("Fy: " + player.force.y.toFixed(3), 5, line);
line += 20;
ctx.fillText("yOff: " + this.yOff.toFixed(1), 5, line);
line += 20;
ctx.fillText("mass: " + player.mass.toFixed(1), 5, line);
line += 20;
ctx.fillText("onGround: " + this.onGround, 5, line);
line += 20;
ctx.fillText("crouch: " + this.crouch, 5, line);
line += 20;
ctx.fillText("isHeadClear: " + this.isHeadClear, 5, line);
line += 20;
ctx.fillText("HeadIsSensor: " + headSensor.isSensor, 5, line);
line += 20;
ctx.fillText("frictionAir: " + player.frictionAir.toFixed(3), 5, line);
line += 20;
ctx.fillText("stepSize: " + this.stepSize.toFixed(2), 5, line);
line += 20;
ctx.fillText("zoom: " + game.zoom.toFixed(4), 5, line);
line += 20;
ctx.fillText("on " + this.onBody.type + " id: " + this.onBody.id + ", index: " + this.onBody.index, 5, line);
line += 20;
ctx.fillText('action: ' + this.onBody.action, 5, line);
};
};
const mech = new mechProto();
//bullets**************************************************************
//*********************************************************************
//*********************************************************************
//*********************************************************************
const bullet = [];
//mouse click events
window.onmousedown = function(e) {
game.mouseDown = true;
};
window.onmouseup = function(e) {
game.mouseDown = false;
};
function fireBullet(type) {
const len = bullet.length;
//bullet[len] = Bodies.polygon(e.x - mech.transX, e.y- mech.transY, 5, 5);
const dist = 15 //radial distance mech head
const dir = (Math.random() - 0.5) * 0.1 + mech.angle
//spawn as a rectangle
bullet[len] = Bodies.rectangle(mech.x + dist * Math.cos(mech.angle), mech.y + dist * Math.sin(mech.angle), 10, 3, {
angle: dir,
//density: 0.001,
//friction: 0.05,
frictionAir: 0,
//frictionStatic: 0.2,
restitution: 0.25,
//sleepThreshold: 30, //bullets despawn on sleep after __ cycles
collisionFilter: {
group: -2 //can't collide with player (at first)
}
});
//fire polygons
// bullet[len] = Bodies.polygon(mech.x + dist*Math.cos(mech.angle), mech.y + dist*Math.sin(mech.angle),5, 5,{
// angle: Math.random(),
// collisionFilter: {group: -2 }
// );
//fire circles
//bullet[len] = Bodies.circle(mech.x + dist*Math.cos(mech.angle), mech.y + dist*Math.sin(mech.angle), 3,{ restitution: 0.5, sleepThreshold: 15, collisionFilter: { group: -2 }});
bullet[len].birthCycle = game.cycle;
bullet[len].blankfunc = function() { //blank functino for later
}
//bullet velocity in direction of player plus player velocity
// Matter.Body.setVelocity(bullet[len], {
// x: mech.Vx + vel * Math.cos(dir),
// y: mech.Vy + vel * Math.sin(dir)
// });
Matter.Body.setVelocity(bullet[len], {
x: mech.Vx,
y: mech.Vy
});
//add force to fire bullets
const vel = 0.0025;
const f = {
x: vel * Math.cos(dir) / game.delta,
y: vel * Math.sin(dir) / game.delta
}
bullet[len].force = f;
//equal but opposite force on player
player.force.x -= f.x;
player.force.y -= f.y;
World.add(engine.world, bullet[len]); //add bullet to world
}
let fireBulletCD = 0;
function bulletLoop() {
//fire check
//if (game.mouseDown && !(game.cycle % 2) && !game.isPaused) {
if (mech.canFire && game.mouseDown && fireBulletCD < game.cycle && !game.isPaused) {
console.log(mech.canFire)
fireBulletCD = game.cycle + 5;
fireBullet();
}
//all bullet loop
let i = bullet.length;
while (i--) {
//soon after spawn bullets can collide with player
//this may need to be removed
if (bullet[i].collisionFilter.group != 1 && bullet[i].birthCycle + 5 < game.cycle) {
bullet[i].collisionFilter.group = 1;
}
//bullet[i].force.y -= engine.world.gravity.scale*0.01; //make bullets have less wieght
//bullets despawn
//if (bullet[i].isSleeping || bullet[i].birthCycle + 360 < game.cycle) {
if (bullet[i].birthCycle + 200 < game.cycle && !game.isPaused) {
Matter.World.remove(engine.world, bullet[i]);
bullet.splice(i, 1);
}
}
}
//matter.js ***********************************************************
//*********************************************************************
//*********************************************************************
//*********************************************************************
//*********************************************************************
// module aliases
const Engine = Matter.Engine,
World = Matter.World,
Events = Matter.Events,
Composites = Matter.Composites,
Composite = Matter.Composite,
Constraint = Matter.Constraint,
Vertices = Matter.Vertices,
Query = Matter.Query,
Body = Matter.Body,
Bodies = Matter.Bodies;
// create an engine
const engine = Engine.create();
//engine.enableSleeping = true; //might want to turn this off to improve accuracy
//define player *************************************************************
//***************************************************************************
//player as a series of vertices
let vector = Vertices.fromPath('0 40 0 115 20 130 30 130 50 115 50 40');
const playerBody = Matter.Bodies.fromVertices(0, 0, vector);
//this sensor check if the player is on the ground to enable jumping
var jumpSensor = Bodies.rectangle(0, 46, 40, 20, {
sleepThreshold: 99999999999,
isSensor: true
});
//this part of the player lowers on crouch
vector = Vertices.fromPath('0 -66 18 -82 0 -37 50 -37 50 -66 32 -82');
const playerHead = Matter.Bodies.fromVertices(0, -55, vector);
//a part of player that senses if the player's head is empty and can return after crouching
const headSensor = Bodies.rectangle(0, -57, 48, 45, {
sleepThreshold: 99999999999,
isSensor: true,
});
const player = Body.create({ //combine jumpSensor and playerBody
parts: [playerBody, playerHead, jumpSensor, headSensor],
inertia: Infinity, //prevents player rotation
friction: 0.002,
//frictionStatic: 0.5,
restitution: 0.3,
sleepThreshold: Infinity,
collisionFilter: {
group: -2
},
});
//Matter.Body.setPosition(player, mech.spawnPos);
//Matter.Body.setVelocity(player, mech.spawnVel);
mech.setPosToSpawn();
Matter.Body.setMass(player, mech.mass);
World.add(engine.world, [player]);
//holding body constraint
const holdConstraint = Constraint.create({
pointA: {
x: 0,
y: 0
},
//setting constaint to jump sensor because it has to be on something until the player picks up things
bodyB: jumpSensor,
stiffness: 0.4,
});
World.add(engine.world, holdConstraint);
//spawn bodies *************************************************************
//***************************************************************************
//arrays that hold all the elements that are drawn by the renderer
const body = []; //non static bodies
const map = []; //all static bodies
const cons = []; //all constaints between a point and a body
const consBB = []; //all constaints between two bodies
spawn();
function spawn() { //spawns bodies and map elements
function bodyRect(x, y, width, height, properties) { //speeds up adding reactangles to map array
body[body.length] = Bodies.rectangle(x + width / 2, y + height / 2, width, height, properties);
}
//premade property options
//Object.assign({}, propsHeavy, propsBouncy, propsNoRotation) //will combine properties into a new object
const propsBouncy = {
friction: 0,
frictionAir: 0,
frictionStatic: 0,
restitution: 1,
}
const propsSlide = {
friction: 0.01,
frictionAir: 0.01,
frictionStatic: 0.1,
restitution: 0.1,
}
const propsOverBouncy = {
friction: 0,
frictionAir: 0,
frictionStatic: 0,
restitution: 1.05,
}
const propsHeavy = {
density: 0.01 //default density 0.001
}
const propsNoRotation = {
inertia: Infinity, //prevents player rotation
}
function constraintPB(x, y, bodyIndex, stiffness) {
cons[cons.length] = Constraint.create({
pointA: {
x: x,
y: y
},
bodyB: body[bodyIndex],
stiffness: stiffness,
})
}
function constraintBB(bodyIndexA, bodyIndexB, stiffness) {
consBB[consBB.length] = Constraint.create({
bodyA: body[bodyIndexA],
bodyB: body[bodyIndexB],
stiffness: stiffness,
})
}
function mapRect(x, y, width, height, action, properties) { //addes reactangles to map array
var len = map.length;
map[len] = Bodies.rectangle(x + width / 2, y + height / 2, width, height, properties);
if (action) {
map[len].action = action;
console.log(action)
}
}
function mapVertex(x, y, vector, action, properties) { //addes reactangles to map array
var len = map.length;
map[len] = Matter.Bodies.fromVertices(x, y, Vertices.fromPath(vector), properties);
if (action) map[len].action = action;
}
//mapN();
testingsMap();
//mapPuzzler1();
function mapN(){
mech.spawnPos = {
x: 0,
y: 0
};
bodyRect(1475, 0, 100, 800);
mapRect(-1000,1000,2000,20)//ground1
}
function mapPuzzler1(){
mech.spawnPos = {
x: 0,
y: 600
};
mapRect(0,0,2000,2000)//ground1
//mech.spawnPos = { x: 2205, y: 803 };
mech.setPosToSpawn();
mech.canFire = false;
mapRect(0,0,2000,2000)//ground1
mapRect(-1000,900,2200,100)//ground1
mapRect(-1000,670,500,230)//left ledge
bodyRect(-800,400,70,70,propsSlide)//cube on left ledge
bodyRect(200,800,70,70,propsSlide)//cube on ground
mapRect(400,550,500,350)//right ledge
bodyRect(790, -300, 100, 900); //huge tall vertical box
mapRect(1800,900,2000,100)//ground2
mapRect(2310,890,70,10, 'launch')//launchpad
mapRect(2400,-300,500,1200)//launch adjacent ledge
for (let i = 0; i < 10; i++) { //stack of medium hexagons
body[body.length] = Bodies.polygon(2650, -330 - i * 70, 6, 40, {
angle: Math.PI / 2,
});
}
}
function testingsMap(){
mech.spawnPos = {
x: 675,
y: 750
};
mech.canFire = true;
//mech.spawnPos = { x: 0, y: 0 };
mech.setPosToSpawn();
bodyRect(1475, 0, 100, 800); //huge tall vertical box
bodyRect(800, 438, 250, 10); //long skinny box
for (let i = 0; i < 10; i++) { //random bouncy circles
body[body.length] = Bodies.circle(-800 + (0.5 - Math.random()) * 200, 600 + (0.5 - Math.random()) * 200, 7 + Math.ceil(Math.random() * 30), {
restitution: 0.8,
})
}
for (let i = 0; i < 10; i++) { //stack of medium hexagons
body[body.length] = Bodies.polygon(-400, 30 - i * 70, 6, 40, {
angle: Math.PI / 2,
});
}
for (let i = 0; i < 5; i++) { //stairs of boxes taller on left
for (let j = 0; j < 5 - i; j++) {
const r = 40;
body[body.length] = Bodies.rectangle(50 + r / 2 + i * r, 900 - r / 2 - i * r, r, r, {
restitution: 0.8,
});
}
}
for (let i = 0; i < 10; i++) { //stairs of boxes taller on right
for (let j = 0; j < i; j++) {
const r = 120;
body[body.length] = Bodies.rectangle(2639 + r / 2 + i * r, 900 + r - i * r, r, r, {
restitution: 0.6,
friction: 0.3,
frictionStatic: 0.9,
});
}
}
for (let i = 0; i < 12; i++) { //a stack of boxes
body[body.length] = Bodies.rectangle(936, 700 + i * 21, 25, 21);
}
for (let i = 0; i < 12; i++) { //a stack of boxes
body[body.length] = Bodies.rectangle(464, 700 + i * 21, 25, 21);
}
(function newtonsCradle() { //build a newton's cradle
const x = -600;
const r = 20;
const y = 200;
for (let i = 0; i < 5; i++) {
body[body.length] = Bodies.circle(x + i * r * 2, 490, r, Object.assign({}, propsHeavy, propsOverBouncy, propsNoRotation));
constraintPB(x + i * r * 2, 200, body.length - 1, 0.9);
}
body[body.length - 1].force.x = 0.02 * body[body.length - 1].mass; //give the last one a kick
})()
// body[body.length] = Bodies.circle(0, 570, 20)
// body[body.length] = Bodies.circle(30, 570, 20)
// body[body.length] = Bodies.circle(0, 600, 20)
// constraintBB(body.length - 2, body.length - 3, 0.2)
// constraintBB(body.length - 2, body.length - 1, 0.2)
//map statics **************************************************************
//***************************************************************************
mapRect(-2000,900,4000,200)//ground
mapRect(2600,900,2000,200) //far right ground
//mapVertex(-1700, 700, '0 0 0 -500 500 -500 1000 -400 1500 0'); //large ramp
//mapVertex(1285, 867, '200 0 200 100 0 100'); // ramp
mapVertex(1400, 854, '0 100 600 100 600 0 150 0'); // ramp
mapVertex(-1300, 670, '0 0 -500 0 -500 200'); //angeled ceiling
//mapVertex(-1650, 700, '0 0 500 0 500 200'); //angeled ceiling
//mapRect(1350, 800, 300, 100) //ground
mapRect(650, 890, 50, 10) //ground bump
mapRect(-600, 0, 400, 200); //left cave roof
mapRect(-600, 600, 400, 194); //left cave
mapRect(-50, 700, 100, 200, 'launch'); //left wall
mapRect(0, 100, 300, 25); //left high platform
mapRect(550, 450, 300, 25); //wide platform
mapRect(650, 250, 100, 25); //wide platform
mapRect(1000, 450, 400, 25); //platform
mapRect(1200, 250, 200, 25); //platform
mapRect(1300, 50, 100, 25); //platform
mapRect(-350, 885, 20, 20); //ground bump
mapRect(450,635,500,30); //first platform
}
//add arrays to the world******************************************************
//*****************************************************************************
for (let i = 0; i < body.length; i++) {
body[i].collisionFilter.group = 1;
World.add(engine.world, body[i]); //add to world
}
for (let i = 0; i < map.length; i++) {
map[i].collisionFilter.group = -1;
Matter.Body.setStatic(map[i], true); //make static
World.add(engine.world, map[i]); //add to world
}
for (let i = 0; i < cons.length; i++) {
World.add(engine.world, cons[i]);
}
for (let i = 0; i < consBB.length; i++) {
World.add(engine.world, consBB[i]);
}
}
// matter events *********************************************************
//************************************************************************
//************************************************************************
//************************************************************************
function findindexFromID(id) { //used to find the index of what the player is standing on
/* for (let i = 0; i < body.length; i++) {
if (body[i].id === id) {
return {
id: id,
index: i,
type: "body"
}
}
} */
for (let i = 0; i < map.length; i++) {
if (map[i].id === id) {
return {
id: id,
index: i,
type: "map",
action: map[i].action
}
}
}
/* for (let i = 0; i < bullet.length; i++) {
if (bullet[i].id === id) {
return {
id: id,
index: i,
type: "bullet"
}
}
} */
return {
id: null,
index: null,
type: "none",
action: null
}
}
function playerOnGroundCheck(event) { //runs on collisions events
function enter() {
mech.numTouching++;
if (!mech.onGround) mech.enterLand();
}
const pairs = event.pairs;
for (let i = 0, j = pairs.length; i != j; ++i) {
let pair = pairs[i];
if (pair.bodyA === jumpSensor) {
if (mech.onBody.id != pair.bodyB.id) { //if the id has changed
var touching = findindexFromID(pair.bodyB.id);
if (touching.type === "map") {
mech.onBody = touching;
}
}
enter();
} else if (pair.bodyB === jumpSensor) {
enter();
mech.onBody = pair.bodyA.id;
}
}
}
function playerOffGroundCheck(event) { //runs on collisions events
function enter() {
if (mech.onGround && mech.numTouching === 0) mech.enterAir();
}
const pairs = event.pairs;
for (let i = 0, j = pairs.length; i != j; ++i) {
let pair = pairs[i];
if (pair.bodyA === jumpSensor) {
enter();
} else if (pair.bodyB === jumpSensor) {
enter();
}
}
}
function playerHeadCheck(event) { //runs on collisions events
if (mech.crouch) {
mech.isHeadClear = true;
const pairs = event.pairs;
for (let i = 0, j = pairs.length; i != j; ++i) {
let pair = pairs[i];
if (pair.bodyA === headSensor) {
mech.isHeadClear = false;
} else if (pair.bodyB === headSensor) {
mech.isHeadClear = false;
}
}
}
}
Events.on(engine, "beforeUpdate", function(event) {
mech.numTouching = 0;
mech.onBody = {
id: null,
index: null,
type: "none"
}
});
//determine if player is on the ground
Events.on(engine, "collisionStart", function(event) {
playerOnGroundCheck(event);
playerHeadCheck(event);
});
Events.on(engine, "collisionActive", function(event) {
playerOnGroundCheck(event);
playerHeadCheck(event);
});
Events.on(engine, 'collisionEnd', function(event) {
playerOffGroundCheck(event);
});
// render ***********************************************************
//*******************************************************************
//*******************************************************************
//*******************************************************************
function drawMatterWireFrames() {
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "#999";
const bodies = Composite.allBodies(engine.world);
ctx.beginPath();
for (let i = 0; i < bodies.length; i += 1) {
//ctx.fillText(bodies[i].id,bodies[i].position.x,bodies[i].position.y); //shows the id of every body
let vertices = bodies[i].vertices;
ctx.moveTo(vertices[0].x, vertices[0].y);
for (let j = 1; j < vertices.length; j += 1) {
ctx.lineTo(vertices[j].x, vertices[j].y);
}
ctx.lineTo(vertices[0].x, vertices[0].y);
}
ctx.lineWidth = 1;
ctx.strokeStyle = '#000';
ctx.stroke();
}
function drawMap() {
//draw map
ctx.beginPath();
for (let i = 0; i < map.length; i += 1) {
let vertices = map[i].vertices;
ctx.moveTo(vertices[0].x, vertices[0].y);
for (let j = 1; j < vertices.length; j += 1) {
ctx.lineTo(vertices[j].x, vertices[j].y);
}
ctx.lineTo(vertices[0].x, vertices[0].y);
}
ctx.fillStyle = '#444';
ctx.fill();
}
function drawBody() {
//draw body
ctx.beginPath();
for (let i = 0; i < body.length; i += 1) {
let vertices = body[i].vertices;
ctx.moveTo(vertices[0].x, vertices[0].y);
for (let j = 1; j < vertices.length; j += 1) {
ctx.lineTo(vertices[j].x, vertices[j].y);
}
ctx.lineTo(vertices[0].x, vertices[0].y);
}
ctx.lineWidth = 1.5;
ctx.fillStyle = '#777';
ctx.fill();
ctx.strokeStyle = '#222';
ctx.stroke();
}
function drawBullet() {
//draw body
ctx.beginPath();
for (let i = 0; i < bullet.length; i += 1) {
let vertices = bullet[i].vertices;
ctx.moveTo(vertices[0].x, vertices[0].y);
for (let j = 1; j < vertices.length; j += 1) {
ctx.lineTo(vertices[j].x, vertices[j].y);
}
ctx.lineTo(vertices[0].x, vertices[0].y);
}
ctx.fillStyle = '#f25';
//ctx.fillStyle = '#0cc';
ctx.fill();
}
function drawCons() {
//draw body
ctx.beginPath();
for (let i = 0; i < cons.length; i += 1) {
ctx.moveTo(cons[i].pointA.x, cons[i].pointA.y);
ctx.lineTo(cons[i].bodyB.position.x, cons[i].bodyB.position.y);
}
ctx.lineWidth = 1;
ctx.strokeStyle = '#999';
ctx.stroke();
}
function drawPlayerBodyTesting() { //shows the different parts of the player body for testing
//jump
ctx.beginPath();
let bodyDraw = jumpSensor.vertices;
ctx.moveTo(bodyDraw[0].x, bodyDraw[0].y);
for (let j = 1; j < bodyDraw.length; j += 1) {
ctx.lineTo(bodyDraw[j].x, bodyDraw[j].y);
}
ctx.lineTo(bodyDraw[0].x, bodyDraw[0].y);
ctx.fillStyle = 'rgba(255, 0, 0, 0.3)';
ctx.fill();
ctx.strokeStyle = '#000';
ctx.stroke();
//main body
ctx.beginPath();
bodyDraw = playerBody.vertices;
ctx.moveTo(bodyDraw[0].x, bodyDraw[0].y);
for (let j = 1; j < bodyDraw.length; j += 1) {
ctx.lineTo(bodyDraw[j].x, bodyDraw[j].y);
}
ctx.lineTo(bodyDraw[0].x, bodyDraw[0].y);
ctx.fillStyle = 'rgba(0, 255, 255, 0.3)';
ctx.fill();
ctx.stroke();
//head
ctx.beginPath();
bodyDraw = playerHead.vertices;
ctx.moveTo(bodyDraw[0].x, bodyDraw[0].y);
for (let j = 1; j < bodyDraw.length; j += 1) {
ctx.lineTo(bodyDraw[j].x, bodyDraw[j].y);
}
ctx.lineTo(bodyDraw[0].x, bodyDraw[0].y);
ctx.fillStyle = 'rgba(255, 255, 0, 0.3)';
ctx.fill();
ctx.stroke();
//head sensor
ctx.beginPath();
bodyDraw = headSensor.vertices;
ctx.moveTo(bodyDraw[0].x, bodyDraw[0].y);
for (let j = 1; j < bodyDraw.length; j += 1) {
ctx.lineTo(bodyDraw[j].x, bodyDraw[j].y);
}
ctx.lineTo(bodyDraw[0].x, bodyDraw[0].y);
ctx.fillStyle = 'rgba(0, 0, 255, 0.3)';
ctx.fill();
ctx.stroke();
}
//main loop ************************************************************
//**********************************************************************
//**********************************************************************
//**********************************************************************
function cycle() {
stats.begin();
game.timing();
game.wipe();
mech.keyMove();
mech.standingOnActions();
mech.keyHold();
game.keyZoom();
game.gravityFlip();
game.pause();
if (game.testing) {
mech.testingMoveLook();
mech.deathCheck();
bulletLoop();
ctx.save();
game.scaleZoom();
ctx.translate(mech.transX, mech.transY);
mech.draw();
drawMatterWireFrames();
drawPlayerBodyTesting();
ctx.restore();
mech.info();
} else {
mech.move();
mech.deathCheck();
bulletLoop();
mech.look();
game.wipe();
ctx.save();
game.scaleZoom();
ctx.translate(mech.transX, mech.transY);
//ctx.drawImage(background_img, -600, -400);
ctx.drawImage(bmo_img, -600, -400);
drawCons();
drawBody();
mech.draw();
//ctx.drawImage(foreground_img, -700, -1500);
drawMap();
drawBullet();
ctx.restore();
}
//svg graphics , just here until I convert svg to png in inkscape
/* document.getElementById('background').setAttribute('transform',
'translate(' + (canvas.width/2) + ',' + (canvas.height/2) + ')'
+ 'scale(' + game.zoom + ')'
+ 'translate(' + (mech.transX - canvas.width/2) + ',' + (mech.transY - canvas.height/2) + ')'); */
// document.getElementById('foreground').setAttribute('transform',
// 'translate(' + (canvas.width/2) + ',' + (canvas.height/2) + ')'
// + 'scale(' + game.zoom + ')'
// + 'translate(' + (mech.transX - canvas.width/2) + ',' + (mech.transY - canvas.height/2) + ')');
stats.end();
requestAnimationFrame(cycle);
}
const bmo_img = new Image(); // Create new img element
bmo_img.src = 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/464612/Bmo.png'; // Set source path
// const foreground_img = new Image(); // Create new img element
// foreground_img.src = 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/464612/circle3390.png'; // Set source path
// const background_img = new Image(); // Create new img element
// background_img.src = 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/464612/background.png'; // Set source path
function runPlatformer(el) {
el.onclick = null; //removes the onclick effect so the function only runs once
el.style.display = 'none'; //hides the element that spawned the function
document.getElementById("keysright").innerHTML = '';
document.getElementById("keysleft").innerHTML = '';
document.body.appendChild(stats.dom); //show stats.js FPS tracker
Engine.run(engine); //starts game engine
console.clear(); //gets rid of annoying console message about vertecies not working
open();
requestAnimationFrame(cycle); //starts game loop
}
function open() {
const introCycles = 200;
game.zoom = game.cycle / introCycles;
if (game.cycle < introCycles) {
requestAnimationFrame(open);
} else {
ctx.restore();
}
}
<script src="https://rawgit.com/mrdoob/stats.js/master/build/stats.min.js"></script>
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/464612/matter.min.js"></script>

side scroller v0.6 (matter.js)

sidescroller with matter.js physics engine

WASD and mouse to move

E to pick up things & T for testing mode

A Pen by Landgreen on CodePen.

License.

body {
margin: 0;
background-color: #ddd;
cursor: crosshair;
}
canvas {
position: absolute;
top: 0;
left: 0;
z-index: 0;
}
svg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.background {
z-index: -1;
}
.foreground {
z-index: 1;
}
/* svg classes */
@keyframes slideinRL {
0% {
transform: translate(-900px, 0);
}
100% {
transform: translate(0px, 0);
}
}
@keyframes slideinLR {
0% {
transform: translate(900px, 0);
}
100% {
transform: translate(0px, 0);
}
}
@keyframes fadein {
0% {
opacity: 0
}
100% {
opacity: 1;
}
}
.intro {
background-color: #ddd;
z-index: 2;
}
#title {
animation: slideinRL 1s ease-out 1;
}
#instruct {
transform: translate(900px, 0);
animation: slideinLR 1s ease-out 1;
animation-delay: 1s;
animation-fill-mode: forwards;
}
#keysleft {
position: absolute;
z-index: 3;
font-family: "Arial", sans-serif;
color: #333;
top: 10px;
left: 10px;
}
#keysright{
position: absolute;
z-index: 3;
font-family: "Arial", sans-serif;
color: #333;
top: 10px;
right: 10px;
text-align: right;
}
/* #controls {
animation: fadein 3s ease-out 1;
} */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment