2D platformer game, make player camera view in html5 - javascript

I want to modify the platformer game that I find in codepen.
http://codepen.io/loktar00/pen/JEdqD
the original is like the below image.
I want to change it to be:
I want to zoom in the viewport to player and making a camera-like view where I could scroll a level within a canvas element.
Like this game http://www.html5quintus.com/quintus/examples/platformer_full/
The viewport and camera is tracking the player.
This is my code:
(function () {
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
window.requestAnimationFrame = requestAnimationFrame;
})();
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
width = 100,
height = 100,
player = {
x: width / 2,
y: height - 15,
width: 5,
height: 5,
speed: 3,
velX: 0,
velY: 0,
jumping: false,
grounded: false
},
keys = [],
friction = 0.8,
gravity = 0.3;
var boxes = [];
// player's position
var playerX = 20;
var playerY = 20;
// how far offset the canvas is
var offsetX = 0;
var offsetY = 0;
// dimensions
boxes.push({
x: 0,
y: 0,
width: 10,
height: height
});
boxes.push({
x: 0,
y: height - 2,
width: width,
height: 50
});
boxes.push({
x: width - 10,
y: 0,
width: 50,
height: height
});
boxes.push({
x: 120,
y: 10,
width: 80,
height: 80
});
boxes.push({
x: 170,
y: 50,
width: 80,
height: 80
});
boxes.push({
x: 220,
y: 100,
width: 80,
height: 80
});
boxes.push({
x: 270,
y: 150,
width: 40,
height: 40
});
canvas.width = width;
canvas.height = height;
function update() {
// check keys
if (keys[38] || keys[32] || keys[87]) {
// up arrow or space
if (!player.jumping && player.grounded) {
player.jumping = true;
player.grounded = false;
player.velY = -player.speed * 2;
}
}
if (keys[39] || keys[68]) {
// right arrow
if (player.velX < player.speed) {
player.velX++;
offsetX--;
}
}
if (keys[37] || keys[65]) {
// left arrow
if (player.velX > -player.speed) {
player.velX--;
offsetX++;
}
}
player.velX *= friction;
player.velY += gravity;
ctx.clearRect(0, 0, width, height);
ctx.fillStyle = "black";
ctx.beginPath();
player.grounded = false;
for (var i = 0; i < boxes.length; i++) {
ctx.rect(boxes[i].x, boxes[i].y, boxes[i].width, boxes[i].height);
var dir = colCheck(player, boxes[i]);
if (dir === "l" || dir === "r") {
player.velX = 0;
player.jumping = false;
} else if (dir === "b") {
player.grounded = true;
player.jumping = false;
} else if (dir === "t") {
player.velY *= -1;
}
}
if(player.grounded){
player.velY = 0;
}
player.x += player.velX;
player.y += player.velY;
ctx.save();
ctx.translate(offsetX, offsetY);
// clear the viewport
ctx.clearRect(-offsetX, -offsetY, 100,100);
ctx.fill();
ctx.fillStyle = "red";
ctx.fillRect(player.x, player.y, player.width, player.height);
ctx.fillRect(playerX-offsetX, playerY-offsetY, 8, 8);
// draw the other stuff
var l = boxes.length;
for (var i = 0; i < l; i++) {
// we should really only draw the things that intersect the viewport!
// but I am lazy so we are drawing everything here
var x = boxes[i][0];
var y = boxes[i][1];
ctx.fillStyle = 'lightblue';
ctx.fillRect(x, y, 8, 8);
ctx.fillStyle = 'black';
ctx.fillText(x + ', ' + y, x, y) // just to show where we are drawing these things
}
ctx.restore();
requestAnimationFrame(update);
}
function colCheck(shapeA, shapeB) {
// get the vectors to check against
var vX = (shapeA.x + (shapeA.width / 2)) - (shapeB.x + (shapeB.width / 2)),
vY = (shapeA.y + (shapeA.height / 2)) - (shapeB.y + (shapeB.height / 2)),
// add the half widths and half heights of the objects
hWidths = (shapeA.width / 2) + (shapeB.width / 2),
hHeights = (shapeA.height / 2) + (shapeB.height / 2),
colDir = null;
// if the x and y vector are less than the half width or half height, they we must be inside the object, causing a collision
if (Math.abs(vX) < hWidths && Math.abs(vY) < hHeights) {
// figures out on which side we are colliding (top, bottom, left, or right)
var oX = hWidths - Math.abs(vX),
oY = hHeights - Math.abs(vY);
if (oX >= oY) {
if (vY > 0) {
colDir = "t";
shapeA.y += oY;
} else {
colDir = "b";
shapeA.y -= oY;
}
} else {
if (vX > 0) {
colDir = "l";
shapeA.x += oX;
} else {
colDir = "r";
shapeA.x -= oX;
}
}
}
return colDir;
}
document.body.addEventListener("keydown", function (e) {
keys[e.keyCode] = true;
});
document.body.addEventListener("keyup", function (e) {
keys[e.keyCode] = false;
});
window.addEventListener("load", function () {
update();
});
<h3>A, D or Arrow keys to move, W or space to jump</h3>
<canvas id="canvas"></canvas>
But this does not work.

You can use the same code that you had in the codepen, but add a couple lines in the load event, before the first update().
Let's say something like:
window.addEventListener("load", function () {
ctx.scale(2,2);
ctx.translate(-100,-100);
update();
});
This will zoom by two times and center on new coordinates, BUT keep in mind that you have to do it yourself if you want to re-center when the player is going out of the view.
As a partial way of doing this, you can check if the player moved and translate the canvas using the opposite values of player.velX and player.velY. Something like:
var playerMoved = false;
if (keys[38] || keys[32] || keys[87]) {
playerMoved = true;
//...Other stuff
}
if (keys[39] || keys[68]) {
playerMoved = true;
//...Other stuff
}
if (keys[37] || keys[65]) {
playerMoved = true;
//...Other stuff
}
if (playerMoved) {
// You still need to check quite a few things, like
// the player being out of bounds, the view not translating
// when x less than or bigger then a specific value, and so on
ctx.translate(-player.velX, -player.velY);
}
This is not a complete solution because that would require quite some code, but it should get you started.

Related

Resolve Collision between a Rectangle and a Circle

I am working on a little platformer game. The player is a circle, and I am going to implement the obstacles as rectangles. Does anyone know how to make sure the player can't go through the obstacle? I have tried some methods, but they come out glitchy.
Here is what I have so far (It is a bit long, so the method that I need is inside the resolveCollision function):
var canvas = document.createElement("CANVAS");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.id = "CANVAS";
document.body.appendChild(canvas);
document.body.style.margin = "0px 0px 0px 0px";
document.body.style.overflow = "hidden";
document.body.style.backgroundColor = "#202020";
var ctx = canvas.getContext("2d");
var objects = [];
var player = {
x: window.innerWidth / 2 - 25,
y: 0,
height: 25,
width: 25,
old: {
x: 0,
y: 0,
},
velocity: {
x: 0,
y: 0,
},
jumping: true,
color: "#ff0000",
};
var obstacle = {
x: 50,
y: 300,
width: 50,
height: 50
}
var control = {
left: false,
right: false,
up: false,
keyListener: function (event) {
var keyState;
if (event.type == "keydown") {
keyState = true;
} else {
keyState = false;
}
switch (event.keyCode) {
case 37:
control.left = keyState;
break;
case 38:
control.up = keyState;
break;
case 39:
control.right = keyState;
break;
}
}
};
var checkCollision = function (object) {
var distX = Math.abs(player.x - object.x - object.width / 2);
var distY = Math.abs(player.y - object.y - object.height / 2);
if (distX > (object.width / 2 + (player.width + player.height) / 2)) { return false; }
if (distY > (object.height / 2 + (player.width + player.height) / 2)) { return false; }
if (distX <= (object.width / 2)) { return true; }
if (distY <= (object.height / 2)) { return true; }
var dx = distX - object.width / 2;
var dy = distY - object.height / 2;
return (dx * dx + dy * dy <= ((player.width + player.height) / 2 * (player.width + player.height) / 2));
}
var resolveCollision = function (object) {
// Add Resolve Collision Code
if (checkCollision(object)) {
throw new SyntaxError("Couldn't resolve collision between player and object!");
}
}
var renderFrame = function () {
if (control.up && player.jumping == false) {
player.velocity.y -= 20;
player.jumping = true;
}
if (control.left) {
player.velocity.x -= 0.5;
}
if (control.right) {
player.velocity.x += 0.5;
}
player.velocity.y += 1.5;
player.x += player.velocity.x;
player.y += player.velocity.y;
player.velocity.x *= 0.95;
player.velocity.y *= 0.95;
player.old.x = player.x - player.velocity.x * 1.2;
player.old.y = player.y - player.velocity.y * 1.2;
if (player.y > window.innerHeight - 25) {
player.jumping = false;
player.y = window.innerHeight - 25;
player.velocity.y = 0;
}
ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
ctx.fillStyle = player.color;
ctx.beginPath();
ctx.globalAlpha = 0.5;
ctx.ellipse(player.old.x, player.old.y, player.width, player.height, Math.PI / 4, 0, 2 * Math.PI);
ctx.fill();
ctx.beginPath();
ctx.globalAlpha = 1;
ctx.ellipse(player.x, player.y, player.width, player.height, Math.PI / 4, 0, 2 * Math.PI);
ctx.fill();
ctx.fillRect(obstacle.x, obstacle.y, obstacle.width, obstacle.height);
if (checkCollision(obstacle)) {
resolveCollision(obstacle)
}
window.requestAnimationFrame(renderFrame);
};
renderFrame();
window.addEventListener("keydown", control.keyListener);
window.addEventListener("keyup", control.keyListener);
Thanks in advance!
EDIT: I didn't make it clear enough before, but I need a way to resolve collision once they are detected. I have a method to detect collisions already.
You're talking about bounding box and bounding circle calculations.
Here is a real simple explanation of the algorithms for each type of geometry:
https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection
Of course if they're all the same shape that may simplify it, one solution would be to convert the circles to boxes:
https://math.stackexchange.com/questions/2360970/how-to-calculate-a-bounding-box-of-a-circle
Then just use bounding box collision detection.
But I think if you want to support both then the general strategy will be to attempt to detect if the Circle intersects with the box.
Here is a description of that algorithm:
https://www.lazyfoo.net/tutorials/SDL/29_circular_collision_detection/index.php
To check if a box and circle collided we need to find the closest point on the box...
Here we find the closest y position much like we did the x position. If the distance squared between the closest point on the box and the center of the circle is less than the circle's radius squared, then there is a collision.

Is there an error in the way this simulation calculates gravitational attraction and body collision?

N-Body gravity simulation seems to be working fine at first glance, and the same is true for body collisions, but once gravitationally attracted objects start to collide, they start to spiral around each other frantically and the collection of them as a whole have very erratic motion... The code (html-javascript) will be included below, and to reproduce what I'm talking about, you can create a new body by clicking in a random location on the screen.
The math for gravitational attraction is done in the Body.prototype.gravityCalc() method of the Body object type (line 261). The math for the collision resolution is found in the dynamic collision section of the bodyHandle() function (line 337).
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// event handling
document.addEventListener('keydown', keyDown);
document.addEventListener('mousedown', mouseDown)
document.addEventListener('mouseup', mouseUp)
document.addEventListener('mousemove', mouseMove);
document.addEventListener('touchstart', touchStart);
document.addEventListener('touchmove', touchMove);
document.addEventListener('touchend', touchEnd);
window.addEventListener('resize', resize);
window.onload = function() {reset()}
mouseDown = false;
nothingGrabbed = true;
mouseX = 0;
mouseY = 0;
function keyDown(data) {
if(data.key == "r") {
clearInterval(loop);
reset();
}
else if(data.key == 'g') {
gravityOn = !gravityOn;
}
else if(data.key == 'Delete') {
for(i = 0; i < bodies.length ; i++) {
if(((mouseX - bodies[i].x)**2 + (mouseY - bodies[i].y)**2) <= bodies[i].radius**2) {
bodies.splice(i, 1);
}
}
}
else if(data.key == 'c') {
gravity_c *= -1;
}
else if(data.key == 'f') {
falling = !falling;
}
else if(data.key == 'a') {
acceleration *= -1;
}
}
function mouseDown(data) {
mouseDown = true;
nothingGrabbed = true;
mouseX = data.clientX;
mouseY = canvas.height - data.clientY;
}
function mouseUp(data) {
mouseDown = false;
nothingGrabbed = true;
for(i = 0; i < bodies.length; i++) {
bodies[i].grabbed = false
}
}
function mouseMove(data) {
mouseX = data.clientX;
mouseY = canvas.height - data.clientY;
}
function touchStart(data) {
mouseDown = true;
nothingGrabbed = true;
mouseX = data.touches[0].clientX;
mouseY = canvas.height - data.touches[0].clientY;
}
function touchMove(data) {
mouseX = data.touches[0].clientX;
mouseY = canvas.height - data.touches[0].clientY;
}
function touchEnd(data) {
mouseDown = false;
nothingGrabbed = true;
for(i=0;i<bodies.length;i++) {
bodies[i].grabbed = false;
}
}
function resize(data) {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Initialize Variables
function reset() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.color = 'rgb(70, 70, 70)';
scale = Math.min(canvas.width, canvas.height);
fps = 120;
running = true;
loop = setInterval(main, 1000/fps);
gravityOn = true // if true, objects are gravitationally attracted to each other
gravity_c = 334000 // universe's gravitational constant
boundaryCollision = true // if true, objects collide with edges of canvas
wallDampen = 0.7 // number to multiply by when an objects hit a wall
bodyCollision = true // if true, bodies will collide with each other
bodyDampen = 0.4 // number to multiply when two objects collide
falling = false // if true, objects will fall to the bottom of the screen
acceleration = 400
bodies = [] // a list of each Body object
collidingPairs = [] // a list of pairs of colliding bodies
/*
var bounds = 200;
for(i = 0; i<70; i++) { // randomly place bodies
Body.create({
x: Math.floor(Math.random()*canvas.width),
y: Math.floor(Math.random()*canvas.height),
a: Math.random()*Math.PI*2,
xV: Math.floor(Math.random() * (bounds - -bounds)) + -bounds,
yV: Math.floor(Math.random() * (bounds - -bounds)) + -bounds,
mass: Math.ceil(Math.random()*23)
})
} */
/*
Body.create({
x: canvas.width/2 - 50,
xV: 10,
yV: 0,
aV: 3,
y: canvas.height/2 + 0,
mass: 10
});
Body.create({
x: canvas.width/2 + 50,
xV: 0,
aV: 0,
y: canvas.height/2,
mass: 10
});
*/
Body.create({
x: canvas.width/2,
y: canvas.height/2,
mass: 24,
xV: -10.83
});
Body.create({
x: canvas.width/2,
y: canvas.height/2 + 150,
mass: 1,
xV: 260,
color: 'teal'
});
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Body Type Object
function Body(params) {
this.x = params.x || canvas.width/2;
this.y = params.y || canvas.height/2;
this.a = params.a || 0;
this.xV = params.xV || 0;
this.yV = params.yV || 0;
this.aV = params.aV || 0;
this.xA = params.xA || 0;
this.yA = params.yA || 0;
this.aA = params.aA || 0;
this.grabbed = false;
this.edgeBlock = params.edgeBlock || boundaryCollision;
this.gravity = params.gravityOn || gravityOn;
this.mass = params.mass || 6;
this.density = params.density || 0.008;
this.radius = params.radius || (this.mass/(Math.PI*this.density))**0.5;
this.color = params.color || 'crimson';
this.lineWidth = params.lineWidth || 2;
}
Body.create = function(params) {
bodies.push(new Body(params));
}
Body.prototype.move = function() {
this.xV += this.xA/fps;
this.yV += this.yA/fps;
this.aV += this.aA/fps;
this.x += this.xV/fps;
this.y += this.yV/fps;
this.a += this.aV/fps;
if(this.edgeBlock) {
if(this.x + this.radius > canvas.width) {
this.x = canvas.width - this.radius;
this.xV *= -wallDampen
}
else if(this.x - this.radius < 0) {
this.x = this.radius;
this.xV *= -wallDampen;
}
if(this.y + this.radius > canvas.height) {
this.y = canvas.height - this.radius;
this.yV *= -wallDampen;
}
else if(this.y - this.radius < 0) {
this.y = this.radius;
this.yV *= -wallDampen;
}
}
if(this.grabbed) {
this.xA = 0;
this.yA = 0;
this.xV = 0;
this.yV = 0;
this.x = mouseX;
this.y = mouseY;
}
}
Body.prototype.draw = function() {
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.lineWidth = this.lineWidth;
ctx.fillStyle = this.color;
ctx.arc(this.x, canvas.height - this.y, this.radius, 0, Math.PI*2, true);
ctx.fill();
ctx.stroke();
ctx.closePath()
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.lineWidth = this.linewidth;
ctx.moveTo(this.x, canvas.height - this.y);
ctx.lineTo(this.x + this.radius*Math.cos(this.a), canvas.height - (this.y + this.radius*Math.sin(this.a)))
ctx.stroke();
ctx.closePath();
}
// calculates gravitational attraction to 'otherObject'
Body.prototype.gravityCalc = function(otherObject) {
var x1 = this.x;
var y1 = this.y;
var x2 = otherObject.x;
var y2 = otherObject.y;
var distSquare = ((x2-x1)**2 + (y2-y1)**2);
var val = (gravity_c*otherObject.mass)/((distSquare)**(3/2));
var xA = val * (x2 - x1);
var yA = val * (y2 - y1);
return [xA, yA]
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Physics Code
function bodyHandle() {
for(i = 0; i < bodies.length; i++) {
if(mouseDown && nothingGrabbed) {
if(Math.abs((mouseX - bodies[i].x)**2 + (mouseY - bodies[i].y)**2) <= bodies[i].radius**2) {
bodies[i].grabbed = true;
nothingGrabbed = false;
}
}
bodies[i].draw()
if(running) {
if(falling) {
bodies[i].yV -= acceleration/fps;
}
bodies[i].move();
}
bodies[i].xA = 0;
bodies[i].yA = 0;
collidingPairs = []
if(gravityOn || bodyCollision) {
for(b = 0; b < bodies.length; b++) {
if(i != b) {
if(bodyCollision) {
var x1 = bodies[i].x;
var y1 = bodies[i].y;
var x2 = bodies[b].x;
var y2 = bodies[b].y;
var rSum = bodies[i].radius + bodies[b].radius;
var dist = { // vector
i: x2 - x1,
j: y2 - y1,
mag: ((x2-x1)**2 + (y2-y1)**2)**0.5,
norm: {
i: (x2-x1)/(((x2-x1)**2 + (y2-y1)**2)**0.5),
j: (y2-y1)/(((x2-x1)**2 + (y2-y1)**2)**0.5)
}
}
if(dist.mag <= rSum) { // static collision
var overlap = rSum - dist.mag;
bodies[i].x -= overlap/2 * dist.norm.i;
bodies[i].y -= overlap/2 * dist.norm.j;
bodies[b].x += overlap/2 * dist.norm.i;
bodies[b].y += overlap/2 * dist.norm.j;
collidingPairs.push([bodies[i], bodies[b]]);
}
}
if(gravityOn) {
if(bodies[i].gravity) {
var accel = bodies[i].gravityCalc(bodies[b]);
bodies[i].xA += accel[0];
bodies[i].yA += accel[1];
}
}
}
}
}
for(c = 0; c < collidingPairs.length; c++) { // dynamic collision
var x1 = collidingPairs[c][0].x;
var y1 = collidingPairs[c][0].y;
var r1 = collidingPairs[c][0].radius;
var x2 = collidingPairs[c][1].x;
var y2 = collidingPairs[c][1].y;
var r2 = collidingPairs[c][1].radius;
var dist = { // vector from b1 to b2
i: x2 - x1,
j: y2 - y1,
mag: ((x2-x1)**2 + (y2-y1)**2)**0.5,
norm: {
i: (x2-x1)/(((x2-x1)**2 + (y2-y1)**2)**0.5),
j: (y2-y1)/(((x2-x1)**2 + (y2-y1)**2)**0.5)
}
}
var m1 = collidingPairs[c][0].mass;
var m2 = collidingPairs[c][1].mass;
var norm = { // vector normal along 'wall' of collision
i: -dist.j/(((dist.i)**2 + (-dist.j)**2)**0.5),
j: dist.i/(((dist.i)**2 + (-dist.j)**2)**0.5)
}
var perp = { // vector normal pointing from b1 to b2
i: dist.norm.i,
j: dist.norm.j
}
var vel1 = { // vector of b1 velocity
i: collidingPairs[c][0].xV,
j: collidingPairs[c][0].yV,
dot: function(vect) {
return collidingPairs[c][0].xV * vect.i + collidingPairs[c][0].yV * vect.j
}
}
var vel2 = { // vector of b2 velocity
i: collidingPairs[c][1].xV,
j: collidingPairs[c][1].yV,
dot: function(vect) {
return collidingPairs[c][1].xV * vect.i + collidingPairs[c][1].yV * vect.j
}
}
// new velocities along perp^ of b1 and b2
var nV1Perp = (vel1.dot(perp))*(m1-m2)/(m1+m2) + (vel2.dot(perp))*(2*m2)/(m1+m2);
var nV2Perp = (vel1.dot(perp))*(2*m1)/(m1+m2) + (vel2.dot(perp))*(m2-m1)/(m1+m2);
/* testing rotation after collision
// velocities of the points of collision on b1 and b2
var pVel1M = vel1.dot(norm) + collidingPairs[c][0].aV*r1;
var pVel2M = vel2.dot(norm) + collidingPairs[c][1].aV*r2;
// moment of inertia for b1 and b2
var I1 = 1/2 * m1 * r1**2;
var I2 = 1/2 * m2 * r2**2;
// new velocities of the points of collisions on b1 and b2
var newpVel1M = ((I1-I2)/(I1+I2))*pVel1M + ((2*I2)/(I1+I2))*pVel2M;
var newpVel2M = ((2*I1)/(I1+I2))*pVel1M + ((I2-I1)/(I1+I2))*pVel2M;
var vectToCol1 = { // vector from x1,y1 to point of collision on b1
i: r1*perp.i,
j: r1*perp.j
};
var vectToCol2 = { // vector from x2,y2 to point of collision on b2
i: r2*-perp.i,
j: r2*-perp.j
};
// sign of cross product of pVelM and vectToCol
var vCrossR1 = (pVel1M*norm.i)*(vectToCol1.j) - (pVel1M*norm.j)*(vectToCol1.i);
vCrossR1 = vCrossR1/Math.abs(vCrossR1);
var vCrossR2 = (pVel2M*norm.i)*(vectToCol2.j) - (pVel2M*norm.j)*(vectToCol2.i);
vCrossR2 = vCrossR2/Math.abs(vCrossR2);
collidingPairs[c][0].aV = vCrossR1 * (newpVel1M)/r1;
collidingPairs[c][1].aV = vCrossR2 * (newpVel2M)/r2;
/* draw collision point velocity vectors [debugging]
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.moveTo(x1 + vectToCol1.i, canvas.height - (y1 + vectToCol1.j));
ctx.lineTo((x1+vectToCol1.i) + pVel1M*norm.i, (canvas.height- (y1+vectToCol1.j + pVel1M*norm.j)));
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.strokeStyle = 'white';
ctx.moveTo(x2 + vectToCol2.i, canvas.height - (y2 + vectToCol2.j));
ctx.lineTo((x2+vectToCol2.i) + pVel2M*norm.i, (canvas.height- (y2+vectToCol2.j + pVel2M*norm.j)));
ctx.stroke();
ctx.closePath();
console.log(pVel1M, pVel2M);
clearInterval(loop);
*/
collidingPairs[c][0].xV = vel1.dot(norm)*norm.i + nV1Perp*perp.i * bodyDampen;
collidingPairs[c][0].yV = vel1.dot(norm)*norm.j + nV1Perp*perp.j * bodyDampen;
collidingPairs[c][1].xV = vel2.dot(norm)*norm.i + nV2Perp*perp.i * bodyDampen;
collidingPairs[c][1].yV = vel2.dot(norm)*norm.j + nV2Perp*perp.j * bodyDampen;
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Main Loop
function main() {
// blank out canvas
ctx.fillStyle = canvas.color;
ctx.fillRect(0, 0, canvas.width, canvas.height);
bodyHandle();
if(nothingGrabbed && mouseDown) {
bodies.push(new Body({x: mouseX,
y: mouseY,
mass: 90}));
bodies[bodies.length-1].move();
bodies[bodies.length-1].draw();
}
}
<html>
<meta name='viewport' content='width=device-width,height=device-height'>
<body>
<canvas id="canvas" width='300px' height='300px'></canvas>
<style>
body {
padding: 0;
margin: 0;
}
canvas {
padding: 0;
margin: 0;
}
</style>
</html>
I cannot tell you much about the code. Personally it seems to me that the animations could be correct.
If you want to test your code you could try to test if laws of conservation of energy and momentum are respected. You could, for example, sum the momentum of every object (mass times velocity) and see if the number are maintained constant when there are no forces from the outside (collisions with the wall). To do this I would suggest to make the free space available larger. Another quantity is the total energy (kinetic plus potential) which is a bit harder, but still easy to compute (to compute tot. pot. energy you have to sum over all pairs).

How to trigger animations in correct order in p5.js?

I'm trying to trigger animations in a simple pong game when the ball bounces off each edge. However, I'm struggling to get the animations to appear the way I want them to. I want the ellipses to draw on top of each other in the order that the edges are hit. This sometimes happens, but I believe the problem is that when two animation booleans are true at the same time, whichever animation appears later in the program flow will draw over the other. So the way I've laid it out below, the blue will always draw over the yellow if both are true, and the red will always draw over both blue and yellow if both are true.
Any assistance with this would be much appreciated! Thanks.
var bg = 220;
var x = 0;
var y = 200;
var speed = 3;
var speedY = 4;
var leftAnim = false;
var leftX;
var leftY;
var leftDiam = 40;
var rightAnim = false;
var rightX;
var rightY;
var rightDiam = 40;
var topAnim = false;
var topX;
var topY;
var topDiam = 40;
function setup() {
createCanvas(400, 400);
}
function draw() {
background(bg);
noStroke();
edgeAnimation();
fill("white");
ellipse(x, y, 40, 40);
x += speed;
y += speedY;
if (x > width) {
//set rightAnim boolean to true to trigger right edge animation
rightAnim = true;
//update animating ellipse position variables to draw it at the same point where ball bounced
rightX = x;
rightY = y;
//reverse x direction of ball
speed *= -1;
}
if (x < 0) {
leftAnim = true;
leftX = x;
leftY = y;
speed *= -1;
}
// if the ball hits the top of the canvas, reverse the y direction of the ball
if (y < 0) {
topAnim = true;
topX = x;
topY = y;
speedY *= -1;
}
//if ball hits bottom of the canvas, stop the ball's motion
if (y > height) {
speed = 0;
speedY = 0;
}
//conditional to check for collision with paddle
if (x > mouseX && x < mouseX + 100 && y == height - 60) {
speedY *= -1;
}
// Paddle
rect(mouseX, height - 40, 100, 30);
}
function edgeAnimation() {
if (leftAnim == true) {
fill("gold");
ellipse(leftX, leftY, leftDiam);
leftDiam += 20;
//if animating ellipse fills the canvas, change the background color to the ellipse color, change leftAnim boolean to false and reset the diameter's size
if (leftDiam > 1150) {
bg = "gold";
leftAnim = false;
leftDiam = 40;
}
}
if (rightAnim == true) {
fill("RoyalBlue");
ellipse(rightX, rightY, rightDiam);
rightDiam += 20;
if (rightDiam > 1150) {
bg = "RoyalBlue";
rightAnim = false;
rightDiam = 40;
}
}
if (topAnim == true) {
fill("crimson");
ellipse(topX, topY, topDiam);
topDiam += 20;
if (topDiam > 1150) {
bg = "crimson";
topAnim = false;
topDiam = 40;
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/p5.js"></script>
Don't use boolean states, but use list of animations.
var animation = []
Is a new edge is hit the append a new animation data set at the end (.push) of the animation list:
function draw() {
// [...]
if (x > width) {
animation.push( {color: "RoyalBlue", x: x, y: y, diam:40} );
speed *= -1;
}
if (x < 0) {
animation.push( {color: "gold", x: x, y: y, diam: 40} );
speed *= -1;
}
if (y < 0) {
animation.push( {color: "crimson", x: x, y: y, diam: 40} );
speedY *= -1;
}
// [...]
}
The animations can be drawn in a loop. Keep only that animations which doesn't exceed the limit:
function edgeAnimation() {
var keepAnimation = []
for (let i = 0; i < animation.length; ++i) {
fill( animation[i].color );
ellipse( animation[i].x, animation[i].y, animation[i].diam );
animation[i].diam += 20;
if (animation[i].diam > 1150) {
bg = animation[i].color;
} else {
keepAnimation.push(animation[i]);
}
}
animation = keepAnimation;
}
See the example, wher I applied the suggestions to the code of the question:
var bg = 220;
var x = 0;
var y = 200;
var speed = 3;
var speedY = 4;
var animation = []
function setup() {
createCanvas(400, 400);
}
function draw() {
background(bg);
noStroke();
edgeAnimation();
fill("white");
ellipse(x, y, 40, 40);
x += speed;
y += speedY;
if (x > width) {
animation.push( {color: "RoyalBlue", x: x, y: y, diam:40} );
speed *= -1;
}
if (x < 0) {
animation.push( {color: "gold", x: x, y: y, diam: 40} );
speed *= -1;
}
if (y < 0) {
animation.push( {color: "crimson", x: x, y: y, diam: 40} );
speedY *= -1;
}
//if ball hits bottom of the canvas, stop the ball's motion
if (y > height) {
x = random(20, width-20);
y = 20;
speed = random(2,4);
speedY = 6 - speed;
}
//conditional to check for collision with paddle
if (x > mouseX && x < mouseX + 100 && y < height - 59 + speedY && y > height - 60) {
speedY *= -1;
y = height - 60
}
// Paddle
rect(mouseX, height - 40, 100, 30);
}
function edgeAnimation() {
var keepAnimation = []
for (let i = 0; i < animation.length; ++i) {
fill( animation[i].color );
ellipse( animation[i].x, animation[i].y, animation[i].diam );
animation[i].diam += 20;
if (animation[i].diam > 1150) {
bg = animation[i].color;
} else {
keepAnimation.push(animation[i]);
}
}
animation = keepAnimation;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/p5.js"></script>

How to avoid repeating code which makes an ellipse move? Currently I have to repeat it for each ellipse

I'm trying to add many flying dots to the canvas that connect to each other with a line when the distance between them is between 0 and 300 and have the line's opacity change based on the distance between them. As you can see, I've done all that completely with two dots. However, I'm facing two problems now.
Problem number one - If I wanted to add 15 ellipses, I'd more or less have to copy the code for each new ellipse. I'm fairly certain this is not the way to do it.
The second problem is that I'm hard-coding which dots should connect to each other. I want each dot to connect to each nearby dot, however, I'm not sure how to do that exactly.
var el1 = {
x: 100,
y: 100,
width: 5,
height: 5
};
var el2 = {
x: 300,
y: 300,
width: 5,
height: 5
};
var speed = 2;
var opacity = 0;
var xdirection = 1;
var ydirection = 1;
function setup() {
createCanvas(windowWidth, windowHeight - 4);
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight - 4);
}
function draw() {
background(51, 51, 51);
fill(55, 90, 80);
noStroke();
ellipse(el1.x, el1.y, el1.width, el1.height);
ellipse(el2.x, el2.y, el2.width, el2.height);
el1.x = el1.x + speed * xdirection;
el1.y = el1.y + speed * ydirection;
if (el1.x > windowWidth || el1.x < 5) {
xdirection *= -1;
}
if (el1.y > windowHeight || el1.y < 5) {
ydirection *= -1;
}
if (dist(el1.x,el1.y,el2.x,el2.y) < 300) {
var opacity = map(dist(el1.x,el1.y,el2.x,el2.y),0, 300, 255, 0);
stroke(55, 90, 80, opacity);
line(el1.x,el1.y,el2.x,el2.y);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.2/p5.js"></script>
Define your ellipse as an object, create them as many as you want (15), put into array and start drawing, constantly detecting the distance between all of them.
Object himself:
var noOfEllipses = 15;
var opacity = 0;
var arrOfEllipses = [];
class Ellips {
constructor(x, y, w, h, s=2, dx=1, dy=1){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.s = s;
this.dx = dx;
this.dy = dy;
};
}
Next, initial drawing. I gave dots random dimensions and locations, you can add any individual property (speed/direction/color/etc).
function randInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}
function setup() {
createCanvas(windowWidth, windowHeight - 4);
for(i=0; i<noOfEllipses;i++){
var el = new Ellips(
randInt(3, windowWidth), //x
randInt(3, windowHeight), //y
randInt(3, 8), //width
randInt(3, 8), //height
randInt(10, 50)/10 //speed
);
arrOfEllipses.push(el); //put them into array
}
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight - 4);
}
Animation. Iterate through array of dots, location/speed/etc. are inner properties. Second for{} is needed to calculate distance and draw lines.
function draw() {
background(51, 51, 51);
fill(200, 200, 80);
noStroke();
for(var i=0; i<arrOfEllipses.length; i++){
el = arrOfEllipses[i];
el.x = el.x + el.s * el.dx;
el.y = el.y + el.s * el.dy;
if (el.x > windowWidth || el.x < 5) {
el.dx *= -1;
}
if (el.y > windowHeight || el.y < 5) {
el.dy *= -1;
}
for(var j=i+1; j<arrOfEllipses.length; j++){
el2 = arrOfEllipses[j];
if (dist(el.x,el.y,el2.x,el2.y) < 300) {
var opacity = map(dist(el.x,el.y,el2.x,el2.y),0, 300, 255, 0);
stroke(200, 200, 80, opacity);
line(el.x,el.y,el2.x,el2.y);
}
}
ellipse(el.x, el.y, el.w, el.h);
}
}

Why does the ball in pong get stuck at the bottom?

I recently made a JS Pong game. It works well, but the ball rarely gets stuck at the bottom or top. It looks like it is halfway through the wall and constantly bouncing. Video of the issue happening. You can try the game here. I do not know why this issue is happening because the logic seems right and works 90% of the time correctly. Here are the main two functions of my program:
function moveAll() {
if (showingWinScreen) {
return;
}
computerMovement();
ballX += ballSpeedX;
ballY += ballSpeedY;
if (ballY <= 10) {
ballSpeedY = -ballSpeedY;
} else if (ballY >= HEIGHT - 10) {
ballSpeedY = -ballSpeedY;
}
if (ballX >= WIDTH - 10) {
if ((ballY > paddleY) && (ballY < paddleY + 100)) {
ballSpeedX = -ballSpeedX;
var deltaY = ballY - paddleY - 50;
ballSpeedY = deltaY / 5;
} else {
player1Score++;
ballReset();
}
} else if (ballX <= 10) {
if ((ballY > mouseY - 50) && (ballY < mouseY + 50)) {
ballSpeedX = -ballSpeedX;
deltaY = ballY - mouseY;
ballSpeedY = deltaY / 6;
} else {
player2Score++;
ballReset();
}
}
}
function drawAll() {
if (showingWinScreen) {
colorRect(0, 0, WIDTH, HEIGHT, "black");
canvas.fillStyle = "yellow";
canvas.fillText("Click to continue!", 300, 300);
if (player1Score == WINNING_SCORE) {
canvas.fillText("You won!", 360, 500);
} else if (player2Score == WINNING_SCORE) {
canvas.fillText("The computer beat you!", 280, 500);
}
return;
}
colorRect(0, 0, WIDTH, HEIGHT, "black");
drawNet();
makeCircle(ballX, ballY, 10, 0, Math.PI * 2, "red");
colorRect(790, paddleY, 10, 100, "cyan");
colorRect(0, mouseY - 50, 10, 100, "yellow");
canvas.fillStyle = "white";
canvas.fillText(player1Score + " " + player2Score, 360, 100);
}
Thank you for your help!
I think there's only one case in which this could happen: when, in a colliding frame, you decrease the speed.
When the speed remains the same, no matter what, your ball will always bounce back to the previous' frames position:
var cvs = document.querySelector("canvas");
var ctx = cvs.getContext("2d");
var balls = [
Ball(50, 50, 0, 5, 5, "red"),
Ball(100, 50, 0, 5, 10, "blue"),
Ball(150, 50, 0, 5, 15, "green"),
Ball(200, 50, 0, 5, 20, "yellow")
];
var next = () => {
updateFrame(balls);
drawFrame(balls);
}
var loop = () => {
requestAnimationFrame(() => {
next();
loop();
});
}
next();
function Ball(x, y, vx, vy, r, color) {
return {
x: x,
y: y,
vx: vx,
vy: vy,
r: r,
color: color
}
};
function updateBall(b) {
b.x += b.vx;
b.y += b.vy;
if (b.y <= b.r ||
b.y >= cvs.height - b.r) {
b.vy *= -1;
}
};
function drawBall(b) {
ctx.beginPath();
ctx.fillStyle = b.color;
ctx.arc(b.x, b.y, b.r, 0, 2 * Math.PI, false);
ctx.fill();
}
function updateFrame(balls) {
balls.forEach(updateBall);
}
function drawFrame(balls) {
ctx.clearRect(0, 0, cvs.width, cvs.height);
balls.forEach(drawBall);
};
<canvas width="300" height="150" style="background: #454545"></canvas>
<button onclick="next()">next</button>
<button onclick="loop()">run</button>
But when the speed changes, things get stuck:
var cvs = document.querySelector("canvas");
var ctx = cvs.getContext("2d");
var balls = [
Ball(50, 50, 0, 10, 5, "red"),
Ball(100, 50, 0, 10, 10, "blue"),
Ball(150, 50, 0, 10, 15, "green"),
Ball(200, 50, 0, 10, 20, "yellow")
];
var next = () => {
updateFrame(balls);
drawFrame(balls);
}
var loop = () => {
requestAnimationFrame(() => {
next();
loop();
});
}
next();
function Ball(x, y, vx, vy, r, color) {
return {
x: x,
y: y,
vx: vx,
vy: vy,
r: r,
color: color
}
};
function updateBall(b) {
b.x += b.vx;
b.y += b.vy;
if (b.y <= b.r ||
b.y >= cvs.height - b.r) {
b.vy *= -0.5;
}
};
function drawBall(b) {
ctx.beginPath();
ctx.fillStyle = b.color;
ctx.arc(b.x, b.y, b.r, 0, 2 * Math.PI, false);
ctx.fill();
}
function updateFrame(balls) {
balls.forEach(updateBall);
}
function drawFrame(balls) {
ctx.clearRect(0, 0, cvs.width, cvs.height);
balls.forEach(drawBall);
};
<canvas width="300" height="150" style="background: #454545"></canvas>
<button onclick="next()">next</button>
<button onclick="loop()">run</button>
In your case, I'm thinking this can only happen when there's a paddle collision AND a wall collision simultaneously.
A quick-to-implement solution would be to check if the new position is valid before translating the ball position. If you don't want the precise location, you can place the ball at the point of collision. Note that this will produce a slightly off frame.
E.g.:
var newY = ballY + ballSpeedY;
// Top wall
if(newY <= 10) {
ballY = 10;
ballSpeedY = -ballSpeedY;
}
// Bottom wall
else if(newY >= HEIGHT-10){
ballY = HEIGHT - 10;
ballSpeedY = -ballSpeedY;
}
// No collision
else {
ballY = newY;
}
Update: a more detailed description of what can happen
Let's say your ball collides with the top border of your canvas and with your paddle in the same frame.
First, you move the ball to the colliding position: ballY += ballSpeedY; Say your ballY is 4, and your ballSpeedY is -5, you'll position the ball to -1, inside the wall.
If this were to be the only collision, you should be okay. You flip the speed (ballSpeedY = -ballSpeedY), so in the next frame, your ball should be back at -1 + 5 = 4, so ballY will be 4 again, and your ball will move towards 4 + 5 = 9 in the next frame.
Now a problem arises, when in the -1 positioned frame, you collide with the paddle as well! When the paddle hits the ball, you modify the ballspeed: ballSpeedY = deltaY / 5;. If this turns out to be < 1, your ball won't be able to exit the wall in the next frame. Instead of -1 + 5 = 4, your ball will, for example, move to: -1 + 0.5 = -0.5.
Now, your ball won't be able to get back in to play, since the next frame will, again, calculate a collision and flip the speed. This results in the bouncy, trembling effect you see when the ball gets stuck.
A naive but pretty decent solution, is to only update the position of the ball to a valid position. I.e.: never to a colliding coordinate.
var animate = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function (callback) {
window.setTimeout(callback, 1000 / 60)
};
var canvas = document.createElement("canvas");
var width = 400;
var height = 600;
canvas.width = width;
canvas.height = height;
var context = canvas.getContext('2d');
var player = new Player();
var computer = new Computer();
var ball = new Ball(200, 300);
var keysDown = {};
var render = function () {
context.fillStyle = "#FF00FF";
context.fillRect(0, 0, width, height);
player.render();
computer.render();
ball.render();
};
var update = function () {
player.update();
computer.update(ball);
ball.update(player.paddle, computer.paddle);
};
var step = function () {
update();
render();
animate(step);
};
function Paddle(x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.x_speed = 0;
this.y_speed = 0;
}
Paddle.prototype.render = function () {
context.fillStyle = "#0000FF";
context.fillRect(this.x, this.y, this.width, this.height);
};
Paddle.prototype.move = function (x, y) {
this.x += x;
this.y += y;
this.x_speed = x;
this.y_speed = y;
if (this.x < 0) {
this.x = 0;
this.x_speed = 0;
} else if (this.x + this.width > 400) {
this.x = 400 - this.width;
this.x_speed = 0;
}
};
function Computer() {
this.paddle = new Paddle(175, 10, 50, 10);
}
Computer.prototype.render = function () {
this.paddle.render();
};
Computer.prototype.update = function (ball) {
var x_pos = ball.x;
var diff = -((this.paddle.x + (this.paddle.width / 2)) - x_pos);
if (diff < 0 && diff < -4) {
diff = -5;
} else if (diff > 0 && diff > 4) {
diff = 5;
}
this.paddle.move(diff, 0);
if (this.paddle.x < 0) {
this.paddle.x = 0;
} else if (this.paddle.x + this.paddle.width > 400) {
this.paddle.x = 400 - this.paddle.width;
}
};
function Player() {
this.paddle = new Paddle(175, 580, 50, 10);
}
Player.prototype.render = function () {
this.paddle.render();
};
Player.prototype.update = function () {
for (var key in keysDown) {
var value = Number(key);
if (value == 37) {
this.paddle.move(-4, 0);
} else if (value == 39) {
this.paddle.move(4, 0);
} else {
this.paddle.move(0, 0);
}
}
};
function Ball(x, y) {
this.x = x;
this.y = y;
this.x_speed = 0;
this.y_speed = 3;
}
Ball.prototype.render = function () {
context.beginPath();
context.arc(this.x, this.y, 5, 2 * Math.PI, false);
context.fillStyle = "#000000";
context.fill();
};
Ball.prototype.update = function (paddle1, paddle2) {
this.x += this.x_speed;
this.y += this.y_speed;
var top_x = this.x - 5;
var top_y = this.y - 5;
var bottom_x = this.x + 5;
var bottom_y = this.y + 5;
if (this.x - 5 < 0) {
this.x = 5;
this.x_speed = -this.x_speed;
} else if (this.x + 5 > 400) {
this.x = 395;
this.x_speed = -this.x_speed;
}
if (this.y < 0 || this.y > 600) {
this.x_speed = 0;
this.y_speed = 3;
this.x = 200;
this.y = 300;
}
if (top_y > 300) {
if (top_y < (paddle1.y + paddle1.height) && bottom_y > paddle1.y && top_x < (paddle1.x + paddle1.width) && bottom_x > paddle1.x) {
this.y_speed = -3;
this.x_speed += (paddle1.x_speed / 2);
this.y += this.y_speed;
}
} else {
if (top_y < (paddle2.y + paddle2.height) && bottom_y > paddle2.y && top_x < (paddle2.x + paddle2.width) && bottom_x > paddle2.x) {
this.y_speed = 3;
this.x_speed += (paddle2.x_speed / 2);
this.y += this.y_speed;
}
}
};
document.body.appendChild(canvas);
animate(step);
window.addEventListener("keydown", function (event) {
keysDown[event.keyCode] = true;
});
window.addEventListener("keyup", function (event) {
delete keysDown[event.keyCode];
});
http://jsfiddle.net/kHJr6/2/

Categories