在 HTML Canvas 中更新分数的函数之外的其他函数中使用更新分数时遇到问题

Having trouble using updated score in other functions outside the function where score is being updated in HTML Canvas

我正在创建一个基本的射击游戏,您可以在其中杀死怪物来获得积分。在创建新敌人的过程中,我想要一个当你的分数达到一定数量时它们会生成的系统,但由于某种原因,分数不会在我的动画函数之外更新,导致新敌人不会生成。这是我的情况的相关代码。这是我的第一个问题,所以如果我在提问时做错了什么,请告诉我。谢谢!

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()
}

我不是专家,但我猜分数没有更新是因为您使用的是 let 术语。您可能希望使用 var 来使范围成为全局范围。按照您现在的代码,分数只会在函数内部发生变化。

// 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);

所以最后一个 if 语句只在浏览器解析您的代码时运行一次。但是,到那时,显然用户不会达到 50 分,因此 if 语句永远不会运行 spawnBigEnemies().

如何解决:您需要一种方法来“观察”分数以确定何时产生新的敌人。我不能准确地给你一个解决方案,因为我看不到你所有的代码。但如果你想在 JS 中“观察”一个变量,这里供你参考:How can I detect when a variable changes value in JavaScript?

P/S: 您还只希望在分数达到 50 后调用函数 spawnBigEnemies() 一次,因为您在此函数中使用了 setInterval

编辑

let isSpawning = true;
const spawnEnemies = () => {
    setInterval(() => {
        //your codes    
        ...
        if (score >= 50 && isSpawning) {
            spawnBigEnemies();
            isSpawning = false;
        }
    }, 1000)
}

我看到您在 javascript 执行后立即检查分数 > 50。由于分数最初为零,因此这将永远行不通。相反,当您更改乐谱时进行此检查。在你的情况下,只有当普通敌人被杀死时你的分数才会改变(在大敌人之前),所以只需在那里添加检查。

在此处添加检查 -

 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;
 }

最终代码-

//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>