p2s
Version:
A JavaScript 2D physics engine.
253 lines (219 loc) • 7.3 kB
HTML
<html lang="en">
<head>
<title>p2.js Platformer example</title>
<meta charset="utf-8">
<script src="../../build/p2.js"></script>
<script src="js/p2.KinematicCharacterController.js"></script>
</head>
<body>
<canvas width="600" height="400" id="myCanvas"></canvas>
<p>Use arrow keys to control character</p>
<code id="debug"></code>
<script>
var canvas;
var ctx;
var w, h;
var cameraPos = [0, 0];
var zoom = 50;
var fixedDeltaTime = 1 / 60;
var maxSubSteps = 10;
var world;
var characterBody;
var rayDebugData = [];
var player;
// Collision groups
var SCENERY_GROUP = 0x01;
var PLAYER_GROUP = 0x02;
init();
requestAnimationFrame(animate);
function init(){
// Init canvas
canvas = document.getElementById("myCanvas");
w = canvas.width;
h = canvas.height;
ctx = canvas.getContext("2d");
ctx.lineWidth = 1 / zoom;
// Init world
world = new p2.World();
// Add some scenery
addStaticBox(-3, 3, 0, 3, 1);
addStaticBox(0, -1, 0, 7, 1);
addStaticBox(-6, 0, Math.PI / 4, 1, 7);
addStaticBox(4, 2, 0, 1, 6);
addStaticCircle(-9, 1, 1, 2);
// Add a character body
var characterShape = new p2.Box({
width: 1,
height: 1.5,
collisionGroup: PLAYER_GROUP
});
characterBody = new p2.Body({
mass: 0,
position:[0,3],
fixedRotation: true,
damping: 0,
type: p2.Body.KINEMATIC
});
characterBody.addShape(characterShape);
world.addBody(characterBody);
// Create the character controller
player = new p2.KinematicCharacterController({
world: world,
body: characterBody,
collisionMask: SCENERY_GROUP,
velocityXSmoothing: 0.0001,
timeToJumpApex: 0.4,
skinWidth: 0.1
});
// Update the character controller after each physics tick.
world.on('postStep', function(){
rayDebugData.length = 0;
player.update(world.lastTimeStep);
});
// Store ray debug data
player.on('raycast', function(evt){
rayDebugData.push(
evt.ray.from[0],
evt.ray.from[1],
evt.ray.to[0],
evt.ray.to[1]
);
});
// Set up key listeners
var left = 0, right = 0;
window.addEventListener('keydown', function(evt){
switch(evt.keyCode){
case 38: // up key
case 32: player.setJumpKeyState(true); break; // space key
case 39: right = 1; break; // right key
case 37: left = 1; break; // left key
}
player.input[0] = right - left;
});
window.addEventListener('keyup', function(evt){
switch(evt.keyCode){
case 38: // up
case 32: player.setJumpKeyState(false); break;
case 39: right = 0; break;
case 37: left = 0; break;
}
player.input[0] = right - left;
});
}
function addStaticCircle(x, y, angle, radius){
var shape = new p2.Circle({
collisionGroup: SCENERY_GROUP,
radius: radius
});
var body = new p2.Body({
position: [x, y],
angle: angle
});
body.addShape(shape);
world.addBody(body);
}
function addStaticBox(x, y, angle, width, height){
var shape = new p2.Box({
collisionGroup: SCENERY_GROUP,
width: width,
height: height
});
var body = new p2.Body({
position: [x, y],
angle: angle
});
body.addShape(shape);
world.addBody(body);
}
function drawBody(body){
var x = body.interpolatedPosition[0],
y = body.interpolatedPosition[1],
s = body.shapes[0];
ctx.save();
ctx.translate(x, y); // Translate to the center of the box
ctx.rotate(body.interpolatedAngle); // Rotate to the box body frame
if(s instanceof p2.Box){
ctx.fillRect(-s.width/2, -s.height/2, s.width, s.height);
} else if(s instanceof p2.Circle){
ctx.beginPath();
ctx.arc(0, 0, s.radius, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath();
}
ctx.restore();
}
function drawRay(startX, startY, endX, endY){
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.lineTo(endX, endY);
ctx.stroke();
ctx.closePath();
}
function render(){
ctx.fillStyle='black';
ctx.fillRect(0,0,w,h);
// Transform the canvas
// Note that we need to flip the y axis since Canvas pixel coordinates
// goes from top to bottom, while physics does the opposite.
ctx.save();
ctx.translate(w/2, h/2); // Translate to the center
ctx.scale(zoom, -zoom); // Zoom in and flip y axis
p2.vec2.lerp(
cameraPos,
cameraPos,
[-characterBody.interpolatedPosition[0], -characterBody.interpolatedPosition[1]],
0.05
);
ctx.translate(
cameraPos[0],
cameraPos[1]
);
// Draw all bodies
ctx.strokeStyle='none';
ctx.fillStyle='white';
for(var i=0; i<world.bodies.length; i++){
var body = world.bodies[i];
drawBody(body);
}
ctx.strokeStyle='red';
for(var i=0; i<rayDebugData.length; i+=4){
drawRay(
rayDebugData[i+0], rayDebugData[i+1],
rayDebugData[i+2], rayDebugData[i+3]
);
}
// Restore transform
ctx.restore();
}
var lastTime;
// Animation loop
function animate(time){
requestAnimationFrame(animate);
// Compute elapsed time since last frame
var deltaTime = lastTime ? (time - lastTime) / 1000 : 0;
deltaTime = Math.min(1 / 10, deltaTime);
// Move physics bodies forward in time
world.step(fixedDeltaTime, deltaTime, maxSubSteps);
// Render scene
render();
updateDebugLog();
lastTime = time;
}
function updateDebugLog(){
debug.innerHTML = [
'player.collisions.above: ' + player.collisions.above,
'player.collisions.below: ' + player.collisions.below,
'player.collisions.left: ' + player.collisions.left,
'player.collisions.right: ' + player.collisions.right,
'player.collisions.climbingSlope: ' + player.collisions.climbingSlope,
'player.collisions.descendingSlope: ' + player.collisions.descendingSlope,
'player.collisions.slopeAngle: ' + player.collisions.slopeAngle,
'player.collisions.slopeAngleOld: ' + player.collisions.slopeAngleOld,
'player.collisions.faceDir: ' + player.collisions.faceDir,
'player.collisions.fallingThroughPlatform: ' + player.collisions.fallingThroughPlatform
].join('<br>');
}
</script>
</body>
</html>