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

求助:Swing程序绘制3个JLabel而非1个的异常问题

问题根源与解决方案:重复显示棋子的原因

你遇到的问题核心在于每次Swing触发重绘时,你都在创建并添加新的JLabel棋子组件,导致多个组件叠加显示,看起来像是出现了多个兵。

为什么会重复出现?

Swing的paintComponent方法会被多次自动调用:比如窗口初始化、最小化后恢复、窗口被其他窗口覆盖后重新显示、甚至窗口移动时,都会触发重绘。而你的draw方法是在paintComponent里调用的,每次调用draw都会循环遍历Figure.figures,创建新的JLabel并add到GamePanel中。次数多了,面板里就堆积了多个相同的JLabel,自然就看到多个兵了。

另外你还存在一个小问题:棋子的位置计算错误。你直接用figure.position[0] +5作为像素位置,但position是棋盘的行列索引(0-7),需要乘以格子大小UNIT_SIZE才能对应到正确的棋盘格位置,这也是你觉得“位置随机”的原因之一。

解决方案

我给你两种可行的解决思路,你可以根据需求选择:


思路1:提前创建棋子组件,只更新位置

组件只需要创建并添加一次,重绘时只更新它们的位置即可:

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.List;

public class GamePanel extends JPanel implements MouseListener {

    static final int SCREEN_EDGE = 800;
    static final int GAME_UNITS = 64;
    static final int UNIT_SIZE = 100;
    final int[] x = new int[GAME_UNITS];
    final int[] y = new int[GAME_UNITS];
    boolean running = false;
    // 新增:保存棋子组件的集合
    private final List<JLabel> figureLabels = new ArrayList<>();

    public GamePanel() {
        this.setPreferredSize(new Dimension(SCREEN_EDGE, SCREEN_EDGE));
        this.setFocusable(true);
        this.addKeyListener(new MyKeyAdapter());
        this.addMouseListener(this); // 别忘了注册鼠标监听器!
        startGame();
        int[] position = {0,0};
        new Pawn(position,-1);
        // 初始化棋子组件,只执行一次
        initFigureLabels();
    }

    private void startGame() {}

    private void initFigureLabels() {
        for (Figure figure : Figure.figures) {
            if (figure != null) {
                JLabel figureSprite = new JLabel(figure.image, JLabel.CENTER);
                figureSprite.setSize(90,90);
                updateFigurePosition(figureSprite, figure);
                this.add(figureSprite);
                figureLabels.add(figureSprite);
            }
        }
    }

    private void updateFigurePosition(JLabel label, Figure figure) {
        // 修正位置计算:行列索引 * 格子大小 + 偏移量
        int pixelX = figure.position[0] * UNIT_SIZE + 5;
        int pixelY = figure.position[1] * UNIT_SIZE + 5;
        label.setLocation(pixelX, pixelY);
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        draw(g);
    }

    public void draw(Graphics g){
        // 绘制棋盘的代码保持不变
        int counter = 1;
        for (int y = 0; y < SCREEN_EDGE/UNIT_SIZE; y++) {
            int color = (y % 2 == 0) ? 1 : 2;
            for (int x = 0; x < SCREEN_EDGE/UNIT_SIZE; x++) {
                g.setColor(color == 1 ? new Color(239,217, 181) : new Color(180, 136,98));
                g.fillRect(x*UNIT_SIZE, y*UNIT_SIZE, UNIT_SIZE, UNIT_SIZE);
                color = color == 1 ? 2 : 1;
            }
        }

        // 重绘时只更新位置,不再创建新组件
        int index = 0;
        for (Figure figure : Figure.figures) {
            if (figure != null && index < figureLabels.size()) {
                updateFigurePosition(figureLabels.get(index), figure);
                index++;
            }
        }
    }

    // ... 原有鼠标事件、KeyAdapter代码保持不变
}

思路2:直接用Graphics绘制棋子(更推荐游戏场景)

如果你担心JLabel的鼠标事件处理麻烦,直接用Graphics绘制图片是更简洁的方案,还能避免组件管理的问题:

public class GamePanel extends JPanel implements MouseListener {

    // ... 原有属性不变

    public GamePanel() {
        this.setPreferredSize(new Dimension(SCREEN_EDGE, SCREEN_EDGE));
        this.setFocusable(true);
        this.addKeyListener(new MyKeyAdapter());
        this.addMouseListener(this); // 注册鼠标监听器
        startGame();
        int[] position = {0,0};
        new Pawn(position,-1);
    }

    // ... 原有startGame、paintComponent代码不变

    public void draw(Graphics g){
        // 绘制棋盘的代码保持不变
        // ... 棋盘绘制代码

        // 直接用Graphics绘制棋子图片,无需JLabel
        for (Figure figure : Figure.figures) {
            if (figure != null && figure.image != null) {
                int pixelX = figure.position[0] * UNIT_SIZE + 5;
                int pixelY = figure.position[1] * UNIT_SIZE + 5;
                figure.image.paintIcon(this, g, pixelX, pixelY);
            }
        }
    }

    // 处理鼠标点击:判断点击的棋盘格是否有棋子
    @Override
    public void mouseClicked(MouseEvent e) {
        // 计算点击的棋盘格行列
        int gridX = e.getX() / UNIT_SIZE;
        int gridY = e.getY() / UNIT_SIZE;

        // 查找该位置的棋子
        for (Figure figure : Figure.figures) {
            if (figure != null && figure.position[0] == gridX && figure.position[1] == gridY) {
                // 这里可以处理棋子的点击逻辑,比如选中、移动等
                System.out.println("点击了位于(" + gridX + "," + gridY + ")的棋子");
                break;
            }
        }
    }

    // ... 其他鼠标事件、KeyAdapter代码保持不变
}

另外建议把Figure类中的数组换成ArrayList,避免手动扩容的麻烦:

public abstract class Figure{
    // ... 原有属性不变
    // 替换数组为ArrayList
    protected static List<Figure> figures = new ArrayList<>();

    public Figure(int value, int[] position, int[][] possibleMoves , int whiteOrBlack) {
        this.value = value;
        this.position = position;
        this.possibleMoves = possibleMoves;
        this.whiteOrBlack = whiteOrBlack;
        // 直接添加到列表,无需手动扩容
        figures.add(this);
    }

    // ... 原有抽象方法不变
}

总结

重复显示棋子的核心原因是在重绘方法中重复添加组件,修正这个逻辑后就能解决问题。推荐使用第二种直接绘制图片的方式,更适合游戏开发的场景,鼠标事件处理也更灵活。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 17:10:51