Let’s Make a 4X Space Game with Unity – Part 2 Making the Stars Unique
Hi guys! Welcome to part 2 of a tutorial series on how to make a 4X space game in Unity. In part 1 we looked at how to make a galaxy with the Unity primitive spheres as our stars.
In this part of the tutorial we will look at:
- How to stop the stars from overlapping with each other.
- Creating data for each star so it is unique.
- Look at how we can make a ring galaxy instead of a disc galaxy.
1. Stopping the Stars Overlapping
There are several ways we could stop the stars from overlapping. For example we could make a list of the stars positions and make it so the game checks the positions before it creates the new GameObject. This would take up memory and be relatively slow. We could use the stars collider component to check there isn’t a collision and if there is to try again. This could be good but it could mean stars are right next to each other with very tiny gaps between them.
A better solution would be to create a temporary collider which has a larger diameter than the star (the default primitive sphere size is 1 unit). That way we can control the minimum distance between stars. Unity has a handy way to make such a collider – Physics.OverlapSphere. This requires an array of collisions because it could be possible to collide with more than one other star. This will be added to our for loop in the Galaxy script as well as an if/else statement: If there is no collision make the star, Else try again.
If you press play now none of the stars should be overlapping and the minimum distance between them should be one unit (between their centres).
We can improve the way this works by adding a public float to our class which will allow us to control the minimum distance the stars are away from each other. Then we change the 2f in the positionCollider to the name of the new float. Lets call it minDistBetweenStars to make it clear what this float does.
Now we can easily change this number to make the stars spread out more. However, there is a limit to how much we can spread them out. Using our current example of 300 stars in a maximum radius of 100, the maximum number we can put in is approximately 7. Go over this limit and unity crashes. This is because we have created an infinite loop in the program and the stars will never get placed. The maths of fitting circles inside circles is very complex and so a good solution would probably be to settle on a few galaxy sizes (for example small, medium and large) which have set radii and star numbers. This is getting a bit ahead of ourselves though so instead let’s insert a count to our loop to count how many times the placement fails. If it fails a certain number of times we will break the loop. This number should be relatively high as to not accidentally abort in a viable galaxy. Declare a new int before the for loop called failCount and set it equal to 0. Then in the if portion of our loop we need to set this to 0 and in the else part we need to add 1 to the count. We also need to add a new if statement to break the loop if the failCount goes above a value. Lets set this limit as numberOfStars.
Finally we should add an error message to let us know when we have aborted a loop.
Now we should be safe from infinite loops. We can test this by setting the minDistBestweenStars to 10 with our default 300 stars and 100 radius settings.
So we now have a safe(ish) setting where we can change how spread out the stars are and not crash unity in the process.
Before we move on there is just one more thing we need to do. At the moment the sphere is the first thing created in our loop. If you run through all the sphere locations you might notice some overlap at (0,0,0). This is because if the loop fails and retries the old sphere will still be there. We should therefore move the line which creates the sphere to the if statement so the sphere will only be created if there are no collisions.
2. Creating Data for Each Star so it’s Unique
It’s time to write a new script, so in the scripts folder create a new script and call it Star. This will be a pure class so delete the “: Monobehaviour” next to the class name and the Update and Start methods.
So what do we want our stars to have? A name would be a good start so lets declare a string and call it starName. Stars also have planets so we should add an int and call it numberOfPlanets. You could also add other things like star colour or type, age or size etc. I will leave these choices to you; for now I am sticking with just name and planets to keep things simple.
Now what do we do with these? We need to add a method to be our Constructor and we will name it Star to match the Class name. It will need to be public so we can call it from other scripts like out Galaxy script. We should also add getters and setters to our star attributes we declared before. The setter should be protected so that the attributes can only be changed in the Star Class.
So what do we add to our star method? Well firstly we need to pass it a string and an int so we can set the starName and numberOfPlanets for the Star (if you added more also pass variable types which match the variables you declared; for example if you also added an int for star age, pass it an int). You can either call the passed variables the same thing as the declared ones or change the name. I will be changing the names just so its a bit more obvious whats going on. We then need to make our starName and numberOfPlanets equal to our newly passed variables.
Note: The “this” part of the variable setting lines is completely optional but I will be as obvious as possible throughout these tutorials so it is clear what is going on.
Now we have a Star class we can go back to our Galaxy script and make some star data for our GameObjects. In our for loop add a local variable called starData, its type will be Star. Set this equal to a new star with the name set as “Star” + i . This will name all our stars StarI where I is the number the loop was on when the star was created. In a later tutorial we will look at importing a name list for the stars but this will do for now. Use Random.Range to set a range of how many planets a star can have. I will be using 1 – 10 for my range but feel free to have any range you like.
Now if we press play we create the data for each sphere object, but at the moment we can’t see this data! In the next tutorial we will look at how we can connect the two things so that if we click on the sphere the star name and number of planets appears in the console. However, for now we will just add a Debug.Log command after we create the starData to output all the stars in the console. This will prove to us the concept is working.
Before we move on to discussing ring galaxies, we should name our spheres the same name as the star data created for that sphere. This is simple enough all we do is set GameObject.name equal to starData.starName.
3. Ring Galaxies
In most 4X games, the player can choose from a selection of galaxy shapes. So far we have made a purely disk shaped galaxy. In future tutorials we will look at spiral galaxies and how to make them with different numbers of arms. However today we will look at a type of galaxy that sometimes appears in games, a ring galaxy. There are no stars in the centre only around the edges in a donut shape.
To go from a disk to a ring is really easy. All we have to do is add a new public int called minimumRadius to our galaxy script and add this variable to our distance Random.Range as the first number (the smallest number the range can be).
Play around with the number and see what results you get. Remember you may need to reduce the number of stars or the minimum distance between them because as minimumRadius increases the less area there is to insert the stars.
One last thing before this tutorial ends. At the moment the minimumRadius can be set higher than the maximumRadius. This will probably cause bad things to happen if that was the case so we should just add a simple if statement to the beginning of our start method to check minimumRadius is smaller than maximumRadius and if it isn’t they should swap values.
And that’s the end of this tutorial. In the next part we will tidy up the code we have so far and start setting up solar systems!
scripts_for_part_2 (zip file)