I have "const player" and "const enemy", both using "new Sprite()". However, if I move using "document.onkeydown", only one of them moves. Why?

Viewed 36

I'm doing a tutorial from the YouTube channel Chris Courses (https://www.youtube.com/watch?v=vyqbNFMDRGQ&list=UU9Yp2yz6-pwhQuPlIDV_mjA&index=4), but at the same time trying to implement a few things I already know. In this case I'm trying to achieve the same that he does, but using a few different approaches.

I have a problem though. I created a class "Sprite" and a "const enemy" as well as "const player", both using the Sprite class. The Sprite class uses another class, which is the "Controls" class.

If I only declare "const player" (or "const enemy"), but not the second one, I can move the character just fine. If both are declared, however, only one of them moves.

I don't understand why, though. In my understanding const player = new Sprite(); and const enemy = new Sprite(); means, that I create a new set of data for player and enemy. I expect that both move at the same time if I press a key. But only the last declared one moves, not the prior one.

I appreciate your time trying to explain this to me. Also, advice is always welcomed!

"use strict"

// sprite.js
class Sprite {
    constructor({ character, position, velocity, width, height, gravity = 0.2 }) {
        this.character = character;
        this.position = position;
        this.velocity = velocity;
        this.width = width;
        this.height = height;
        this.gravity = gravity;
        
        this.controls = new Controls();
    }

    draw() {
        context.beginPath();
        context.fillStyle = "red";
        context.fillRect(
            this.position.x,
            this.position.y,
            this.width,
            this.height
        );
        context.closePath();
    }

    update() {
        this.position.y += this.velocity.y;
        this.position.x += this.velocity.x;

        this.#applyGravity();
        this.#move();
    }

    #applyGravity() {
        if(this.position.y + this.height + this.velocity.y >= canvas.height) {
            this.velocity.y = 0;
        }
        else {
            this.velocity.y += this.gravity;
        }
    }

    #move() {
        if(this.controls) {
            if(this.controls.up && this.controls.lastKey === "up") {
                this.velocity.y = -1;
            }
            else if(this.controls.down && this.controls.lastKey === "down") {
                this.velocity.y = 1;
            }
            else if(this.controls.left && this.controls.lastKey === "left") {
                this.velocity.x = -1;
            }
            else if(this.controls.right && this.controls.lastKey === "right") {
                this.velocity.x = 1;
            }
        }
    }
}




// controls.js
class Controls {
    constructor() {
        this.up = false;
        this.down = false;
        this.left = false;
        this.right = false;
        this.lastKey = "";

        this.#addEventListeners();
    }

    #addEventListeners() {
        document.onkeydown = (event => {
            switch(event.key) {
                case "ArrowUp":
                    this.up = true;
                    this.lastKey = "up";
                    break;

                case "ArrowDown":
                    this.down = true;
                    this.lastKey = "down";
                    break;

                case "ArrowLeft":
                    this.left = true;
                    this.lastKey = "left";
                    break;

                case "ArrowRight":
                    this.right = true;
                    this.lastKey = "right";
                    break;
            }
        });

        document.onkeyup = (event => {
            switch(event.key) {
                case "ArrowUp":
                    this.up = false;
                    break;

                case "ArrowDown":
                    this.down = false;
                    break;

                case "ArrowLeft":
                    this.left = false;
                    break;

                case "ArrowRight":
                    this.right = false;
                    break;
            }
        });
    }
}

// script.js
const canvas = document.getElementById("canvas");
const context = canvas.getContext("2d");

const player = new Sprite({
    position: {
        x: 100,
        y: 100
    },
    velocity: {
        x: 0,
        y: 0
    },
    width: 50,
    height: 150
});

const enemy = new Sprite({
    position: {
        x: 800,
        y: 100
    },
    velocity: {
        x: 0,
        y: 0
    },
    width: 50,
    height: 150
});

let animationID;

function animate() {
    animationID = window.requestAnimationFrame(animate);

    canvas.width = 1024;
    canvas.height = 576;

    player.draw();
    player.update();
    enemy.draw();
    enemy.update();
}

animate();
* {
    padding: 0px;
    margin: 0px;
    overflow: hidden;
    background-color: darkgrey;
}
body {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
}
#canvas {
    background-color: black;
}
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Fighting Game inspired by Chris Courses</title>
    <link rel="stylesheet" href="styles.css">
</head>

<body>
    <canvas id="canvas"></canvas>
</body>

<!-- commented out since I merged controls.js and sprite.js into one file for this code snippet -->
<!-- <script src="controls.js"></script> -->
<!-- <script src="sprite.js"></script> -->
<script src="script.js"></script>

</html>

1 Answers

In your case the problem is coming from document.onkeydown, which can be only defined once per DOM element. So when you are creating two instances of your Sprite object, the second constructor overrides the onkeydown event from the first sprite.

But you can add multiple event listeners with addEventListener(), just keep in mind that tose events don't have on in their names. So it's just keydown and keyup. A full list of events are here

I modified your code to show it's usage.

"use strict"

// sprite.js
class Sprite {
    constructor({ character, position, velocity, width, height, gravity = 0.2 }) {
        this.character = character;
        this.position = position;
        this.velocity = velocity;
        this.width = width;
        this.height = height;
        this.gravity = gravity;
        
        this.controls = new Controls();
    }

    draw() {
        context.beginPath();
        context.fillStyle = "red";
        context.fillRect(
            this.position.x,
            this.position.y,
            this.width,
            this.height
        );
        context.closePath();
    }

    update() {
        this.position.y += this.velocity.y;
        this.position.x += this.velocity.x;

        this.#applyGravity();
        this.#move();
    }

    #applyGravity() {
        if(this.position.y + this.height + this.velocity.y >= canvas.height) {
            this.velocity.y = 0;
        }
        else {
            this.velocity.y += this.gravity;
        }
    }

    #move() {
        if(this.controls) {
            if(this.controls.up && this.controls.lastKey === "up") {
                this.velocity.y = -1;
            }
            else if(this.controls.down && this.controls.lastKey === "down") {
                this.velocity.y = 1;
            }
            else if(this.controls.left && this.controls.lastKey === "left") {
                this.velocity.x = -1;
            }
            else if(this.controls.right && this.controls.lastKey === "right") {
                this.velocity.x = 1;
            }
        }
    }
}




// controls.js
class Controls {
    constructor() {
        this.up = false;
        this.down = false;
        this.left = false;
        this.right = false;
        this.lastKey = "";

        this.#addEventListeners();
    }

    #addEventListeners() {
        document.addEventListener('keydown', (event) => {
            switch(event.key) {
                case "ArrowUp":
                    this.up = true;
                    this.lastKey = "up";
                    break;

                case "ArrowDown":
                    this.down = true;
                    this.lastKey = "down";
                    break;

                case "ArrowLeft":
                    this.left = true;
                    this.lastKey = "left";
                    break;

                case "ArrowRight":
                    this.right = true;
                    this.lastKey = "right";
                    break;
            }
        });

        document.addEventListener('keyup', (event) => {
            switch(event.key) {
                case "ArrowUp":
                    this.up = false;
                    break;

                case "ArrowDown":
                    this.down = false;
                    break;

                case "ArrowLeft":
                    this.left = false;
                    break;

                case "ArrowRight":
                    this.right = false;
                    break;
            }
        });
    }
}

// script.js
const canvas = document.getElementById("canvas");
const context = canvas.getContext("2d");

const enemy = new Sprite({
    position: {
        x: 800,
        y: 100
    },
    velocity: {
        x: 0,
        y: 0
    },
    width: 50,
    height: 200
});

const player = new Sprite({
    position: {
        x: 100,
        y: 100
    },
    velocity: {
        x: 0,
        y: 0
    },
    width: 50,
    height: 150
});

let animationID;

function animate() {
    animationID = window.requestAnimationFrame(animate);

    canvas.width = 1024;
    canvas.height = 576;

    enemy.draw();
    enemy.update();
        player.draw();
    player.update();
}

animate();
* {
    padding: 0px;
    margin: 0px;
    overflow: hidden;
    background-color: darkgrey;
}
body {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
}
#canvas {
    background-color: black;
}
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Fighting Game inspired by Chris Courses</title>
    <link rel="stylesheet" href="styles.css">
</head>

<body>
    <canvas id="canvas"></canvas>
</body>

<!-- commented out since I merged controls.js and sprite.js into one file for this code snippet -->
<!-- <script src="controls.js"></script> -->
<!-- <script src="sprite.js"></script> -->
<script src="script.js"></script>

</html>

Related