Just a Physics Demo
Created
August 16, 2017 19:56
-
-
Save adriancmiranda/ecb33adfb82c27c6dd60d672f0476fb8 to your computer and use it in GitHub Desktop.
Physics Demo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <div id="keysleft"> | |
| move: WASD | |
| <br>Hold/Drop/Fire(hold it): E | |
| <br>Pause: F | |
| <br>Gravity: R | |
| <br>Have fun | |
| </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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| "use strict"; | |
| /* 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 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 | |
| reverse time | |
| negate all velocities, x,y angular | |
| can't reverse time this way because of friction | |
| 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 | |
| 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 | |
| FIX************************************************************ | |
| pause doesn't track rotation | |
| anything else pause wipes? | |
| 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 = "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.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 = {}; | |
| this.numTouching = 0; | |
| this.crouch = false; | |
| this.isHeadClear = true; | |
| this.spawnPos = { | |
| x: 675, | |
| y: 750 | |
| }; | |
| 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.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.deathCheck = function() { | |
| if (this.y > 4000) { // if player is 4000px deep reset to spawn Position and Velocity | |
| 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.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 > 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 < 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 > 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() { | |
| 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.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).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); | |
| line += 20; | |
| ctx.fillText("zoom: " + game.zoom.toFixed(4), 5, line); | |
| line += 20; | |
| ctx.fillText("on body id: " + this.onBody, 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 (game.mouseDown && fireBulletCD < game.cycle && !game.isPaused) { | |
| 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].birthCycle + 5 < game.cycle){ | |
| bullet[i].collisionFilter.group = 1; | |
| } | |
| //bullets despawn if the sleep or if they fall down or after some cycles | |
| //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); | |
| 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 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, | |
| }) | |
| } | |
| 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 ************************************************************** | |
| //*************************************************************************** | |
| function mapRect(x, y, width, height, properties) { //addes reactangles to map array | |
| map[map.length] = Bodies.rectangle(x + width / 2, y + height / 2, width, height, properties); | |
| } | |
| function mapVertex(x, y, vector, properties) { //addes reactangles to map array | |
| map[map.length] = Matter.Bodies.fromVertices(x, y, Vertices.fromPath(vector), properties); | |
| } | |
| //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 | |
| mapRect(-600, 600, 400, 194); //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(-350, 885, 20, 20); //ground bump | |
| map[map.length] = Bodies.rectangle(700, 650, 500, 30); //platform 1 | |
| 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 (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 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) { | |
| mech.onBody = pair.bodyB.id; | |
| 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; | |
| }); | |
| //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); | |
| 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 = '#f34'; | |
| //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.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); | |
| 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(); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <script src="//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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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