如何在HTML游戏中基于LocalStorage实现本地高分保存系统?
实现基于LocalStorage的本地高分保存功能
我看到你已经有了highScore函数的雏形,但还需要把它和游戏的得分逻辑、碰撞结束流程联动起来。下面是完整的修改方案,一步步帮你搞定这个功能:
1. 完善得分计算与显示逻辑
你的现有代码里myScore组件只是初始化了,但没有实时更新得分。我们可以用游戏的帧号作为基础得分(每过一帧加1),在updateGameArea里实时更新:
function updateGameArea() { // ... 原有碰撞检测代码 ... // 实时更新当前得分 myScore.text = "Score: " + myGameArea.frameNo; // ... 原有绘制元素代码 ... }
2. 碰撞时自动更新高分
当游戏因碰撞结束时,需要把当前得分和本地保存的高分对比,如果当前得分更高,就更新LocalStorage里的记录:
function updateGameArea() { for (i = 0; i < myObstacles.length; i += 1) { if (myGamePiece.crashWith(myObstacles[i])) { const currentScore = myGameArea.frameNo; const savedHighScore = highScore(); // 对比得分并更新 if (currentScore > savedHighScore) { highScore(currentScore); alert(`新纪录!你的得分是 ${currentScore},之前的高分是 ${savedHighScore}`); } else { alert(`游戏结束!你的得分是 ${currentScore},当前高分是 ${savedHighScore}`); } myGameArea.stop(); document.getElementsByClassName("resetButton")[0].innerHTML='<button onclick="location.reload();" style="height:27px;width:815px">Restart Game</button>'; return; } } // ... 原有其他代码 ... }
3. 优化highScore函数的健壮性
你的初始函数已经不错,我调整成更规范的写法,避免潜在的异常:
function highScore(score) { // 用getItem/setItem更规范,避免属性访问的潜在问题 let saved = localStorage.getItem('highScore'); // 处理首次访问或保存值无效的情况 saved = saved ? parseFloat(saved) : 0; if (!isNaN(score)) { // 只有传入有效数字时才更新本地存储 localStorage.setItem('highScore', score.toString()); return score; } // 确保返回的是有效数字 return isNaN(saved) ? 0 : saved; }
4. 游戏初始化时显示当前高分
可以在游戏开始时就展示当前的高分,提升用户感知:
function startGame() { // ... 原有初始化代码 ... // 添加高分显示组件 myHighScoreDisplay = new component("30px", "Algerian", "gold", 600, 30, "text"); myHighScoreDisplay.text = "High Score: " + highScore(); myGameArea.start(); }
然后在updateGameArea里保持高分显示的更新:
function updateGameArea() { // ... 原有代码 ... // 实时更新高分显示 myHighScoreDisplay.text = "High Score: " + highScore(); // 绘制所有组件 myBackground.update(); myGamePiece.update(); myScore.update(); myHighScoreDisplay.update(); // ... 原有障碍物绘制代码 ... }
5. 修复HTML按钮的语法错误
你的按钮标签存在嵌套错误和未闭合的问题,修复后:
<button onmousedown="accelerate(-0.2)" onmouseup="accelerate(0.05)" style="height:27px;width:815px">Click to Accelerate</button> <div class="resetButton"></div> <button type="button" onclick="alert('Owner: Landon Sturtevant')" style="height:27px;width:815px">Credits</button> <button type="button" onclick="alert('Click OK to Continue')" style="height:27px;width:815px">Pause</button> <button type="button" onclick="alert('High Score is ' + highScore());" style="height:27px;width:815px">HighScore</button>
完整修改后代码
我把所有修改整合到你的代码里,还补全了被截断的障碍物部分:
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <style> canvas { border:7px solid #00aaff; background-color: #00aaff; } </style> </head> <body onload="startGame()"> <br> <button onmousedown="accelerate(-0.2)" onmouseup="accelerate(0.05)" style="height:27px;width:815px">Click to Accelerate</button> <div class="resetButton"></div> <button type="button" onclick="alert('Owner: Landon Sturtevant')" style="height:27px;width:815px">Credits</button> <button type="button" onclick="alert('Click OK to Continue')" style="height:27px;width:815px">Pause</button> <button type="button" onclick="alert('High Score is ' + highScore());" style="height:27px;width:815px">HighScore</button> <script> function highScore(score) { let saved = localStorage.getItem('highScore'); saved = saved ? parseFloat(saved) : 0; if (!isNaN(score)) { localStorage.setItem('highScore', score.toString()); return score; } return isNaN(saved) ? 0 : saved; } var myGamePiece; var myBackground; var myObstacles = []; var myScore; var myHighScoreDisplay; var gameSpeed = 1; function startGame() { myBackground = new component(800, 380,"http://static.giantbomb.com/uploads/original/29/297846/2789895-cool+space+backgrounds+desktop+hd+wallpaper.jpg", 0, 0, "image"); myGamePiece = new component(50, 50, "http://images.tourismholdings.com/public/2016/02/56bd3c714ecc6.png", 35, 200, "image"); myScore = new component("30px", "Algerian", "turquoise", 15, 30, "text"); myHighScoreDisplay = new component("30px", "Algerian", "gold", 600, 30, "text"); myHighScoreDisplay.text = "High Score: " + highScore(); myGameArea.start(); } var myGameArea = { canvas : document.createElement("canvas"), start : function() { this.canvas.width = 800; this.canvas.height = 290; this.context = this.canvas.getContext("2d"); document.body.insertBefore(this.canvas, document.body.childNodes[0]); this.frameNo = 0; this.interval = setInterval(updateGameArea, 20); }, clear : function() { this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); }, stop : function() { clearInterval(this.interval); } } function component(width, height, color, x, y, type) { if (type == "image") { this.image = new Image(); this.image.src = color; } this.type = type; this.width = width; this.height = height; this.speedX = 0; this.speedY = 0; this.gravity = 0.05; this.gravitySpeed = 0; this.x = x; this.y = y; this.text = ""; // 新增text属性用于文本组件 this.update = function() { ctx = myGameArea.context; if (type == "image") { ctx.drawImage(this.image, this.x, this.y, this.width, this.height); } else if (this.type == "text") { ctx.font = this.width + " " + this.height; ctx.fillStyle = color; ctx.fillText(this.text, this.x, this.y); } } this.newPos = function() { this.gravitySpeed += this.gravity; this.x += this.speedX; this.y += this.speedY + this.gravitySpeed; this.hitBottom(); } this.hitBottom = function() { var rockbottom = myGameArea.canvas.height - this.height; if (this.y > rockbottom) { this.y = rockbottom; } } this.crashWith = function(otherobj) { var myleft = this.x; var myright = this.x + (this.width); var mytop = this.y; var mybottom = this.y + (this.height); var otherleft = otherobj.x; var otherright = otherobj.x + (otherobj.width); var othertop = otherobj.y; var otherbottom = otherobj.y + (otherobj.height); var crash = true; if ((mybottom < othertop) || (mytop > otherbottom) || (myright < otherleft) || (myleft > otherright)) { crash = false; } return crash; } } function everyinterval(n) { if ((myGameArea.frameNo / n) % 1 == 0) {return true;} return false; } function accelerate(n) { myGamePiece.gravity = n; } function updateGameArea() { var x, height, gap, minHeight, maxHeight, minGap, maxGap; for (i = 0; i < myObstacles.length; i += 1) { if (myGamePiece.crashWith(myObstacles[i])) { const currentScore = myGameArea.frameNo; const savedHighScore = highScore(); if (currentScore > savedHighScore) { highScore(currentScore); alert(`新纪录!你的得分是 ${currentScore},之前的高分是 ${savedHighScore}`); } else { alert(`游戏结束!你的得分是 ${currentScore},当前高分是 ${savedHighScore}`); } myGameArea.stop() document.getElementsByClassName("resetButton")[0].innerHTML='<button onclick="location.reload();" style="height:27px;width:815px">Restart Game</button>'; return; } } myGameArea.clear(); myGameArea.frameNo += 1; // 更新得分与高分显示 myScore.text = "Score: " + myGameArea.frameNo; myHighScoreDisplay.text = "High Score: " + highScore(); if (myGameArea.frameNo == 1 || everyinterval(150)) { x = myGameArea.canvas.width; minHeight = 20; maxHeight = 200; height = Math.floor(Math.random()*(maxHeight-minHeight+1)+minHeight); minGap = 150; maxGap = 250; gap = Math.floor(Math.random()*(maxGap-minGap+1)+minGap); myObstacles.push(new component(15, height, "https://orig00.deviantart.net/bb11/f/2014/127/f/e/hexagon_wallpaper___version_3_by_designedby_jack-d7hh9xo.png", x, 0, "image")); myObstacles.push(new component(15, x - height - gap, "https://orig00.deviantart.net/bb11/f/2014/127/f/e/hexagon_wallpaper___version_3_by_designedby_jack-d7hh9xo.png", x, height + gap, "image")); } // 移动障碍物 for (i = 0; i < myObstacles.length; i += 1) { myObstacles[i].x -= gameSpeed; myObstacles[i].update(); } myBackground.update(); myGamePiece.newPos(); myGamePiece.update(); myScore.update(); myHighScoreDisplay.update(); } </script> </body> </html>
关键说明
- 用
localStorage.getItem/setItem替代直接属性访问,避免潜在的命名冲突和异常。 - 游戏结束时自动对比得分,更新后给用户明确提示。
- 实时显示当前得分和高分,让用户随时了解进度。
- 修复了原有HTML按钮的语法错误,确保页面正常渲染。
内容的提问来源于stack exchange,提问作者Landon
相关产品推荐
相关产品推荐

