如何为Canvas僵尸射击游戏添加昵称输入与重新开始功能?
僵尸射击游戏功能扩展实现
需求
我通过YouTube教程制作了一款Canvas僵尸射击游戏,目前运行正常,希望实现以下功能:
- 游戏开始前支持输入昵称,点击开始按钮后启动游戏,并在屏幕中上侧显示昵称;
- 游戏结束后弹出包含分数与重新开始按钮的窗口,点击按钮可重新开始游戏。
修改后的完整代码
HTML 文件
<!DOCTYPE html> <html lang="zh-CN"> <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"> <title>僵尸射击游戏</title> <link rel="stylesheet" href="style.css"> </head> <body> <!-- 开始界面 --> <div class="start-screen"> <h1>僵尸射击游戏</h1> <input type="text" id="nicknameInput" placeholder="请输入你的昵称" required> <button id="startBtn">开始游戏</button> </div> <!-- 游戏画布 --> <canvas id="canvas1"></canvas> <canvas id="collisionCanvas"></canvas> <!-- 自定义光标 --> <div class="inner-cursor"></div> <div class="outer-cursor"></div> <!-- 游戏结束弹窗 --> <div class="game-over-modal" id="gameOverModal"> <div class="modal-content"> <h2>游戏结束</h2> <p>你的昵称: <span id="modalNickname"></span></p> <p>最终得分: <span id="modalScore"></span></p> <button id="restartBtn">重新开始</button> </div> </div> <script src="script.js"></script> </body> </html>
CSS 文件
*{ cursor: none; margin: 0; padding: 0; box-sizing: border-box; } canvas{ position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-image: url('board-bg.jpg'); } #collisionCanvas{ opacity: 0; } .inner-cursor{ position: fixed; width: 15px; height: 15px; transform: translate(-50%, -50%); background-color: white !important; mix-blend-mode: difference; border-radius: 50%; pointer-events: none; } .outer-cursor{ position: fixed; width: 70px; height: 70px; transform: translate(-50%, -50%); border: 5px solid white; mix-blend-mode: difference; border-radius: 50%; pointer-events: none; } /* 开始界面样式 */ .start-screen{ position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: rgba(0,0,0,0.8); padding: 40px; border-radius: 10px; text-align: center; z-index: 100; } .start-screen h1{ color: white; margin-bottom: 20px; font-size: 3rem; } .start-screen input{ padding: 10px 15px; font-size: 1.2rem; margin-bottom: 20px; border-radius: 5px; border: none; outline: none; } .start-screen button{ padding: 12px 30px; font-size: 1.2rem; background-color: #ff4444; color: white; border: none; border-radius: 5px; cursor: pointer; transition: background-color 0.3s; } .start-screen button:hover{ background-color: #cc0000; } /* 游戏结束弹窗样式 */ .game-over-modal{ position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.9); display: none; justify-content: center; align-items: center; z-index: 100; } .modal-content{ background-color: #333; padding: 40px; border-radius: 10px; text-align: center; color: white; } .modal-content h2{ font-size: 2.5rem; margin-bottom: 20px; color: #ff4444; } .modal-content p{ font-size: 1.5rem; margin: 10px 0; } .modal-content button{ margin-top: 20px; padding: 12px 30px; font-size: 1.2rem; background-color: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer; transition: background-color 0.3s; } .modal-content button:hover{ background-color: #45a049; }
JavaScript 文件
const canvas = document.getElementById('canvas1'); const ctx = canvas.getContext('2d'); canvas.width = window.innerWidth; canvas.height = window.innerHeight; const collisionCanvas = document.getElementById('collisionCanvas'); const collisionCtx = collisionCanvas.getContext('2d'); collisionCanvas.width = window.innerWidth; collisionCanvas.height = window.innerHeight; // DOM元素 const startScreen = document.querySelector('.start-screen'); const nicknameInput = document.getElementById('nicknameInput'); const startBtn = document.getElementById('startBtn'); const gameOverModal = document.getElementById('gameOverModal'); const modalNickname = document.getElementById('modalNickname'); const modalScore = document.getElementById('modalScore'); const restartBtn = document.getElementById('restartBtn'); //游戏状态变量 let gameOver = false; let score = 0; let lives = 3; let hit = false; let clicked = false; let playerNickname = ''; let animationId = null; //生成设置 let timeToNextZombie = 0; let zombieInterval = 400; let lastTime = 0; let zombies =[]; //鼠标 let innerCursor = document.querySelector('.inner-cursor'); let outerCursor = document.querySelector('.outer-cursor'); document.addEventListener('mousemove', moveCursor) function moveCursor(e){ let x = e.clientX; let y = e.clientY; innerCursor.style.left = `${x}px`; innerCursor.style.top = `${y}px`; outerCursor.style.left = `${x}px`; outerCursor.style.top = `${y}px`; } class Zombie{ constructor(){ this.spriteWidth = 200; //僵尸帧宽度 this.spriteHeight = 312; //僵尸帧高度 this.sizeModifier = Math.random()* 0.8 + 0.6; //僵尸尺寸系数 this.width = this.spriteWidth * this.sizeModifier; this.height = this.spriteHeight * this.sizeModifier; this.x = canvas.width; this.y =Math.random() * (canvas.height -this.height); //僵尸在屏幕上下范围内生成 this.directionX = Math.random() * 4 + 1; //僵尸移动速度 if(Math.floor(Math.random() * 20) == 0) this.directionX = 10; //5%概率生成快速奔跑的僵尸 this.markedForDeletion = false; //标记是否可删除对象 this.image = new Image(); this.image.src = 'walkingdead.png'; this.frame = 0; this.maxFrame = 8; this.timeSinceFlap = 0; this.flapInterval = Math.random()*50+50; //每帧切换间隔随机 this.randomColors = [Math.floor(Math.random()*255), Math.floor(Math.random()*255), Math.floor(Math.random()*255)]; this.color = 'rgb(' + this.randomColors[0] + ',' + this.randomColors[1] + ',' + this.randomColors[2] + ')'; } update(deltatime){ this.x -= this.directionX; this.timeSinceFlap +=deltatime; if (this.x < 0-this.width){ this.markedForDeletion = true; } //僵尸超出屏幕范围 if (this.timeSinceFlap > this.flapInterval){ if (this.frame > this.maxFrame) this.frame = 0; else this.frame++; this.timeSinceFlap = 0; } if (this.x < 0 -this.width){ lives-=1; if(lives == 0){ gameOver = true; showGameOverModal(); } } } //绘制僵尸 draw(){ collisionCtx.fillStyle = this.color; collisionCtx.fillRect(this.x, this.y, this.width, this.height); ctx.drawImage(this.image, this.frame * this.spriteWidth, 0, this.spriteWidth, this.spriteHeight, this.x, this.y, this.width, this.height); } } function drawScore(){ ctx.font = '50px Impact'; ctx.fillStyle = 'white'; // 绘制昵称 ctx.textAlign = 'center'; ctx.fillText(`玩家: ${playerNickname}`, canvas.width/2, 80); // 绘制分数和生命值 ctx.textAlign = 'left'; ctx.fillText('Score: ' + score, 55, 80); if(lives == 3) ctx.fillText('❤ ❤ ❤', 55, 130); if(lives == 2) ctx.fillText('❤ ❤', 55, 130); if(lives == 1) ctx.fillText('❤', 55, 130); } function showGameOverModal(){ modalNickname.textContent = playerNickname; modalScore.textContent = score; gameOverModal.style.display = 'flex'; } function resetGame(){ // 重置所有游戏状态 gameOver = false; score = 0; lives = 3; hit = false; clicked = false; zombies = []; timeToNextZombie = 0; lastTime = 0; // 隐藏弹窗 gameOverModal.style.display = 'none'; // 重启动画 cancelAnimationFrame(animationId); animationId = requestAnimationFrame(animate); } window.addEventListener('click', function(e){ if(gameOver) return; const detectPixelColor = collisionCtx.getImageData(e.x, e.y, 1, 1); const pc = detectPixelColor.data; //获取僵尸碰撞盒信息 hit = false; zombies.forEach(object =>{ if(object.randomColors[0] === pc[0] && object.randomColors[1] === pc[1] && object.randomColors[2] === pc[2]){ object.markedForDeletion = true; //从地图中移除该对象 hit = true; } }) clicked = true; }); //帧处理 function animate(timestamp) { if(clicked){ if(hit) score+=12 else score-=6; clicked = false; hit = false; } ctx.clearRect(0, 0, canvas.width, canvas.height); collisionCtx.clearRect(0, 0, canvas.width, canvas.height); let deltatime = timestamp - lastTime; lastTime = timestamp; timeToNextZombie += deltatime; if (timeToNextZombie > zombieInterval){ zombies.push(new Zombie()); //生成新僵尸 timeToNextZombie = 0; zombies.sort(function(a, b){ return a.width - b.width; //排序使小僵尸在大僵尸后方 }) }; drawScore(); //为每个僵尸执行更新操作,object代表单个僵尸对象 [...zombies].forEach(object => object.update(deltatime)); [...zombies].forEach(object => object.draw()); zombies = zombies.filter(object => !object.markedForDeletion); //移除超出屏幕的僵尸 if(!gameOver) animationId = requestAnimationFrame(animate); } // 开始游戏按钮事件 startBtn.addEventListener('click', () => { const nickname = nicknameInput.value.trim(); if(nickname){ playerNickname = nickname; startScreen.style.display = 'none'; animationId = requestAnimationFrame(animate); } else { alert('请输入昵称!'); } }); // 重新开始按钮事件 restartBtn.addEventListener('click', resetGame);
关键修改说明
- 新增开始界面:添加带昵称输入框和开始按钮的弹窗,游戏初始时显示,输入昵称并点击开始后隐藏,同时启动游戏动画。
- 昵称显示:在
drawScore函数中新增屏幕中上侧的昵称绘制逻辑,居中显示玩家昵称。 - 游戏结束弹窗:创建包含昵称、最终分数和重新开始按钮的弹窗,游戏结束时自动弹出。
- 游戏重置功能:新增
resetGame函数,重置所有游戏状态变量,关闭弹窗并重启动画循环。 - 事件逻辑优化:调整点击事件判断,避免游戏结束后仍响应点击;将动画启动逻辑移至开始按钮点击事件中,初始不自动运行游戏。
内容的提问来源于stack exchange,提问作者fantomx775
相关产品推荐
相关产品推荐

