hmtl5 canvas collision with multiple objects? - javascript

So i am doing a flappy bird clone as coding exercise... and now i got to the part where i have to detect the collision bettwen the bird and the pipes.
I would to know how would you suggest be the best way of detecting the collision of the bird with the pipes, taking into account that i am pushing the pipes into an array
this are the main parts of the code
//Pipes proto that the bird most avoid
function Pipes(x1, y1, height1, width1, dx1, dy1, x2, y2, height2,
width2, dx2, dy2) {
this.x1 = x1;
this.y1 = y1;
this.height1 = height1;
this.width1 = width1;
this.dx1 = dx1;
this.dy1 = dy1;
this.x2 = x2;
this.y2 = y2;
this.height2 = height2;
this.width2 = width2;
this.dx2 = dx2;
this.dy2 = dy2;
this.draw = function () {
c.fillStyle = "green";
c.fillRect(x1, y1, height1, width1)
c.fillStyle = "green";
c.fillRect(x2, y2, height2, width2)
}
this.update = function () {
x1 += -dx1;
x2 += -dx2;
this.draw();
}
}
function Bird(x, y, dx, dy, radius, color, flap) {
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.radius = radius;
this.color = color;
this.draw = function () {
c.beginPath();
c.arc(x, y, radius, 0, 2 * Math.PI, false);
c.fillStyle = color;
c.fill();
};
this.update = function (){
flap = false;
x += dx
//gravity manager
var gravity = 0.4;
y += dy
dy += gravity;
//Screen collision manager
if (y + dy > innerHeight) {
y = innerHeight - radius;
}
if(radius < crashObj.x1 || radius < crashObj.x2 || radius <
crashObj.x1){
}
this.draw()
}
};
//Main GameLoop
function animate() {
c.clearRect(0, 0, innerWidth, innerHeight);
//canvas Color
c.fillStyle = "#C5D3E2";
c.fillRect(0, 0, window.innerWidth, window.innerHeight);
//Updates pipes
for (var i = 0; i < pipesArr.length; i++) {
pipesArr[i].update();
}
//draw and update bird into the screen
bird.update();
requestAnimationFrame(animate);
}
animate();
thanks in advance fo the answer!

What you are looking for is a circle-rectangle collision.
User markE has posted an in-depth answer on how to achieve it, along with a JS Fiddle demonstrating it.
Then on the update function, run through all the Pipes and check each of them for collision. For example:
for(var i = 0; i < pipesArr.length; i++){
...
if(RectCircleColliding(bird, pipe)){
die()
}
}

Related

How to check for collision on canvas for objects created from the same class?

I am building a ball physics simulator in Javascript. I understand how to detect collisions for individually created objects, but struggle when the objects are constructed using ES6 classes. Here is the code I have so far.
Ball Constructor
const Ball = class {
constructor(x, y, dx, dy) {
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
}
checkForWall() {
if (this.x + ballRadius < ballRadius) {
this.dx = -this.dx;
}
if (this.y + ballRadius < ballRadius) {
this.dy = -this.dy;
}
if (this.x + ballRadius > canvas.width) {
this.dx = -this.dx;
}
if (this.y + ballRadius > canvas.height) {
this.dy = -this.dy;
}
}
moveBall() {
this.x += this.dx;
this.y += this.dy;
}
};
Ball Drawing Function on Canvas
const drawBalls = function (x, y) {
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI * 2);
ctx.fillStyle = `green`;
ctx.fill();
ctx.closePath();
};
Drawing Function on canvas with interval timer
const draw = function () {
ctx.clearRect(0, 0, canvas.width, canvas.clientHeight);
drawBalls(ballOne.x, ballOne.y);
drawBalls(ballTwo.x, ballTwo.y);
ballTwo.moveBall();
ballTwo.checkForWall();
ballOne.moveBall();
ballOne.checkForWall();
}
let interval = setInterval(draw, 10);
Is there a way to implement code in the above class to check whether or not balls constructed from this class have collided with other balls constructed from the same class in the same way that I am able to check for collision with the canvas walls? Do I need to create a parent class and a child class?

Calling object methods in Javascript

I have practically no experience in JS (I mostly program in C#). I'm trying to create a random layout of circles (stars) in an html5 canvas and I have them moving at a random x and y velocity. I created a Star class to make many of these objects and manipulate them, but when I call the Update() method, I receive this TypeError:
TypeError: Cannot read property 'Update' of undefined
at animate (file:///F:/Code/Website/main.js:67:20)
at file:///F:/Code/Website/main.js:71:1
Here is the code that is throwing the error:
//Circle object
class Star
{
constructor(X, Y, VX, VY, R)
{
this.x = X;
this.y = Y;
this.vx = VX;
this.vy = VY;
this.r = R;
}
draw() {
c.beginPath();
c.arc(this.x, this.y, this.r, 0, Math.PI * 2, false);
c.strokeStyle = "blue";
c.stroke();
};
Update() {
if (this.x + this.r > pageWidth || this.x - this.r < 0) {
this.vx = -this.vx;
}
if (this.y + this.r > pageHeight || this.y - this.r < 0) {
this.vy = -this.vy;
}
//add velocity
this.x += this.vx;
this.y += this.vy;
this.draw();
};
}
for (var i = 0; i < 50; i++) {
var x = Math.random() * canvas.width;
var y = Math.random() * canvas.height;
var vx = Math.random();
var vy = Math.random();
var radius = 30;
let star = new Star(x, y, vx, vy, radius);
starArr.push(star);
}
console.log(starArr);
function animate() {
"use strict";
window.requestAnimationFrame(animate);
c.clearRect(0, 0, pageWidth, pageHeight);
for (var j = 0; j < starArr.length; j++); {
starArr[j].Update();
}
}
animate();
You have a typo at the for loop that calls the Update method: an extra semicolon ;
for (var j = 0; j < starArr.length; j++); {
const canvas = document.querySelector("canvas");
const c = canvas.getContext("2d");
let pageWidth = canvas.width = window.innerWidth;
let pageHeight = canvas.height = window.innerHeight;
let starArr = []
//Circle object
class Star{
constructor(X, Y, VX, VY, R){
this.x = X;
this.y = Y;
this.vx = VX;
this.vy = VY;
this.r = R;
}
draw() {
c.beginPath();
c.arc(this.x, this.y, this.r, 0, Math.PI * 2, false);
c.strokeStyle = "blue";
c.stroke();
};
Update() {
if (this.x + this.r > pageWidth || this.x - this.r < 0) {
this.vx = -this.vx;
}
if (this.y + this.r > pageHeight || this.y - this.r < 0) {
this.vy = -this.vy;
}
//add velocity
this.x += this.vx;
this.y += this.vy;
this.draw();
};
}
for (var i = 0; i < 50; i++) {
var x = Math.random() * canvas.width;
var y = Math.random() * canvas.height;
var vx = Math.random();
var vy = Math.random();
var radius = 30;
let star = new Star(x, y, vx, vy, radius);
starArr.push(star);
}
function animate() {
//"use strict";
window.requestAnimationFrame(animate);
c.clearRect(0, 0, pageWidth, pageHeight);
for (var j = 0; j < starArr.length; j++) {
starArr[j].Update();
}
}
animate();
<canvas></canvas>

Spawning balls glitch

I created a small simulation have balls spawning when the mouse is left clicked on the canvas, then the balls start falling. The script is written in javascript:
var ctx = canvas.getContext("2d");
var vy = 0;
var a = 0.5;
var bouncing_factor = 0.9;
var balls = [];
class Ball {
constructor(x, y, r){
this.x = x;
this.y = y;
this.r = r;
}
show () {
ctx.fillStyle="red";
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
ctx.fill();
}
bounce () {
var ground = canvas.height - this.r;
if(this.y > ground && Math.abs(vy) < a + 1) {
cancelAnimationFrame(draw);
}
else if (this.y < ground) {
vy += a;
this.y += vy;
}
else {
vy = -vy * bouncing_factor;
this.y = ground - 1;
}
}
}
function spawn(event) {
var r = (Math.random()*20)+10;
var b = new Ball(event.clientX, event.clientY, r);
balls.push(b);
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < balls.length; i++) {
balls[i].show();
balls[i].bounce();
}
}
setInterval(draw,10);
The problem here is that if you run this script, it works fine for the first ball but when you spawn another ball; it follows the bouncing as the first one.
Any help will be highly appreciated.
I've made a few changes in your code: The speed vx and the acceleration a are now part of the ball object. The speed starts at 3 and the acceleration will decrease every time when the ball hits the ground. The ball stops bouncing when the acceleration this.a < .1
const canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var bouncing_factor = 0.9;
var balls = [];
class Ball {
constructor(x, y, r){
this.x = x;
this.y = y;
this.r = r;
this.vy = 3;
this.a = 0.5;
}
show () {
ctx.fillStyle="red";
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
ctx.fill();
}
bounce () {
var ground = canvas.height - this.r;
if (this.y < ground) {
this.vy += this.a;
this.y += this.vy;
}
else {
this.vy = -this.vy * bouncing_factor;
this.y = ground - 1;
this.a *=.95;
}
if(Math.abs(this.a) < .1){this.a=0; this.vy=0}
}
}
function spawn(event) {
var r = (Math.random()*20)+10;
var b = new Ball(event.clientX, event.clientY, r);
balls.push(b);
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < balls.length; i++) {
balls[i].show();
balls[i].bounce();
}
}
setInterval(draw,10);
canvas.addEventListener("click", spawn)
<canvas id="myCanvas" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>

Javascript Ball Class? - EDIT

I have been trying to play around with the idea of a Ball class within Javascript that is within an HTML file. Check out my code to see what my hang up is. I have seen people use both a function and a class to accomplish this but I can't seem to get this to work. I feel like I am doing more of a C++ style rather than Javascript. Any suggestions?
class Ball {
constructor(x, y, radius, dx, dy) {
this.x = x;
this.y = y;
this.radius = radius;
this.dx = dx;
this.dy = dy;
}
get X() {
return this.x;
}
get Y() {
return this.y;
}
get Radius() {
return this.radius;
}
get Dx() {
return this.dx;
}
get Dy() {
return this.dy;
}
drawBall() {
ctx.beginPath();
ctx.arc(get X(), get Y(), get Radius(), 0, Math.PI*2);
ctx.fillStyle() = "black";
ctx.fill();
ctx.closePath();
}
};
var Ball1 = new Ball(canvas.width/2, canvas.height-30, 10, 2, -2);
function drawBall() {
ctx.beginPath();
ctx.arc(get X(), get Y(), get Radius(), 0, Math.PI*2);
ctx.fillStyle = "blue";
ctx.fill();
ctx.closePath();
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBall();
drawBall2();
if((Ball1.get X() + Ball1.get Dx()) > canvas.width-Ball1.get Radius() || (Ball1.get X() + Ball1.get Dx()) < Ball1.get Radius()) {
Ball1.set Dx(-(Ball1.get Dx()));
}
if(Ball1.get Y() + Ball1.get Dy() > canvas.height-Ball1.get Radius() || y + dy < ballRadius) {
dy = -dy;
}
if(x2 + dx2 > canvas.width-ballRadius || x2 + dx2 < ballRadius) {
dx2 = -dx2;
}
if(y2 + dy2 > canvas.height-ballRadius || y2 + dy2 < ballRadius) {
dy2 = -dy2;
}
x += dx;
y += dy;
x2 += dx2;
y2 += dy2;
}
I know there are discrepancies in my code like things not matching but I don't think I am on the right track. Thanks ahead of time!
EDIT!
Here is the code that I started with that works so far:
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>My Game</title>
<style>
* { padding: 0; margin: 0; }
canvas { background: #eee; display: block; margin: 0 auto; }
</style>
</head>
<body>
<canvas id="myCanvas" width="600" height="580"></canvas>
<script>
//Javascript goes here
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var ballRadius = 10;
//ball 1
var x = canvas.width/2;
var y = canvas.height-30;
var dx = 2;
var dy = -2;
//ball 2
var x2 = canvas.width/3;
var y2 = canvas.height-30;
var dy2 = -4;
var dx2 = 4;
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI*2);
ctx.fillStyle = "blue";
ctx.fill();
ctx.closePath();
}
function drawBall2() {
ctx.beginPath();
ctx.arc(x2, y2, ballRadius, 0, Math.PI*2);
ctx.fillStyle = "red";
ctx.fill();
ctx.closePath();
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBall();
drawBall2();
if(x + dx > canvas.width-ballRadius || x + dx < ballRadius) {
dx = -dx;
}
if(y + dy > canvas.width-ballRadius || y + dy < ballRadius) {
dy = -dy;
}
if(x2 + dx2 > canvas.width-ballRadius || x2 + dx2 < ballRadius) {
dx2 = -dx2;
}
if(y2 + dy2 > canvas.height-ballRadius || y2 + dy2 < ballRadius) {
dy2 = -dy2;
}
x += dx;
y += dy;
x2 += dx2;
y2 += dy2;
}
setInterval(draw, 10);
</script>
</body>
</html>
I fixed your code a little bit just to give you an Idea. This is still not a very good code design. The example will only work in modern Browsers because you use es6 features like class. I think you should start with some javascript fundamentals first.
"use strict";
class Ball {
constructor(x, y, radius, dx, dy) {
this.x = x;
this.y = y;
this.radius = radius;
this.dx = dx;
this.dy = dy;
}
get X() {
return this.x;
}
get Y() {
return this.y;
}
get Radius() {
return this.radius;
}
get Dx() {
return this.dx;
}
get Dy() {
return this.dy;
}
draw(ctx) {
ctx.beginPath();
ctx.arc(this.X, this.Y, this.Radius, 0, Math.PI * 2);
ctx.fillStyle = "black";
ctx.stroke();
}
};
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var Ball1 = new Ball(canvas.width / 2, canvas.height - 30, 10, 2, -2);
Ball1.draw(ctx);
<canvas id="myCanvas" width="200" height="200"></canvas>
Simple Javascript
var canvas = document.getElementById("my-canvas");
var ctx = canvas.getContext("2d");
function drawBall(ctx, x, y, r) {
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI * 2);
ctx.fillStyle = "black";
ctx.stroke();
}
drawBall(ctx, canvas.width / 2, canvas.height - 30, 10, 2, -2);

How to make multiple balls drop on click?

I have a ball that drops from cursor location, and redrops when the cursor is moved to another location. I am trying get a new ball to drop every time I click the mouse. I tried:
canvas.addEventListener('click', function(event) {
ball.draw();
});
But it doesn't seem to do anything. Is there some way to draw a NEW ball on click instead of just redrawing the same ball over and over again?
Here's the rest of the code:
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d");
var W = window.innerWidth,
H = window.innerHeight;
var running = false;
canvas.height = H; canvas.width = W;
var ball = {},
gravity = .5,
bounceFactor = .7;
ball = {
x: W,
y: H,
radius: 15,
color: "BLUE",
vx: 0,
vy: 1,
draw: function() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2, false);
ctx.fillStyle = this.color;
ctx.fill();
ctx.closePath();
}
};
function clearCanvas() {
ctx.clearRect(0, 0, W, H);
}
function update() {
clearCanvas();
ball.draw();
ball.y += ball.vy;
ball.vy += gravity;
if(ball.y + ball.radius > H) {
ball.y = H - ball.radius;
ball.vy *= -bounceFactor;
}
}
canvas.addEventListener("mousemove", function(e){
ball.x = e.clientX;
ball.y = e.clientY;
ball.draw();
});
setInterval(update, 1000/60);
ball.draw();
Just rewrite the ball object so it becomes instantiate-able:
function Ball(W, H) {
this.x = W;
this.y = H;
this.radius = 15;
this.color = "blue";
this.vx = 0;
this.vy = 1;
}
Move the methods to prototypes (this will make them shareable across instances). In addition, add an update method so you can localize updates:
Ball.prototype = {
draw: function() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2, false);
ctx.fillStyle = this.color;
ctx.fill();
ctx.closePath();
},
update: function() {
this.y += this.vy;
this.vy += gravity;
if(this.y + this.radius > H) {
this.y = H - this.radius;
this.vy *= -bounceFactor;
}
}
};
In the click event (consider renaming the array to plural form - it's easier to distinguish that way. In your code you're overriding the "array" (which is defined as an object) with a single ball object later):
var balls = []; // define an array to hold the balls
For the click event to use the x and y position of the mouse as start point for the ball, we first need to adjust it as it is relative to client window and not the canvas. To do this we get the absolute position of canvas and subtract it from the client coordinates:
canvas.addEventListener('click', function(event) {
var rect = this.getBoundingClientRect(), // adjust mouse position
x = event.clientX - rect.left,
y = event.clientY - rect.top;
balls.push(new Ball(x, y)); // add a new instance
});
Now in the main animation loop just iterate over the array. Every time there is a new ball it will be considered and updated - we just let the loop run until some condition is met (not shown):
function update() {
clearCanvas();
for(var i = 0, ball; ball = balls[i]; i++) {
ball.draw(); // this will draw current ball
ball.update(); // this will update its position
}
requestAnimationFrame();
}
Live example
If you put these together you will get:
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
W = canvas.width, // simplified for demo
H = canvas.height,
gravity = .5,
bounceFactor = .7;
function Ball(x, y) {
this.x = x;
this.y = y;
this.radius = 15;
this.color = "blue";
this.vx = 0;
this.vy = 1
}
Ball.prototype = {
draw: function() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
ctx.closePath();
},
update: function() {
this.y += this.vy;
this.vy += gravity; // todo: limit bounce at some point or this value will be added
if (this.y + this.radius > H) {
this.y = H - this.radius;
this.vy *= -bounceFactor;
}
}
};
function clearCanvas() {
ctx.clearRect(0, 0, W, H);
}
var balls = []; // define an array to hold the balls
canvas.addEventListener('click', function(event) {
var rect = this.getBoundingClientRect(), // adjust mouse position
x = event.clientX - rect.left,
y = event.clientY - rect.top;
balls.push(new Ball(x, y)); // add a new instance
});
(function update() {
clearCanvas();
for (var i = 0, ball; ball = balls[i]; i++) {
ball.draw(); // this will draw current ball
ball.update(); // this will update its position
}
requestAnimationFrame(update);
})();
canvas {background:#aaa}
<canvas id="canvas" width=600 height=400></canvas>

Categories