Home ยป What is actually moving in an endless runner?

What is actually moving in an endless runner?


I slightly disagree with Philipp’s answer; or at least with how he presented it. It gives the impression that moving the world around the player might be a better idea; when it’s the exact opposite. So here is my own answer…

Both options can work, but it’s generally a bad idea to “invert the physics” by moving the world around the player rather than the player around the world.

Performance loss/waste:

The world will usually have a lot of objects; many if not most, static or sleeping. The player will have one, or relatively few, objects. Moving the whole world around the player, means moving everything in the scene except for the player. Static objects, sleeping dynamic objects, active dynamic objects, light-sources, sound-sources, etc; all have to be moved.

That’s (obviously) considerably more expensive than moving only what’s actually moving (the player, and maybe a few more stuff).

Maintainability & Extensibility:

Moving the world around the player makes the world (and everything in it) be the point where things are most actively happening. Any bug or change in the system means that, potentially, everything changes. This is not a good way to do things; you want bugs/changes to be as isolated as possible, so that you don’t get unexpected behaviors somewhere you haven’t made changes to.

There are also many other problems with this approach. For example, it breaks many assumptions of how things are supposed to work in-engine. You’d not be able to use dynamic RigidBody for anything other than the player, for example; as an object with an attached RigidBody not set to kinematic will behave unexpectedly when setting position/rotation/scale (which you’d be doing every frame, for every object in the scene, except the player ๐Ÿ˜จ)

So the answer is to only move the player then!

Well…yes and no. As mentioned in Philipp’s answer, in an infinite-runner type of game (or any game with a large seamless explorable area), going too far from origin would eventually introduce noticeable FPPEs (Floating-Point Precision Errors), and further still, eventually, overflow the numeric type, either causing your game to crash, or, basically, make the game-world smoke crack…On steroids! ๐Ÿ˜ต (because by this point, FPPEs would make the game already be on “normal” crack)

The actual solution:

Do neither and do both! You should keep the world static, and move the player around it. But “re-root” both, the player and the world, when the player starts getting too far from the root (position [0, 0, 0]) of the scene.

If you keep the relative position of things (player to the world around it), and do this process in a single frame-update, the (actual) player won’t even notice!

To do it, you have two primary options:

  1. Move the player to the root of the scene, and move the world chunk to it’s new position relative to the player.
  2. Think of the world like a grid; move the part of the grid that the player is in to the root, and move the player to it’s new position relative to that part of the grid.

Here is an example of this process in action

But, how far is too far?

If you look at Unity’s source-code, they use 1e-5 (0.00001) as the basis for considering two floating-point values “equal”, inside of Vector2 and Vector3 (the data-types responsible for objects’ positions, [euler-]rotations and scales). Since floating-point precision loss happens both ways away from zero, it’s safe to assume that anything under 1e+5 (100000) units away from the scene root/origin is safe to work with.

But! Since…

  1. It’s more appropriate to make a system to handle these re-rooting processes automatically.
  2. Whatever your game is, there is no need for a contiguous “section” of the world to be 100000 units (meters[?]) wide.

… then it’s probably a good idea to re-root far sooner/more often than that 100000 units mark. The example video I provided seems to do it every 1000 units or so, for example.

Both options work.

But if you want the endless runner to be truly endless, you will have to keep the player stationary and move the world. Otherwise you will eventually hit the limits of the variables you use to store the X-position. An integer would eventually overflow and a floating point variable would become increasingly less accurate which would make the gameplay glitchy after a while. But you can avoid this problem by using a large enough type that nobody will encounter these problems within the timespan one could conceivably play in one session (when a player moves 1000 pixels per second, a 32 bit integer will overflow after 49 days).

So do whatever feels conceptually more intuitive to you.

Building off of XenoRo’s answer, instead of the re-rooting method they describe, one could do the following:

Create a circular buffer of parts of your infinite map generated, which your character moves through with position updated with modulo arithmetic (so you just run around the circular buffer). Start replacing parts of your buffer as soon as your character leaves the chunk. The players update equation would be something like:

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

here is a pictoral example of what I’m talking about:

enter image description here

Here is an example of what happens on wrapping at the end.

enter image description here

With this method you don’t run into precision errors ever, but you may need to make a very large buffer if you intend to do this in 3d with very far view distance (as you will still need to be able to see ahead of yourself). If its flappy bird your chunk size will probably only be as large as it needs to be to hold a single scene for a single screen, and your buffer can be very tiny.

Note that you will start to get repeating results with ANY prng, and the max number of non repeating sequences of a PRNG generation is typically less than the length of pow(2,number of bits used internally), with merzenne twister this isn’t much of an issue, as it uses 2.5k of internal state, but if you use the tiny variant, you have 2^127-1 max iterations before repeat (or worse), this is still an astronomically large number however. You can fix repeating period problems even if your PRNG has a short period via good avalanche mixing functions for the seed (as you add more state implicitly) repeatedly.

Related Solutions

Extract file from docker image?

You can extract files from an image with the following commands: docker create $image # returns container ID docker cp $container_id:$source_path $destination_path docker rm $container_id According to the docker create documentation, this doesn't run the...

Transfer files using scp: permission denied

Your commands are trying to put the new Document to the root (/) of your machine. What you want to do is to transfer them to your home directory (since you have no permissions to write to /). If path to your home is something like /home/erez try the following:...

What’s the purpose of DH Parameters?

What exactly is the purpose of these DH Parameters? These parameters define how OpenSSL performs the Diffie-Hellman (DH) key-exchange. As you stated correctly they include a field prime p and a generator g. The purpose of the availability to customize these...

How to rsync multiple source folders

You can pass multiple source arguments. rsync -a /etc/fstab /home/user/download bkp This creates bkp/fstab and bkp/download, like the separate commands you gave. It may be desirable to preserve the source structure instead. To do this, use / as the source and...

Benefits of Structured Logging vs basic logging

There are two fundamental advances with the structured approach that can't be emulated using text logs without (sometimes extreme levels of) additional effort. Event Types When you write two events with log4net like: log.Debug("Disk quota {0} exceeded by user...

Interfaces vs Types in TypeScript

2019 Update The current answers and the official documentation are outdated. And for those new to TypeScript, the terminology used isn't clear without examples. Below is a list of up-to-date differences. 1. Objects / Functions Both can be used to describe the...

Get total as you type with added column (append) using jQuery

One issue if that the newly-added column id's are missing the id number. If you look at the id, it only shows "price-", when it should probably be "price-2-1", since the original ones are "price-1", and the original ones should probably be something like...

Determining if a file is a hard link or symbolic link?

Jim's answer explains how to test for a symlink: by using test's -L test. But testing for a "hard link" is, well, strictly speaking not what you want. Hard links work because of how Unix handles files: each file is represented by a single inode. Then a single...

How to restrict a Google search to results of a specific language?

You can do that using the advanced search options: http://www.googleguide.com/sharpening_queries.html I also found this, which might work for you: http://www.searchenginejournal.com/how-to-see-google-search-results-for-other-locations/25203/ Just wanted to add...

Random map generation

Among the many other related questions on the site, there's an often linked article for map generation: Polygonal Map Generation for Games you can glean some good strategies from that article, but it can't really be used as is. While not a tutorial, there's an...

How to prettyprint a JSON file?

The json module already implements some basic pretty printing in the dump and dumps functions, with the indent parameter that specifies how many spaces to indent by: >>> import json >>> >>> your_json = '["foo", {"bar":["baz", null,...

How can I avoid the battery charging when connected via USB?

I have an Android 4.0.3 phone without root access so can't test any of this but let me point you to /sys/class/power_supply/battery/ which gives some info/control over charging issues. In particular there is charging_enabled which gives the current state (0 not...

How to transform given dataset in python? [closed]

From your expected result, it appears that each "group" is based on contiguous id values. For this, you can use the compare-cumsum-groupby pattern, and then use agg to get the min and max values. # Sample data. df = pd.DataFrame( {'id': [1, 2, 2, 2, 2, 2, 1, 1,...

Output of the following C++ Program [closed]

It works exactly like this non-recursive translation: int func_0() { return 2; } int func_1() { return 3; } int func_2() { return func_1() + func_0(); } // Returns 3 + 2 = 5 int func_3() { return func_2() + func_1(); } // Returns 5 + 3 = 8 int func_4() { return...

Making a circle out of . (periods) [closed]

Here's the maths and even an example program in C: http://pixwiki.bafsoft.com/mags/5/articles/circle/sincos.htm (link no longer exists). And position: absolute, left and top will let you draw: http://www.w3.org/TR/CSS2/visuren.html#choose-position Any further...

Should I use a code converter (Python to C++)?

Generally it's an awful way to write code, and does not guarantee that it will be any faster. Things which are simple and fast in one language can be complex and slow in another. You're better off either learning how to write fast Python code or learning C++...

tkinter: cannot concatenate ‘str’ and ‘float’ objects

This one line is more than enough to cause the problem: text="แƒ แƒ”แƒ’แƒฃแƒšแƒแƒ แƒ˜ >> "+2.23+ 'GEL' 2.23 is a floating-point value; 'GEL' is a string. What does it mean to add an arithmetic value and a string of letters? If you want the string label 'แƒ แƒ”แƒ’แƒฃแƒšแƒแƒ แƒ˜...