我目前正在使用内置 GUI 构建 RPG 游戏,并尝试在 JDialogue 上显示简单的战斗场景。我的部分代码如下:
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);
mcDm.setVisible(false);
mcFight.setVisible(true);
enemyFight.setVisible(true);
fight(c);
}
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);
}
mc.setAtkbr(mc.getAtkbr() - 10);
c.setAtkbr(c.getAtkbr() - 10);
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() - damage1[1]);
c.setHP(c.getHP() - damage2[1]);
mc.setAtkbr(mc.getAtkspd());
c.setAtkbr(c.getAtkspd());
}
else if (mc.getAtkbr() == 0) {
damage1 = mc.attack(c);
mc.setHP(mc.getHP() - damage1[1]);
mc.setAtkbr(mc.getAtkspd());
}
else if (c.getAtkbr() == 0) {
damage2 = c.attack(mc);
c.setHP(c.getHP() - damage2[1]);
c.setAtkbr(c.getAtkspd());
}
}
if (mc.getHP() <= 0) {
((MC) mc).defeat();
}
else {
((MC) mc).pickUp(((Enemy) c).defeat());
}
}
但这就是代码运行和 while 循环运行时显示的内容(基本上是空的,没有任何组件):
这些组件仅在“假定的”战斗场景结束后(在
((MC) mc).pickUp(((Enemy) c).defeat());
行之后由代码完成模拟)以及最终更新的数据显示在 JDialogue 上。
有人可以帮我吗?我真的很感激。
我尝试延长
Thread.sleep(10);
时间,因为我认为这是因为10毫秒对于计算机更新对话上的任何信息来说是短的两秒。但它似乎暂停了一切,而不仅仅是 while 循环本身。
Swing 是单线程的,这意味着您不应该在其上下文中执行长时间运行或阻塞操作(例如执行诸如
Thread.sleep
之类的操作)。
相反,您需要以某种方式解耦工作流程,要么在单独的线程中运行逻辑,要么利用某种定时回调(即
Timer
)。您将使用哪个取决于您想要实现的目标。
请注意,以下示例旨在演示解决方案如何工作,以便您可以采用该概念并在代码中实现它们,而不是提供基于您的代码的“复制粘贴”解决方案。这些是完全可运行的示例,因此您可以复制并运行它们以查看它们是如何工作的。
SwingWorker
请参阅工作线程和 SwingWorker 了解更多详细信息...
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame("Test");
frame.add(
new FightPane(
new Entity("A", 100),
new Entity("B", 100)
)
);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class Entity {
private String name;
private int hitPonts;
private int health;
private Random rnd = new Random();
public Entity(String name, int hitPonts) {
this.name = name;
this.health = hitPonts;
this.hitPonts = hitPonts;
}
public String getName() {
return name;
}
public int getHitPonts() {
return hitPonts;
}
public int getHealth() {
return health;
}
public int attack() {
return rnd.nextInt(0, 25);
}
public void damage(int amount) {
health = Math.max(0, health - amount);
}
public void heal(int amount) {
health = Math.min(hitPonts, health + amount);
}
@Override
public String toString() {
return getName() + " - " + getHealth() + "/" + getHitPonts();
}
}
public class EntityPane extends JPanel {
private JLabel name;
private JLabel health;
private JProgressBar pbHealth;
private Entity entity;
public EntityPane(Entity entity) {
this.entity = entity;
name = new JLabel();
name.setFont(name.getFont().deriveFont(Font.BOLD, 16));
health = new JLabel();
pbHealth = new JProgressBar();
pbHealth.setMinimum(0);
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.LINE_START;
gbc.gridwidth = GridBagConstraints.REMAINDER;
add(name, gbc);
add(health, gbc);
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.weightx = 1;
add(pbHealth, gbc);
setBorder(new CompoundBorder(new LineBorder(Color.BLACK, 1, true), new EmptyBorder(8, 8, 8, 8)));
refreshDetails();
}
public void didAttack() {
setBackground(null);
refreshDetails();
}
public void wasAttacked() {
setBackground(Color.ORANGE);
refreshDetails();
}
public void didWin() {
setBackground(Color.GREEN);
refreshDetails();
}
public void didLose() {
setBackground(Color.RED);
refreshDetails();
}
public Entity getEntity() {
return entity;
}
// This would be better handled via an observer directly on Entity
// but for example purposes
public void refreshDetails() {
Entity entity = getEntity();
if (entity == null) {
name.setText("---");
health.setText("---");
pbHealth.setValue(0);
}
name.setText(entity.getName());
health.setText(entity.getHealth() + "/" + entity.getHitPonts());
pbHealth.setMaximum(entity.getHitPonts());
pbHealth.setValue(entity.getHealth());
}
}
protected class FightPane extends JPanel {
private EntityPane fighterOnePane;
private EntityPane fighterTwoPane;
public FightPane(Entity fighter1, Entity fighter2) {
setBorder(new EmptyBorder(8, 8, 8, 8));
setLayout(new GridBagLayout());
JLabel title = new JLabel("Fight!");
title.setFont(title.getFont().deriveFont(Font.BOLD, 32));
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(8, 8, 8, 8);
gbc.gridwidth = GridBagConstraints.REMAINDER;
add(title, gbc);
fighterOnePane = new EntityPane(fighter1);
fighterTwoPane = new EntityPane(fighter2);
JPanel fighters = new JPanel(new GridBagLayout());
GridBagConstraints gbcFight = new GridBagConstraints();
gbcFight.gridheight = GridBagConstraints.REMAINDER;
gbcFight.insets = new Insets(8, 8, 8, 8);
fighters.add(fighterOnePane, gbcFight);
fighters.add(new JLabel("vs"), gbcFight);
fighters.add(fighterTwoPane, gbcFight);
add(fighters, gbc);
JButton fightButton = new JButton("Start");
add(fightButton, gbc);
fightButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
fightButton.setEnabled(false);
FightWorker worker = new FightWorker(fighterOnePane.getEntity(), fighterTwoPane.getEntity(), new FightWorker.Obsever() {
@Override
public void update(Entity fighter) {
if (fighter == fighterOnePane.getEntity()) {
fighterOnePane.wasAttacked();
fighterTwoPane.didAttack();
} else if (fighter == fighterTwoPane.getEntity()) {
fighterOnePane.didAttack();
fighterTwoPane.wasAttacked();
}
}
@Override
public void winner(Entity fighter) {
EntityPane target = null;
if (fighter == fighterOnePane.getEntity()) {
fighterOnePane.didWin();
fighterTwoPane.didLose();
title.setText(fighter.getName() + " did win!");
} else if (fighter == fighterTwoPane.getEntity()) {
fighterOnePane.didLose();
fighterTwoPane.didWin();
title.setText(fighter.getName() + " did win!");
} else {
fighterOnePane.didLose();
fighterTwoPane.didLose();
title.setText("No one won!");
}
// Don't tap the button twice, but this shows away
// you could re-enable controls or do things
// when the fight is over
fightButton.setEnabled(true);
}
});
worker.execute();
}
});
}
}
protected class FightWorker extends SwingWorker<Entity, Entity> {
// This provides a simpilified callback workflow and decouples
// the worker from the obsever. The worker focucs only on the
// interaction between the entities and nothing else
public interface Obsever {
public void update(Entity fighter);
public void winner(Entity fighter);
}
private Entity fighter1;
private Entity fighter2;
private Obsever obsever;
public FightWorker(Entity fighter1, Entity fighter2, Obsever obsever) {
this.fighter1 = fighter1;
this.fighter2 = fighter2;
this.obsever = obsever;
// This allows for a self monitoring workflow, so when the
// worker is "done", we can easily notify the obeserver. These
// notifications occur on the EDT
addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (isDone()) {
try {
Entity winner = get();
obsever.winner(winner);
} catch (InterruptedException | ExecutionException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
obsever.winner(null);
}
}
}
});
}
@Override
protected void process(List<Entity> chunks) {
// This is called within the EDT, so we can update the UI
for (Entity entity : chunks) {
obsever.update(entity);
}
}
@Override
protected Entity doInBackground() throws Exception {
Random rnd = new Random();
while (fighter1.getHealth() > 0 && fighter2.getHealth() > 0) {
// Randomly choose who gets to attack
if (rnd.nextBoolean()) {
fighter2.damage(fighter1.attack());
publish(fighter2);
} else {
fighter1.damage(fighter2.attack());
publish(fighter1);
}
Thread.sleep(500);
}
Entity winner = null;
if (fighter1.getHealth() > 0 && fighter2.getHealth() == 0) {
winner = fighter1;
} else if (fighter1.getHealth() == 0 && fighter2.getHealth() > 0) {
winner = fighter2;
}
return winner;
}
}
}