Skip to content

Instantly share code, notes, and snippets.

@lilgreenland
Last active October 2, 2016 23:54
Show Gist options
  • Save lilgreenland/e4c6f18de0e8a4dbb9fa9dc6dbdc9dc5 to your computer and use it in GitHub Desktop.
Save lilgreenland/e4c6f18de0e8a4dbb9fa9dc6dbdc9dc5 to your computer and use it in GitHub Desktop.
side scroller v0.2 (matter.js)

2-D side scroller (matter.js)

Base platformer structure

with matter.js physics engine

WASD and mouse to move

A Pen by lilgreenland on CodePen.

License.

<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="500" y="664" width="20" height="237" fill='#999' stroke-width="0"/>
<rect x="880" y="664" width="20" height="237" fill='#999' stroke-width="0"/>
<rect x="350" y="300" width="340" height="600" fill='#d0d0d0' stroke-width="0"/>
<rect x="1350" y="300" width="340" height="600" fill='#d0d0d0' stroke-width="0"/>
<rect x="123" y="500" width="1000" height="400" fill='#cccccc' stroke-width="0"/> -->
<!--<circle cx="730" cy="800" r="20" stroke="red" fill="transparent" stroke-width="4"/> -->
</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'>
<!-- <rect x="-4000" y="-1000" width="13000" height="3000" fill="url(#sunset)" stroke-width="0"/> -->
<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="800" width="400" height="100" 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;
}
.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="750">click to begin</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="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="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="#555" 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>

platformer (matter.js)

Base platformer structure

with matter.js physics engine

WASD and mouse to move

A Pen by lilgreenland on CodePen.

License.

//matter.js version: edge (Oct 2 2016)
/* TODO: **************************************************************
make a graphics that looks like the player has (loose wires / a tail / a rope) to indicate player motion
draw images on top of bodies
make an svg convert to png and add it to canvas
add a foreground layer for shadows and 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...
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
build a method for drawing all constaints
but not player constaint
track foot positions with velocity better as the player walks/crouch/runs
BUGS************************************************************
sensor triggers sometimes off of the box of a triangle or hexagon or nonrectangle vertex defined body
not occuring after adding sensor to player body composite (no idea why that worked...)
done
crouch doesn't actually trigger headsensor T/F if the player is touching stuff?
pause in matter isn't working/possible??
slowing time down makes all the bodies bounce around.
holding a body with a constraint pushes on other bodies too easily
mostly fixed by capping the mass of what player can hold
*/
//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 = "17px 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
var 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;
}
}
});
var 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 *********************************************
//*********************************************************************
var gameProto = function(){
this.testing = false; //testing mode: shows wireframe and some variables
//time related vars and methods
this.cycle = 0;
this.lastTimeStamp = 0; //tracks time stamps for measuing delta
this.delta = 0; //measures how slow the engine is running compared to 60fps
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
}
}
var game = new gameProto();
// player Object Prototype *********************************************
//*********************************************************************
var mechProto = function() {
this.width = 50;
this.radius = 30;
this.stroke = "#333";
this.fill = "#eee";
this.restHeight = 95;
this.yOff = 52;
this.yOffGoal = 52;
this.onGround = false; //checks if on ground or in air
this.crouch = false;
this.isHeadClear = true;
this.spawnPos = {
x: 0,
y: 300
};
this.spawnVel = {
x: 0,
y: 0
};
this.x = this.spawnPos.x;
this.y = this.spawnPos.y;
this.Sy = this.y; //adds a smoothing effect to vertical only
this.Vx = 0;
this.VxMax = 7;
this.Vy = 0;
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.testingControls = function() {
//move
Matter.Body.setAngularVelocity(player, 0); //keep player from rotating
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() {
Matter.Body.setAngularVelocity(player, 0); //keep player from rotating
this.x = player.position.x;
//this.y = player.position.y - (this.height + 30) / 2 + 30;
//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() {
//this.angle = Math.atan2(this.mouse.y - this.y, this.mouse.x - this.x);
//set a max on mouse look
var 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;
}
var 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.98 * this.Sy + 0.02 * (this.y);
this.transY = this.canvasY - this.Sy;
//rapid smoothing if player goes off window
// if (this.transY > -300 || this.transY < -canvas.height+300){
// this.Sy = 0.96 * this.Sy + 0.04 * (this.y);
// 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.buttonCD_jump = 0; //cooldown for player buttons
this.keyMove = function() {
if (this.onGround) { //on ground **********************
if (keys[40] || keys[83]) { //on ground && pressing crouch:down/ s
if (!this.crouch) { //on first crouch cycle shorten the player body
this.crouch = true;
headSensor.isSensor = true;
}
} else if (this.crouch) { //on ground && not pressing crouch && crouch is true
if (this.isHeadClear) {
this.crouch = false;
headSensor.isSensor = false;
}
//on ground && !pressing crouch && !crouching && pressing jump: up/w/space && it is >20 cycles from last jump
} else if ((keys[32] || keys[38] || keys[87]) && this.buttonCD_jump + 20 < game.cycle) {
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
}
//set correct height and friction
if (this.crouch) {
player.frictionAir = 0.5;
this.yOffGoal = 25;
} else if (this.onGround) {
player.frictionAir = 0.12;
this.yOffGoal = 52;
}
//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 **********************************
if (!this.crouch){
this.yOffGoal = 70;
}
player.frictionAir = 0.001;
//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.deathCheck = function() {
if (this.y > 5000) { // if player is 5000px deep reset to spawn Position/Velocity
Matter.Body.setPosition(player, this.spawnPos);
Matter.Body.setVelocity(player, this.spawnVel);
this.dropBody();
}
};
this.holdKeyDown = 0;
this.buttonCD_hold = 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
var Dx = body[this.holdingBody].position.x - holdConstraint.pointA.x;
var 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 > 100) 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_hold < 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_hold = game.cycle + 20;
this.holdingBody = this.closest.index;
//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() {
var timer; //reset player collision
function resetPlayerCollision() {
timer = setTimeout(function(){
var dx = mech.x - body[mech.holdingBody].position.x
var dy = mech.y - body[mech.holdingBody].position.y
if (dx*dx+dy*dy > 20000){
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() {
var 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 (var i = 0; i < body.length; i++) {
var Px = body[i].position.x - (this.x + 50 * Math.cos(this.angle));
var 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.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.translate(this.x, this.y);
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);
var 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 + 42;
var Ymax = this.yOff + 42; //95
if (this.foot.y > Ymax) this.foot.y = Ymax;
//calculate knee position as intersection of circle from hip and foot
var 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));
var l = (this.legLength1 * this.legLength1 - this.legLength2 * this.legLength2 + d * d) / (2 * d);
var 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;
this.calcLeg(Math.PI, -3);
this.drawLeg('#444');
this.calcLeg(0, 0);
this.drawLeg('#333');
//draw body
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.angle);
ctx.strokeStyle = this.stroke;
ctx.lineWidth = 2;
//ctx.fillStyle = this.fill;
var 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() {
var 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).toFixed(2), 5, line);
line += 20;
ctx.fillText("mY = " + (this.mouse.y - this.transY).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);
};
};
var mech = new mechProto();
//add in mouse click events
// window.onclick = function(e) {
// };
//matter.js ***********************************************************
//*********************************************************************
//*********************************************************************
// module aliases
var 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
var engine = Engine.create();
//define player *************************************************************
//***************************************************************************
// var playerBody = Bodies.rectangle(0, 0, 40, 130);
//player as a series of vertices
//var playerVector = Vertices.fromPath('0 0 0 130 50 130 50 0');
//var playerVector = Vertices.fromPath('0 0 0 115 20 130 30 130 50 115 50 0');
var playerVector = Vertices.fromPath('0 40 0 115 20 130 30 130 50 115 50 40');
var playerBody = Matter.Bodies.fromVertices(0, 0, playerVector);
var headSensor = Bodies.rectangle(0, -62, 50, 40, {
isSensor: false,
collisionFilter: {
group: -1
},
});
//this sensor check if the player is on the ground to enable jumping
var jumpSensor = Bodies.rectangle(0, 46, 40, 15, {
isSensor: true,
collisionFilter: {
group: -1
},
});
var player = Body.create({ //combine jumpSensor and playerBody
parts: [playerBody, jumpSensor, headSensor],
friction: 0,
frictionStatic: 0,
restitution: 0.3,
collisionFilter: {
group: -2
},
});
Matter.Body.setPosition(player, mech.spawnPos);
Matter.Body.setVelocity(player, mech.spawnVel);
Matter.Body.setMass(player, mech.mass);
World.add(engine.world, [player]);
//holding body constraint
var 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);
//array that holds all the elements that are drawn by the renderer___
var body = []; //non static bodies
var map = []; //all static bodies
//spawn bodies *************************************************************
//***************************************************************************
spawn();
function spawn() {
function BodyRect(x, y, width, height) { //addes reactangles to map array
body[body.length] = Bodies.rectangle(x + width / 2, y + height / 2, width, height);
}
BodyRect(1475, 0, 100, 800);
var particleOptions = {
friction: 0.05,
frictionStatic: 0.1,
render: {
visible: true
}
};
//body[body.length] = Composites.softBody(350, 500, 5, 5, 0, 0, true, 18,particleOptions);
//World.add(engine.world, Composites.softBody(300, 300, 3, 3, 0, 0, true, 18,particleOptions));
/* for (var 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 (var 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 (var i = 0; i < 5; i++) { //stairs of boxes taller on left
for (var j = 0; j < 5 - i; j++) {
var r = 40;
body[body.length] = Bodies.rectangle(50 + r / 2 + i * r, 900 - r / 2 - i * r, r, r, {
restitution: 0.8,
});
}
}
for (var i = 0; i < 10; i++) { //stairs of boxes taller on right
for (var j = 0; j < i; j++) {
var 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 (var i = 0; i < 12; i++) { //a stack of boxes
body[body.length] = Bodies.rectangle(936, 700 + i * 21, 25, 21);
}
for (var i = 0; i < 12; i++) { //a stack of boxes
body[body.length] = Bodies.rectangle(464, 700 + i * 21, 25, 21);
}
// body[body.length] = Bodies.circle(350, 780, 20,{
// friction: 0,
// frictionAir: 0,
// frictionStatic: 0,
// restitution: 0,
// }) //medium circle
// constraint1 = Constraint.create({
// pointA: {
// x: 350,
// y: 780
// },
// bodyB: body[body.length - 1],
// stiffness: 0.1,
// })
// World.add(engine.world, constraint1);
body[body.length] = Bodies.rectangle(700, 550, 200, 10); //long box
//map statics **************************************************************
//***************************************************************************
function mapRect(x, y, width, height) { //addes reactangles to map array
map[map.length] = Bodies.rectangle(x + width / 2, y + height / 2, width, height);
}
function mapVertex(x, y, vector) { //addes reactangles to map array
map[map.length] = Matter.Bodies.fromVertices(x, y, Vertices.fromPath(vector));
}
//mapVertex(-1700, 700, '0 0 0 -500 500 -500 1000 -400 1500 0'); //large ramp
mapVertex(1230, 867, '200 0 200 100 0 100'); // ramp
mapVertex(-1300, 700, '0 0 -500 0 -500 200'); //angeled ceiling
mapVertex(-1650, 700, '0 0 500 0 500 200'); //angeled ceiling
mapRect(650, 890, 50, 10) //ground bump
mapRect(-600, 0, 400, 800); //left cave
mapRect(-50, 700, 100, 200); //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(1150, 888, 45, 20); //ground bump
map[map.length] = Bodies.rectangle(700, 650, 500, 30); //platform 1
map[map.length] = Bodies.rectangle(1500, 850, 300, 100); //ground bump
map[map.length] = Bodies.rectangle(0, 1000, 4000, 200); //ground
map[map.length] = Bodies.rectangle(4600, 1000, 4000, 200); //far right ground
//add arrays to the world
for (var i = 0; i < body.length; i++) {
body[i].collisionFilter.group = 1;
World.add(engine.world, body[i]); //add to world
}
for (var 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
}
}
// matter events *********************************************************
//************************************************************************
function playerGroundCheck(event, ground) { //runs on collisions events
var pairs = event.pairs;
for (var i = 0, j = pairs.length; i != j; ++i) {
var pair = pairs[i];
if (pair.bodyA === jumpSensor) {
mech.onGround = ground;
} else if (pair.bodyB === jumpSensor) {
mech.onGround = ground;
}
}
}
function playerHeadCheck(event, ground) { //runs on collisions events
if (mech.crouch) {
var pairs = event.pairs;
for (var i = 0, j = pairs.length; i != j; ++i) {
var pair = pairs[i];
if (pair.bodyA === headSensor) {
mech.isHeadClear = ground;
} else if (pair.bodyB === headSensor) {
mech.isHeadClear = ground;
}
}
}
}
// function holdingCheck(event, ground) { //runs on collisions events
// var pairs = event.pairs
// for (var i = 0, j = pairs.length; i != j; ++i) {
// var pair = pairs[i];
// if (pair.bodyA === body[mech.holdingBody]) {
// mech.dropBody();
// } else if (pair.bodyB === body[mech.holdingBody]) {
// mech.dropBody();
// }
// }
// }
//determine if player is on the ground
Events.on(engine, "collisionStart", function(event) {
playerGroundCheck(event, true);
playerHeadCheck(event, false);
//if (mech.isHolding) holdingCheck(event, true)
});
Events.on(engine, "collisionActive", function(event) {
playerGroundCheck(event, true);
playerHeadCheck(event, false);
});
Events.on(engine, 'collisionEnd', function(event) {
playerGroundCheck(event, false);
playerHeadCheck(event, true);
});
//runs the game loop event tick.
Events.on(engine, "afterTick", function(event) {
//cycle();
});
// render ***********************************************************
//*******************************************************************
function drawMatterWireFrames() {
var bodies = Composite.allBodies(engine.world);
ctx.beginPath();
for (var i = 0; i < bodies.length; i += 1) {
var vertices = bodies[i].vertices;
//ctx.moveTo(bodies[i].position.x, bodies[i].position.y);
ctx.moveTo(vertices[0].x, vertices[0].y);
for (var 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 (var i = 0; i < map.length; i += 1) {
var vertices = map[i].vertices;
ctx.moveTo(vertices[0].x, vertices[0].y);
for (var 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 (var i = 0; i < body.length; i += 1) {
var vertices = body[i].vertices;
ctx.moveTo(vertices[0].x, vertices[0].y);
for (var 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 drawPlayerBodyTesting() {
//draw one body
ctx.beginPath();
var bodyDraw = jumpSensor.vertices;
ctx.moveTo(bodyDraw[0].x, bodyDraw[0].y);
for (var 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();
//draw one body
ctx.beginPath();
bodyDraw = playerBody.vertices;
ctx.moveTo(bodyDraw[0].x, bodyDraw[0].y);
for (var 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, 0, 0.06)';
ctx.fill();
ctx.stroke();
//draw one body
ctx.beginPath();
bodyDraw = headSensor.vertices;
ctx.moveTo(bodyDraw[0].x, bodyDraw[0].y);
for (var 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();
ctx.clearRect(0, 0, canvas.width, canvas.height);
mech.keyMove();
mech.keyHold();
if (game.testing) {
mech.testingControls();
mech.deathCheck();
ctx.save();
ctx.translate(mech.transX, mech.transY);
mech.draw();
drawMatterWireFrames();
drawPlayerBodyTesting();
ctx.restore();
mech.info();
} else {
mech.move();
mech.deathCheck();
mech.look();
ctx.save();
ctx.translate(mech.transX, mech.transY);
drawBody();
mech.draw();
drawMap();
ctx.restore();
}
//ctx.drawImage(space_img,1800,0);
//ctx.drawImage(bmo_img,-300,200);
//ctx.drawImage(bgtest_img,-300,200);
//svg graphics
document.getElementById('background').setAttribute(
'transform', 'translate(' + (mech.transX) + ',' + (mech.transY) + ')');
document.getElementById('foreground').setAttribute(
'transform', 'translate(' + (mech.transX) + ',' + (mech.transY) + ')');
stats.end();
requestAnimationFrame(cycle);
}
// var 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
// var bgtest_img = new Image(); // Create new img element
// bgtest_img.src = 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/464612/backgroundtest.png'; // Set source path
// var space_img = new Image(); // Create new img element
// space_img.src = 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/464612/Space.jpg'; // 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.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
requestAnimationFrame(cycle); //starts game loop
}
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/464612/matter.min.js"></script>
<script src="//rawgit.com/mrdoob/stats.js/master/build/stats.min.js"></script>

side scroller (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 lilgreenland on CodePen.

License.

side scroller v0.2 (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 lilgreenland 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;
}
/* #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