Search code examples
javascripthtml5-canvascollision-detectiongame-physics

How do I correctly add collisions between a player and wall in JavaScript Canvas


I've made a codepen in order to better explain my situation. https://codepen.io/kennyfully1988/pen/yLqpBVp

I'm working on a game where the player collects apples. The collision is working correctly when a player touches an apple. (The player will get 1 point and the apple will be erased from the apples array).

What I'm confused about is the following function

    checkSolidCollisions(walls) {
        // check to see if player is colliding with walls
        for (let i = 0; i < walls.length; i++) {
            if (
                this.dx <= walls[i].x + walls[i].width &&
                this.dx + this.dw >= walls[i].x &&
                this.dy <= walls[i].y + walls[i].height &&
                this.dy + this.dh >= walls[i].y
            ) {
                console.log(true);
                return true;
            }
        }
        return false;
    }

I created this function hoping that this will be a check to see if the player is in collision with a wall (the wall array). However, it doesn't seem to work at all. I'm willing to provide as much information as needed in order to solve this problem. I want you all to know that this is OOP JavaScript.

So, what I tried to do is make the player not be able to pass the wall. However, the player always passes the wall.

the bug where the player always passes the wall


Solution

  • So after a long time of testing everything out and rebuilding samples, I noticed what I did wrong. I didn't write the proper logic to see what happens after the player collides with the wall, such as pushing the player back into a safer position after the collision.

    // Working code

    'use strict';
    
    window.onload = () => {
        const gameBox = document.querySelector('.game-box');
        const ctx = gameBox.getContext('2d');
        const collisionCheckerLabel = document.querySelector(
            '.collision-checker-label',
        );
        let collisionChecker = false;
    
        const playerImage = new Image();
        playerImage.src =
            'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAApNJREFUeJztm7FuwjAQhg/Eg4QwsDEFiYGVZ2DhPXgG3oOl6iN0zVApnbpVqC0rb9Ahwh2QLcfY8V0S20W9fwGj5D7f1Q253wEgoPKsEHlWiJCMvhphDtKT+Dq/oc/5eB8DAMB8cSWdR2X10QQzGZkIAMB8UQjsxOaLK2kyetEkC4BWiLYVZ4tjDWwmricScwWYfF8Mk2m+t83jLqAZBAP2JdI3BqWQg64ATIC2iXRZAUPxsWoElOCnl1frwdvNCjWRrgmk4KsP8qwQErzdrBqTsI1twfokkJSfZ4WoTrWoTrXIs0IIIYT8zBxXp1o8lz933+9mDPneNf4r/DFYNJsuW8e2yut/vc/vSlXbHAMAHI5l68UqJv+uADKQ6/V8qVsn0yWBlPwxwO1/Qq+QXk05Pl9qBd/v1s4LUZcEUvK93wL6CfvdWk3YBOvLUEq/eJlxsBex0PxB7wP6JJCKH6TZiHkj8xf5LCn2A9gPYD9Anc9+APsB7Ac8jh/gU2g/ICZ/RIUD3G4vzfv5Pgmk5Fv9AKqoCfj8gJh8cgEw/XhIDc23+gEuMKYf9wnjB8TkB/UDXOBYfgCGz35AW8CuSt2Pp+azpNgPYD+A/QB1PvsB7AewH/BYfoC5J29OJLQfEItv9QPklvJsurQGw/TjlARS8p3tMGVL25WAq8PD+AGx+I0CbDerRuVc1ceIkkBK/gRA9eONKppwTDur7+e7lmCLH5CEz36A7WD2A3oqdT+ems+SYj+A/QD2A9T57AewH8B+wGP5ASY45u8FYvK9zwfYenPq/rwvgZR81PY45Xl9VwKU3wvE5HsLoPfmsl+nqos3EIuPej5APq9/OJZwOJak/XlMAin5/8YP0GOxH8B+AEvpF88gNOQKrAefAAAAAElFTkSuQmCC';
    
        class Player {
            constructor(config) {
                this._rng = String(Math.floor(Math.random() * 100)).padEnd(3, '0'); // random number generator
                this._id = `player_${new Date().getTime()}${this._rng}`; // generated id
                this.image = playerImage;
                this.currentFrame = 1; // helper variable to help with requestAnimationFrame()
                this.totalFps = 60; // helper variable to help with requestAnimationFrame()
                this.totalImageFrames = 4; // the number of frames in the sprite
                this.sx = config?.sx || 0; // source x position
                this.sy = config?.sy || 0; // source y position
                this.sw = config?.sw || 16; // source width
                this.sh = config?.sh || 16; // source height
                this.dx = config?.dx || 0; //destination x position
                this.dy = config?.dy || 0; //destination y position
                this.dw = config?.dw || 16; //destination width
                this.dh = config?.dh || 16; //destination height
                this.animation = config?.animation || 0; // lazy way to play animations
    
                this.movement = config?.movement || 'idle'; // player movement
                window.addEventListener('keydown', (e) => {
                    e.preventDefault();
                    if (e.key === 'ArrowUp') {
                        this.movement = 'up';
                    } else if (e.key === 'ArrowDown') {
                        this.movement = 'down';
                    } else if (e.key === 'ArrowLeft') {
                        this.movement = 'left';
                    } else if (e.key === 'ArrowRight') {
                        this.movement = 'right';
                    } else {
                        this.movement = '';
                    }
                });
    
                window.addEventListener('keyup', (e) => {
                    e.preventDefault();
                    this.movement = 'idle';
                });
            }
    
            render(ctx) {
                ctx.drawImage(
                    this.image,
                    Math.floor(
                        this.currentFrame / (this.totalFps / this.totalImageFrames),
                    ) * 16,
                    this.animation * 16,
                    this.sw,
                    this.sh,
                    this.dx,
                    this.dy,
                    this.dw,
                    this.dh,
                );
            }
    
            update(ctx) {
                // player animation
                if (this.currentFrame >= this.totalFps - 1) {
                    this.currentFrame = 1;
                } else {
                    this.currentFrame++;
                }
                // player movement
                if (this.movement === 'up') {
                    this.animation = 1;
                    if (this.dy <= 0) return;
                    this.dy--;
                } else if (this.movement === 'down') {
                    this.animation = 0;
                    if (this.dy + 16 >= ctx.canvas.height) return;
                    this.dy++;
                } else if (this.movement === 'left') {
                    this.animation = 2;
                    if (this.dx <= 0) return;
                    this.dx--;
                } else if (this.movement === 'right') {
                    this.animation = 3;
                    if (this.dx + 16 >= ctx.canvas.width) return;
                    this.dx++;
                } else {
                    return;
                }
            }
        }
    
        class Rectangle {
            constructor(config) {
                this.color = config?.color || 'black';
                this.x = config?.x || 0;
                this.y = config?.y || 0;
                this.width = config?.width || 16;
                this.height = config?.height || 16;
            }
    
            render(ctx) {
                ctx.fillStyle = this.color;
                ctx.fillRect(this.x, this.y, this.width, this.height);
            }
        }
    
        const rects = [
            new Rectangle({ color: 'red', x: 32, y: 32 }),
            new Rectangle({ color: 'green', x: 160 - 32, y: 160 - 32 }),
            new Rectangle({ color: 'blue', x: 96, y: 96 }),
        ];
    
        const player = new Player({ dx: 0, dy: 0 });
    
        const renderScene = () => {
            ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    
            rects.forEach((rect) => {
                rect.render(ctx);
            });
    
            player.render(ctx);
            player.update(ctx);
    
            requestAnimationFrame(() => renderScene());
    
            for (let i = 0; i < rects.length; i++) {
                if (
                    //Check x
                    player.dx + player.dw > rects[i].x && // right side
                    player.dx < rects[i].x + rects[i].width && // left side
                    // Check y
                    player.dy + player.dh > rects[i].y && // bottom side
                    player.dy < rects[i].y + rects[i].height // top side
                ) {
                    collisionChecker = true;
                    collisionCheckerLabel.innerText = collisionChecker;
                    if (player.movement === 'up') {
                        player.dy++;
                    } else if (player.movement === 'down') {
                        player.dy--;
                    } else if (player.movement === 'left') {
                        player.dx++;
                    } else if (player.movement === 'right') {
                        player.dx--;
                    }
                    return;
                }
                collisionChecker = false;
                collisionCheckerLabel.innerText = collisionChecker;
            }
        };
    
        renderScene();
    };
    * {
        box-sizing: border-box;
        margin: 0;
        padding: 0;
        font-family: Arial, Helvetica, sans-serif;
    }
    
    body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        background-color: lightgray;
        height: 100vh;
    }
    
    .game-box {
        border: 0.5rem solid black;
        height: 90%;
        image-rendering: pixelated;
    }
    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8" />
            <meta http-equiv="X-UA-Compatible" content="IE=edge" />
            <meta
                name="viewport"
                content="width=device-width, initial-scale=1.0, user-scalable=no"
            />
            <link rel="icon" type="image/x-icon" href="#" />
            <link rel="stylesheet" href="app.css" />
            <script defer src="app.js"></script>
            <title>Rect Collisions</title>
        </head>
        <body>
            <h1>Rect Collisions</h1>
            <canvas class="game-box" width="160" height="160"></canvas>
            <p>
                Are the objects colliding? <span class="collision-checker-label">No</span>
            </p>
        </body>
    </html>

    Collisions working on walls