将命令行驱动的回合制 rpg 改编为 swing 的事件驱动范式
Adapting a command line-driven turn-based rpg to swing's event-driven paradigm
背景:
在过去的几个月里,我一直在 Java 空闲时间开发一款回合制角色扮演游戏。我只进行了大约一年的编程,所以我的专业知识主要限于命令行应用程序和我在初级 CS 中学到的面向对象编程原则 classes.
开发进展顺利,直到我开始调整游戏,从从命令行获取输入到使用我开始使用 Swing 构建的定制 UI。这主要是因为基于命令行的输入让我可以停止我正在做的一切并等待来自扫描仪或类似东西的输入,但从我在这里阅读的线程来看 (Waiting for multiple button inputs in Java Swing, Waiting till button is pressed), Swing 的事件驱动性质实际上不允许您在不中断 EDT 和冻结 GUI 的情况下等待任何事情,这对我的目的来说是一件坏事。
问题:
虽然 Swing 对我来说很新奇,但我已经在主菜单上使用它,看看它如何很容易地应用于数据相对集中在几个对象之间的游戏,这些对象利用用户的简单命令来移动或攻击或你有什么。我的问题是我的角色扮演游戏在战斗场景中有 2 到 16 个活动角色,可以由用户控制或 AI 控制,可以使用任意数量的不同命令(包括 16 种不同的技能)并且玩家角色必须是能够访问它们之间的公共库存。可以这么说,我的游戏在很大程度上 菜单驱动 。例如,我将 post 从我 运行 在战斗中循环调用的方法:
//initializes a turn; written as a function to cut down on duplicate code
public battleData initializeTurn(battleData toInitialize){
if(toInitialize.initializeSkill()) {
if(toInitialize.initializeTarget(playerParty, playerMinions, 1) == 0)
return initializeTurn(toInitialize); //recursive call if the user cancelled
} //the skill they selected.
else {
if(toInitialize.initializeTarget(enemyParty, enemyMinions, -1) == 0)
return initializeTurn(toInitialize); //see above
}
return toInitialize;
}
该方法调用gameCharacter class 中编写的两个函数,initializeTarget 和initializeSkill 从两个传递的目标数组中选择一个目标,并从角色的两个技能列表中选择一个技能。派生自 gameCharacter class 的是 playerCharacter 和 Monster classes,它们分别根据用户输入和基本 AI 计算方法的 return 值。由于我有很多关于技能和项目的数据存储在不同的 classes 中,并且用户输入的行为会有所不同,具体取决于哪个角色正在输入,无论他们是选择目标还是选择技能,以及他们在哪里都在技能选择菜单的范围内,我发现很难制定一个算法让我让整个事情成为事件驱动的,因为事件可能意味着很多不同的事情,具体取决于正在查看菜单的哪个部分目前
解决方案:
我读过诸如用于处理后台任务的 swingworker、用于将输入馈送到不同位置的状态以及用于定期更新图形组件的计时器之类的内容,但我对 Swing 还不够熟悉,无法真正了解知道此时最好的路线是什么。我更想寻找一个通用的算法来实现,而不是一个神奇的解决方案,它可以让我在不更改大部分代码的情况下解决我的问题。我非常愿意重塑我目前所写的很多内容,所以不要害怕告诉我,如果您认为我将不得不重做很多,这会使程序变得更好。
谢谢你的包容;如果您需要更多上下文,我很乐意解释或 post 更多代码。一开始我会 post 编辑更多内容,但我提到的大多数菜单都是 1-200 行,解释起来比实际复制更容易。
首先,您可以将 GUI 视为在更新屏幕时不断什么都不做 的东西,而不是命令行 在等待输入时没有做任何事情。由于这是一款回合制游戏,因此必须对何时认为回合已准备好进行计算提出要求。通常这发生在用户完成输入时。在您的情况下,根据我收集到的信息,输入本质上是对不同游戏角色的命令。因此,每次用户命令做某事(单击游戏角色的命令按钮)时,都必须进行类似于 isReadyToCompute()
的检查。当方法 returns 为真时你可以调用 computeTurn()
。下面是代码中的一些粗略表示。您需要添加一些与游戏玩法相关的检查,但它应该让您知道从哪里开始
skillButton1.addActionListener(e -> {
// change cursor to "select" type cursor
skill1 = true;
});
public class Mouse implements MouseListener {
public void mouseClicked(MouseEvent e) {
// get mouse x y
// find target game character at x y or if none then return
// remember the character as target
// of skill1 of selected character
// and count this as order complete for selected character
skill1 = false;
// change cursor to normal
if (isReadyToCompute()) computeTurn();
}
}
总体思路是您不需要循环,因为 GUI 已经有自己的内部循环并且它在 EDT 上运行。 EDT 捕获并分发由动作和鼠标侦听器处理的事件。使用命令行时,您等待侦听输入事件的扫描器,类似地,在 GUI 中,您等待上述侦听器,后者依次侦听按钮按下或鼠标事件。更多关于如何编写监听器的信息请参考Oracle官方教程:
https://docs.oracle.com/javase/tutorial/uiswing/events/actionlistener.html
https://docs.oracle.com/javase/tutorial/uiswing/events/mouselistener.html
如果您觉得一切都一样,我建议您切换到 JavaFX,它是一个更丰富的图形框架,更好的游戏选择,并且可以说是 Swing 的后继者。 http://docs.oracle.com/javase/8/javafx/get-started-tutorial/jfx-overview.htm#JFXST784
背景:
在过去的几个月里,我一直在 Java 空闲时间开发一款回合制角色扮演游戏。我只进行了大约一年的编程,所以我的专业知识主要限于命令行应用程序和我在初级 CS 中学到的面向对象编程原则 classes.
开发进展顺利,直到我开始调整游戏,从从命令行获取输入到使用我开始使用 Swing 构建的定制 UI。这主要是因为基于命令行的输入让我可以停止我正在做的一切并等待来自扫描仪或类似东西的输入,但从我在这里阅读的线程来看 (Waiting for multiple button inputs in Java Swing, Waiting till button is pressed), Swing 的事件驱动性质实际上不允许您在不中断 EDT 和冻结 GUI 的情况下等待任何事情,这对我的目的来说是一件坏事。
问题:
虽然 Swing 对我来说很新奇,但我已经在主菜单上使用它,看看它如何很容易地应用于数据相对集中在几个对象之间的游戏,这些对象利用用户的简单命令来移动或攻击或你有什么。我的问题是我的角色扮演游戏在战斗场景中有 2 到 16 个活动角色,可以由用户控制或 AI 控制,可以使用任意数量的不同命令(包括 16 种不同的技能)并且玩家角色必须是能够访问它们之间的公共库存。可以这么说,我的游戏在很大程度上 菜单驱动 。例如,我将 post 从我 运行 在战斗中循环调用的方法:
//initializes a turn; written as a function to cut down on duplicate code
public battleData initializeTurn(battleData toInitialize){
if(toInitialize.initializeSkill()) {
if(toInitialize.initializeTarget(playerParty, playerMinions, 1) == 0)
return initializeTurn(toInitialize); //recursive call if the user cancelled
} //the skill they selected.
else {
if(toInitialize.initializeTarget(enemyParty, enemyMinions, -1) == 0)
return initializeTurn(toInitialize); //see above
}
return toInitialize;
}
该方法调用gameCharacter class 中编写的两个函数,initializeTarget 和initializeSkill 从两个传递的目标数组中选择一个目标,并从角色的两个技能列表中选择一个技能。派生自 gameCharacter class 的是 playerCharacter 和 Monster classes,它们分别根据用户输入和基本 AI 计算方法的 return 值。由于我有很多关于技能和项目的数据存储在不同的 classes 中,并且用户输入的行为会有所不同,具体取决于哪个角色正在输入,无论他们是选择目标还是选择技能,以及他们在哪里都在技能选择菜单的范围内,我发现很难制定一个算法让我让整个事情成为事件驱动的,因为事件可能意味着很多不同的事情,具体取决于正在查看菜单的哪个部分目前
解决方案:
我读过诸如用于处理后台任务的 swingworker、用于将输入馈送到不同位置的状态以及用于定期更新图形组件的计时器之类的内容,但我对 Swing 还不够熟悉,无法真正了解知道此时最好的路线是什么。我更想寻找一个通用的算法来实现,而不是一个神奇的解决方案,它可以让我在不更改大部分代码的情况下解决我的问题。我非常愿意重塑我目前所写的很多内容,所以不要害怕告诉我,如果您认为我将不得不重做很多,这会使程序变得更好。
谢谢你的包容;如果您需要更多上下文,我很乐意解释或 post 更多代码。一开始我会 post 编辑更多内容,但我提到的大多数菜单都是 1-200 行,解释起来比实际复制更容易。
首先,您可以将 GUI 视为在更新屏幕时不断什么都不做 的东西,而不是命令行 在等待输入时没有做任何事情。由于这是一款回合制游戏,因此必须对何时认为回合已准备好进行计算提出要求。通常这发生在用户完成输入时。在您的情况下,根据我收集到的信息,输入本质上是对不同游戏角色的命令。因此,每次用户命令做某事(单击游戏角色的命令按钮)时,都必须进行类似于 isReadyToCompute()
的检查。当方法 returns 为真时你可以调用 computeTurn()
。下面是代码中的一些粗略表示。您需要添加一些与游戏玩法相关的检查,但它应该让您知道从哪里开始
skillButton1.addActionListener(e -> {
// change cursor to "select" type cursor
skill1 = true;
});
public class Mouse implements MouseListener {
public void mouseClicked(MouseEvent e) {
// get mouse x y
// find target game character at x y or if none then return
// remember the character as target
// of skill1 of selected character
// and count this as order complete for selected character
skill1 = false;
// change cursor to normal
if (isReadyToCompute()) computeTurn();
}
}
总体思路是您不需要循环,因为 GUI 已经有自己的内部循环并且它在 EDT 上运行。 EDT 捕获并分发由动作和鼠标侦听器处理的事件。使用命令行时,您等待侦听输入事件的扫描器,类似地,在 GUI 中,您等待上述侦听器,后者依次侦听按钮按下或鼠标事件。更多关于如何编写监听器的信息请参考Oracle官方教程:
https://docs.oracle.com/javase/tutorial/uiswing/events/actionlistener.html
https://docs.oracle.com/javase/tutorial/uiswing/events/mouselistener.html
如果您觉得一切都一样,我建议您切换到 JavaFX,它是一个更丰富的图形框架,更好的游戏选择,并且可以说是 Swing 的后继者。 http://docs.oracle.com/javase/8/javafx/get-started-tutorial/jfx-overview.htm#JFXST784