帮助游戏开发。渲染循环?

发布于 2024-09-01 07:56:16 字数 367 浏览 3 评论 0原文

我正在开发一个简单的游戏,这是我的第一个游戏项目。

我发现的大多数示例都有一个渲染循环,其中所有的游戏逻辑也是如此,但我只是不喜欢这样。 假设我有一个 X=0 的球,X=10 的墙,在一台慢速机器中,第一个循环将球放置在 X=7 中,在第二个循环中,它将球放置在 X=14 中。这只会让游戏崩溃!

这种“渲染循环”是制作游戏的正确方法吗?我应该编写代码来检查每一帧中的类似情况吗?例如,新帧 X=14,最后一帧 X=7,所以我应该检查是否有从 X=7 到 X=14 的任何内容?

我在想我应该为游戏逻辑设置一个单独的线程,并且在渲染循环中,我应该“拍摄当前游戏逻辑的快照”并显示它,不是吗?

经验丰富的游戏开发人员如何解决这个问题?

谢谢!

I'm working on a simple game, this is my first game project.

Most of the samples I find have a Render Loop where all the game logic is made too and I just don't like this.
Let's say I have a ball with X=0, and a wall in X=10 and in a slow machine, the first loop places the ball in X=7 and in a second loop, it places the ball in X=14. It would just crash the game!

Is this "render loop" the right way to make games? Should I write code to check for things like this in every frame? Example, new frame X=14, last frame have X=7, so I should check if there's anything from X=7 to X=14??

I was thinking that I should have a separated thread for the game logic and in the render loop, I should just "take a snapshot" of the current game logic and display that, no?

How do you guys, experienced game developers work around this?

thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

睫毛上残留的泪 2024-09-08 07:56:16

正如另一个答案所述,你看到的问题被称为“隧道”,这是“子弹穿过纸”的问题,子弹移动得很快,纸很薄,你怎么知道发生了碰撞?

如果你的世界边界很简单,那就很容易。例如,在俄罗斯方块中,块只允许左右移动,直到它们撞到侧面,并且很容易测试最底部的坐标是否撞到“地面”。这些测试很简单,因为您一次可以执行一个轴,并且与侧面的碰撞与与坑底的碰撞意味着不同的情况。如果您有一个矩形房间,如果移动对象的运动已将其移出房间,则只需通过固定其坐标来“停止”移动对象即可。即,如果房间宽度从 -3 到 +3,并且您的对象的 X 为 5,只需将其更改为 3 即可。

如果你想处理更复杂的世界,那就有点棘手了。您需要阅读“扫掠”几何碰撞。基本上,如果你有一个圆圈,则需要使用胶囊进行碰撞测试,胶囊的形状是通过将圆圈从起点“扫过”到终点而形成的。它就像一个两端各有半圆的矩形。数学非常简单(恕我直言),但要正确计算并真正理解正在发生的事情可能很棘手。不过还是值得的!

编辑:关于线程问题 - 无需使事情复杂化。一根线就好了。跳过更新帧也会变得混乱,并且非常先进,因为您实际上需要弄清楚“未来”,然后对截至该点的所有有趣值进行插值。我自己并不将其称为“渲染”循环,因为渲染循环只是该过程的一部分。

def GameLoop():
   while True:
      ReadInputs()
      FigureOutWhatStuffDoes()
      DrawItAll()

编辑2:这似乎是一个有趣的讨论:http://www.gamedev.net/community/forums/topic.asp?topic_id=482397

As another answer stated, the problem you're seeing is called "tunneling" It's the "bullet through paper" problem, the bullet is moving fast, the paper is thin, how do you know that a collision happened?

It's easy if your world boundaries are simple. E.g. in Tetris, the blocks are only allowed to move left and right until they hit the sides, and it's easy to test if the bottom-most coordinate is hitting the "ground." These tests are simple because you can do one axis at a time, and collisions against the sides means something different than collisions against the bottom of the pit. If you have a rectangular room, just "stop" the moving object if its movement has put it outside the room by clamping its coordinates. I.e. if the room width is from -3 to +3, and your object has an X of 5, just change it to 3 and you're done.

If you want to handle more complicated worlds, it's a bit trickier. You'll want to read up on "swept" geometry collision. Basically, if you have a circle, you need to do collision tests with a capsule instead, the shape that would be made by "sweeping" the circle from its start point to its end point. It'll be like a rectangle with semicircles on either end. The math is surprisingly straight forward (IMHO), but it can be tricky to get it right and to truly understand what's going on. It's worth it though!

Edit: On the thread issue- no need to complicate things. One thread is fine. Skipping update frames can get messy too, and is pretty advanced since you actually need to figure out "the future" and then do interpolation of all interesting values up to that point. I don't call it the "render" loop, myself, as the render loop is just one part of the process.

def GameLoop():
   while True:
      ReadInputs()
      FigureOutWhatStuffDoes()
      DrawItAll()

Edit 2: This seems like an interesting discussion: http://www.gamedev.net/community/forums/topic.asp?topic_id=482397

¢蛋碎的人ぎ生 2024-09-08 07:56:16

如果您为此创建一个单独的线程,您还会创建很多您可能不想处理的复杂性。一根线一圈就很容易处理。

基本上,您想要做的是有一个既执行逻辑又执行渲染的循环,但不一定在每次迭代中都执行。看这段伪代码:

while(true) {
   oldTime = currentTime;
   currentTime = systemTime();
   timeStep = currentTime - oldTime;

   // Only do logic x times / second
   if( currentTime > lastLogicTime + logicRefreshTime ){
      doGameLogic( currentTime - lastLogicTime );
      lastLogicTime = currentTime;
   }

   // Extrapolate all movements using timeStep
   renderGraphics( timeStep );

   wait( screenRefreshTime );
}

void doGameLogic( timeStep ) {
   // Update all objects
   for each( gameObject obj )
     obj.move( timeStep );
}

让所有固体可移动物体都继承类SolidObject。当您调用 SolidObject.move(timeStep) 时,该方法会检查对象在给定 timeStep 内可以移动多远。如果在此点之前有一堵墙,那么该物体应该停止、弹跳并改变方向、死亡或任何你喜欢的东西。


编辑:

如果两个物体移动,您可能需要检查它们是否碰撞以及何处碰撞。许多游戏都不能很好地做到这一点,但您可以这样做:

首先计算每个移动对象的 oldTimecurrentTime 之间的移动线。然后比较两条线,看看两条线是否相交。请注意,您需要考虑对象的大小。交点是物体碰撞的地方。使用这种方法,您可以准确地检测移动物体的碰撞。

If you create a separate thread for this you also create a lot of complexity that you might not want to deal with. It's easy to handle with one thread and one loop.

Basically what you want to do is have a loop that does both logic and rendering, but not necessarily in every iteration. See this pseudo-code:

while(true) {
   oldTime = currentTime;
   currentTime = systemTime();
   timeStep = currentTime - oldTime;

   // Only do logic x times / second
   if( currentTime > lastLogicTime + logicRefreshTime ){
      doGameLogic( currentTime - lastLogicTime );
      lastLogicTime = currentTime;
   }

   // Extrapolate all movements using timeStep
   renderGraphics( timeStep );

   wait( screenRefreshTime );
}

void doGameLogic( timeStep ) {
   // Update all objects
   for each( gameObject obj )
     obj.move( timeStep );
}

Let all solid movable objects inherit the class SolidObject. When you call SolidObject.move(timeStep) that method checks to see how far the object can be moved within the given timeStep. If there is a wall before this point then the object should stop, bounce and change direction, die or whatever you like.


Edit:

If two objects move you might want to check if and where they collide. Lots of games don't do this very well, but here's how you do it:

First calculate the line of movement between the oldTime and the currentTime for every object that moves. Then compare the lines to see if two lines intersect. Note, you need to take the objects' size into account. The intersection point is where the objects collide. Using this method you can accurately detect collisions of moving objects.

偏爱你一生 2024-09-08 07:56:16

可以有一个单独的更新线程绘图线程,但这并不容易!通常您需要进行大量互斥检查以防止多线程访问相同的变量,因此这实际上并不可行(而且您不想处理半更新状态)。为了正确实现,您确实需要有某种形式的最后渲染状态的快照。如果您不介意所涉及的困难,可以在这里找到一个很好的实现:

http://blog.slapware.eu/game-engine/programming/multithreaded-renderloop-part1/

http://blog.slapware.eu/game-engine/programming/multithreaded-renderloop-part2/

不要让反对者阻止您。这是可能的、可行的、有效的。唯一的缺点是它非常难以实现,因此可能不值得您花时间(除非您有一个非常占用 CPU 资源的游戏)。

It is possible to have a separate update-thread and a drawing-thread, but it isn't easy! Usually you'll need to do a lot of mutex checking to prevent multithreaded access to the same variables so this isn't really viable (plus you don't want to handle with half-updated states). For a correct implementation you indeed need to have some form of snapshot of the last render state. If you don't mind the difficulty involved, there is a good implementation that van be found here:

http://blog.slapware.eu/game-engine/programming/multithreaded-renderloop-part1/

http://blog.slapware.eu/game-engine/programming/multithreaded-renderloop-part2/

Don't let naysayers discourage you. It is possible, it is viable and efficient. The only downside is that it is very difficult to implement and therefore probably not worth your time (unless you have a very CPU-heavy game).

清晨说晚安 2024-09-08 07:56:16

不要把它串起来——你会造成比你解决的问题更多的问题。您可以对事物进行线程化并分离逻辑更新和渲染,但要正确执行是很困难的,而且大部分游戏循环本质上都是单线程的。

相反,请考虑使用增量时间来扩展游戏循环,以便逻辑更新在很大程度上独立于机器咀嚼帧的能力。

简而言之,如果你使用增量来缩放物体,无论穿过框架需要多长时间,一个球从房间的一侧移动到另一侧将花费相同的时间来以非常快的速度完成它。电脑而且速度很慢。

例如,如果球在一秒内移动 10 个单位,并且您可以确定自上次更新以来已经过去了 0.1 秒(使用高性能计时器或任何可用的设备),您只需将移动缩放 0.1,球就会移动 1 个单位。

例如,

private const float BallSpeedInMetresPerSecond = 10;

public void Update(float deltaTimeInSeconds)
{
    float adjustedSpeed = deltaTimeInSeconds * BallSpeedInMetresPerSecond;
    // set ball's speed / move it etc. using adjusted speed
}

这并不能完全解决您的问题(如果某个东西非常,无论如何它都会卡在墙上!),但这是一种简单而有效的方法,可以让事情保持可预测性和一致性,直到您陷入更复杂的问题。

如果你做到了这一点,然后想要解决更复杂的问题,正如 dash-tom-bang 所说,请研究扫掠碰撞检测。

Don't thread it -- you'll cause more problems than you'll solve. You can thread things and separate logic updates and rendering, but it's tricky to get right and large portions of game loops are inherently single-threaded.

Instead, look into advancing your game loop using a delta time to scale things so that the logic update is largely independent of the machine's ability to chomp through the frames.

In simplified terms, if you use a delta to scale things, regardless of how long it takes to get through a frame, a ball moving from one side of a room to another will take the same amount of time to do it on a really fast PC and a slow one.

E.g. If a ball moves 10 units in one second and you can determine that 0.1 seconds has passed since the last update (use the high performance timer or whatever is available to you), you simply scale the movement by 0.1 and the ball moves 1 unit.

E.g.

private const float BallSpeedInMetresPerSecond = 10;

public void Update(float deltaTimeInSeconds)
{
    float adjustedSpeed = deltaTimeInSeconds * BallSpeedInMetresPerSecond;
    // set ball's speed / move it etc. using adjusted speed
}

This won't entirely solve your problem (if something is really fast, it's going to get stuck in walls regardless!), but it is a simple and effective way to keep things predictable and consistent until you get into more complicated problems.

If you get that working and then want to solve a more complicated problem, as dash-tom-bang said, look into swept collision detection.

今天小雨转甜 2024-09-08 07:56:16

我想我应该有一个
游戏逻辑的独立线程
在渲染循环中,我应该
当前游戏的“快照”
逻辑并显示,不是吗?

没有任何方法可以简单、安全且快速地拍摄大量游戏状态的快照。您可以对其进行双缓冲,这可能是第二好的选择。但无论如何它并不能解决问题,所以不,你不会这样做,至少不会为了这个目的。

假设我有一个 X=0 的球,并且
X=10 中的墙和一台慢速机器中,
第一个循环将球置于 X=7
在第二个循环中,它放置
球在 X=14 中。它只会崩溃
游戏!

将两者线程化并不能解决这个问题,除非你能保证你使用的每台计算机总是足够快来检查 X=1、X=2、X=3...X=10。你无法做出这样的保证。即使可以,也很少使用整数来表示位置。您可以迭代检查 X=0.0000001, X=0.0000002, X=0.0000003 ... X=0.9999999, X=10.00000 吗?没有。

经验丰富的游戏开发人员如何解决这个问题?

我们通常仍然有一个循环。输入、更新、渲染、重复。您提到的碰撞问题是通过使用碰撞检测方法来解决的,该方法可以计算物体将通过的区域,例如。求解 X=[0 到 17]。在一台非常慢的机器上,它可能是 X=[0-50],在一台快速机器上,它可能是 X=[0-5],后跟 X=[5-10],但每个都将按预期工作。

I was thinking that I should have a
separated thread for the game logic
and in the render loop, I should just
"take a snapshot" of the current game
logic and display that, no?

There is no way that is simple, safe, and fast to take a snapshot of a massive lump of game state. You can double-buffer it, which is probably the next best thing. But it doesn't fix the problem anyway, so no, you wouldn't do this, at least not for this purpose.

Let's say I have a ball with X=0, and
a wall in X=10 and in a slow machine,
the first loop places the ball in X=7
and in a second loop, it places the
ball in X=14. It would just crash the
game!

Threading the two wouldn't solve this, unless you could guarantee that every single computer you used would always be fast enough to check X=1, X=2, X=3... X=10. You can't make this guarantee. And even if you could, it's rare to use integer numbers for positions. Can you iteratively check X=0.0000001, X=0.0000002, X=0.0000003 ... X=0.9999999, X=10.00000 ? Nope.

How do you guys, experienced game developers work around this?

We typically still have one loop. input, update, render, repeat. Collision problems as you mention are solved by using a collision detection method that calculates the area that the object would pass through, eg. resolving for X=[0 to 17]. On a really slow machine it might be X=[0-50] and on a fast machine it might be X=[0-5] followed by X=[5-10], but each will work as expected.

世界等同你 2024-09-08 07:56:16

根据我在游戏设计和 AI 方面的有限经验,我想说有一个逻辑循环和一个显示循环(很像 XNA 设置)。逻辑循环(XNA 中的 Update 方法)基本上将处理更新位置等,而显示循环(XNA 中的 Draw 方法)将把所有内容绘制到屏幕上。至于碰撞检测,我个人会将其定位到你的球上。当它移动时,让它寻找碰撞并做出适当的反应。

线程是另一个主题,但在我看来,我会说不要将更新和绘制分开。对我来说,1 次更新可能有 2 次抽奖,反之亦然,这似乎本质上是错误的。如果没有任何更新,为什么要绘制...或者为什么要在向用户显示正在发生的情况之前多次更新。

只是我的意见,希望我没有离谱。

From my limited experience in game design and AI I would say to have a logical loop and a display loop (much like XNA sets up). The logical loop (Update method in XNA) will basically handle updating positions and what not, while the display loop (Draw method in XNA) will draw everything to the screen. As for collision detection, I would personally localize that to your ball. When it moves have it look for collision and react appropriately.

Threading is another topic, but in my opinion I would say not to seperate the update and draw. It just seems intrinsically wrong to me to potentially have 2 draws for 1 update or vice versa. Why draw if nothing has updated... or why update multiple times before showing the user what is happening.

Just my opinions, hope I'm not way off base.

为你拒绝所有暧昧 2024-09-08 07:56:16

如果逻辑更新通常很便宜,而渲染有时很昂贵,那么最简单的事情就是决定每秒进行 N 次逻辑更新。 N=60 很常见,但您应该选择能让游戏正常运行的最小值,然后选择一个值并调整游戏,直到它以该速率运行,或者(更有可能)两者的某种组合。

在运行时,跟踪实际经过的时间,跟踪逻辑上经过了多少时间(就执行的更新而言),并且当差异超过 1.0/N 秒时(因为渲染花费的时间太长)执行额外的更新赶上。这比尝试一次性执行任意一段时间的更新要好,因为它更可预测。 (如果读者不同意,欢迎他们通过困难的方式找出这一点。)

这个系统的缺点是,如果渲染变得特别耗时,并且逻辑因此必须执行太多更新,则两者可以有点不同步,逻辑永远跟不上。如果你的目标是一个固定的系统,这只是表明你试图做太多事情,你必须以某种方式做更少的事情,或者(如果这种情况可能很少见)只是放弃整个想法并做1:1 渲染:更新。如果您的目标是像 Windows PC 这样的可变设备,您只需限制追赶逻辑更新的数量,并希望这能让事情恢复正常。

(如果逻辑成本更高,则这种方法不合适;不过,我从未开发过出现此问题的游戏。)

If logic updates are usually cheap, and rendering is occasionally expensive, the easiest thing to do is decide to have N logic updates per second. N=60 is common -- but you should just pick the smallest value that lets the game work well, pick a value and tweak the game until it works at that rate, or (more likely) some combination of the two.

At runtime, keep track of time actually elapsed, keep track of how much time has logically elapsed (in terms of updates performed), and when there's more than 1.0/N seconds of discrepancy (because the rendering is taking too long) perform extra updates to catch up. This is better than than trying perform an arbitrary period of time's-worth of updates in one go, because it's more predictable. (Should the reader disagree, they are welcome to find this out the hard way.)

The disadvantage of this system is that if the rendering becomes particularly time-consuming, and the logic has to perform too many updates because of this, the two can get a bit out of sync, and the logic will never catch up. If you're targetting a fixed system, this just indicates that you're trying to do to much, and you'll have to somehow do less, or (if this situation is likely to be rare) just dump the whole idea and do a 1:1 render:update. If you're targetting something variable like a Windows PC, you'll just have to clamp the number of catch-up logic updates, and hope that this will let things get back in line.

(If the logic is more expensive, this approach isn't appropriate; I've never worked on a game where this was a problem, though.)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文