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

Java Swing游戏:如何用线程实现双角色同步移动?方案遇阻求助

解决Java Swing双玩家同时移动问题

你之前用多线程的思路踩了Swing的核心坑:Swing是单线程UI框架,所有UI操作必须在事件分发线程(EDT)中执行,多线程直接操作UI会导致界面卡顿、逻辑混乱甚至崩溃。下面是更可靠的实现方案:

核心思路

  1. 按键状态跟踪:用布尔值记录每个按键的"按下/释放"状态,而不是依赖单次按键事件触发移动。
  2. 单游戏循环线程:用一个后台线程统一处理所有角色的移动逻辑,然后将UI更新任务抛给EDT执行。
  3. KeyBindings替代KeyListener:避免焦点问题,只要窗口在前台就能响应按键。

完整代码示例

1. 玩家角色类

class Player {
    int x, y;
    final int speed = 5; // 移动速度

    public Player(int startX, int startY) {
        this.x = startX;
        this.y = startY;
    }

    public void moveUp() { y -= speed; }
    public void moveDown() { y += speed; }
    public void moveLeft() { x -= speed; }
    public void moveRight() { x += speed; }
}

2. 游戏主面板

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;

public class TwoPlayerGame extends JPanel {
    private Player player1; // AWSD控制
    private Player player2; // 方向键控制
    
    // 按键状态跟踪
    private boolean wPressed, aPressed, sPressed, dPressed;
    private boolean upPressed, downPressed, leftPressed, rightPressed;

    public TwoPlayerGame() {
        setPreferredSize(new Dimension(800, 600));
        setBackground(Color.BLACK);

        // 初始化玩家位置
        player1 = new Player(100, 300);
        player2 = new Player(700, 300);

        // 绑定Player1的AWSD按键
        setupKeyBinding("W_PRESSED", KeyStroke.getKeyStroke('w', 0, false), e -> wPressed = true);
        setupKeyBinding("W_RELEASED", KeyStroke.getKeyStroke('w', 0, true), e -> wPressed = false);
        setupKeyBinding("A_PRESSED", KeyStroke.getKeyStroke('a', 0, false), e -> aPressed = true);
        setupKeyBinding("A_RELEASED", KeyStroke.getKeyStroke('a', 0, true), e -> aPressed = false);
        setupKeyBinding("S_PRESSED", KeyStroke.getKeyStroke('s', 0, false), e -> sPressed = true);
        setupKeyBinding("S_RELEASED", KeyStroke.getKeyStroke('s', 0, true), e -> sPressed = false);
        setupKeyBinding("D_PRESSED", KeyStroke.getKeyStroke('d', 0, false), e -> dPressed = true);
        setupKeyBinding("D_RELEASED", KeyStroke.getKeyStroke('d', 0, true), e -> dPressed = false);

        // 绑定Player2的方向键
        setupKeyBinding("UP_PRESSED", KeyStroke.getKeyStroke("UP", 0, false), e -> upPressed = true);
        setupKeyBinding("UP_RELEASED", KeyStroke.getKeyStroke("UP", 0, true), e -> upPressed = false);
        setupKeyBinding("DOWN_PRESSED", KeyStroke.getKeyStroke("DOWN", 0, false), e -> downPressed = true);
        setupKeyBinding("DOWN_RELEASED", KeyStroke.getKeyStroke("DOWN", 0, true), e -> downPressed = false);
        setupKeyBinding("LEFT_PRESSED", KeyStroke.getKeyStroke("LEFT", 0, false), e -> leftPressed = true);
        setupKeyBinding("LEFT_RELEASED", KeyStroke.getKeyStroke("LEFT", 0, true), e -> leftPressed = false);
        setupKeyBinding("RIGHT_PRESSED", KeyStroke.getKeyStroke("RIGHT", 0, false), e -> rightPressed = true);
        setupKeyBinding("RIGHT_RELEASED", KeyStroke.getKeyStroke("RIGHT", 0, true), e -> rightPressed = false);

        // 启动游戏循环(约60帧/秒)
        new Thread(() -> {
            while (true) {
                updatePlayerPositions();
                // 确保UI更新在EDT执行
                SwingUtilities.invokeLater(this::repaint);
                try {
                    Thread.sleep(16);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    // 通用按键绑定方法
    private void setupKeyBinding(String name, KeyStroke keyStroke, AbstractAction action) {
        getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(keyStroke, name);
        getActionMap().put(name, action);
    }

    // 更新所有玩家位置
    private void updatePlayerPositions() {
        // Player1移动逻辑
        if (wPressed) player1.moveUp();
        if (aPressed) player1.moveLeft();
        if (sPressed) player1.moveDown();
        if (dPressed) player1.moveRight();

        // Player2移动逻辑
        if (upPressed) player2.moveUp();
        if (leftPressed) player2.moveLeft();
        if (downPressed) player2.moveDown();
        if (rightPressed) player2.moveRight();

        // 边界检测(防止角色移出窗口)
        player1.x = Math.max(0, Math.min(getWidth() - 50, player1.x));
        player1.y = Math.max(0, Math.min(getHeight() - 50, player1.y));
        player2.x = Math.max(0, Math.min(getWidth() - 50, player2.x));
        player2.y = Math.max(0, Math.min(getHeight() - 50, player2.y));
    }

    // 绘制角色
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(Color.BLUE);
        g.fillRect(player1.x, player1.y, 50, 50); // Player1用蓝色方块
        g.setColor(Color.RED);
        g.fillRect(player2.x, player2.y, 50, 50); // Player2用红色方块
    }

    // 启动游戏
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("双玩家同步移动游戏");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(new TwoPlayerGame());
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }
}

方案优势

  • 线程安全:所有UI更新都通过SwingUtilities.invokeLater()交给EDT执行,完全符合Swing的线程规则。
  • 同步流畅:单游戏循环统一处理移动逻辑,两个角色的移动计算在同一帧完成,不会出现错位。
  • 按键响应稳定:KeyBindings不受组件焦点影响,只要窗口激活就能响应按键。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 22:55:17