JavaScript实现指定时间绘制并移动方块的技术求助
解决Guitar Hero风格项目中音符按时间逐个启动移动的问题
嘿,我看你在做一个基于JavaScript的Guitar Hero期末项目,用方块代替圆形音符对吧?现在遇到的核心问题是所有方块一启动就同时往下跑,没法按指定时间逐个触发移动——这个问题我帮你搞定!
问题根源分析
你现在的代码里,squares数组一开始就把所有方块实例都创建好了,而且直接加入到了update循环里,所以游戏一启动所有方块就同步开始移动。我们需要做的是把音符的“生成时机”和“移动状态”分开:先把每个音符的出现时间和属性存在一个队列里,然后在游戏运行过程中,到了指定时间再把音符加入到活跃移动的列表中。
修改方案与完整代码
下面是调整后的代码,我会重点解释JS部分的修改:
HTML(无需修改)
<!DOCTYPE HTML> <html> <body> <center> </center> <canvas id="canvas"></canvas> </body> </html>
CSS(无需修改)
<style> canvas { border: 3px solid #000000; padding-left: 0; padding-right: 0; margin-left: auto; margin-right: auto; display: block; border-radius: 5px; } .button { background-color: #f4511e; border: none; color: white; padding: 16px 32px; text-align: center; font-size: 16px; margin: 4px 2px; opacity: 0.6; transition: 0.3s; display: inline-block; text-decoration: none; cursor: pointer; } .button:hover {opacity: 1} </style>
JavaScript(核心修改部分)
c = document.getElementById("canvas"); ctx = c.getContext("2d"); c.width = 1000; c.height = 600; // 各类定义保持不变 class CanLine { constructor(x1, y1, x2, y2){ this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; this.strokeStyle = "black"; } DrawingLines() { ctx.beginPath(); ctx.lineWidth="2"; ctx.strokeStyle="black"; ctx.moveTo(this.x1,this.y1); ctx.lineTo(this.x2,this.y2); ctx.stroke(); ctx.closePath(); } } class FinalLine { constructor(x, y, color) { this.x = x; this.y = y; this.color = color; this.height = 20; this.width = 800; } draw() { ctx.fillStyle = this.color; ctx.fillRect(this.x, this.y, this.width, this.height); ctx.fill(); } } class FixedSquare { constructor(x, y, color) { this.x = x; this.y = y; this.color = color; this.size = 80; } draw() { ctx.fillStyle = this.color; ctx.fillRect(this.x, this.y, this.size, this.size); ctx.fill(); } } class Square { constructor(x, y, color, speed) { this.x = x; this.y = y; this.color = color; this.size = 60; this.speed = speed } draw() { ctx.fillStyle = this.color; ctx.fillRect(this.x, this.y, this.size, this.size); ctx.fill(); } update() { this.y += this.speed; // 修改:当方块超出画布底部时,标记为可移除(而不是循环回到顶部) return this.y > c.height; } } // 静态元素数组保持不变 let Lines = [ new CanLine(200, 0, 200, c.height), new CanLine(360, 0, 360, c.height), new CanLine(500, 0, 500, c.height), new CanLine(650, 0, 650, c.height), new CanLine(800, 0, 800, c.height) ]; let Final_Line = [ new FinalLine(100, 500, "purple") ]; let Fixed_squares = [ new FixedSquare(160, 465, "green"), new FixedSquare(321, 465, "gray"), new FixedSquare(460, 465, "blue"), new FixedSquare(610, 465, "yellow"), new FixedSquare(760, 465, "red") ]; // 核心修改1:创建音符队列,存储每个音符的出现时间和属性 // 格式:{time: 出现时间(毫秒), x: 位置x, color: 颜色, speed: 速度} let noteQueue = [ {time: 1000, x: 170, color: "green", speed: 3}, // 1秒后出现 {time: 3000, x: 170, color: "green", speed: 3}, // 3秒后出现 {time: 5000, x: 331, color: "gray", speed: 3}, // 5秒后出现 {time: 7000, x: 470, color: "blue", speed: 3}, // 7秒后出现 {time: 9000, x: 620, color: "yellow", speed: 3}, // 9秒后出现 {time: 11000, x: 770, color: "red", speed: 3} // 11秒后出现 ]; // 核心修改2:跟踪游戏运行时间 let gameStartTime = Date.now(); // 活跃移动的方块数组(初始为空) let activeSquares = []; function DrawingLines() { Lines.forEach(CanLine => CanLine.DrawingLines()); } function draw() { ctx.fillStyle = "white"; ctx.fillRect(0, 0, c.width, c.height); ctx.fill(); activeSquares.forEach(square => square.draw()); Final_Line.forEach(FinalLine => FinalLine.draw()); Fixed_squares.forEach(FixedSquare => FixedSquare.draw()); DrawingLines(); } function update() { // 计算当前游戏运行时间(毫秒) let currentGameTime = Date.now() - gameStartTime; // 核心修改3:检查队列,生成到点的音符 while(noteQueue.length > 0 && noteQueue[0].time <= currentGameTime) { let note = noteQueue.shift(); // 取出队列第一个元素 activeSquares.push(new Square(note.x, 0, note.color, note.speed)); } // 更新活跃方块,同时移除超出画布的方块 activeSquares = activeSquares.filter(square => !square.update()); draw(); requestAnimationFrame(update); } requestAnimationFrame(update);
关键修改点说明
- 音符队列
noteQueue:把每个音符的出现时间和属性提前定义好,而不是一开始就创建所有Square实例。这样我们可以控制每个音符什么时候被加入到移动列表中。 - 游戏时间跟踪:用
gameStartTime记录游戏启动时间,每一帧计算currentGameTime,用来判断是否到了某个音符的出现时机。 - 动态生成音符:在
update函数里,循环检查队列头部的音符,如果当前游戏时间大于等于音符的出现时间,就创建Square实例并加入到activeSquares数组,然后从队列里移除这个音符。 - 方块移除逻辑:修改了Square的
update方法,当方块超出画布底部时返回true,然后用filter方法把这些方块从activeSquares数组中移除,避免内存占用越来越大。
这样修改后,你的方块就会按照你设定的时间逐个从顶部开始向下移动啦~
内容的提问来源于stack exchange,提问作者Josema_23
相关产品推荐
相关产品推荐

