永无止境的跑步者到底在运动什么?


107

例如,著名的《飞扬的小鸟》游戏或任何真正分类的游戏都是玩家(在这种情况下,是小鸟,或者是您喜欢的相机)向前移动或整个世界向后移动(小鸟只改变Y位置并且具有X位置不变)?


1
我们已经从该问题及其答案中删除了许多题外话。我们还移动了一些合理的讨论以进行聊天,有时还会重复进行。尽管如此,一些用户仍认为有必要忽略以下事实:评论不适合进行扩展讨论,而是继续进行。因此,该问题已被暂时锁定。
乔什

Answers:


146

我有点不同意菲利普的回答。或至少与他的介绍方式有关。它给人的印象是,在玩家周围移动世界可能是一个更好的主意。恰恰相反。所以这是我自己的答案...


两种选择都可以使用,但是通过在玩家周围而不是在玩家周围移动世界来“颠倒物理性”通常是个坏主意。


性能损失/浪费:

这个世界通常会有很多物体。许多(如果不是大多数的话),静止的或睡觉的。玩家将拥有一个或相对较少的对象。在玩家周围移动整个世界,意味着在场景中移动除玩家之外的所有事物。静态对象,睡眠动态对象,活动动态对象,光源,声源等;所有都必须搬家。

(显然)这比仅移动实际移动的内容(播放器,也许还有更多的东西)要贵得多。


可维护性和可扩展性:

在玩家周围移动世界使世界(以及其中的一切)成为最活跃的事件。系统中的任何错误或更改都意味着所有内容都有可能更改。这不是做事的好方法。您希望将错误/更改尽可能地隔离,以便在未进行更改的地方不会出现意外的行为。

这种方法还存在许多其他问题。例如,它打破了有关引擎应该如何工作的许多假设。RigidBody例如,除了播放器外,您将无法使用dynamic 。因为RigidBody没有设置运动的附件对象在设置位置/旋转/比例时会表现出异常情况(您要为场景中的每个对象(除了播放器😨以外)对每一帧进行操作)


因此,答案是只能移动播放器!

好吧,是的不是。正如菲利普(Philipp)的回答中所述,在无限跑者类型的游戏(或任何具有大的无缝可探索区域的游戏)中,离原点太远会最终引入引人注目的FPPE(浮点精度错误),并且更进一步,最终,溢出数字类型,或者导致游戏崩溃,或者基本上使游戏世界烟消云散…… 在类固醇上!😵 (因为此时,FPPE使游戏已经处于“正常”状态)


实际的解决方案:

两者都不做!您应该使世界保持静止,并在周围移动玩家。但是,当播放器开始离场景的(位置太远时,播放器世界 “重新建立” 。 [0, 0, 0]

如果您保持事物(玩家对周围世界的相对位置)的相对位置,并在单个帧更新中执行此过程,那么(实际)玩家甚至不会注意到!

为此,您有两个主要选择:

  1. 将玩家移动到场景的根,并将世界块移动到相对于玩家的新位置。
  2. 将世界想象成一个网格;将玩家所在的网格部分移至根部,然后将玩家移至相对于该网格部分的新位置。

这是此过程进行中的示例


但是,距离有多远呢?

如果您查看Unity的源代码,它们将使用1e-50.00001)作为考虑两个浮点值“相等”的基础,这些浮点值在Vector2Vector3(负责对象位置,[euler]]旋转和缩放的数据类型)之间。由于浮点精度损失是从零到零发生的,因此可以安全地假定与场景根/原点相距1e+5100000)单位以下的任何内容都是可以安全使用的。

但!以来...

  1. 使系统自动处理这些重新生成根的过程更为合适。
  2. 无论您使用哪种游戏,都不必将世界上连续的“部分”的宽度设为100000单位(米[?])。

...那么,早于/多于100000个单位重新植根可能是个好主意。例如,我提供的示例视频似乎每1000个单位执行一次。


2
评论不作进一步讨论;此对话已转移至聊天。请使用该聊天而不是在此处发布更多评论。
Alexandre Vaillancourt

> (显然)这比仅移动实际移动的要昂贵得多。渲染时,无论如何,您都必须对所有对象上的摄影机矩阵进行矩阵乘法,以这种方式将摄影机固定在身份上会便宜。
Matsemann '17

游戏开发成为统一开发的那一刻……
LJᛃ17年

@Matsemann-如果您去聊天,您会发现这不是新问题。如前所述,渲染NOT-EQUALS场景;它们是不同的,几乎是完全独立的主题和管道。以对象在场景中的放置方式进行参考框架的反转将意味着您在两端的处理将更加繁重,而不仅仅是渲染。---此外,即使性能也同样交易为你争论,所有其他问题的反转将仍然得不到解决,使得这种方法仍然比重新生根一个更糟糕。
XenoRo

@LJᛃ这个问题专门针对统一性提出的(请参阅D. Everhard的[IMHO 涂污]编辑之前的OP标签),因此量身定制了答案以反映这一点。---但这是最好的部分:是Unity,Unreal,CryEngine,Source等...最好和/或最受欢迎的引擎都以这种方式工作(并且这样做是有原因的),所以答案不仅是一般而言,这是完全正确的,但提出的观点仍处于准确性的顶峰。一般来说,如果您颠倒物理学的参考系,则可能会遇到问题,尤其是在动态对象上。
XenoRo

89

这两个选项都可以。

但是,如果您想让无尽的跑步者真正成为无尽的跑步者,就必须让玩家保持平稳并移动世界。否则,您最终将达到用于存储X位置的变量的极限。整数最终将溢出,并且浮点变量的准确性将越来越低,这将使游戏玩法在一段时间后出现故障。但是您可以通过使用足够大的类型来避免此问题,以至于没人可以在一个会话中玩的时间内遇到这些问题(当玩家每秒移动1000个像素时,49天后32位整数将溢出)。

因此,从概念上讲,您觉得更直观的事情也要做。


评论不作进一步讨论;此对话已转移至聊天
乔什

1
我认为这不能解决任何问题。而不是保持玩家(和摄像机)的位置,而是保持世界的滚动位置-这有什么不同?
Agent_L '17

1
@Agent_L通常,世界是逐个生成的,每个片断都有其X / Y位置。当棋子在玩家之后足够多的位置(例如,屏幕外)被删除时,它们只会预先生成一定数量,因此它们始终处于相对较小的范围内-我制作的一款游戏的座标永远不会更大因此,尽管它是一个无限的随机生成的跑步者,但它的值仍小于或等于1000或小于0,因此我跟踪了每个块中玩家的位置以及序列中每个块的索引。
Nic Hartley

我不购买“变量溢出”的概念。如果播放器不移动,则“背景”的偏移量将与播放器移动时的偏移量相同。两种情况都将遇到完全相同的问题(方向相反),并且都需要在溢出限制时进行特殊处理。
支出者

2
以每秒1000像素的速度计算,要遍历64位整数将花费超过5.86亿年的时间。仅当您使用非常小的类型(例如16位整数)时,这才是真正的问题。
林登·怀特

38

基于XenoRo的answer,而不是他们描述的重新生根方法,可以执行以下操作:

为生成的无限贴图的各个部分创建一个循环缓冲区,角色通过模运算更新位置来移动角色(因此,您只需绕着循环缓冲区运行)。一旦角色离开了块,就开始替换缓冲区的某些部分。玩家更新等式如下所示:

player.position = (player.position + player.velocity) % worldBuffer.width;

这是我所谈论的图画的例子:

在此处输入图片说明

这是结束包装时发生的示例。

在此处输入图片说明

使用这种方法,您永远不会遇到精度错误,但是如果您打算在3d中以非常远的视距进行此操作,则可能需要制作一个很大的缓冲区(因为您仍然需要能够自己先看到)。如果它的鸟儿很大,则块的大小可能只会和为单个屏幕容纳一个场景所需的块大小一样大,并且缓冲区可能很小。

请注意,您将开始使用ANY prng 获得重复结果,并且PRNG生成的非重复序列的最大数目通常小于pow的长度(2,内部使用的位数),对于merzenne twister,这不是这是一个很大的问题,因为它使用2.5k的内部状态,但是如果使用微小变体,则在重复(或更糟糕)之前有2 ^ 127-1次最大迭代,但是,这仍然是一个天文数字。即使您的PRNG周期很短,也可以通过对种子进行良好的雪崩混合功能(因为您隐式添加了更多状态)来解决重复周期问题。


评论不作进一步讨论;此对话已转移至聊天
乔什

15

正如已经被要求和接受的那样,它确实取决于游戏的范围和风格,但是由于没有提到:FlappyBird在屏幕上而不是在世界范围内移动障碍物。

生成器正在以固定的Vector2.left方向在屏幕外实例化对象。


3
这对FlappyBird有用,因为它是一个非常简单的游戏。正如我在回答中提到的那样,尽管仍然可行,但相同的技术在诸如Subway Surfer之类的更复杂(具有更多世界对象/细节)的任何事物上都会有很大问题。
XenoRo

15
我同意,您的回答非常彻底。我只是没有看到任何人提到拍打鸟实际上使用哪种方法,所以我想我会添加它。
斯蒂芬
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.