You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

关键修改点说明

  1. 音符队列noteQueue:把每个音符的出现时间和属性提前定义好,而不是一开始就创建所有Square实例。这样我们可以控制每个音符什么时候被加入到移动列表中。
  2. 游戏时间跟踪:用gameStartTime记录游戏启动时间,每一帧计算currentGameTime,用来判断是否到了某个音符的出现时机。
  3. 动态生成音符:在update函数里,循环检查队列头部的音符,如果当前游戏时间大于等于音符的出现时间,就创建Square实例并加入到activeSquares数组,然后从队列里移除这个音符。
  4. 方块移除逻辑:修改了Square的update方法,当方块超出画布底部时返回true,然后用filter方法把这些方块从activeSquares数组中移除,避免内存占用越来越大。

这样修改后,你的方块就会按照你设定的时间逐个从顶部开始向下移动啦~

内容的提问来源于stack exchange,提问作者Josema_23

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 09:02:47