- Home /
Huge scale solar system.
Hello!
I need assistance on a problem, I'm playing with huge scale scenes, and in this case a Solar System.
Currently there is 1 planet and 1 yellow star. The thing is, that due to the floating point limitations, I cannot just move a planet or player over 100000 Unity Units without getting loss in precision.
I've been searching, and found this: "You don't move in the universe, the universe moves around you." Also I watched Unite 2013 where KSP dev told the same thing.
The player's ship has a simple rigidbody and a controller to move it around. But It cannot move up or down in space.
So, I have all GameObject's (Except the Player) in a parent GameObject, called Universe, now I need to keep the player in the middle of the scene, and make the Universe GameObject move making it look the player would be moving.
First thought is to have triggerboxes all around player, and make universe move to the other direction the triggerbox hit was. But its pretty inaccurate.
Second thought is doing everything in a universe mover script, and make the player reset its position with FixedUpdate function. But how do I get where I am going? The ship controller script is just adding force to the rigidbody when button was pressed.
Third thought is multiple scenes, having an trigger around the edges, and then loading the new scene. Problem is that the planet's have orbits, and rotations.
I like the Second one most, I know how to keep player in the middle, but what about moving? And what if there are NPC's or Asteroid moving at high speed, how are they going to look?
Help.
Answer by Bunny83 · Oct 18, 2015 at 11:36 AM
Having the whole universe in one gameobject will result in the same problem after some time. Your universe gameobject would be far away from you and the float calculations will get imprecise. You have to cut down the actual distance. The best approach in terms of performance is to let the player move in a small area around the origin and when he goes beyond a certain limit in one direction you simply move everything so the player is again close to the center.
The best way to move such a universe is to use two gameobjects which are located at the origin. One is empty the other contains everything. When you want to move the univers you simply move the gameobject that contains everything and then move all objects over to the empty gameobject. That way everything is again relative to the origin. You should group things that belong together into intermediate gameobjects so it's easier to move them and it reduces the relative error due to parenting / unparenting at high distances.
Your can use a UniverseOffset script like this:
// UniverseOffset.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
public class UniverseOffset : MonoBehaviour
{
public Transform player;
public Transform universe;
public int xSector = 0;
public int zSector = 0;
public float sectorSize = 1000f;
private Transform tempUniverse;
void Awake()
{
tempUniverse = new GameObject(universe.name + "_2").transform;
tempUniverse.position = tempUniverse.eulerAngles = Vector3.zero;
}
void Update()
{
Vector3 pos = player.position;
int xMove = 0;
int zMove = 0;
if (Mathf.Abs(pos.x) > sectorSize)
xMove = Mathf.FloorToInt(Mathf.Abs(pos.x) / sectorSize) * ((pos.x > 0) ? 1 : -1);
if (Mathf.Abs(pos.z) > sectorSize)
zMove = Mathf.FloorToInt(Mathf.Abs(pos.z) / sectorSize) * ((pos.z > 0) ? 1 : -1);
if (xMove != 0 || zMove != 0)
{
xSector += xMove;
zSector += zMove;
Vector3 offset = new Vector3(-xMove * sectorSize, 0, -zMove * sectorSize);
universe.position += offset;
Transform tmp = tempUniverse;
List<Transform> childs = universe.OfType<Transform>().ToList();
for (int i = 0; i < childs.Count; i++)
{
childs[i].parent = tmp;
}
tempUniverse = universe;
universe = tmp;
tempUniverse.position = Vector3.zero;
// only needed when the player is not inside the universe gameobject itself
//player.position += offset;
}
}
}
Keep in mind that all coordinates inside the universe need to be offset by
pos +=new Vector3(xSector, 0, zSector)*sectorSize
to get the actual "absolute" position. However relative distances stay the same.
$$anonymous$$eep in $$anonymous$$d that using this approach the individual objects inside the universe still have huge errors at large distances from the origin. You don't see them since the origin is always close to the player, but for example if you have two cubes next to each other so the touch perfectly. If those are "moved" far away and come back they are most likely offset to each other by a slight amount.
One solution for that could be to use actual "sector" gameobject. So all objects are childs of the sector they're in. Sector gameobject would be created on demand. All objects would be relative to their parent sector. The local coordinates should stay correct. However the sector position can still be "corrupted" by the floating point error at large distances. However those can simply be corrected since a sector location is always a multiple of the sector size.
Also keep in $$anonymous$$d that physics is calculated in worldspace. So moving physics objects at a a far distance won't work out. There are solutions to that as well, by placing sectors which need physics updates also near the origin but temporarily put them on a seperate layer so it's not rendered by the camera. However this quickly becomes a mess when you think about the housekeeping of all offsets and relations between objects and sectors.
It all depends on what you actually need. Simple planetary gravity can be calculated manually with doubles.
This actually works, the sector changed and everything worked, did not notice any transition weirdness.
I could check if player is in certain Sector, and then enable physics and events there. Thank you for the help, learned something new.
I have to look at celestial orbiting script now, it does not seem to take the new positions in, and makes the planet remain at same position.
Vector3 offset = new Vector3(-x$$anonymous$$ove * sectorSize, 0, -z$$anonymous$$ove * sectorSize);
@Bunny83 . Wount it be better to do this ins$$anonymous$$d:?
public float sectorSize = 500f;
....
....
Vector3 offset = new Vector3(-x$$anonymous$$ove * (sectorSize*2), 0, -z$$anonymous$$ove * (sectorSize*2));
this way you are representing 1 sector ins$$anonymous$$d of 8 at a time around VectorX.zero.
$$anonymous$$y solar manager is pretty advance at the moment and just wonderring if your method is more practical in the long run.
Please note Im converting global possition from a double vector3 data set.
I'm not sure what you mean by "8 sectors"? This solution currently uses a "flat" universe and only divides into sections on the x and z axis but not on y.
The point of my code is when the player moves more than 1000 units from the scene origin he will be translated to the origin on that axis. This has the advantage that the player can now move in both directions another 1000 units before a new shift happens.
Your solution has the problem that if the player goes beyond "+500" you translate him to -500. This would work when the player continues moving in the same direction. However if the player just moves around such a border you would constantly shift left and right.
so if the player is at x=501 shift left so he's at "-499". But when he does a tiny step back to "-501" you would need to immediately shift back so he's at "+499".
In my case when the player reaches 1000 on x he is moved to "0". However moving backwards is not a problem because the player is actually at the center.
So my implementation prevents frequent universe shifts when the player is close to a section border.
Note that this is not ment to deter$$anonymous$$e in which logical sector an object or the player is. It's just a way to shift the world in a way the player is always close to the center. Imagine at no shift (universe pos [0, 0] ) the player is located at (245, 0, 800). When the universe is shifted to lets say [1, 0] the same player pos would be (-755, 0, 800). At a shift of [2, 0] the same position would be (-1755, 0, 800). However since the absolute value of "-1755" is larger than 1000 the universe would shift back to [1,0]
In electronics such a behaviour is called a Schmitt trigger.
Just to make that clear: If you want to deter$$anonymous$$e the logical sector the player is in you use the current world shift and adjust it when the local player position has a negative number. So sector (0,0) would start at the absolute position (0,0,0) and end at (1000, 0, 1000). That means at a world shift of [1, 0] and a player position of (-200, 0, 350) the actual sector is (0,0) and the player position inside the sector is (800, 0, 350)
So to get the sector of the player you would do
int pSectorX = xSector - ((player.position.x<0)?1:0);
int pSectorZ = zSector - ((player.position.z<0)?1:0);
This will always give you the sector the player is in where the player has always positibe local coordinates. The player sector relative coordinates can be deter$$anonymous$$es pretty much the same way:
var pos = player.position;
pos.x = (pos.x < 0)?sectorSize+pos.x:pos.x;
pos.z = (pos.z < 0)?sectorSize+pos.z:pos.z;
Or if the tenary operator looks confusing:
var pos = player.position;
if (pos.x < 0)
pos.x += sectorSize;
if (pos.z < 0)
pos.z += sectorSize;
Answer by DireDoesGames · Oct 18, 2015 at 09:13 AM
Simpler Question?
Please explain what you are trying to do.
Moving the Universe
If I think I understand from the title why don't you just add a controller to the GameObject "Universe"
Everytime I try to ask something it goes too complex..
Ok, I'm trying to move the Universe back, when the player moves forwards, keeping player in middle of the scene all the time. The Futurama thing.
Now, the Universe controller, moves the universe backward when the player moves forward, and moves it forward when player moves backward. How do I get where the player is going? The rotation thing.
Answer by meat5000 · Oct 18, 2015 at 12:44 PM
Use the "Serious Sam" approach.
Simulate distance by keeping position the same and modifying scale.