Search Unity

I need a little help stopping spawns within an area around procedural objects.

Discussion in 'Scripting' started by Nubnubbud, Mar 7, 2021.

  1. Nubnubbud

    Nubnubbud

    Joined:
    Nov 19, 2014
    Posts:
    49
    alright, so, essentially, this game is 3d, with no true up, so it's a seriously 3D field of procedural objects. basically, it's in space, and there are very few large objects that the small ones must spawn nowhere near but both are pseudo-randomly placed, so I came up with the idea of a 2 tiered spawn system:

    tier1: large objects spawn, even at far distances to make sure they always spawn first
    tier2: small objects spawn, and because the large objects are already there, they may check for them in order to do more creative spawn nonsense

    I'm a bit new to scripting, what would you suggest to make sure tier2(small) objects not spawn within a certain radius of large objects?

    I've already tried using CheckSphere, but to no avail, even though the large objects have colliders on them. it even seemed to work, and my bug log output the result, but the result was always a yes... there's no collider on the test objects, or more accurately, my script needs something that tests the location prior to instantiating the object, just a "check within x radius of point for object" which I know isn't quite how it would work...
     
  2. arfish

    arfish

    Joined:
    Jan 28, 2017
    Posts:
    782
  3. Nubnubbud

    Nubnubbud

    Joined:
    Nov 19, 2014
    Posts:
    49
    well, that was my first consideration, but after all the objects are spawned, (think making 20 within a 10 km cube) there's no deterministic way to know where they are. I think I need some sort of collider check. I'm alright with the large object having a collider, but thousands of small objects, I feel, would affect performance too much. if they all had colliders. I'm moving it over to the DOTS system now, but even then I'll need a way to stop them from spawning within a certain radius of a certain subset of already existing objects. I could have a script on them that destroys them if they spawn within a certain collider, but that's a couple hundred extra instantiates and destroys.

    that's why I tried https://docs.unity3d.com/ScriptReference/Physics.CheckSphere.html , because it would simply see if a radius collides with any nearby colliders, and I can just put a collider inside the large objects somewhere... or am I using it wrong? maybe it's not supposed to be inside the start function?
     
    Last edited: Mar 7, 2021
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,726
    Have you tried it? Computers are extremely high powered these days. Checking distance to ten thousand points should hardly even cause any hiccup at all.

    Be sure you understand that the code doing this is going to essentially be doing the same process as checking everything, but the physics system is just very optimized and good at quickly disregarding distant items. It sitll considers them though, so make sure you're not making this unnecessarily difficult on yourself.

    I would just keep track of the locations of things you don't want to spawn near, check the distance to them, see how it goes. I'm gonna bet that's plenty fast.

    Don't speculatively optimize. It's bad for your game.
     
  5. Nubnubbud

    Nubnubbud

    Joined:
    Nov 19, 2014
    Posts:
    49
    I am aware, it's just likely better than anything I could write at my level. and yeah, I just wanted to avoid nesting loops too far. having three loops for x, y, and z, then looping several dozen times to check all the invalid areas, then instantiating an object... and doing ALL of it several thousand times, that's getting recursive to millions of checks, and I'm probably gonna be adding a few more checks that would multiply it all, too- at least one to determine object type. that's why I'd like to limit it as much as possible. I've already made sure the most intensive checks are skipped if the fastest ones aren't cleared. upon further consideration, these are probably gonna get simple colliders in DOTS so I might as well switch over and use those... think they'll be able to work in the start function?
     
  6. seejayjames

    seejayjames

    Joined:
    Jan 28, 2013
    Posts:
    691
    You don't need three loops for x, y, and z, just choose a random point, run the distance checks (loop through the list of transforms of the large objects), and if it's too close, pick another one. Once it fulfills the criteria, instantiate the object (and store its transform similarly, in another list). There's little processing overhead for these kinds of checks. Use a while loop, but be sure there's plenty of space in the game so you don't get into a hang situation where it keeps having to choose new random points all the time.

    As you instantiate the large objects, store all their positions in an list that you loop through for the distance checks. Never access the actual objects again for information like this, that's wasteful and unnecessary.
     
  7. Nubnubbud

    Nubnubbud

    Joined:
    Nov 19, 2014
    Posts:
    49
    the game is multiplayer, I forgot to add that- sorry for the confusion if there was any, but referencing 3D noise and the chunk system is my primary way to keep track of what already exists, and to stop another player from overpopulating another player's area with asteroids (one of the more common spawns). because they spawn on a grid, and I know their radius before displacing them, my algorithm just reduces the displacement by size so it's nearly impossible to get overlapping asteroids in the first place, and coherently clumps them together in a way that can be mapped, no additional checks needed.

    I know there are other ways to do it, but it's just reliable, fast, and effective... and it'll be even faster if I do this, because I have at least a few dozen checks before it's faster to spawn and delete the ones in bad places.

    I'm going to see if I can get this system using DOTS now, then... maybe it would be faster just to destroy on collider with the large objects.
     
    seejayjames likes this.
  8. seejayjames

    seejayjames

    Joined:
    Jan 28, 2013
    Posts:
    691
    Sounds like a very cool project!
     
  9. Nubnubbud

    Nubnubbud

    Joined:
    Nov 19, 2014
    Posts:
    49
    it's not actually mine! that's part of the reason my hands are tied on some parts. I'm just making worldgen.
     
  10. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,726
    If you really have a lot of items to avoid and want to keep iteration time bounded nicely, you could subdivide your world system into space consisting of cube-shaped volumes of space.

    To do this you would encode the Vector3 position of any item into a key that describes what volume cube it is located in, something like this:

    Code (csharp):
    1. const float CubeSize = 100.0f;
    2.  
    3. int cubeX = (int)(position.x / CubeSize);
    4. int cubeY = (int)(position.y / CubeSize);
    5. int cubeZ = (int)(position.z / CubeSize);
    From that you can make a key generator function:

    Code (csharp):
    1. static string XYZKey( int x, int y, int z)
    2. {
    3.   return x.ToString() + "-" + y.ToString() + "-" + z.ToString();
    4. }
    Next, iterate every item you want to avoid, adding their XYZKey to a HashSet. Since this could still allow an object to be precisely at the edge of a cube and have something placed very closely to it in an adjacent cube, you can iterate a 3x3x3 volume of cubes to block:

    Code (csharp):
    1. var blockedCubes = new HashSet<string>();
    2.  
    3. for (int k = -1; k <= -1; k++)
    4. {
    5.   for (int j = -1; j <= -1; j++)
    6.   {
    7.     for (int i = -1; i <= -1; i++)
    8.     {
    9.       string key = XYZKey( cubeX + x, cubeY + y, cubeZ + z);
    10.       blockedCubes.Add( key);
    11.     }
    12.   }
    13. }
    Once you have done that with all the blockers you can now confidently place future items into this world and check in O(1) time that they don't go in any cubes of space already occupied.

    You would again get the cube coordinates of the proposed object location, make an XYZKey and check against the
    blockedCubes
    HashSet.

    If it's already in there, choose a new location.

    You would adjust the cube size to suit, and adjust the extra area around a spot to suit. Doesn't have to be cube-shaped extra cubes either: you could make it a more interestingly-shaped object with an array of custom offset cube coordinates, such as a 5x5x5 volume that is missing its corner cubes to be closer to a true sphere.
     
    Last edited: Mar 9, 2021
  11. ensiferum888

    ensiferum888

    Joined:
    May 11, 2013
    Posts:
    317
    It could be overkill but have you considered using an oct-tree to check for nearby objects quickly? I'm using this in my game for most spacial distribution systems and I can check for neighbors very quickly multiple times per frames even on trees that contain 40k objects. (using a quadtree but oct-tree should be just as fast)
     
  12. Nubnubbud

    Nubnubbud

    Joined:
    Nov 19, 2014
    Posts:
    49
    so, I figured out my issues. CheckSphere was not working correctly for some reason, but the code

    CheckLocation.Set(x*asteroidDistMult, y*asteroidDistMult, z*asteroidDistMult);
    if(obstructedSpawn = Physics.CheckSphere(CheckLocation, StarClearRadius, 31) == false)
    {
    SpawnAsteroid(x, y, z);
    Debug.Log("spawn asteroid");
    }
    else
    {
    Debug.Log("Can't spawn Asteroid!");
    }

    worked!... notice how I have a distance multiplier in there. I forgot the asteroid spawn points were multiplied by distance, so they were acting as if they were like 500 times closer to world center was all! it's also a SUPER fast check, and no need to use an array. now if only I could actually get it to only check for the object layer I told it to...
     
    Kurt-Dekker likes this.