我应该为此动画使用 SwingWorker、线程还是递归更新?
正如我在之前的问题中所说,我对 Java 还是新手。 我正在用 Java 开发一个基于图块的游戏。 运动是在网格上进行的。 我正处于实施角色移动的阶段。
我的计划是使用我在这个问题中粘贴的代码。 然而,这不起作用,我被告知应该使用 SwingWorker。
作为一个解决问题的人,我这一天一直在思考这个问题,我有了一个想法。 我可以通过做一些不同的事情来达到相同的效果。
我提到我有一个计时器,每 20 毫秒关闭一次,调用重绘。
我的想法是,我可以在计时器事件中的重绘之前进行一个方法调用,这会将所有人员移动 1 个单位,使其距离目的地(下一个方块/图块)更近。 这样的事情有用吗? 到后来的游戏,可能会有100人左右。 20 毫秒的时间是否足以循环所有人员并将他们移动到离目的地更近的一个单位? 20ms的时间是不是太短了? 难道我根本就没有任何意义吗?
欢迎您的意见/答案/想法:)
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.
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.
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.
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?
Your opinions / answers / thoughts are welcome :)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
好的,首先回答20毫秒的问题。 你可以在 20 毫秒内完成相当多的游戏逻辑。 但我想说更新显示太频繁了。 您还应该记住,操作系统通常会按 10-20 毫秒时间片的顺序分配 CPU——换句话说,任何时刻的另一个进程都可以轻松地将您的进程延迟大约这个时间。 例如,看看我关于 Thread.sleep() 行为的文章< /a>-- 从图中注意到,由于系统适度繁忙,操作系统无法满足我们要求的睡眠时间。 如果您需要 100 毫秒的帧之间的平均睡眠时间,那么这里和那里 20 毫秒左右的抖动也不会太糟糕。 但是,当您要求 20 毫秒时,20 毫秒的抖动可能会更明显......
所以我可能会以每秒 10 帧(100 毫秒暂停)开始,看看效果如何。
就代码而言,如果您的游戏逻辑每次tick花费或多或少相同的时间,那么我会在每个tick上从logic-repaint-sleep开始。 请记住,您需要同步正在绘制的内容,因此在游戏逻辑线程中,理想情况下您需要避免持有锁太久。
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...
So I'd probably start at 10 frames per second (100ms pauses) and see how that looks.
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.
我几乎同意 Bill 所说的一切,尤其是关于 Swing 神秘的部分(尽管并不比其他图形环境神秘得多)。
仅供参考,30 fps(这是大多数隔行扫描屏幕产生的速度)是每 33 毫秒 1 帧。 60 fps 大约是人类可以感知的最高速度(1 帧/16 毫秒),大多数 LCD 显示器以 60 或 75 Hz 刷新,这绝对是您实际可以生产的最快速度。 20 毫秒/帧是 50 fps 的帧速率,这也与欧洲地区的电频率一致,并且刚好被人眼注意到。
我建议反对的主要做法是在紧密的 while 循环中完成所有操作。 这将导致您的游戏速度高度依赖于您所玩的系统。 在速度更快的系统上,即使在较旧的系统上可以合理地播放,您也可能会比玩家的反应速度更快地出现帧喷出(或者在破旧的机器上会遇到相反的问题)。 使用计时器可以使您的速率更加一致,但您必须进行一些防御性检查,以防错过帧截止时间。 这意味着您的计时器需要知道计算何时完成,因此如果另一帧经过但尚未完成,那么它将跳过下一帧。 更好的是,您可以记录计算之间实际经过的时间,并相应地调整角色的实际运动。
另一个警告:虽然绘图必须在 AWT 线程上完成并进行计算(以保持程序响应),但状态也需要在 AWT 线程上更新。 如果您让执行计算的线程更新游戏状态,那么 AWT 线程将在重绘过程中看到这一点,从而导致称为撕裂的效果。 因此,您可能需要制作状态的副本以发布到 AWT 线程。 这就是 SwingWorker 的用途,您可能会将其与 Timer 结合使用。
令人惊讶的是,与我在您的其他问题上发布的内容相比,我所说的大部分内容实际上都是新的。
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).
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.
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.
Amazingly, most of what I said is actually new when compared to what I posted on your other question.
这个秋千区域可能是爪哇岛最“黑魔法”的区域。 这可能很棘手,并且有很多方法。
最重要的规则是永远不要修改屏幕,除非您位于 AWT 线程中,但除了修改屏幕之外,不要使用 AWT 线程。
由于每个摆动“回调”(如按钮侦听器)都会进入 AWT 线程,因此您通常不必考虑这一点,但对于活动渲染情况,您必须密切注意。
通常,在像这样的大型系统中,您会立即执行整个计算(移动所有数据),然后重新绘制需要一步绘制的所有内容。
因此,在大多数情况下,您在连续线程中完全在绘制系统之外进行计算,然后在顶级组件中调用重绘函数。 Repaint 应该将绘制事件传递给 AWT 线程上的所有组件,因此这应该不是问题。
然后每个组件应该查看其状态(已更新)并使用该信息来绘制自身。
这就是应该如何完成的基本操作,但它可能会很慢,并且有很多技巧可以加快速度。 您可能想找一本涉及摇摆编程和游戏的书。
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.
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.
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.
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.
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.