多人 2D 游戏中的数据结构 (Java)
Data Structures in 2D-Games with Multiplayer (Java)
我有以下问题:
我编写了一个具有多人游戏功能的 2D 游戏。现在我将其他玩家数据和游戏对象存储在两个 ArrayList 中(世界以其他方式存储)。有时网络线程会发送无法应用的更新,因为游戏会绘制 Players/Game 对象 (java.util.ConcurrentModificationException)。因为这个绘图过程每秒发生大约 60 次(因为动画)问题经常出现(每 2 秒)。这是玩家 ArrayList 的代码:
抽签球员:
for (Player p : oPlayer) {
if (p != null) {
int x = (int) ((width / 2) + (p.x - getPlayerX()) * BLOCK_SIZE);
int y = (int) ((height / 2) + (p.y - getPlayerY()) * BLOCK_SIZE);
g.drawImage(onlinePlayer, x, y, BLOCK_SIZE, BLOCK_SIZE, null);
FontMetrics fm = g.getFontMetrics();
g.setColor(Color.DARK_GRAY);
g.drawString(p.getName(), x + (BLOCK_SIZE / 2) - (fm.stringWidth(p.getName()) / 2), y - 5);
}
}
在网络线程中编辑信息:
case "ADP": //add Player
Game.oPlayer.add(new Player(message, id));
sendX();
sendY();
break;
case "SPX": // set X
for (Player p : Game.oPlayer) {
if (p.getId() == id) {
p.setX(Short.parseShort(message));
break;
}
}
break;
case "SPY": // set Y
for (Player p : Game.oPlayer) {
if (p.getId() == id) {
p.setY(Short.parseShort(message));
break;
}
}
break;
case "PDI": // remove Player
for (Player p : Game.oPlayer) {
if (p.getId() == id) {
Game.oPlayer.remove(p);
break;
}
}
break;
提前谢谢你:)
如果列表在一个线程和另一个线程中迭代或修改,您将得到 ConcurrentModficationException
。在一般的用户界面应用程序中,修改模型数据仅限于单个线程,通常是用户界面线程,例如 Swing 的事件分发线程,或者 JavaFX 中的平台线程。
顺便说一句,对于 JavaFX,存在 game library 为游戏开发提供开箱即用的技术。一般来说,JavaFX 比 AWT 或 Swing 更适合图形密集型工作。
您尝试过使用 Vector 吗?它是集合的一部分并且是同步的。
这里发生的是,2 个线程在同一个列表上工作。
第一个是读取列表 (for (Player p : oPlayer) {
),第二个是修改它 (Game.oPlayer.add(new Player(message, id));
)。这使 oPlayer 列表进入(某种)"inconsistent" 状态。 Java 看到您修改了您正在阅读的内容并抛出此异常让您知道,该内容不符合 kosher。
可以找到有关 ConcurrentModificationExceptions 的更多信息 here
为了澄清,您深入了解了所谓的 Readers-writer problem。您有一个 reader(线程)读取 Game.oPlayer 的数据,还有一个写入器(线程)将数据写入 Game.oPlayer.
解决方案
同步关键字
synchronized
关键字解释here。你会像这样使用它:
private final List<Player> players = ...;
public void addPlayer(Player player) {
synchronized(players) {
players.add(player);
}
}
public void removePlayer(Player player) {
synchronized(players) {
players.remove(player);
}
}
请注意,列表必须是最终的。此外,我使用的是本地属性而不是静态属性。用 Game.oPlayer
删除 players
以获得合适的解决方案。
这只允许 1 个线程访问 players.add()
和 players.remove()
.
锁定
可以找到有关如何使用锁的信息 here。
简单地说,你创建一个这样的块:
try {
lock.lock();
// work ..
} finally {
lock.unlock();
}
这样只有一个线程可以通过说 lock.lock()
访问工作部分。如果任何其他线程使用 lock.lock() 锁定了工作部分并且没有解锁它,则当前线程将等待直到 lock.unlock()
被调用。使用 try-finall 块来确保锁定已解锁,即使您的工作部分正在抛出一个 throwable。
此外,我建议像这样迭代 "copy" 个播放器列表:
List<Player> toIterate;
synchronized(players) {
toIterate = new ArrayList<>(getPlayerList());
}
for(Player player : toIterate) {
// work
}
或者像这样完全同步这部分:
synchronized(players) {
for(Player player : players) {
// work
}
}
第一个为您提供该实例的副本,这基本上意味着它包含与原始列表相同的对象,但它不是同一个列表。它通过让更多线程自己工作 "list" 并完成它们的工作来帮助您,而不管当前的更新如何,因为第二个示例将在以下情况下阻塞:
- 任何线程都想阅读列表。
- 任何线程修改列表。
所以你只需要同步第一个例子中的复制部分。
甚至更进一步(不是您问题的一部分,但仍然可以使它变得更容易)我建议不要使用静态,正如您在 Game.oPlayer.[...]
中所述并查看 Dependency Injection .
您可以修改您的 Game-class 以提供方法 addPlayer(Player player);
、removePlayer(Player player);
和 getPlayerList();
以 Object Oriented 方式编写代码。
使用该设计,您可以轻松修改代码以处理新的并发问题。
我有以下问题:
我编写了一个具有多人游戏功能的 2D 游戏。现在我将其他玩家数据和游戏对象存储在两个 ArrayList 中(世界以其他方式存储)。有时网络线程会发送无法应用的更新,因为游戏会绘制 Players/Game 对象 (java.util.ConcurrentModificationException)。因为这个绘图过程每秒发生大约 60 次(因为动画)问题经常出现(每 2 秒)。这是玩家 ArrayList 的代码:
抽签球员:
for (Player p : oPlayer) {
if (p != null) {
int x = (int) ((width / 2) + (p.x - getPlayerX()) * BLOCK_SIZE);
int y = (int) ((height / 2) + (p.y - getPlayerY()) * BLOCK_SIZE);
g.drawImage(onlinePlayer, x, y, BLOCK_SIZE, BLOCK_SIZE, null);
FontMetrics fm = g.getFontMetrics();
g.setColor(Color.DARK_GRAY);
g.drawString(p.getName(), x + (BLOCK_SIZE / 2) - (fm.stringWidth(p.getName()) / 2), y - 5);
}
}
在网络线程中编辑信息:
case "ADP": //add Player
Game.oPlayer.add(new Player(message, id));
sendX();
sendY();
break;
case "SPX": // set X
for (Player p : Game.oPlayer) {
if (p.getId() == id) {
p.setX(Short.parseShort(message));
break;
}
}
break;
case "SPY": // set Y
for (Player p : Game.oPlayer) {
if (p.getId() == id) {
p.setY(Short.parseShort(message));
break;
}
}
break;
case "PDI": // remove Player
for (Player p : Game.oPlayer) {
if (p.getId() == id) {
Game.oPlayer.remove(p);
break;
}
}
break;
提前谢谢你:)
如果列表在一个线程和另一个线程中迭代或修改,您将得到 ConcurrentModficationException
。在一般的用户界面应用程序中,修改模型数据仅限于单个线程,通常是用户界面线程,例如 Swing 的事件分发线程,或者 JavaFX 中的平台线程。
顺便说一句,对于 JavaFX,存在 game library 为游戏开发提供开箱即用的技术。一般来说,JavaFX 比 AWT 或 Swing 更适合图形密集型工作。
您尝试过使用 Vector 吗?它是集合的一部分并且是同步的。
这里发生的是,2 个线程在同一个列表上工作。
第一个是读取列表 (for (Player p : oPlayer) {
),第二个是修改它 (Game.oPlayer.add(new Player(message, id));
)。这使 oPlayer 列表进入(某种)"inconsistent" 状态。 Java 看到您修改了您正在阅读的内容并抛出此异常让您知道,该内容不符合 kosher。
可以找到有关 ConcurrentModificationExceptions 的更多信息 here
为了澄清,您深入了解了所谓的 Readers-writer problem。您有一个 reader(线程)读取 Game.oPlayer 的数据,还有一个写入器(线程)将数据写入 Game.oPlayer.
解决方案
同步关键字
synchronized
关键字解释here。你会像这样使用它:
private final List<Player> players = ...;
public void addPlayer(Player player) {
synchronized(players) {
players.add(player);
}
}
public void removePlayer(Player player) {
synchronized(players) {
players.remove(player);
}
}
请注意,列表必须是最终的。此外,我使用的是本地属性而不是静态属性。用 Game.oPlayer
删除 players
以获得合适的解决方案。
这只允许 1 个线程访问 players.add()
和 players.remove()
.
锁定
可以找到有关如何使用锁的信息 here。
简单地说,你创建一个这样的块:
try {
lock.lock();
// work ..
} finally {
lock.unlock();
}
这样只有一个线程可以通过说 lock.lock()
访问工作部分。如果任何其他线程使用 lock.lock() 锁定了工作部分并且没有解锁它,则当前线程将等待直到 lock.unlock()
被调用。使用 try-finall 块来确保锁定已解锁,即使您的工作部分正在抛出一个 throwable。
此外,我建议像这样迭代 "copy" 个播放器列表:
List<Player> toIterate;
synchronized(players) {
toIterate = new ArrayList<>(getPlayerList());
}
for(Player player : toIterate) {
// work
}
或者像这样完全同步这部分:
synchronized(players) {
for(Player player : players) {
// work
}
}
第一个为您提供该实例的副本,这基本上意味着它包含与原始列表相同的对象,但它不是同一个列表。它通过让更多线程自己工作 "list" 并完成它们的工作来帮助您,而不管当前的更新如何,因为第二个示例将在以下情况下阻塞:
- 任何线程都想阅读列表。
- 任何线程修改列表。
所以你只需要同步第一个例子中的复制部分。
甚至更进一步(不是您问题的一部分,但仍然可以使它变得更容易)我建议不要使用静态,正如您在 Game.oPlayer.[...]
中所述并查看 Dependency Injection .
您可以修改您的 Game-class 以提供方法 addPlayer(Player player);
、removePlayer(Player player);
和 getPlayerList();
以 Object Oriented 方式编写代码。
使用该设计,您可以轻松修改代码以处理新的并发问题。