I am currently trying to build a Digital Table Tennis game and I have a lot in place so far, but whenever I run the program, the non-AI player's bat (The green rectangle) isn't showing up. It doesn't seem to be there at all. Is there some bug in my code? Also make sure to mainly pay attention to the bottom of the code, where the animate() function is declared. The outcome so far is here: https://Digital-Table-Tennis.programprodigy.repl.co. PLS HELP!
let canvas = document.querySelector('canvas');
let ctx = canvas.getContext('2d');
canvas.width = innerWidth;
canvas.height = innerHeight;
let mouse = {
x: null
}
window.onmousemove = e => {
mouse.x = e.x;
}
class Ball {
constructor() {
this.dx = Math.random() * 4 - 2;
this.dy = Math.random() * 4 - 2;
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
}
draw() {
ctx.fillStyle = 'orange';
ctx.beginPath();
ctx.arc(this.x, this.y, 10, 0, Math.PI * 2);
ctx.fill();
}
update() {
this.x += this.dx;
this.y += this.dy;
if (
this.x >= mouse.x &&
this.x <= mouse.x + 50 &&
this.y >= canvas.height - 5 &&
this.y <= canvas.height
) {
this.dx = -this.dx;
this.dy = -this.dy;
}
if (
this.x <= 0 ||
this.x >= canvas.width ||
this.y <= 0 ||
this.y >= canvas.height
) {
this.dx = -this.dx;
this.dy = -this.dy;
}
this.draw();
}
}
let ball = new Ball();
class AI {
constructor(lvl) {
this.x = 0;
this.color = 'red';
this.speed = lvl * 5;
}
draw() {
ctx.fillStyle = this.color;
ctx.fillRect(this.x, 0, 50, 5);
}
update() {
if (ball.y < canvas.height / 2) {
let distance = ball.x - this.x;
this.x += distance / this.speed;
}
if (
ball.x >= this.x &&
ball.x <= this.x + 50 &&
ball.y >= 0 &&
ball.y <= 5
) {
ball.dx = -ball.dx;
ball.dy = -ball.dy;
}
this.draw();
}
}
let ai_player = new AI(5);
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'red';
ctx.fillRect(mouse.x - 25, canvas.height - 5, 50, 5);
ball.update();
ai_player.update();
requestAnimationFrame(animate);
}
animate();
I actually have a red player rectangle on my end, however I had to scroll down to see it since the canvas height was more than my viewport.
You specified that the player rectangle should be green, but it's declared in animate() as ctx.fillStyle = 'red';.
As for solving the scrolling issues, it should be a quick CSS fix by specifying a max-width of 100vw and a max-height of 100vh on your canvas element, assuming you don't have any conflicting styles.
I have created an element called Particle. Single animation of this element also works. But as soon as I try to run multiple animations, only one animation gets performed. I think the problem is the requestAnimationFrame (this.animate.bind(this))-call, but I don't know how to change it to accept multiple animations at once. Any ideas on how to fix this?
Code:
//gloabl vars
let particels = [];
let numberParticels = 120;
let canvas;
let ctx;
let title;
let mesaureTitle;
let boundRadius;
let animations;
window.onload = function () {
this.init();
for(let i = 0; i < numberParticels; i++){
particels[i].update();
particels[i].draw();
particels[i].animate(0);
}
}
function init(){
canvas = document.getElementById("c");
ctx = canvas.getContext("2d");
title = document.getElementById("title");
mesaureTitle = title.getBoundingClientRect();
bound = {
x: mesaureTitle.x,
y: mesaureTitle.y,
width: mesaureTitle.width,
height: mesaureTitle.height,
};
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
for(let i = 0; i < numberParticels; i++){
let x = Math.floor(Math.random() * window.innerWidth);
let y = Math.floor(Math.random() * window.innerHeight);
let size = Math.floor(Math.random() * 25) + 3;
let weight = Math.floor(Math.random() * 11) + 2;
particels.push(new Particel(x, y, size, weight));
}
}
class Particel {
constructor (x,y,size, weight) {
this.x = x;
this.y = y;
this.size = size;
this.directionX = 0.15332;
this.resetWeight = weight;
this.weight = weight;
this.lastTime = 0;
this.interval = 1000/60;
this.timer = 0;
}
update(){
this.weight += 0.02;
this.y = this.y + this.weight;
this.x += this.directionX;
//check for collision with textField
if (this.x < bound.x + bound.width
&& this.x + this.size > bound.x &&
this.y < bound.y + bound.height &&
this.y + this.size > bound.y) {
this.y -= 3;
this.weight *= -0.3;
}
}
draw(){
if(this.y > canvas.height){
this.y = 0 - this.size;
this.weight = this.resetWeight;
//create random start point
this.x = Math.floor(Math.random() * canvas.width);
}
ctx.fillStyle = "rgb(0, 180, 97)";
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
}
animate(timeStamp){
const deltaTime = timeStamp - this.lastTime;
this.lastTime = timeStamp;
if(this.timer > this.interval){
ctx.clearRect(0,0, canvas.width, canvas.height);
this.update();
this.draw();
this.timer = 0;
}else {
this.timer += deltaTime;
}
ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
requestAnimationFrame(this.animate.bind(this));
}
}
The major problem is clearing the canvas inside the animate method of each particle. So if you draw multiple particles, each particle update call clears the canvas, overwriting previous particle data, which only leaves the last particle visible.
You could try removing the
ctx.clearRect(0,0, canvas.width, canvas.height);
line from where it is and create a createAnimationFrame call back in init to clear the canvas before amimating particles:
function init() {
// ....
requestAnimationFrame( ()=> ctx.clearRect(0,0, canvas.width, canvas.height));
// existing for loop:
for(let i = 0; i < numberParticels; i++){
// ... .
}
However this creates (one plus the number of particles) requests for an animation frame. A better solution would be to remove requesting animation frames from the Particel class and create a single requestAnimationFrame callback which goes through the particels array and calls a class method to redraw each particle on the canvas with updated position.
Also the code generates an error in strict mode that bound has not been declared. I suggest declaring it globally rather than relying on sloppy mode JavaScript creating it as a window property for you.
When a mouse is hovering a image. It gets detect by this if statement:
if ((distance(circles[this.index].x, circles[this.index].y, mouse.x, mouse.y)) < circles[this.index].radius)
I also want to detect when a mouse it outside a image.
After that previous if statement I cannot use else the reason is because:
When I generate multiple images on screen and when my mouse if hovering over 1 image. It does hover of that image and the code detects it but it also doesnt hover of all the other images. That is the reason that is display 4 times "outside circle" and 1 time "inside circle"
As seen in the log:
Console.log output:
Mouse inside circle
Mouse outside circle 4
Mouse inside circle
Mouse outside circle 4
Im looking for a way the detect when the mouse is leaving a circle.
You can find the code I'm working with below:
PS: it it important that it detect in what (index) circle the mouse is and leaves.
I want to create a huge amount of pictures, but in the code below I used 5 for demo purpeses.
var mouse = {
x: innerWidth / 2,
y: innerHeight / 2
};
// Mouse Event Listeners
addEventListener('mousemove', event => {
mouse.x = event.clientX;
mouse.y = event.clientY;
});
//Calculate distance between 2 objects
function distance(x1, y1, x2, y2) {
let xDistance = x2 - x1;
let yDistance = y2 - y1;
return Math.sqrt(Math.pow(xDistance, 2) + Math.pow(yDistance, 2));
}
// Sqaure to circle
function makeCircleImage(radius, src, callback) {
var canvas = document.createElement('canvas');
canvas.width = canvas.height = radius * 2;
var ctx = canvas.getContext("2d");
var img = new Image();
img.src = src;
img.onload = function() {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// we use compositing, offers better antialiasing than clip()
ctx.globalCompositeOperation = 'destination-in';
ctx.arc(radius, radius, radius, 0, Math.PI*2);
ctx.fill();
callback(canvas);
};
}
function Circle( x, y, radius, index ) {
//Give var for circle
this.x = x;
this.y = y;
this.dx = 1;
this.dy = 1;
this.radius = radius;
this.index = index;
}
// use prototyping if you wish to make it a class
Circle.prototype = {
//Draw circle on canvas
draw: function () {
var
x = (this.x - this.radius),
y = (this.y - this.radius);
// draw is a single call
c.drawImage( this.image, x, y );
},
//Updates position of images
update: function () {
var
max_right = canvas.width + this.radius,
max_left = this.radius * -1;
this.x += this.dx;
if( this.x > max_right ) {
this.x += max_right - this.x;
this.dx *= -1;
}
if( this.x < max_left ) {
this.x += max_left - this.x;
this.dx *= -1;
}
if ((distance(circles[this.index].x, circles[this.index].y, mouse.x, mouse.y)) < circles[this.index].radius) {
// Mouse inside circle
console.log("Mouse inside circle")
} else{
//The mouse is in one circle
//And out of 4 other circles
console.log("Mouse outside circle")
}
},
init: function(callback) {
var url = "https://t4.ftcdn.net/jpg/02/26/96/25/240_F_226962583_DzHr45pyYPdmwnjDoqz6IG7Js9AT05J4.jpg";
makeCircleImage( this.radius, url, function(img) {
this.image = img;
callback();
}.bind(this));
}
};
//Animate canvas
function animate() {
c.clearRect(0, 0, window.innerWidth, window.innerHeight);
circles.forEach(function( circle ) {
circle.update();
});
circles.forEach(function( circle ) {
circle.draw();
});
requestAnimationFrame(animate);
}
//Init canvas
var canvas = document.querySelector('canvas');
var c = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
//init circle objects
var circles = [
new Circle(10, 100, 50,0),
new Circle(10, 200, 30,1),
new Circle(10, 300, 50,2),
new Circle(10, 400, 50,3),
new Circle(10, 500, 50,4)
];
var ready = 0;
circles.forEach(function(circle) {
circle.init(oncircledone);
});
function oncircledone() {
if(++ready === circles.length) {
animate()
}
}
<canvas></canvas>
just add another property to circle
function Circle(x, y, radius, index) {
//Give var for circle
this.x = x;
this.y = y;
this.dx = 1;
this.dy = 1;
this.radius = radius;
this.index = index;
this.mouseInside = false
}
and then the update logic change to this
if ((distance(this.x, this.y, mouse.x, mouse.y)) < circles[this.index].radius) {
if (!this.mouseInside) {
this.mouseInside = true
console.log(`mouse enter circele at ${this.index}`)
}
}
else if (this.mouseInside) {
this.mouseInside = false
console.log(`mouse leave circele at ${this.index}`)
}
check if circles overlap and the you can decide if you want to update
var overlapsCircles = circles.filter(circle => {
var diffrentId = circle.index != this.index
var overlapping =
distance(this.x, this.y, circle.x, circle.y) < this.radius
return diffrentId && overlapping
})
if (overlapsCircles.length > 0) {
var overlapCircle = overlapsCircles.map(circle => circle.index)
console.log('overlap circle with index ' + overlapCircle)
}
var mouse = {
x: innerWidth / 2,
y: innerHeight / 2
};
// Mouse Event Listeners
addEventListener('mousemove', event => {
mouse.x = event.clientX;
mouse.y = event.clientY;
});
//Calculate distance between 2 objects
function distance(x1, y1, x2, y2) {
let xDistance = x2 - x1;
let yDistance = y2 - y1;
return Math.sqrt(Math.pow(xDistance, 2) + Math.pow(yDistance, 2));
}
// Sqaure to circle
function makeCircleImage(radius, src, callback) {
var canvas = document.createElement('canvas');
canvas.width = canvas.height = radius * 2;
var ctx = canvas.getContext("2d");
var img = new Image();
img.src = src;
img.onload = function () {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// we use compositing, offers better antialiasing than clip()
ctx.globalCompositeOperation = 'destination-in';
ctx.arc(radius, radius, radius, 0, Math.PI * 2);
ctx.fill();
callback(canvas);
};
}
function Circle(x, y, radius, index) {
//Give var for circle
this.x = x;
this.y = y;
this.dx = 1;
this.dy = 1;
this.radius = radius;
this.index = index;
this.mouseInside = false
}
// use prototyping if you wish to make it a class
Circle.prototype = {
//Draw circle on canvas
draw: function () {
var
x = (this.x - this.radius),
y = (this.y - this.radius);
// draw is a single call
c.drawImage(this.image, x, y);
},
//Updates position of images
update: function () {
var
max_right = canvas.width + this.radius,
max_left = this.radius * -1;
this.x += this.dx;
if (this.x > max_right) {
this.x += max_right - this.x;
this.dx *= -1;
}
if (this.x < max_left) {
this.x += max_left - this.x;
this.dx *= -1;
}
if ((distance(this.x, this.y, mouse.x, mouse.y)) < circles[this.index].radius) {
if (!this.mouseInside) {
this.mouseInside = true
console.log(`mouse enter circele at ${this.index}`)
}
}
else if (this.mouseInside) {
this.mouseInside = false
console.log(`mouse leave circele at ${this.index}`)
}
},
init: function (callback) {
var url = "https://t4.ftcdn.net/jpg/02/26/96/25/240_F_226962583_DzHr45pyYPdmwnjDoqz6IG7Js9AT05J4.jpg";
makeCircleImage(this.radius, url, function (img) {
this.image = img;
callback();
}.bind(this));
}
};
//Animate canvas
function animate() {
c.clearRect(0, 0, window.innerWidth, window.innerHeight);
circles.forEach(function (circle) {
circle.update();
});
circles.forEach(function (circle) {
circle.draw();
});
requestAnimationFrame(animate);
}
//Init canvas
var canvas = document.querySelector('canvas');
var c = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
//init circle objects
var circles = [
new Circle(10, 100, 50, 0),
new Circle(10, 200, 30, 1),
new Circle(10, 300, 50, 2),
new Circle(10, 400, 50, 3),
new Circle(10, 500, 50, 4)
];
var ready = 0;
circles.forEach(function (circle) {
circle.init(oncircledone);
});
function oncircledone() {
if (++ready === circles.length) {
animate()
}
}
<canvas id="ctx"></canvas>
Ambiguities
It is not clear what you need in regard to circles and some point (in this answer point is a substitute for mouse and only requires that it have the properties x and y to be valid ).
The lack of information in your question concerns the facts
that many circles can be under the point at the same time.
and that more than one circle can move from under to out or out to under the point per frame.
the wording of the question suggest you are after just one circle which conflicts with the above 2 concerns.
Assumptions
I will assume that the interaction with the circles are more than just a simple on under event like interaction. That they may include animation related behaviors that are triggered by the state related to the point.
I assume that the visual order of the circles will determine how you select circles of interest.
That all circles per frame that meet the required conditions and can be accessed quickly.
That performance is important as you wish to have many circles that interact with a point.
That there is only one point (mouse, touch, other source) per frame that interacts with the circles
There is no requirement for circle circle interaction
Solution
The example below covers the above assumptions and resolves any ambiguities in the question. It is designed to be efficient and flexible.
The circles are stored in an array that has had its properties extended called circles
Rendering and state sets
The function circles.updateDraw(point) updates and draws all the circles. The argument point is a point to check the circle against. It defaults to the mouse.
All circles are drawn with an outline. Circles under the point (eg mouse) are filled with green, Circles just moved to under the point (eg onMouseOver) are filled with yellow, circle that have just move out from under are filled with red.
There are 3 arrays as properties of circles that contain circles as define...
circles.under All circles under the point
circles.outFromUnder All circles just out from under the point
circles.newUnder All circles new to under the point
These array are populated by the function circles.updateDraw(point)
Query all circles point state
Circles also have 3 functions that refer to the above arrays as set the default set is circles.under.
The functions are..
circles.firstInSet(set) Returns the first circle (The visual bottom most) in set or undefined
circles.lastInSet(set) Returns the last circle (The visual top most) in set or undefined
circles.closestInSet(set) Returns the closest circle to the point in set or undefined
For example to get the visual top most circle just under the mouse you would call circles.lastInSet(circles.newUnder) or to get the circle closest to the mouse from all circles under the mouse you would call circles.closestInSet(circles.newUnder) (or as it defaults to set under call circles.closestInSet() )
Circle additional states
Each Circle has some additional properties.
Circle.distSqr is the square of the distance from the point
Circle.rSqr is the square of the radius calculated when constructed.
Circle.underCount This value can be used to apply animations to the circle based on its relative state to the point.
If positive is the number of frames plus 1, the circle is under the point.
If this value is 1 then the circle is just moved from not under to under.
If this value is 0 the it has just moved out from under the point.
If negative this value is the number of frames the circle is not under the point
Running Demo
Use mouse to move over circles.
The circle closest and under the mouse is filled with white with alpha = 0.5
addEventListener('mousemove', event => {
mouse.x = event.clientX;
mouse.y = event.clientY;
});
Math.TAU = Math.PI * 2;
Math.rand = (min, max) => Math.random() * (max - min) + min;
const CIRCLE_RADIUS = 50;
const UNDER_STYLE = "#0A0";
const NEW_UNDER_STYLE = "#FF0";
const OUT_STYLE = "#F00";
const CIRCLE_STYLE = "#000";
const CIRCLE_LINE_WIDTH = 1.5;
const CIRCLE_COUNT = 100;
const CIRCLE_CLOSEST = "#FFF";
const ctx = canvas.getContext('2d');
const mouse = {x: 0, y: 0};
requestAnimationFrame(() => {
sizeCanvas();
var i = CIRCLE_COUNT;
while (i--) {
const r = Math.rand(CIRCLE_RADIUS / 3, CIRCLE_RADIUS);
circles.push(new Circle(
Math.rand(r, canvas.width - r),
Math.rand(r, canvas.height - r),
Math.rand(-1, 1),
Math.rand(-1, 1),
r
));
}
animate()
});
function animate() {
sizeCanvas();
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
circles.updateDraw();
const c = circles.closestInSet(circles.under);
if(c) {
ctx.globalAlpha = 0.5;
ctx.beginPath();
ctx.fillStyle = CIRCLE_CLOSEST;
c.draw();
ctx.fill();
ctx.globalAlpha = 1;
}
requestAnimationFrame(animate);
}
function sizeCanvas() {
if (canvas.width !== innerWidth || canvas.height !== innerHeight) {
canvas.width = innerWidth;
canvas.height = innerHeight;
}
}
function Circle( x, y, dx = 0, dy = 0, radius = CIRCLE_RADIUS) {
this.x = x + radius;
this.y = y + radius;
this.dx = dx;
this.dy = dy;
this.radius = radius;
this.rSqr = radius * radius; // radius squared
this.underCount = 0; // counts frames under point
}
Circle.prototype = {
draw() {
ctx.moveTo(this.x + this.radius, this.y);
ctx.arc(this.x, this.y, this.radius, 0, Math.TAU);
},
update() {
this.x += this.dx;
this.y += this.dy;
if (this.x >= canvas.width - this.radius) {
this.x += (canvas.width - this.radius) - this.x;
this.dx = -Math.abs(this.dx);
} else if (this.x < this.radius) {
this.x += this.radius - this.x;
this.dx = Math.abs(this.dx);
}
if (this.y >= canvas.height - this.radius) {
this.y += (canvas.height - this.radius) - this.y;
this.dy = -Math.abs(this.dx);
} else if (this.y < this.radius) {
this.y += this.radius - this.y;
this.dy = Math.abs(this.dy);
}
},
isUnder(point = mouse) {
this.distSqr = (this.x - point.x) ** 2 + (this.y - point.y) ** 2; // distance squared
return this.distSqr < this.rSqr;
}
};
const circles = Object.assign([], {
under: [],
outFromUnder: [],
newUnder: [],
firstInSet(set = this.under) { return set[0] },
lastInSet(set = this.under) { return set[set.length - 1] },
closestInSet(set = this.under) {
var minDist = Infinity, closest;
if (set.length <= 1) { return set[0] }
for (const circle of set) {
if (circle.distSqr < minDist) {
minDist = (closest = circle).distSqr;
}
}
return closest;
},
updateDraw(point) {
this.under.length = this.newUnder.length = this.outFromUnder.length = 0;
ctx.strokeStyle = CIRCLE_STYLE;
ctx.lineWidth = CIRCLE_LINE_WIDTH;
ctx.beginPath();
for(const circle of this) {
circle.update();
if (circle.isUnder(point)) {
if (circle.underCount <= 0) {
circle.underCount = 1;
this.newUnder.push(circle);
} else { circle.underCount ++ }
this.under.push(circle);
} else if (circle.underCount > 0) {
circle.underCount = 0;
this.outFromUnder.push(circle);
} else {
circle.underCount --;
}
circle.draw();
}
ctx.stroke();
ctx.globalAlpha = 0.75;
ctx.beginPath();
ctx.fillStyle = UNDER_STYLE;
for (const circle of this.under) {
if (circle.underCount > 1) { circle.draw() }
}
ctx.fill();
ctx.beginPath();
ctx.fillStyle = OUT_STYLE;
for (const circle of this.outFromUnder) { circle.draw() }
ctx.fill();
ctx.beginPath();
ctx.fillStyle = NEW_UNDER_STYLE;
for (const circle of this.newUnder) { circle.draw() }
ctx.fill();
ctx.globalAlpha = 1;
}
});
#canvas {
position: absolute;
top: 0px;
left: 0px;
background: #6AF;
}
<canvas id="canvas"></canvas>
Well, the mouse is moving and you can simply create a Set which will contain circle objects that will store the circle(s) you are in:
let circleOfTrust = new Set();
//At the initialization you need to add any circles your point is currently in
and then at the loop:
circles.forEach(function( circle ) {
circleOfTrust[circle.update(circleOfTrust.has(circle)) ? "add" : "delete"](circle);
});
if (circleOfTrust.size() === 0) {
//point is outside the circles
} else {
//point is inside the circles in the set
}
and the update:
update: function (isInside) {
var
max_right = canvas.width + this.radius,
max_left = this.radius * -1;
this.x += this.dx;
if( this.x > max_right ) {
this.x += max_right - this.x;
this.dx *= -1;
}
if( this.x < max_left ) {
this.x += max_left - this.x;
this.dx *= -1;
}
return distance(circles[this.index].x, circles[this.index].y, mouse.x, mouse.y)) < circles[this.index].radius;
},
I would propose the following:
Keep a stack of figures with the order of how they were created (or any other meaningful order). This is needed to detect moves over overlapping figures.
Implement a function/method that iterates the stack and determines if the cursor is inside any of the figures.
Remember the last state, on state transition inside->ouside triggers an event.
function FiguresCollection(canvas, callback)
{
var buffer = [];
var lastHitFigure = null;
var addFigure = function(figure)
{
buffer.push(figure);
}
var onMouseMove = function(e)
{
var currentHit = null;
// iterating from the other end, recently added figures are overlapping previous ones
for (var i= buffer.length-1;i>=0;i--)
{
if (distance(e.offsetX, e.offsetY, buffer[i].x, buffer[i].y) <= buffer[i].radius) {
// the cursor is inside Figure i
// if it come from another figure
if (lastHitFigure !== i)
{
console.log("The cursor had left figure ", lastHitFigure, " and entered ",i);
callback(buffer[i]);
}
lastHitFigure = i;
currentHit = i;
break; // we do not care about figures potentially underneath
}
}
if (lastHitFigure !== null && currentHit == null)
{
console.log("the cursor had left Figure", lastHitFigure, " and is not over any other ");
lastHitFigure = null;
callback(buffer[lastHitFigure]);
}
}
}
canvas.addEventListener("mousemove", onMouseMove);
this.addFigure = addFigure;
}
Now use it:
var col = new FiguresCollection(canvas, c=> console.log("The cursor had left, ", c) );
for(let i in circles)
{
c.addFigure(circles[i]);
}
// I hope I got the code right. I haven't tested it. Please point out any issues or errors.
I'm working on a JQuery / JavaScript canvas brick break game, so I added the collision detection code if the ball hits the paddle or not, but when I ran the code, the collision detection code didn't work, I was wondering if anyone could help me? Here's the code.
JSFiddle: https://jsfiddle.net/18h7b9fd/
Main.js
$(document).ready(() => {
let fps = 60;
setInterval(init, 1000 / fps);
$(canvas).bind("mousemove", paddleControl);
})
// INIT
let init = () => {
draw(); //draw objects
animate(); //animate objects
collision(); //detect collision
}
// DRAW
let draw = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
paddle.draw();
ball.draw();
}
// ANIMATE
let animate = () => {
ball.animate();
}
// COLLISION
let collision = () => {
ball.collision();
}
// CONTROL
let paddleControl = (e) => {
// get mouse pos
let rect = canvas.getBoundingClientRect();
let root = document.documentElement;
let mouseX = e.pageX - rect.left - root.scrollLeft;
paddle.x = mouseX - paddle.w / 2;
}
Objects.js
class Paddle {
constructor(x, y, w, h, color) {
this.x = canvas.width / 2 - 100 / 2;
this.y = canvas.height - 60;
this.w = 100;
this.h = 10;
this.color = "#fff";
}
draw() {
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.w, this.h);
}
}
class Ball {
constructor(x, y, r, color, speedX, speedY) {
this.x = canvas.width / 2 - 10 / 2;
this.y = canvas.height / 2 - 10 / 2;
this.r = 10;
this.color = "#fff";
this.speedX = 6;
this.speedY = 6;
}
draw() {
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);
ctx.fill();
}
animate() {
this.x += this.speedX;
this.y += this.speedY;
}
collision() {
// BALL TO PADDLE
let paddleTop = paddle.y - paddle.h;
let paddleBottom = canvas.height - paddleTop;
let paddleLeft = paddle.x;
let paddleRight = paddle.x + paddle.w;
if(this.y >= paddleTop && this.y <= paddleBottom &&
this.x >= paddleLeft && this.x <= paddleRight) {
this.speedY *= -1;
}
// BALL TO WALL
if(this.x >= canvas.width) this.speedX *= -1; //left
if(this.x <= 0) this.speedX *= -1; //right
if(this.y >= canvas.height) this.reset(); //bottom
if(this.y <= 0) this.speedY *= -1; //top
}
reset() {
this.x = canvas.width / 2 - this.r / 2;
this.y = canvas.height / 2 - this.r / 2;
this.animate();
}
}
let paddle = new Paddle(this.x, this.y, this.w, this.h, this.color);
let ball = new Ball(this.x, this.y, this.r, this.color, this.speedX, this.speedY);
console.log(paddle);
console.log(ball);
The problem is the way you are setting the paddle top and bottom. Use this instead:
let paddleTop = paddle.y;
let paddleBottom = paddleTop + paddle.h;
Currently the way you are setting the values makes it impossible for the condition to ever be true (as paddle bottom is always less than paddle top).
I have put together a fiddle here: https://jsfiddle.net/gegbqv5p/
You will also notice that I didn't use jQuery. For what you have so far, it really isn't necessary.
I am trying to make a ping pong game. At the moment I got the ball moving and both paddles moving when keys pressed. But the ball does not bounce off the paddles. There is code to bounce off player2 paddle but it does not seem to work. It's a lot of code I know. Can you help me find out what is wrong?
"use strict";
// Variables
var c = document.getElementById("sCanvas");
var ctx = sCanvas.getContext("2d");
var cHeight = sCanvas.height;
var cWidth = sCanvas.width;
//Objects
//create paddle object
class Paddle {
constructor(x, y) {
this.colour = "red";
this.xPoss = x;
this.yPoss = y;
this.width = 12;
this.height = 60;
this.speed = 3;
}
drawMe() {
ctx.fillStyle = this.colour;
ctx.fillRect(this.xPoss, this.yPoss, this.width, this.height);
}
} // end paddle object
//create the sphere object
class Sphere {
constructor() {
this.radius = (10);
this.colour = "blue";
this.xPos = 65; //Math.random() * cWidth;
this.yPos = 100; //Math.random() * cHeight;
this.speedY = 5; //* Math.random();
this.speedX = 5; //* Math.random();
}
drawMe() {
//method to draw itself
ctx.beginPath();
ctx.arc(this.xPos, this.yPos, this.radius, 0, Math.PI * 2, true);
ctx.fillStyle = this.colour;
ctx.fill();
}
//method to move itself
moveMe() {
this.yPos += this.speedY;
this.xPos += this.speedX;
//bounce off the bottom wall
if (this.yPos > cHeight - this.radius) {
this.speedY = -this.speedY;
} //bounce off the top wall
else if (this.yPos < 0 + this.radius) {
this.speedY = -this.speedY;
}
//stop ball if hit right side
if (this.xPos > cWidth) {
this.speedX = 0;
this.speedY = 0;
}
//bounce off player 2 paddle
else if (this.xPos > player2.xPoss && (this.yPos > player2.yPoss && this.yPos < (player2.yPoss + player2.height))) {
this.speedX = -this.speedX;
}
}
//end moveMe function
} // end Sphere object
//******************
// create game objects
//******************
var ball = new Sphere();
var player1 = new Paddle(10, 150);
var player2 = new Paddle(580, 150);
//*********************
// Deal with key presses
// **********************
var keysDown = []; //empty array to store which keys are being held down
window.addEventListener("keydown", function(event) {
keysDown[event.keyCode] = true; //store the code for the key being pressed
});
window.addEventListener("keyup", function(event) {
delete keysDown[event.keyCode];
});
function checkKeys() {
if (keysDown[90]) {
if (player1.yPoss > 0) {
player1.yPoss = -player1.speed; //z
}
}
if (keysDown[88]) {
if (player1.yPoss < (cHeight - player1.height)) {
player1.yPoss += player1.speed; //x
}
}
if (keysDown[190]) {
if (player2.yPoss > 0) {
player2.yPoss = -player2.speed; //"."
}
}
if (keysDown[188]) {
if (player2.yPoss < (cHeight - player2.height)) {
player2.yPoss += player2.speed; //","
}
}
}
// your 2 new sets of code here for 2 more keys for player 2
//*********************
// Make the score board
// **********************
//*********************
// launch the ball from the centre, left and right, on space bar
// **********************
function render() {
requestAnimationFrame(render);
ctx.clearRect(0, 0, cWidth, cHeight);
ball.drawMe();
ball.moveMe();
player1.drawMe();
player2.drawMe();
checkKeys();
}
render(); //set the animation and drawing on canvas going
<canvas id="sCanvas" width="600" height="400" style="border: solid;"></canvas>
I added a simple hit box and detector and drawing the boxes in black to illustrate. Changed xPoss, yPoss to xPos, yPos for consistency. Changed = - to -= on the paddle movement (was snapping to top when moving up). Changed class and methods to JavaScript functions and prototype methods.
This should be enough to go on to see the how JavaScript does objects and how to do a simple hit detection. This isn't the only way to create objects but it is close to what you were trying to do.
update
Made some changes to hit/collision detection. Can now hit ball from bottom and top of paddle.
"use strict";
// Variables
var c = document.getElementById("sCanvas");
var ctx = sCanvas.getContext("2d");
var cHeight = sCanvas.height;
var cWidth = sCanvas.width;
//Objects
//create paddle object
function Paddle(x, y) {
this.colour = "red";
this.xPos = x;
this.yPos = y;
this.width = 12;
this.height = 60;
this.speed = 3;
}
Paddle.prototype.drawMe = function() {
ctx.fillStyle = this.colour;
ctx.fillRect(this.xPos, this.yPos, this.width, this.height);
}; // end paddle object
/***** BEGIN COLLISION DECTECTION FUNCTIONS *****/
// optimized collision of boxes - Does a hit b?
function hit(a, b) {
// Return immediately when the objects aren't touching.
if (
a.x2 < b.x || // a.right is before b.left
b.x2 < a.x || // b.right is before a.left
a.y2 < b.y || // a.bottom is before b.top
b.y2 < a.y // b.bottom is before a.top
) {
return false;
}
// The objects are touching. It is a hit or collision.
return true;
}
// does a hit the top of b?
function hitTop(a, b) {
return (a.y2 > b.y && a.y < b.y) ? true : false;
}
// does a hit the bottom of b?
function hitBottom(a, b) {
return (a.y < b.y2 && a.y2 > b.y2) ? true : false;
}
// Creates an obect of x, y, x2, y2 for hit detection.
function hitObj(obj) {
var h = {x:0, y:0, x2:0, y2:0};
if (obj.radius) {
h.x = obj.xPos - obj.radius;
h.x2 = obj.xPos + obj.radius;
h.y = obj.yPos - obj.radius;
h.y2 = obj.yPos + obj.radius;
} else {
h.x = obj.xPos;
h.x2 = obj.xPos + obj.width | (obj.radius * 2);
h.y = obj.yPos;
h.y2 = obj.yPos + obj.height | (obj.radius * 2);
}
// draw hit box - uncomment to see hit detection
/*
ctx.save();
ctx.strokeStyle = "#000000";
ctx.lineWidth = 5;
ctx.strokeRect(h.x, h.y, h.x2 - h.x, h.y2 - h.y);
ctx.restore();
*/
return h;
}
/***** END COLLISION DETECTION FUNCTIONS *****/
//create the sphere object
function Sphere() {
this.radius = (10);
this.colour = "blue";
this.xPos = 25; //Math.random() * cWidth;
this.yPos = 5; //Math.random() * cHeight;
this.speedY = 5; //* Math.random();
this.speedX = 5; //* Math.random();
}
Sphere.prototype.drawMe = function() {
//method to draw itself
ctx.beginPath();
ctx.arc(this.xPos, this.yPos, this.radius, 0, Math.PI * 2, true);
ctx.fillStyle = this.colour;
ctx.fill();
};
Sphere.prototype.moveMe = function() {
//method to move itself
// save start position. change back to start
// position when there's a hit so we don't
// get stuck in an object.
var pos = {
x: this.xPos,
y: this.yPos
};
// move
this.yPos += this.speedY;
this.xPos += this.speedX;
//bounce off the bottom wall
if (this.yPos > cHeight - this.radius) {
this.yPos = pos.y;
this.speedY = -this.speedY;
} //bounce off the top wall
else if (this.yPos < 0 + this.radius) {
this.yPos = pos.y;
this.speedY = -this.speedY;
}
/*
//stop ball if hit right side
if (this.xPos > cWidth) {
this.speedX = 0;
this.speedY = 0;
}
*/
// Bounce off left and right sides.
if ((this.xPos+this.radius) >= cWidth || this.xPos <= 0) {
this.xPos = pos.x;
this.speedX = -this.speedX;
}
//bounce off player paddle
else {
var pad1 = hitObj(player1);
var pad2 = hitObj(player2);
var ball = hitObj(this);
if (hit(ball, pad2)) {
// hit player2
this.xPos = pos.x;
this.speedX *= -1;
// if the ball travels down and hits top OR
// if the ball travels up and hits bottom then bounce back
if (
(this.speedY > 0 && hitTop(ball, pad2)) ||
(this.speedY < 0 && hitBottom(ball, pad2))
) {
this.yPos = pos.y;
this.speedY *= -1;
}
} else if (hit(ball, pad1)) {
// hit player1
this.xPos = pos.x;
this.speedX *= -1;
// if the ball travels down and hits top OR
// if the ball travels up and hits bottom then bounce back
if (
(this.speedY > 0 && hitTop(ball, pad1)) ||
(this.speedY < 0 && hitBottom(ball, pad1))
) {
this.yPos = pos.y;
this.speedY *= -1;
}
}
}
};
//end moveMe function
//******************
// create game objects
//******************
var ball = new Sphere();
var player1 = new Paddle(10, 150);
var player2 = new Paddle(580, 150);
//*********************
// Deal with key presses
// **********************
var keysDown = []; //empty array to store which keys are being held down
window.addEventListener("keydown", function(event) {
keysDown[event.keyCode] = true; //store the code for the key being pressed
});
window.addEventListener("keyup", function(event) {
delete keysDown[event.keyCode];
});
function checkKeys() {
if (keysDown[90]) {
if (player1.yPos > 0) {
player1.yPos -= player1.speed; //z
}
}
if (keysDown[88]) {
if (player1.yPos < (cHeight - player1.height)) {
player1.yPos += player1.speed; //x
}
}
if (keysDown[190]) {
if (player2.yPos > 0) {
player2.yPos -= player2.speed; //"."
}
}
if (keysDown[188]) {
if (player2.yPos < (cHeight - player2.height)) {
player2.yPos += player2.speed; //","
}
}
}
// your 2 new sets of code here for 2 more keys for player 2
//*********************
// Make the score board
// **********************
//*********************
// launch the ball from the centre, left and right, on space bar
// **********************
function render() {
requestAnimationFrame(render);
ctx.clearRect(0, 0, cWidth, cHeight);
ball.drawMe();
ball.moveMe();
player1.drawMe();
player2.drawMe();
checkKeys();
}
render(); //set the animation and drawing on canvas going
#sCanvas {
width: 300px;
height: 200px;
}
<canvas id="sCanvas" width="600" height="400" style="border: solid;"></canvas>