As I have said in my previous questions, I'm still new to Java. I'm developing a tile-based game in Java. The movement is on a grid. I'm at the stage where I'm implementing character movement.
正如我在之前的问题中所说,我还是Java新手。我正在用Java开发基于磁贴的游戏。运动在网格上。我正处于实施角色运动的阶段。
My plan was to use the code which I pasted in this question. However, this didn't work, and I was told I should use SwingWorker.
我的计划是使用我在这个问题中粘贴的代码。但是,这不起作用,我被告知我应该使用SwingWorker。
Being a problem solver, I've been thinking about this over the day, and I had a thought. I could achieve the same effect by doing things a bit differently.
作为一个解决问题的人,我一整天都在考虑这个问题,我有一个想法。通过做一些不同的事情,我可以达到同样的效果。
I mentioned that I had a timer that went off every 20 milliseconds, calling a repaint.
我提到我有一个定时器,每20毫秒就会关闭一次,重新调用一次。
My idea was that I could have a method call before the repaint in the event for the timer, which would move all of the people 1 unit nearer to their destination (next square/tile). Would that sort of thing work? Later on in the game, there may be 100 or so people. Would 20ms be enough time to loop through all the people and move them one unit closer to their destination? Is 20ms too short a time? Am I just making no sense at all?
我的想法是,我可以在重复事件之前为定时器进行方法调用,这将使所有人1个单元更靠近他们的目的地(下一个方块/瓦片)。这种事情有用吗?在游戏的后期,可能有100个左右的人。 20ms是否有足够的时间来遍历所有人并将他们移动到离目的地更近的一个单位? 20ms太短了吗?我根本没有意义吗?
Your opinions / answers / thoughts are welcome :)
欢迎您的意见/答案/想法:)
3 个解决方案
#1
OK, first to answer the 20 millis question. You can get quite a lot of game logic done in 20ms. But I'd say it's too frequently to be updating the display. You should also bear in mind that the OS generally dishes out CPU in the order of 10-20 ms timeslices-- in other words, another process at any moment could easily delay your process by about that amount of time. Have a look, for example, at my article on the behaviour of Thread.sleep()-- notice from the graph that as the system is moderately busy, the OS can't honour the sleep time we asked for. If you need an average sleep between frames of 100ms, then 20ms or so jitter here and there won't be too bad. But 20ms jitter when you asked for 20ms will probably be more noticeable...
好的,首先回答20毫米问题。您可以在20ms内完成相当多的游戏逻辑。但我会说更新显示器太频繁了。您还应该记住,操作系统通常会以10-20毫秒的时间顺序排出CPU - 换句话说,任何时候的另一个进程都可能很容易地将您的进程延迟大约一段时间。看看,例如,在我关于Thread.sleep()行为的文章中 - 从图中注意到,由于系统中等繁忙,操作系统无法满足我们要求的睡眠时间。如果你需要在100ms的帧之间平均睡眠,那么在这里20ms左右抖动并且不会太糟糕。但是当你要求20ms时,20ms的抖动可能会更明显......
So I'd probably start at 10 frames per second (100ms pauses) and see how that looks.
所以我可能以每秒10帧(100毫秒暂停)开始,看看它看起来如何。
As far as the code is concerned, if your game logic will take more or less the same amount of time each tick, then I'd start with logic-repaint-sleep on each tick. Remember you need to synchronize on the things you're painting, so in your game logic thread, you ideally need to avoid holding on to locks for too long.
就代码而言,如果你的游戏逻辑每次打勾所需的时间或多或少相同,那么我将从每次打勾的逻辑重绘 - 睡眠开始。记住你需要同步你正在绘制的东西,所以在你的游戏逻辑线程中,理想情况下你需要避免长时间保持锁定。
#2
I agree with pretty much everything Bill said, especially the part about Swing being enigmatic (although not really much more so than other graphics environments).
我同意比尔所说的几乎所有内容,特别是关于Swing神秘的部分(尽管不比其他图形环境真的那么多)。
To give a few references, 30 fps (which is what most interlaced screens produce at) is 1 frame every 33 ms. 60 fps is about the highest that humans can perceive (1 frame / 16 ms), and most LCD monitors refresh at 60 or 75 Hz, which would be the absolute fastest you could actually produce. 20 ms/frame is a frame rate of 50 fps, which also coincides with the electric frequency in European territories and is just noticeable by the human eye.
为了给出一些参考,30 fps(这是大多数隔行扫描屏幕产生的)是每33毫秒1帧。 60 fps是人类可以感知的最高频率(1帧/ 16 ms),大多数LCD监视器以60或75 Hz的频率刷新,这将是您实际生产的绝对最快速度。 20毫秒/帧是50 fps的帧速率,这也与欧洲地区的电频率一致,并且只是人眼可以注意到。
The main thing I would recommend against is to just do everything in a tight while loop. This will cause your game speed to be highly dependent on the system on which you play. On a faster system, you may get frame spewing faster than the player can react, even if it plays reasonably on an older one (or you'll have the converse problem on a decrepit machine). Using a timer will allow you to be more consistent in the rate, but you have to make some defensive checks in case you miss a frame deadline. This means that your timer needs to know when the calculations are finished so if another frame ticks by and it hasn't finished, then it will skip the next frame. Even better, you log how long actually passes between calculations and adjust the actual movement traveled by your characters accordingly.
我建议反对的主要是在紧张的循环中做所有事情。这将导致您的游戏速度高度依赖于您所玩的系统。在一个更快的系统上,你可能会得到比玩家反应更快的帧喷射,即使它在较旧的系统上合理地运行(或者你会在破旧的机器上有相反的问题)。使用计时器可以让您在速率上更加一致,但是如果错过了帧截止日期,您必须进行一些防御性检查。这意味着您的计时器需要知道计算何时完成,因此如果另一帧正在计时并且尚未完成,则它将跳过下一帧。更好的是,您可以记录计算之间实际传递的时间长度,并相应地调整角色的实际移动距离。
Another warning: while the drawing must be done on the AWT thread and the calculations off of it (to keep your program responsive), the state also needs to be updated on the AWT thread. If you let the thread doing the calculations update the game state, then the AWT thread will see this in the middle of a repaint, leading to an effect called tearing. Thus, you may need to make a copy of the state to post to the AWT thread. This is what a SwingWorker is for, which you'll probably use in conjunction with a Timer.
另一个警告:虽然绘图必须在AWT线程上完成并且计算结束(为了保持程序响应),状态也需要在AWT线程上更新。如果让执行计算的线程更新游戏状态,那么AWT线程将在重绘过程中看到这个,导致称为撕裂的效果。因此,您可能需要复制状态以发布到AWT线程。这就是SwingWorker的用途,您可能会将其与Timer结合使用。
Amazingly, most of what I said is actually new when compared to what I posted on your other question.
令人惊讶的是,与我在你的另一个问题上发布的内容相比,我说的大多数内容实际上都是新的。
#3
This area of swing has probably the most "Black Magic" of anything in Java. It can be tricky and there are a lot of approaches.
这个摆动区域可能是Java中最具“黑魔法”的东西。它可能很棘手,而且有很多方法。
The most important rule is to never modify the screen unless you are in the AWT thread, but don't use the AWT thread for much more than modifying the screen.
最重要的规则是永远不要修改屏幕,除非你在AWT线程中,但不要使用AWT线程比修改屏幕更多。
Since every swing "Callback" (like button listener) comes in on the AWT thread, you normally don't have to think about this, but for an active rendering situation you have to pay close attention.
由于每个摇摆“回调”(如按钮监听器)都在AWT线程上,因此您通常不必考虑这一点,但对于活动渲染情况,您必须密切关注。
Generally in a larger system like this, you do an entire calculation at once (move all your data), then you redraw everything that needs to be drawn in one step.
通常在像这样的较大系统中,您可以一次完成整个计算(移动所有数据),然后重新绘制需要一步完成的所有内容。
So for the most part, you calculate outside your paint system altogether in a continual thread, then you'd call a repaint function at a top-level component. Repaint should deliver paint events to all your components on the AWT thread, so that shouldn't be a problem.
因此,在大多数情况下,您在连续线程中完全在绘制系统之外进行计算,然后在顶层组件中调用重绘函数。重绘应该将绘制事件传递给AWT线程上的所有组件,因此这不应该是一个问题。
Then each component should look at its' state (which is already updated) and use that info to draw itself.
然后每个组件应该查看其状态(已经更新)并使用该信息绘制自己。
This is how it should be done, the basics, but it can be slow and there are a lot of tricks to speed it up. You might want to look for a book that involves swing programming and games.
这是应该如何完成的,基础知识,但它可能很慢,并且有很多技巧可以加快速度。你可能想找一本涉及秋千编程和游戏的书。
#1
OK, first to answer the 20 millis question. You can get quite a lot of game logic done in 20ms. But I'd say it's too frequently to be updating the display. You should also bear in mind that the OS generally dishes out CPU in the order of 10-20 ms timeslices-- in other words, another process at any moment could easily delay your process by about that amount of time. Have a look, for example, at my article on the behaviour of Thread.sleep()-- notice from the graph that as the system is moderately busy, the OS can't honour the sleep time we asked for. If you need an average sleep between frames of 100ms, then 20ms or so jitter here and there won't be too bad. But 20ms jitter when you asked for 20ms will probably be more noticeable...
好的,首先回答20毫米问题。您可以在20ms内完成相当多的游戏逻辑。但我会说更新显示器太频繁了。您还应该记住,操作系统通常会以10-20毫秒的时间顺序排出CPU - 换句话说,任何时候的另一个进程都可能很容易地将您的进程延迟大约一段时间。看看,例如,在我关于Thread.sleep()行为的文章中 - 从图中注意到,由于系统中等繁忙,操作系统无法满足我们要求的睡眠时间。如果你需要在100ms的帧之间平均睡眠,那么在这里20ms左右抖动并且不会太糟糕。但是当你要求20ms时,20ms的抖动可能会更明显......
So I'd probably start at 10 frames per second (100ms pauses) and see how that looks.
所以我可能以每秒10帧(100毫秒暂停)开始,看看它看起来如何。
As far as the code is concerned, if your game logic will take more or less the same amount of time each tick, then I'd start with logic-repaint-sleep on each tick. Remember you need to synchronize on the things you're painting, so in your game logic thread, you ideally need to avoid holding on to locks for too long.
就代码而言,如果你的游戏逻辑每次打勾所需的时间或多或少相同,那么我将从每次打勾的逻辑重绘 - 睡眠开始。记住你需要同步你正在绘制的东西,所以在你的游戏逻辑线程中,理想情况下你需要避免长时间保持锁定。
#2
I agree with pretty much everything Bill said, especially the part about Swing being enigmatic (although not really much more so than other graphics environments).
我同意比尔所说的几乎所有内容,特别是关于Swing神秘的部分(尽管不比其他图形环境真的那么多)。
To give a few references, 30 fps (which is what most interlaced screens produce at) is 1 frame every 33 ms. 60 fps is about the highest that humans can perceive (1 frame / 16 ms), and most LCD monitors refresh at 60 or 75 Hz, which would be the absolute fastest you could actually produce. 20 ms/frame is a frame rate of 50 fps, which also coincides with the electric frequency in European territories and is just noticeable by the human eye.
为了给出一些参考,30 fps(这是大多数隔行扫描屏幕产生的)是每33毫秒1帧。 60 fps是人类可以感知的最高频率(1帧/ 16 ms),大多数LCD监视器以60或75 Hz的频率刷新,这将是您实际生产的绝对最快速度。 20毫秒/帧是50 fps的帧速率,这也与欧洲地区的电频率一致,并且只是人眼可以注意到。
The main thing I would recommend against is to just do everything in a tight while loop. This will cause your game speed to be highly dependent on the system on which you play. On a faster system, you may get frame spewing faster than the player can react, even if it plays reasonably on an older one (or you'll have the converse problem on a decrepit machine). Using a timer will allow you to be more consistent in the rate, but you have to make some defensive checks in case you miss a frame deadline. This means that your timer needs to know when the calculations are finished so if another frame ticks by and it hasn't finished, then it will skip the next frame. Even better, you log how long actually passes between calculations and adjust the actual movement traveled by your characters accordingly.
我建议反对的主要是在紧张的循环中做所有事情。这将导致您的游戏速度高度依赖于您所玩的系统。在一个更快的系统上,你可能会得到比玩家反应更快的帧喷射,即使它在较旧的系统上合理地运行(或者你会在破旧的机器上有相反的问题)。使用计时器可以让您在速率上更加一致,但是如果错过了帧截止日期,您必须进行一些防御性检查。这意味着您的计时器需要知道计算何时完成,因此如果另一帧正在计时并且尚未完成,则它将跳过下一帧。更好的是,您可以记录计算之间实际传递的时间长度,并相应地调整角色的实际移动距离。
Another warning: while the drawing must be done on the AWT thread and the calculations off of it (to keep your program responsive), the state also needs to be updated on the AWT thread. If you let the thread doing the calculations update the game state, then the AWT thread will see this in the middle of a repaint, leading to an effect called tearing. Thus, you may need to make a copy of the state to post to the AWT thread. This is what a SwingWorker is for, which you'll probably use in conjunction with a Timer.
另一个警告:虽然绘图必须在AWT线程上完成并且计算结束(为了保持程序响应),状态也需要在AWT线程上更新。如果让执行计算的线程更新游戏状态,那么AWT线程将在重绘过程中看到这个,导致称为撕裂的效果。因此,您可能需要复制状态以发布到AWT线程。这就是SwingWorker的用途,您可能会将其与Timer结合使用。
Amazingly, most of what I said is actually new when compared to what I posted on your other question.
令人惊讶的是,与我在你的另一个问题上发布的内容相比,我说的大多数内容实际上都是新的。
#3
This area of swing has probably the most "Black Magic" of anything in Java. It can be tricky and there are a lot of approaches.
这个摆动区域可能是Java中最具“黑魔法”的东西。它可能很棘手,而且有很多方法。
The most important rule is to never modify the screen unless you are in the AWT thread, but don't use the AWT thread for much more than modifying the screen.
最重要的规则是永远不要修改屏幕,除非你在AWT线程中,但不要使用AWT线程比修改屏幕更多。
Since every swing "Callback" (like button listener) comes in on the AWT thread, you normally don't have to think about this, but for an active rendering situation you have to pay close attention.
由于每个摇摆“回调”(如按钮监听器)都在AWT线程上,因此您通常不必考虑这一点,但对于活动渲染情况,您必须密切关注。
Generally in a larger system like this, you do an entire calculation at once (move all your data), then you redraw everything that needs to be drawn in one step.
通常在像这样的较大系统中,您可以一次完成整个计算(移动所有数据),然后重新绘制需要一步完成的所有内容。
So for the most part, you calculate outside your paint system altogether in a continual thread, then you'd call a repaint function at a top-level component. Repaint should deliver paint events to all your components on the AWT thread, so that shouldn't be a problem.
因此,在大多数情况下,您在连续线程中完全在绘制系统之外进行计算,然后在顶层组件中调用重绘函数。重绘应该将绘制事件传递给AWT线程上的所有组件,因此这不应该是一个问题。
Then each component should look at its' state (which is already updated) and use that info to draw itself.
然后每个组件应该查看其状态(已经更新)并使用该信息绘制自己。
This is how it should be done, the basics, but it can be slow and there are a lot of tricks to speed it up. You might want to look for a book that involves swing programming and games.
这是应该如何完成的,基础知识,但它可能很慢,并且有很多技巧可以加快速度。你可能想找一本涉及秋千编程和游戏的书。