1

I'm creating a basic shooter game where you kill monsters to gain points. In the process of creating new enemies, I wanted a system where they spawn when your score reaches a certain number, but for some reason, the score does not update outside of my animate function, causing the new enemies to not spawn. Here is the relevant code for my situation. This is my first question, so please let me know if I am doing anything wrong in the way I am asking my question. Thank you!

let score = 0;

//enemy variables
let enemyX;
let enemyY;
let enemyRadius;
let enemyColor;
let enemySpeed;
let enemyHealth;
//big enemy variables
let bigenemyX;
let bigenemyY;
let bigenemyRadius;
let bigenemyColor;
let bigenemySpeed;
let bigenemyHealth;

//spawn enemies function; responsible for spawning the basic enemies
const spawnEnemies = () => {
    setInterval(() => {
        //enemy variables
        enemyY = Math.random() * canvas.height;
        enemyRadius = 50;
        enemyX = canvas.width + enemyRadius;
        enemyColor = 'green';
        enemySpeed = 7;
        enemyHealth = 150;
        //creates a new enemy into the enemies array every second
        enemies.push(new Enemy(enemyX, enemyY, enemyRadius, enemyColor, enemySpeed, enemyHealth))
    }, 1000)
}

//spawn big enemies function; responsible for spawning the big enemies
const spawnBigEnemies = () => {
    setInterval(() => {
        //big enemy variables
        bigenemyY = Math.random() * canvas.height;
        bigenemyRadius = 100;
        bigenemyX = canvas.width + bigenemyRadius;
        bigenemyColor = 'pink';
        bigenemySpeed = 3;
        bigenemyHealth = 500;
        //creates a new big enemy into the enemies array every ten seconds
        enemies.push(new Enemy(bigenemyX, bigenemyY, bigenemyRadius, bigenemyColor, bigenemySpeed, bigenemyHealth))
    }, 10000)
}


const animate = () => {
enemies.forEach((enemy, index) => {
        //enemy dies
        if (enemy.health === 0) {
            //removes enemy from enemies array
            enemies.splice(index, 1)
            //increases score by 1 if regular enemy is killed
            if (enemy.radius === enemyRadius) {
                score ++;
                scoreHTML.innerHTML = score;
            }
            //increases score by 5 if big enemy is killed
            else if (enemy.radius === bigenemyRadius) {
                score += 5;
                scoreHTML.innerHTML = score;
            }
        }
        
        //game ends if rocket and enemy collide or no lives remaining
        if (collides(rocket, enemy) || lives === 0) {
            //pauses animation on canvas
            cancelAnimationFrame(animationId)
            //game over screen displays
            gameOverModal.style.display = 'flex'
            //displays score on game over screen
            pointsHTML.innerHTML = score;
        }
        //if enemy goes off screen
        if (enemy.x - enemy.radius <= 0) {
            //deletes the enemy from enemies array
            enemies.splice(index, 1);
            //lives go down
            lives --;
            //lives counter is updated
            livesHTML.innerHTML = lives;
        }
    })
}
}

//spawns enemies on screen
spawnEnemies()
//TESTING for big enemy spawning
if (score >= 50) {
    spawnBigEnemies()
}
Vishwas R
  • 3,340
  • 1
  • 16
  • 37
  • Is it all of your codes? Because as I can tell, you `setInterval` to generate new enemies every second. It doesn't have anything to do with score? or am I wrong? – KienHT Dec 12 '20 at 17:14
  • This isn't all of the code, but most of the relevant code related to my issue. I use the score at the end of the file to call the spawn enemies function if the score reaches 50 using an if statement. You might have to scroll to see it. – totallyfarhan Dec 12 '20 at 17:24
  • So you mean the regular enemies still spawn as normal, but not the big enemies? – KienHT Dec 12 '20 at 17:41
  • Pretty much. I only want the big enemies to be called if the score reaches 50. – totallyfarhan Dec 12 '20 at 18:08

3 Answers3

1

So the last if statement runs only one time right when the browser parse your code. However, by that time, obviously the user won't achieve the score of 50, so that if statement never runs spawnBigEnemies().

How to fix: You need a way to "watch" the score in order to determine when to spawn new enemies. I can't presicely give you a solution because I can't see all of your codes. But this is for your reference if you want to "watch" a variable in JS: How can I detect when a variable changes value in JavaScript?

P/S: You also only want the function spawnBigEnemies() being called once after the score reaches 50 because you use setInterval in this function

EDIT

let isSpawning = true;
const spawnEnemies = () => {
    setInterval(() => {
        //your codes    
        ...
        if (score >= 50 && isSpawning) {
            spawnBigEnemies();
            isSpawning = false;
        }
    }, 1000)
}
KienHT
  • 1,098
  • 7
  • 11
  • Thank you very much! I'll look into the link you sent and let you know if it helps me out! – totallyfarhan Dec 12 '20 at 18:08
  • Would it be helpful if I sent a jsfiddle link to you with all the code? – totallyfarhan Dec 12 '20 at 18:10
  • Yes sure. I'll be happy to figure it out – KienHT Dec 12 '20 at 18:11
  • Heres the link: https://jsfiddle.net/totallyfarhan/Lu23xhyv/ – totallyfarhan Dec 12 '20 at 18:14
  • You're welcome. So I think, you don't really need to precisely spawn big enemies right after the score reaches 50. You can move the `if` statement inside the `setInterval` of the `spawnEnemies()` function. This will allow you to check the `if` statement every second. – KienHT Dec 12 '20 at 18:24
  • However, you have to have a new variable to make sure the `if` statement only evaluates to `true` once – KienHT Dec 12 '20 at 18:26
  • Sorry if this is too much of a bother, but if possible could you type it out in code? I'm having a little bit of trouble trying to visualize what you said. – totallyfarhan Dec 12 '20 at 18:28
  • I saw you have the boolean `spawn` but never being used, you can use this variable. I will add this piece of code at the end of my answer for your reference – KienHT Dec 12 '20 at 18:30
  • Hey, it worked! I added the if statement inside the spawnBigEnemies function, and it starts spawns the big enemies when it reaches the desired code. I did not implement the spawn boolean variable, and it still works, but I'll check if that causes bugs along the way. Thank you so much for your help! – totallyfarhan Dec 12 '20 at 18:43
  • This is just my quick workaround. You might come up with a better idea =)). For instance, you could just move your original `if` statement into `spawnEnemies() ` function, change the operator from `>=` to `===`, I believe it will work just fine because you increment the score by 1, so it has to reach 50 at a certain point, no need to use extra variable. – KienHT Dec 12 '20 at 18:45
  • If you find my answer helpful, please mark it as accepted and upvote it. Happy coding~ =)) – KienHT Dec 12 '20 at 18:52
  • This will evaluate the if condition every second, which is not really needed. You just need to know when the score is changing and check the if condition there. Since this is a light weight game it is fine, but when you try to add more things to it, it will hamper your performance. – Nikhil Patil Dec 12 '20 at 18:57
  • @NikhilPatil You are partially right. It all depends. His game's flow is pretty fast, you can even get 3 points in a second. In your case, it will evaluate 3 times, whereas mine only once. But if the player stops, mine is still evaluating. My point is there are different factors we need to take into account. – KienHT Dec 12 '20 at 19:07
0

I'm no expert, but I'm guessing that the score is not update because you are using the let term. You may want to use var instead to make the scope global. The way your code is now, the score will only change inside the function.

// let is declared here.
let i = 0;

for (let i=0; i<5; i++) {
  console.log("let iteration");
}

// i will still be zero.
console.log("i is", i);

// var is declared here
var j = 0;

for (var j=0; j<5; j++) {
  console.log("var iteration");
}

// j will still be four.
console.log("j is", j);
  • I don't think it worked. Gave me the same results. I might be wrong, but I thought `let` was almost the same thing as `var`, just that `let` was block scoped. I'm not sure if that could be related to the problem I have though. – totallyfarhan Dec 12 '20 at 17:11
0

I can see that you are checking score > 50 immediately as soon as your javascript is executed. Since score is initially zero this would never work. Instead do this check when you change the score. In your case, only when the normal enemies are killed your score will change (before big enemies), so just add the check there.

Added the check here -

 if (enemy.radius === enemyRadius) {
            score ++;                
            //TESTING for big enemy spawning
            if (score >= 5) { // do additional check with a flag so that this condition only evaluates to true only once
                spawnBigEnemies()
            }
            scoreHTML.innerHTML = score;
 }

Final code -

//canvas setup
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d', {alpha: false});

//sets canvas dimensions
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

//global variables
let score = 0;
let lives = 10;
let up = false;
let down = false;
let spawn = false;

//dom variables
let scoreHTML = document.getElementById('score');
let livesHTML = document.getElementById('lives');
const startGameButton = document.getElementById('startgamebtn');
const gameOverModal = document.getElementById('gameovermodal');
let pointsHTML = document.getElementById('points');

//player variables
let playerX = 100;
let playerY = 100;
let playerWidth = 100;
let playerHeight = 100;
let playerColor = 'red';
let playerSpeed = 12;
//bullet variables
let bulletRadius = 5;
let bulletColor = 'white';
let bulletSpeed = 20;
//enemy variables
let enemyX;
let enemyY;
let enemyRadius;
let enemyColor;
let enemySpeed;
let enemyHealth;
//big enemy variables
let bigenemyX;
let bigenemyY;
let bigenemyRadius;
let bigenemyColor;
let bigenemySpeed;
let bigenemyHealth;

//Rocket class
class Rocket {
    constructor(x, y, width, height, color, dy) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
        this.color = color;
        this.dy = dy;
    }
    //rocket draw func
    draw() {
        ctx.fillStyle = this.color;
        ctx.fillRect(this.x, this.y, this.width, this.height);
    }
    //rocket update func
    update() {
        this.draw();
        //makes rocket move up and down
        this.y += this.dy;
    }
}

//Bullet class
class Bullet {
    constructor(x, y, radius, color, dx) {
        this.x = x;
        this.y = y;
        this.radius = radius;
        this.color = color;
        this.dx = dx;
    }
    //bullet draw func
    draw() {
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
        ctx.fillStyle = this.color;
        ctx.fill();
        ctx.closePath();
    }
    //bullet update func
    update() {
        this.draw();
        //makes bullet move right
        this.x += this.dx;
    }
}

//Enemy class
class Enemy {
    constructor(x, y, radius, color, dx, health) {
        this.x = x;
        this.y = y;
        this.radius = radius;
        this.color = color;
        this.dx = dx;
        this.health = health;
    }
    //enemy draw func
    draw() {
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
        ctx.fillStyle = this.color;
        ctx.fill();
        ctx.closePath();
    }
    //enemy update func
    update() {
        this.draw();
        //makes enemies move left
        this.x -= this.dx;
    }
}

//rocket declaration
let rocket = new Rocket(playerX, playerY, playerWidth, playerHeight, playerColor, 0);
//bullet array declaration
let bullets = [];
//enemies array declaration
let enemies = [];

//initiation function: called when game is restarted; used to reset everything
const init = () => {
    rocket = new Rocket(100, 100, 100, 100, 'red', 0);
    bullets = [];
    enemies = [];
    lives = 10;
    score = 0;
    scoreHTML.innerHTML = score;
    livesHTML.innerHTML = lives;
    pointsHTML.innerHTML = score;
    spawn = false;
}

//spawn enemies function; responsible for spawning the basic enemies
const spawnEnemies = () => {
    setInterval(() => {
        //enemy variables
        enemyY = Math.random() * canvas.height;
        enemyRadius = 50;
        enemyX = canvas.width + enemyRadius;
        enemyColor = 'green';
        enemySpeed = 7;
        enemyHealth = 150;
        //creates a new enemy into the enemies array every second
        enemies.push(new Enemy(enemyX, enemyY, enemyRadius, enemyColor, enemySpeed, enemyHealth))
    }, 1000)
}

//spawn big enemies function; responsible for spawning the big enemies (WORK IN PROGRESS)
const spawnBigEnemies = () => {
    setInterval(() => {
        //big enemy variables
        bigenemyY = Math.random() * canvas.height;
        bigenemyRadius = 100;
        bigenemyX = canvas.width + bigenemyRadius;
        bigenemyColor = 'pink';
        bigenemySpeed = 3;
        bigenemyHealth = 500;
        //creates a new big enemy into the enemies array every ten seconds
        enemies.push(new Enemy(bigenemyX, bigenemyY, bigenemyRadius, bigenemyColor, bigenemySpeed, bigenemyHealth))
    }, 10000)
}

//collides function; responsible for collision between rocket and enemies
const collides = (rect, circle, collide_inside) => {
    // compute a center-to-center vector
    let half = { x: rect.width / 2, y: rect.height / 2 };
    let center = {
        x: circle.x - (rect.x + half.x),
        y: circle.y - (rect.y + half.y)};

    // check circle position inside the rectangle quadrant
    let side = {
        x: Math.abs (center.x) - half.x,
        y: Math.abs (center.y) - half.y
    };
    if (side.x >  circle.radius || side.y >  circle.radius) // outside
        return false; 
    if (side.x < -circle.radius && side.y < -circle.radius) // inside
        return collide_inside;
    if (side.x < 0 || side.y < 0) // intersects side or corner
        return true;

    // circle is near the corner
    return side.x * side.x + side.y * side.y  < circle.radius * circle.radius;
}

//used for pausing animation frames
let animationId;

const animate = () => {
    animationId = requestAnimationFrame(animate);
    //clear canvas
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    //canvas background
    ctx.fillStyle = 'black';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    //rockets update function so it can move
    rocket.update();
    //bullet foreach iterator
    bullets.forEach((bullet, index) => {
        //allows all bullets to move
        bullet.update()
        //deletes bullets if it moves outside canvas
        if (bullet.x - bullet.radius > canvas.width) {
            setTimeout(() => {
                bullets.splice(index, 1);
            }, 0)
        }

    })
    //enemies foreach iterator
    enemies.forEach((enemy, index) => {
        //allows all enemies to move
        enemy.update()
        //responsible for bullet and enemy collision
        bullets.forEach((bullet, bulletIndex) => {
            //variable used to measure distance between bullet and enemy
            const dist = Math.hypot(bullet.x - enemy.x, bullet.y - enemy.y);
            if (dist - enemy.radius - bullet.radius < 1) {
                //deletes the bullet from bullets array
                bullets.splice(bulletIndex,1)
                //decreases enemy's health
                enemy.health -= 50;
            }
        })
        //enemy dies
        if (enemy.health === 0) {
            //removes enemy from enemies array
            enemies.splice(index, 1)
            //increases score by 1 if regular enemy is killed
            if (enemy.radius === enemyRadius) {
                score ++;                
                //TESTING for big enemy spawning
                if (score >= 5) {
                    spawnBigEnemies()
                }
                scoreHTML.innerHTML = score;
            }
            //increases score by 5 if big enemy is killed
            else if (enemy.radius === bigenemyRadius) {
                score += 5;
                scoreHTML.innerHTML = score;
            }
        }
        
        //game ends if rocket and enemy collide or no lives remaining
        if (collides(rocket, enemy) || lives === 0) {
            //pauses animation on canvas
            cancelAnimationFrame(animationId)
            //game over screen displays
            gameOverModal.style.display = 'flex'
            //displays score on game over screen
            pointsHTML.innerHTML = score;
        }
        //if enemy goes off screen
        if (enemy.x - enemy.radius <= 0) {
            //deletes the enemy from enemies array
            enemies.splice(index, 1);
            //lives go down
            lives --;
            //lives counter is updated
            livesHTML.innerHTML = lives;
        }
    })
}

//responsible for character movement
addEventListener('keydown', evt => {
        //W key is pressed
        if (evt.code === "KeyW") {
            //player moves up
            rocket.dy = -playerSpeed; 
            //stops player from moving up too far   
            if (rocket.y < 10) {
                //used for condition for keeping character in canvas
                up = true
            }
            if (up) {
                //brings rocket back to canvas
                rocket.y = 0
                //rocket velocity is 0
                rocket.dy = 0
                //resets up bool
                up = false;
            }
        }
        //S key is pressed
        else if (evt.code === "KeyS") {
            //player moves down
            rocket.dy = playerSpeed;
            //stops player from moving down too far
            if (rocket.y > canvas.height - rocket.height) {
                //used for condition for keeping character in canvas
                down = true;
            }
            if (down) {
                //brings rocket back to canvas
                rocket.y = canvas.height - rocket.height + 10
                //rocket velocity is 0
                rocket.dy = 0
                //resets up bool
                down = false;
            }
        }
});

//when keys are released
addEventListener('keyup', evt => {
    if (evt.code === "KeyW" || evt.code === "KeyS") {
        //stops player from moving if keys are released
        rocket.dy = 0;
    }
})

//shooting
addEventListener('click', event => {
    //if mouse is clicked new bullet is created and added to bullets array
    bullets.push(new Bullet(rocket.x + rocket.width, rocket.y + (rocket.height / 2), bulletRadius, bulletColor, bulletSpeed))
});

//start game button in game over modal; used to restart the game
startGameButton.addEventListener('click', () => {
    //calls the function to reset all data
    init();
    //makes the game start animating again
    animate();
    //makes game over modal go away
    gameOverModal.style.display = 'none';
})

//spawns enemies on screen
spawnEnemies()
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>New Game</title>
        <link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">
        <style>
            body {
                background: black;
                margin: 0;
                overflow: hidden;
            }
        </style>
    </head>
    <body>
        <div class="fixed text-white m-3 select-none">
            <span>Score: </span>
            <span id="score">0</span>
        </div>
        <div class="fixed text-white mt-9 ml-3 select-none">
            <span>Lives: </span>
            <span id="lives">10</span>
        </div>
        <div class="fixed inset-0 flex items-center justify-center select-none" id="gameovermodal">
            <div class="bg-white max-w-md w-full p-6 text-center">
                <h2 class="text-3xl font-bold mt-2 leading-none" id="points">0</h1>
                <p class="text-sm">Points</p>
                <div>
                    <button class="bg-red-500 w-full py-3 rounded-full mt-2 text-white focus:outline-none" id="startgamebtn">Start Game</button>
                </div>
            </div>
        </div>
        <canvas id="game"></canvas>
        <script src="game.js" type="text/javascript"></script>
    </body>
</html>
Nikhil Patil
  • 2,480
  • 1
  • 7
  • 20