Search code examples
javascripthtml5-canvascollision-detection2d-games

Problem with collision detection is JS canvas game


I'm trying to build a small game similar to Chicken Invaders, but I'm facing an issue with collision detection. When the player fires a shot, if it hits an enemy, this enemy should disappear. But I can't get it to work. Here's the pen:

https://codepen.io/Undefined_Variable/pen/bxxqBo

Here's the code:

//Drawing & Animating: Classes & Variables Definitions
let canvas = document.body.querySelector("#gameCanvas");
let ctx = canvas.getContext("2d");
canvas.width =  window.innerWidth;
canvas.height = 0.9 * window.innerHeight;
let CW = canvas.width;
let CH = canvas.height;
let easyPlaneArr = [];
let hotShotsArr = [];
let easyPlaneCounter = 0;
class plane {
    constructor(x, y, size, color) {
        this.x = x;
        this.y = y;
        this.size = size;
        this.color = color;
    }
    drawPlane() {
        ctx.save();
        ctx.translate(this.x, this.y);
        ctx.scale(this.size, this.size)
        ctx.strokeStyle = this.color;
        ctx.fillStyle = this.color;
        ctx.beginPath();
        ctx.moveTo(0, 0);
        ctx.lineTo(10, -30);
        ctx.lineTo(10, -100);
        ctx.lineTo(40, -100);
        ctx.lineTo(40, -120);
        ctx.lineTo(-10, -100);
        ctx.lineTo(-40, -100);
        ctx.lineTo(-40, -120);
        ctx.lineTo(10, -100);
        ctx.lineTo(-10, -100);
        ctx.lineTo(-10, -75);
        ctx.lineTo(-70, -75);
        ctx.lineTo(-60, -60);
        ctx.lineTo(-10, -60);
        ctx.lineTo(10, -60);
        ctx.lineTo(60, -60);
        ctx.lineTo(70, -75);
        ctx.lineTo(10, -75);
        ctx.lineTo(-10, -60);
        ctx.lineTo(-10, -30);
        ctx.stroke();
        ctx.fill();
        ctx.closePath();
        ctx.clearRect(-140, 0, 0, -120)
        ctx.restore();
    }
    animatePlane() {
        this.y += 5;
    }
}
class hotShot {
    constructor(x) {
        this.x = x;
        this.y = 0.95 * CH;
    }
}
let player = {
    x: CW / 2,
    y: 0.95 * CH
}
//Drawing & Animating: Functions Definitions
function drawPlayer() {
    ctx.save();
    ctx.translate(player.x, player.y);
    ctx.clearRect(-20, -40, 20, 0);
    ctx.beginPath();
    ctx.moveTo(0, -40);
    ctx.lineTo(-20, 0);
    ctx.lineTo(0, -10);
    ctx.lineTo(20, 0);
    ctx.closePath();
    ctx.stroke();
    ctx.fill()
    ctx.restore();
}
function drawHotShots() {
    for (let shot of hotShotsArr) {
        ctx.save()
        ctx.translate(shot.x, shot.y)
        ctx.fillRect(-10,0,20,20)
        ctx.restore()
        shot.y -= 10
    }
}
function detectHits() {
    for (let shot in hotShotsArr) {
        easyPlaneCounter = 0;
        for (let plane in easyPlaneArr) {
            if (shot.y > 0) {
                easyPlaneArr.splice(easyPlaneCounter, 1)
            }
            easyPlaneCounter++;
        }
    }
}
function mainLoop() {
    canvas.width = window.innerWidth;
    canvas.height = 0.9 * window.innerHeight;
    CW = canvas.width;
    CH = canvas.height;
    player.y = 0.95 * CH;

    for (let plane of easyPlaneArr) {
        plane.drawPlane();
        plane.animatePlane();
    }

    drawHotShots();
    drawPlayer();
    detectHits()
    requestAnimationFrame(mainLoop);
}
//Drawing & Animating: Program Flow
let easyPlanes = setInterval(function () {
    easyPlaneArr.push(new plane(Math.random() * ((CW - 150) - 150) + 150, -100, 1, "green"));
}, 1000)
canvas.addEventListener("mousemove", function (e) {
    player.x = e.clientX;
})
canvas.addEventListener("click", function (e) {
    hotShotsArr.push(new hotShot(player.x))
})
mainLoop();
body{
    margin: 0px auto;
    overflow:hidden;
}
#levelDiv {
    height:10vh;
    width:100vw;
    border-bottom: 1px solid black;
    background:linear-gradient(90deg, yellow 40%, red)
}
<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>Attack On Ship!</title>
</head>
<body>
    <div id="levelDiv"></div>
    <canvas id="gameCanvas"></canvas>
    <script src="AttackOnShip.js"></script>
</body>
</html>

I've tried this with collision detection but it didn't work:

function detectHits() {
    for (let shot in hotShotsArr) {
        easyPlaneCounter = 0;
        for (let plane in easyPlaneArr) {
            if ((shot.x - 10) > (plane.x - 70) && (shot.x + 10) < (plane.x + 70)) {
                easyPlaneArr.splice(easyPlaneCounter, 1)
            }
            easyPlaneCounter++;
        }
    }
}

Solution

  • Your detectHits function - while it's not really a proper collision detection - doesn't work because it's using for..in instead of for..of loops. Try implementing it this way:

    function detectHits() {
        for (let shot of hotShotsArr) {
            easyPlaneCounter = 0;
            for (let plane of easyPlaneArr) {
                if (shot.y > 0) {
                    easyPlaneArr.splice(easyPlaneCounter, 1)
                }
                easyPlaneCounter++;
            }
        }
    }
    

    A somewhat better collision detection could check whether the shot X and Y is inside the plane's bounding box, for example:

    const PlaneWidth = 100.0;
    const PlaneHeight = 100.0;
    function detectHits() {
        for (let shot of hotShotsArr) {
            easyPlaneCounter = 0;
            for (let plane of easyPlaneArr) {
                if (Math.abs(shot.x - plane.x) < 0.5 * PlaneWidth && Math.abs(shot.y - plane.y) < 0.5 * PlaneHeight) {
                    easyPlaneArr.splice(easyPlaneCounter, 1)
                }
                easyPlaneCounter++;
            }
        }
    }