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

Java ACM库移动GObject时残留黑线拖影问题排查

问题描述

实现打砖块游戏的矩形GObject挡板跟随鼠标在画布移动的效果时,程序运行后移动矩形会在移动路径上留下黑色线条,已知问题和未移除旧绘制矩形实例相关,原有实现代码如下:

// TODO: comment this program

import acm.graphics.*;     // GOval, GRect, etc.
import acm.program.*;      // GraphicsProgram
import acm.util.*;         // RandomGenerator
import java.awt.*;         // Color
import java.awt.event.*;   // MouseEvent

public class Breakout extends BreakoutProgram {

    public void run() {
        setCanvasSize(CANVAS_WIDTH, CANVAS_HEIGHT);
        brickBuilder();
    }
    
    // 构建砖块阵列,按行设置砖块颜色
    private void brickBuilder() {
        int i=0;//列计数
        int j=-1;//行计数,初始值设为-1避免y坐标和颜色匹配逻辑出错
        while(j<(NBRICK_ROWS-1)) {
            j++;
            i=0;//每次行循环重置列计数
            double x_coordinate = getWidth()/2-(NBRICK_COLUMNS*BRICK_WIDTH+((NBRICK_COLUMNS-1)*BRICK_SEP))/2;
            double y_coordinate = BRICK_Y_OFFSET;
            y_coordinate +=j*(BRICK_HEIGHT+BRICK_SEP);
            while(i<NBRICK_COLUMNS) {
                i++;
                x_coordinate +=(BRICK_WIDTH+BRICK_SEP);
                GRect brickWall = new GRect(x_coordinate,y_coordinate,BRICK_WIDTH,BRICK_HEIGHT);
                brickWall.setFilled(true);
                switch((j+1)%10) {//按行循环设置砖块颜色
                case 1: brickWall.setFillColor(Color.RED); break;
                case 2: brickWall.setFillColor(Color.RED); break;
                case 3: brickWall.setFillColor(Color.ORANGE); break;
                case 4: brickWall.setFillColor(Color.ORANGE); break;
                case 5: brickWall.setFillColor(Color.YELLOW); break;
                case 6: brickWall.setFillColor(Color.YELLOW); break;
                case 7: brickWall.setFillColor(Color.GREEN); break;
                case 8: brickWall.setFillColor(Color.GREEN); break;
                case 9: brickWall.setFillColor(Color.CYAN); break;
                case 0: brickWall.setFillColor(Color.CYAN); break;
                }
                add(brickWall);
            }
        }
    }
    
    public void mouseMoved(MouseEvent e) {
        GRect paddle = new GRect(e.getX(), getHeight()-PADDLE_Y_OFFSET, PADDLE_WIDTH, PADDLE_HEIGHT);
        paddle.setFilled(true);
        add(paddle);
    }
    
    public void init() {
        addMouseListeners();
    }
}

初步判断问题出在mouseMoved和init方法的实现逻辑上,需要定位具体错误原因,给出可运行的正确实现方案。

错误原因定位
  • init方法逻辑无问题:addMouseListeners()是ACM库注册鼠标事件监听的标准写法,不需要修改。
  • 核心错误在mouseMoved方法:每次鼠标移动触发事件时,代码都会新建一个GRect挡板实例并添加到画布上,但从未删除之前生成的旧挡板对象。所有历史绘制的挡板会一直保留在画布上,连续排列就形成了路径上的黑色拖影。
  • 存在额外交互缺陷:现有逻辑没有做挡板的边界限制,鼠标移动到画布边缘时,挡板会整体或部分移出可视区域,不符合打砖块游戏的交互规则。
正确实现方案

实现逻辑调整如下:

  1. 将挡板声明为类的成员变量,仅在程序初始化阶段创建1次挡板实例,不要在鼠标移动事件里重复新建对象
  2. 鼠标移动时仅更新已有挡板的坐标位置,不需要重复执行add()操作添加对象
  3. 增加x坐标边界判断,保证挡板始终完整显示在画布范围内

具体修改步骤:

  • 首先在类内部声明挡板成员变量:
private GRect paddle;
  • 在run()方法中,调用brickBuilder()完成砖块绘制后,初始化挡板并添加到画布:
// 初始化挡板,y坐标固定在距离底部PADDLE_Y_OFFSET的位置
paddle = new GRect(0, getHeight() - PADDLE_Y_OFFSET, PADDLE_WIDTH, PADDLE_HEIGHT);
paddle.setFilled(true);
add(paddle);
  • 重写mouseMoved方法,替换原有重复新建对象的逻辑:
public void mouseMoved(MouseEvent e) {
    // 计算挡板x坐标,默认让挡板中心对齐鼠标位置
    double paddleX = e.getX() - PADDLE_WIDTH / 2;
    // 左边界限制:挡板最左侧不能超出画布左边缘
    if (paddleX < 0) {
        paddleX = 0;
    }
    // 右边界限制:挡板最右侧不能超出画布右边缘
    if (paddleX > getWidth() - PADDLE_WIDTH) {
        paddleX = getWidth() - PADDLE_WIDTH;
    }
    // 直接更新挡板位置,不需要新建对象
    paddle.setLocation(paddleX, getHeight() - PADDLE_Y_OFFSET);
}

修改后运行程序,挡板会平滑跟随鼠标移动,不会再留下黑色拖影,同时不会超出画布边界。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 05:15:18