For example the famous Flappy Bird game, or anything that sort really, is the player(in this case the bird, or the camera whichever you prefer) moving forward or is the whole world moving backwards(the bird only changes Y position and has a constant X position)?
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.
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.
The world will usually have a lot of static or sleeping objects. 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:
- Move the player to the root of the scene, and move the world chunk to it's new position relative to the player.
- 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.
But, how far is too far?
If you look at Unity's source-code, they use
0.00001) as the basis for considering two floating-point values "equal", inside of
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
100000) units away from the scene root/origin is safe to work with.
- It's more appropriate to make a system to handle these re-rooting processes automatically.
- 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.
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:
Here is an example of what happens on wrapping at the end.
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.
As already asked and accepted, it really depends on the scope and style of your game, but since it wasn't mentioned: FlappyBird moves the obstacle across the screen, rather than the player across the world.
A spawner is instantiating objects off screen with a fixed speed in the
protected by Alexandre Vaillancourt♦ Dec 6 '17 at 20:02
Thank you for your interest in this question.
Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).
Would you like to answer one of these unanswered questions instead?