JDialogue组件在while循环结束前不显示的问题求助
Java Swing战斗场景JDialog实时更新问题解决
问题根源
你遇到的核心问题是Swing事件调度线程(EDT)被阻塞。Swing的所有UI更新、事件处理都必须在EDT中执行,而你的fight()方法是由UI触发的事件(比如按钮点击)调用的,属于EDT线程。里面的while循环加上Thread.sleep()会完全占用EDT,导致UI无法进行实时重绘,直到整个循环执行完毕,才会一次性刷新所有组件的最终状态。
解决方案1:使用Swing Timer替代while循环(推荐)
Swing Timer是专为UI定时任务设计的,它会自动在EDT中调度任务,不会阻塞UI线程。我们可以用它来拆分战斗循环的每一步:
public void setupFight(Character c) { fightPopup.setLocationRelativeTo(this); fightPopup.setVisible(true); mcBar.setMinimum(0); mcBar.setMaximum(mc.getAtkspd()); enemyBar.setMinimum(0); enemyBar.setMaximum(c.getAtkspd()); enemyDm.setVisible(false); enemyDmType.setVisible(false); mcDm.setVisible(false); mcFight.setVisible(true); enemyFight.setVisible(true); // 初始化战斗状态 mc.setAtkbr(mc.getAtkspd()); c.setAtkbr(c.getAtkspd()); startFightLoop(c); } private void startFightLoop(Character c) { // 每10ms触发一次战斗步骤 Timer fightTimer = new Timer(10, e -> { if (mc.getHP() > 0 && c.getHP() > 0) { mc.setAtkbr(mc.getAtkbr() - 10); c.setAtkbr(c.getAtkbr() - 10); // 实时更新UI组件 mcBar.setValue(mcBar.getMaximum() - mc.getAtkbr()); enemyBar.setValue(enemyBar.getMaximum() - c.getAtkbr()); enemyDmType.setText("hola"); int[] damage1; int[] damage2; if (mc.getAtkbr() == 0 && c.getAtkbr() == 0) { damage1 = mc.attack(c); damage2 = c.attack(mc); mc.setHP(mc.getHP() - damage2[1]); // mc承受敌人攻击伤害 c.setHP(c.getHP() - damage1[1]); // 敌人承受mc攻击伤害 mc.setAtkbr(mc.getAtkspd()); c.setAtkbr(c.getAtkspd()); } else if (mc.getAtkbr() == 0) { damage1 = mc.attack(c); c.setHP(c.getHP() - damage1[1]); mc.setAtkbr(mc.getAtkspd()); } else if (c.getAtkbr() == 0) { damage2 = c.attack(mc); mc.setHP(mc.getHP() - damage2[1]); c.setAtkbr(c.getAtkspd()); } } else { // 战斗结束,停止定时器 ((Timer)e.getSource()).stop(); // 处理战斗结果 if (mc.getHP() <= 0) { ((MC) mc).defeat(); } else { ((MC) mc).pickUp(((Enemy) c).defeat()); } } }); fightTimer.start(); }
解决方案2:后台线程+SwingUtilities.invokeLater更新UI
如果坚持使用while循环,需要把战斗逻辑放到后台线程中,所有UI更新操作必须通过SwingUtilities.invokeLater()提交到EDT执行:
public void setupFight(Character c) { fightPopup.setLocationRelativeTo(this); fightPopup.setVisible(true); mcBar.setMinimum(0); mcBar.setMaximum(mc.getAtkspd()); enemyBar.setMinimum(0); enemyBar.setMaximum(c.getAtkspd()); enemyDm.setVisible(false); enemyDmType.setVisible(false); mcDm.setVisible(false); mcFight.setVisible(true); enemyFight.setVisible(true); // 启动后台线程执行战斗逻辑 new Thread(() -> fight(c)).start(); } public void fight(Character c) { mc.setAtkbr(mc.getAtkspd()); c.setAtkbr(c.getAtkspd()); int[] damage1 = new int[2]; int[] damage2 = new int[2]; System.out.println(mc.getAtk()); while (mc.getHP() > 0 && c.getHP() > 0) { try { Thread.sleep(10); } catch (InterruptedException ex) { Logger.getLogger(Character.class.getName()).log(Level.SEVERE, null, ex); Thread.currentThread().interrupt(); break; } mc.setAtkbr(mc.getAtkbr() - 10); c.setAtkbr(c.getAtkbr() - 10); // UI更新操作必须提交到EDT执行 SwingUtilities.invokeLater(() -> { mcBar.setValue(mcBar.getMaximum() - mc.getAtkbr()); enemyBar.setValue(enemyBar.getMaximum() - c.getAtkbr()); enemyDmType.setText("hola"); }); if (mc.getAtkbr() == 0 && c.getAtkbr() == 0) { damage1 = mc.attack(c); damage2 = c.attack(mc); mc.setHP(mc.getHP() - damage2[1]); c.setHP(c.getHP() - damage1[1]); mc.setAtkbr(mc.getAtkspd()); c.setAtkbr(c.getAtkspd()); } else if (mc.getAtkbr() == 0) { damage1 = mc.attack(c); c.setHP(c.getHP() - damage1[1]); mc.setAtkbr(mc.getAtkspd()); } else if (c.getAtkbr() == 0) { damage2 = c.attack(mc); mc.setHP(mc.getHP() - damage2[1]); c.setAtkbr(c.getAtkspd()); } } // 战斗结束后的UI操作同样要提交到EDT SwingUtilities.invokeLater(() -> { if (mc.getHP() <= 0) { ((MC) mc).defeat(); } else { ((MC) mc).pickUp(((Enemy) c).defeat()); } }); }
重要提示
- 原代码逻辑修正:你原代码中HP更新的逻辑完全写反了(比如mc攻击后反而自己减血),上面的方案已经修正了这个问题,必须同步修改才能保证战斗逻辑正确。
- EDT线程规则:Swing是单线程模型,任何UI相关操作都不能在后台线程直接执行,否则会导致UI异常。
内容的提问来源于stack exchange,提问作者Tony Chai
相关产品推荐
相关产品推荐

