Search code examples
javascriptcanvasbounding-boxpong

Collision between ball and paddle in pong game on canvas


The ball seems to bounce off one side of the paddle, but when it comes from the side it glitches through the paddle. I just can't find a way behind it and it really bothers me. I am using some logic gates to define where the ball's direction is need to be invereted

function startGame() {
    GameArea.start();
	Ball1 = new CircleComp('white' , window.innerWidth - 200 , window.innerHeight - 20);
	Ball1.ySpeed = 13.5;
	Ball1.xSpeed = 6;
	Paddle1 = new PaddleComp( 87, 83, 0, window.innerHeight / 2.5, 10, 70);
	Paddle2 = new PaddleComp( 38, 40, window.innerWidth - 10, window.innerHeight / 2.5, 10 , 70);
}
var GameArea = {
	canvas : canvas = document.querySelector("canvas"),
	start : function (){
		this.canvas.width = window.innerWidth;
		this.canvas.height = window.innerHeight;
		this.ctx = this.canvas.getContext('2d');
		this.interval = setInterval(updateGameArea, 20);
        window.addEventListener('keydown', function (e) {
            GameArea.keys = (GameArea.keys || []);
            GameArea.keys[e.keyCode] = true;
        })
        window.addEventListener('keyup', function (e) {
            GameArea.keys[e.keyCode] = false; 
        })
    }, 
	clear : function() {
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    }
}
function CircleComp(color, x , y){
	this.x = x;
	this.y = y;
	this.width = 8;
    this.height = 8;
	var context1 = GameArea.ctx;
	this.update = function(){
		context1.beginPath();
		context1.fillStyle = color;
		context1.fillRect(this.x, this.y, this.width, this.height);
		context1.fill();
		context1.stroke();
		this.updatePosition();
	}
	this.updatePosition = function(){
		this.y += this.ySpeed;	
		this.x += this.xSpeed;
		if(this.x + this.width > GameArea.canvas.width){
			this.xSpeed = -this.xSpeed;
		}
		if(this.y + this.height > GameArea.canvas.height){
			this.ySpeed = -this.ySpeed;;	
		}
		if(this.x - this.width < 0){
			this.xSpeed = -this.xSpeed;
		}	
		if(this.y - this.height < 0){
			this.ySpeed = -this.ySpeed;
		}
		if(this.y + this.height > Paddle2.y && this.y - this.width < (Paddle2.y + 130) && this.x + this.width > Paddle2.x ){
			this.xSpeed = -this.xSpeed;
		}	
		if(this.y + this.height > Paddle1.y && this.y - this.width < (Paddle1.y + 70) && this.x - this.height < Paddle1.x + 10){
			this.xSpeed = -this.xSpeed;
		}
	}
}
function PaddleComp(Upkey, Downkey, x, y, width, height){
	this.x = x;
	this.y = y;
	this.width = width;
	this.height = height;
	this.ySpeed = 0;
	var context2 = GameArea.ctx;
	this.update = function(){
	context2.fillStyle = 'white';
	context2.fillRect(x,this.y,this.width,this.height);	
	this.updatePosition();
	}
	this.updatePosition = function() {
		this.ySpeed = 0;	
		if (GameArea.keys && GameArea.keys[Upkey]) {
			this.ySpeed = -15; //console.log('Up');
		}
		if (GameArea.keys && GameArea.keys[Downkey]) {
			this.ySpeed = 15; //console.log('Down');
		}
		if ((GameArea.keys && GameArea.keys[Downkey]) && this.y + 130 > window.innerHeight){
			this.ySpeed = this.ySpeed -15 ;	
		}
		if ((GameArea.keys && GameArea.keys[Upkey]) && this.y < 0 ){
			this.ySpeed = this.ySpeed +15 ;	
		}
		this.y += this.ySpeed;			
	}
}
function updateGameArea(){
	GameArea.clear();
	Paddle1.update();
	Paddle2.update();
	Ball1.update();
}
<html>
	<head>
		<meta charset='urf-8'>
		<style>
			canvas{
				border: 0px solid black;
				background-color: black;
			}
			body{
				margin: 0;
				overflow: hidden;
			}
		</style>
	</head>
	<body onload='startGame()'>
		<canvas></canvas>
		<script src='Pong.js'></script>
	</body>
</html>


Solution

  • Could not see directly what the problem was with your code so i just rewrote the code with the ball, bat (paddle) test function in the ball Object and called from the players object. ball.checkPad(player); tests if the ball has hit the players bat. To help picture what is happening I have slowed it all down and made the bats real phat. When the ball hits the bat it will turn yellow and the bat red for a second or so.

    There is plenty of comments in the parts you asked about,

    Hope it helps

    Demo copied from OP question.

    const setting = {
        speed : 2, // of ball
        left : 0,
        width : 400,
        height : 200,
        padWidth : 50,
        padHeight : 80,
        padSpeed : 4, // double balls
        hitPauseCount : 30,  // nuber of frames to hold when there is a collisiotn so you
                              // can check all is good
    }
    const keys = {
        ArrowUp : false,
        ArrowDown : false,
        ArrowLeft : false,
        ArrowRight : false,
        keyEvent(e) { // dont use keyCode it has depreciated
            if (keys[e.code] !== undefined) {
                keys[e.code] = e.type === "keydown";
                e.preventDefault();
            }
        }
    }
    var ctx;
    var ball1, paddle1, paddle2;
    var gameArea = {
        start() {
            canvas.width = setting.width;
            canvas.height = setting.height;
            ctx = canvas.getContext('2d');
            requestAnimationFrame(updateGameArea);
        },
        clear() {
            ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
        }
    }
    gameArea.start();
    ball = new CircleComp('white', window.innerWidth - 200, window.innerHeight - 20);
    ball.ySpeed = setting.speed;
    ball.xSpeed = setting.speed;
    paddle1 = new PaddleComp("ArrowUp", "ArrowDown", setting.left, setting.height / 2, setting.padWidth, setting.padHeight);
    paddle2 = new PaddleComp("ArrowLeft", "ArrowRight", setting.width - setting.padWidth, setting.height / 2, setting.padWidth, setting.padHeight);
    window.addEventListener('keydown', keys.keyEvent);
    window.addEventListener('keyup', keys.keyEvent);
    
    
    function CircleComp(color, x, y) {
        this.x = x;
        this.y = y;
        this.width = 8;
        this.height = 8;
        this.xSpeed = setting.speed;
        var hit = 0;
        var restartCount;
        var serveDirection;
    
        this.reset = function(){
            this.x = ctx.canvas.width /2;
            this.y = ctx.canvas.height / 2;
            this.xSpeed = -this.xSpeed
            this.ySpeed = setting.speed * Math.sign(Math.random() - 0.5);
            restartCount = 60;
        }
        this.draw = function () {
            if(hit > 0){ 
                hit -= 1;
                ctx.fillStyle = "yellow";
            }else{
                ctx.fillStyle = color;
            }
            ctx.fillRect(this.x, this.y, this.width, this.height);
        }
        // next funtion is called by the player objects
        this.checkPad = function (player) {
            if (player.x > canvas.width / 2) { // is player on left or right
                if (this.xSpeed > 0) { // player on right only check if ball moving rigth
                    if (this.x + this.width > player.x) { // ball is in paddles zone
                        //if not  bottom of ball above top of bat or top of ball bellow bottom of bat
                        if (!(this.y + this.height <= player.y || this.y >= player.y + player.height)) {
                            // ball and bat in contact
                            // is ball moving down and the balls top edge above the player
                            // then ball has hit the top side of the bat
                            if(this.ySpeed > 0 && this.y <= player.y){ 
                                this.y = player.y - this.width;
                                this.ySpeed =  -setting.speed;
                            }else if(this.ySpeed < 0 && this.y + this.height >= player.y + player.height){ // do bottom check
                                this.y = player.y + player.height;
                                this.ySpeed =  setting.speed;
                            }else{  // ball hit front of bat
                                this.x = player.x - this.width;
                                this.xSpeed =  - setting.speed;
                            }
                            player.hit = setting.hitPauseCount;  // counters to show FX when a hit happens
                            hit = setting.hitPauseCount;
                        }
                    }
                }
    
            } else { // player must be left
                if (this.xSpeed < 0) { // ball must move left
                    if (this.x < player.x + player.width) { // ball is in paddles zone
                        if (!(this.y + this.height <= player.y || this.y >= player.y + player.height)) {
                            // ball and bat in contact
                            // ball and bat in contact
                            // is ball moving down and the balls top edge above the player
                            // then ball has hit the top side of the bat
                            if(this.ySpeed > 0 && this.y <= player.y){ 
                                this.y = player.y - this.width;
                                this.ySpeed =  -setting.speed;
                            }else if(this.ySpeed < 0 && this.y + this.height >= player.y + player.height){ // do bottom check
                                this.y = player.y + player.height;
                                this.ySpeed =  setting.speed;
                            }else{  // ball hit front of bat
                                this.x = player.x + player.width;
                                this.xSpeed = setting.speed;
                            }
                            player.hit = setting.hitPauseCount;  // counters to show FX when a hit happens
                            hit = setting.hitPauseCount;
                        }
                    }
                }
            }
        }
        this.update = function () {
            if(restartCount > 0){ // wait for restart pause
                restartCount -= 1;
            }else{
                if(hit > 0){  // do nothing if paused
                    return;
                }
                this.y += this.ySpeed;
                this.x += this.xSpeed;
                if (this.x + this.width >= canvas.width) {
                    this.reset();  // point
                } else if (this.x < 0) {
                    this.reset(); // point
                }
                if (this.y + this.height >= canvas.height) {
                    this.y = canvas.height - this.height;
                    this.ySpeed = -setting.speed;
                } else if (this.y < 0) {
                    this.y = 0;
                    this.ySpeed = setting.speed;
                }
            }
        }
        this.reset();
    }
    function PaddleComp(upKey, downKey, x, y, width, height) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
        this.hit = 0;
        this.draw = function () {
            if(this.hit > 0){ 
                this.hit -= 1;
                ctx.fillStyle = "red";
            }else{
                ctx.fillStyle = '#9CF';
            }
            ctx.fillRect(this.x, this.y, this.width, this.height);
        }
        this.update = function () {
            if (keys[upKey]) {
                this.y -= setting.padSpeed;
            };
            if (keys[downKey]) {
                this.y += setting.padSpeed;
            };
            if (this.y < 0) {
                this.y = 0;
            }
            if (this.y + this.height >= canvas.height) {
                this.y = canvas.height - this.height;
            }
            ball.checkPad(this);
        }
    }
    function updateGameArea() {
        gameArea.clear();
        paddle1.update();
        paddle2.update();
        ball.update();
        paddle1.draw();
        paddle2.draw();
        ball.draw();
        requestAnimationFrame(updateGameArea);
    }
    <canvas id=canvas style='background:#69C;border:2px blue solid'></canvas>