Search Unity

Games Ants Hills - A game with the scope of a small universe...

Discussion in 'Projects In Progress' started by Max_Bol, Nov 20, 2021.

  1. Max_Bol

    Max_Bol

    Joined:
    May 12, 2014
    Posts:
    168
    Hey all, Max here!
    You might not know me, but well I don't blame you since I'm just a nobody who has been around for a while. (Discovered Unity in 2014 and, since then, been around learning and, at some point, trying to help others here and there.)

    The project I'm sharing on this new thread is called Ants Hills.
    It's a game demonstrating the concept of simulating an universe in the most optimized way while being able to play as a single human in it on many... and I mean many ways.

    The concept of the idea behind this project came to me when I though about ways of handling float precision issues with Unity's units-based coordinates. It has been something on my mind of years with experiments here and there, but I never took a new perspective on it than anything others have already been doing until now. Around the night, the idea of the principle I'm current building came as a flash to me while I was unable to sleep due to pain. It works so well, on paper, that I decided to mix another project I had into it because it fits the bill and... why not?

    An important note is that I'm not simulating our actual Universe, but I'm just creating one from scratch with a seeded integer as its source.

    Phase 1: Creating an Universe and traveling around in it.
    The primary challenge of this phase is to be able to move in the Universe at various speed and actually being able to move at ANY stars you see on screen without loading or wormholes or anything. Being able to move at various speeds from the speed of a snail to the speed of actual light and all that being purely visible in real-time.

    Phase 2: Adding a customizable ship system + space stations + factions around
    This is the core of the gameplay around traveling the universe. I want to make it so that you can see stations appear from a single dot on screen and allow you move closer to it at various speed. The challenge of this phase is mostly the AI and physics involving around it and the stuff moving around. I'm going for something a big insane where I want the player to be able to physically move their player in the station and ships, including having their ship boarded or boarding another ship (or stations).

    Phase 3: Adding civilizations of various sizes on planets and in ships around.
    I'm not going for an intergalactic-species generated on the spot like No Man Sky, but I'm aiming at having human-based civilization around the small Universe with a certain level of interactivity. The challenge of this phase is adding time-relative evolution (or devolution) of civilizations around as well as adding ways for settlements to start existing, grow, disappears and so on. It also extend on the phase 2 as I would like to make it so that the player can walk around those civilizations on foot.

    This phase includes a manageable way of adding planetary rotation around the stars.

    I would also like to include some kind of aliens stuff in this phase. A few gameplay-like modes events where the player can join in for the fun (or seriousness) of it for some reward.

    Phase 4: Adding multiplayer.
    One player playing being able to play is fine, but it's better if more can play in the same Universe. This extends on all the previous phases and the main challenge is to how relatively complex it can become when you consider the whole time and traveling thing.

    I'm obviously at Phase 1 at this moment, but it goes actually well... maybe too well.
    For example, I'm already generating this:
    InterestingStarCount.png
    ... and yes, everyone of those stars, planets and bodies have a random stellar position in the Universe. (well, random based on the Universe seed.) I can't really display all of their data because any display on the editor UI is kinda overkill, but the whole data is generated relatively butter-smooth in 0.025ms in the Editor itself.

    Now, the big question that some might be wondering is How the heck can I manage an universe-wide amount of data in such a tiny amount of time and without melting down my CPU?

    The answer is... well I generate data on a low-profile way and only use what is needed when needed. I could say that I over-simplify complex problems into primary-school-level-problems, but that's a bit of a stretch. (I did try explaining my train of though to a colleague at my job in a large store and his look toward me was a bit strange. Maybe it was because I told him that Creating an universe felt a bit too simple first in a project of mine.)

    I'm not using Unity XYZ coordinates to manage the universe positions, but instead I'm using 3 arrays of up to 10 integers for pretty much anything in the Universe. I created a serializable class called uPosition and all of the thousands of planets and bodies have one of those. In that class, there's a function that allow to convert those 3 arrays of integers into Unity standard Vector3 float based coordinates.

    To example the concept, you got to see this kind of length units chart:

    0 = Milimeter (mm) = 0.000001 Km
    1 = Meter (m) = 0.001 Km
    2 = Kilometer (Km) = 1Km
    3 = Megameter (Mm) = 1000Km
    4 = Gigameter (Gm) = 1,000,000 Km
    5 = Terameter (Tm) = 1,000,000,000 Km
    6 = Petameter (Pm) = 1,000,000,000,000 Km
    7 = Exameter (Em) = 1,000,000,000,000,000 Km
    8 = Zettameter (Zm) = 1,000,000,000,000,000,000 Km
    9 = Yottameter (Ym) = 1,000,000,000,000,000,000,000 Km

    Some of those unit values are not well known (or used at all), but they are the actual name of those values lenght, based on the metric latin-based rules of values. (Does some of you, at this point, notice how I included 10 units and previously mentioned arrays of 10 integers for each 3 arrays in the uPosition?)
    Each of those steps are 1000 times apart, so what if each of the integer in each array are value that can get between 0 and 999 and whenever it reach -1 or 1000, 1 is subtracted or added from the next value in the array in the list? It's some really basic algorithms, right?

    If you move at 1Km/h, add 278 to the int[0] each second.
    If you move at 1000km/h, add 278 to the int[1] each second.
    If you move at 1AU/h, add 41 to int[3], 554 to int[2], 964to int[1] and 84 to int[0] each seconds.

    1 AU (Astronomical unit) is the calculated distance between the Sun and Earth.
    1 Light Year is equal to 63,240AU (meaning that light could move that many times the distance of Earth and Sun in a single year.)

    Now, it's all good on paper and math, but what about the rendering? You can render thousands of planets and bodies at once, right? No, I'm not going the break you mind with some insane math solution, but instead I'll simply put it this way: Why rendering anything that can't be seen? You see stars, ok, but you don't really see the stars' planets, right? Well, maybe with the exception of the planets in the star system you're in (if applicable), obviously.

    Now, again, with the array above, the average distance between 2 solar system is approx. 5 light years in our solar system (as reference only).
    To put it into the metric system above, that's a value of 9.4605634068 Petameters. (That's a decimal dot after 9.) If we know that 1 AU is 149,597,870.7 Km and 1 Light Year is 63,240 AU, we can get the light years in Km, which mean we can get its value in Petameter by pushing the decimal 9 step to the left.

    This means that we can cut turn any solar systems that has a differential value of 9 or above on the int[6] and turn them in shiny dots in the background. If we consider our solar system as big as 1,921 AU and the biggest known solar system at this moment is 140x bigger, we could safely state that an huge solar system could be 300,000 AU. That's 44 in int[6] and 85 in int[5] of differential value. But, we could do a local swipe of the planets within a system and turn of anything too far to be seen anyway. For the sake of rules, I set my planet count to be between 0 and 60 for a single star and we can verify the size of the planet, then based on its size, check the relevant integer in the array and if it turn out too small, not rendering it at all.

    That's the trick... the smaller we get, the lower on the int[x] we check to see if we should render while also ignoring anything too far (size-related). A planet big enough to be seen on screen, but at barely 1-3 differential value in int[3] would be the size of the moon in our sky and you can't really see the small stuff on it. (This excludes the planetary belts and other kind of stuff that requires a bit of tinkering.)

    The world only generate visible stuff, in game, within the 2,000 + (A) units around the player's view and layer all in a matter of distance from the player's uPosition value. Yes, I'm using an absolute zero fashion to move the space around. The active (if applicable) solar system is within the 2,000 first units and the (A) extra units are layered stars system (dots) placed in a fashion based on the "grid-like" emplacement in the Universe.

    The (A) varies depending on how many star systems are in the Universe. I use a simple "half of the Universe has a star" system so that the Universe is filled, but not too much data-wise. Also since each star might appear on screen, 1 star = 4 vertices at least. Having 1 million star might feel okay, but that 4 millions vertices (at least) wasted away and we have no planet or whatever in view. In my attempt, I'm going with 4096 stars which is 16384 vertices in the scene always visible (unless close to a sun enough for it to not just be a texture which turn it to 16380), for experimenting.

    So generating the Universe with a 3D cubic grid system of 32x32x32 slots (total of 32768 slots) and take 4096 random slots out of those for a star system per slot taken. Some might be next to each others while some might be far away. (well... next to each others... as in only 5 light years apart. It's all relativity after all!)
    (The slots amount is a simple formula of Cubic Rooting the number of stars, roofing the value (to avoid 0), doubling it then exposing it by 3.) This is not exactly a doubling, but the concept comes from the idea:
    If a grid of 16x16x16 (4096 total) is filled, how would those be randomly placed in a 32x32x32 grid which is twice the volume. A bit of algebra and I ended up with that concept. By the way, a grid like that of 32x32x32 possible system where 1 unit is 5 lightyears, it's possible to convert the whole grid as a cubic Exameter (roof-rounded a tad bit). Obviously, our universe is way bigger than that, but that's still insanely practical since my uPosition can check the Exameter value on the int[7] position in the arrays.

    Now, the last part of this post for now on my project (and adventure) is the one key element I had to think about.

    What about the edges of the universe?

    That's a big question (pun intended) and also one that is almost existential. I really felt like the mix of a scientist and monk thinking about it from both a technical point of view and a visualization. I thought of many ways of handling this with ways of managing what is seen and how. The grid-based system previously briefly explained came from my selected solution.

    I'm using a treadmill solution to the problem which is actually also one of the theory behind the way reality in the universe is built and while I though about it make me believe that this might be how the universe is, if overly simplified, actually built.

    First, what is a treadmill solution? Well, that's the solution to a lot of locomotion problems, but that's not what we're talking about here. It's a solution for sequential numeration within a limited range toward continuation.

    Think of it as Egyptians building pyramids by rolling stones on logs. They didn't cut new logs whenever the stone reached the last log (I hope at least), but instead brought the unused ones behind and put them on the front and so on. It's the solution that allow a armored tank to use its track system to "roll" around. It's the solution that might be located in your house or apartment that allows you to endlessly (in theory) run without actually move around.

    A sequence of 0-1-2-3-4 becomes 4-0-1-2-3 with a treadmill solution if moved forward by 1 and becomes 1-2-3-4-0 if moved by -1. Now, imagine this with the universe. If you cross the universe from 0 to 1 Em (or int[7] on one of the 3 arrays) in the Universe, you end up at the same relative position as where you started and, while you moved, you keep seeing stars moving around and filling up the star with never really feeling any edge anywhere.

    (As such, my mention of how the universe is built is far more complicated where reality might not be a straight line, but a curved that always end up onto itself which, also, allow the universe to be both infinite in size, but finite in its content explaining the idea being why it may expand without having a limit in the first place. It's a lot complicated and we don't need to simulate it in a game if we can just cheat our way with a basic treadmill conversion in straight 3 axis way.)

    This is a serious project, but on the learning side. I might not finish it up to phase 4, but I'm already advanced on Phase 1 so let's hope, right?
     
    mgear likes this.
  2. Max_Bol

    Max_Bol

    Joined:
    May 12, 2014
    Posts:
    168
    Just a small addition after reading my post again.
    I made a tiny bit of a mistake which I'll have to work on for the stars.
    I wrote You see stars, ok, but you don't really see the stars' planets, right?

    That's technically not true since a big planet could hide you from seeing the sun (a.k.a. star).
    I might have to find a way to quickly check if any planet, in a system, is between the star and the player (in its orbit) and if that planet is big enough (at a distance) to hide the sun's light. At such point, just hide the star dot. It's not that heavy since you don't have to look up this "check" more than once unless the player move fast around that particular star which is light years away. (so, again, only check it if the player is moving in a new star system or, while not in star system, now and then. Even if the star appears a minute late while moving at a few Gigameter per sec is not a big deal.)
     
  3. Max_Bol

    Max_Bol

    Joined:
    May 12, 2014
    Posts:
    168
    A small update on the Phase 1 of the project.

    While everything still works just fine, it's funny how I end up switching things up a gear to make the work simpler and faster without any real impact on the system.

    In my explanation of the previous post, I wrote how I used an array of 10 integers per axis for managing super huge distances and positions, right? Well, I still do, but I decided to turn that array into its own class and built upon that class by adding tools to make things simpler when accessing it. I called this new class an uFloat and I'm adding, into it, proper functions (a.k.a. tools) to do simple mathf-like changes to its arrays of integers if needed. I noticed that directly reaching out for the full array every time felt a bit wasteful in time and lines of codes. There are also function to create quick data such as a string of the uFloat full value and such.

    One key thing I decided is to make the uFloat an absolute positive and looping value through a treadmill solution directly into the class. This means that the uFloat can only return and store values between 0 and 999,999,999,999,999,999,999,999,999,999 inclusively.

    This means that dropping 1 below 0 turn it into 999,999,999,999,999,999,999,999,999,999 and vise-versa.

    By the way, while this might feel limiting, if I'm storing the uFloat with millimeters in mind as its smallest value, it allow me to reach 999.99 Yottameters (as shown in the list of units of the previous post) with no lost in data due to precision. That's plenty for my case of use. To give an idea, our own Galaxy, the Milky Way, is estimated to a size of approx. 499.99 Exameters and I could reach 2,000 times that size with a tiny bit (0.92%) of leeway. Now I will not do so because filling that whole thing would be a data-accessing-nightmare. I would focus too much on just filling up a box of sand and not enough on adding the toys to play in it. ;)

    The implementation of the high-speed-movement treadmill where reaching the "end" of the Universe bring you back to where you were is not related to this by-design limitation, but a separate system. After all, at this moment, I only plan to reach 1 Exameter (So the player will move between 0 and 999,999,999,999,999.999999 Km )

    Some might wonder how will I add smoothing into this uFloat when moving stuff around since I'm using integers. After all, moving by increment of 1mm is not a smooth movement if seen from our own eye perspective (which I'm planning to allow since I want the player to be able to walk his character around on planets and ships). The key to this is within the conversion from uPosition (containing 3 uFloats for x, y and z) to Transform.Position (being a vector3); Based on the distance between the player and whatever is moving, I'm skipping any too-small-adjustment (until they becomes relevant) and smoothly adjust changes that suddenly appears. As millimeters is the smallest size-related movement calculated by the uFloat capacity in my case, I simply smooth and rounds the change between whenever that value changes.
     
  4. Denizent

    Denizent

    Joined:
    Jun 22, 2022
    Posts:
    5
    Hey Max, hows this project doing now? Curious if you've made it to Phase 2?