Welcome to our nineteenth blog post!
We welcome you back to the second part of our Open World system. If you missed the first part and would like to check it out, click here. We’ll be continuing on from there.
We must first begin with our Map Generator- we’re using a fairly interesting algorithm that allows for very organic shapes being randomly generated, with some (but not perfect) persistence via a Seed. This algorithm is known as Cellular Automaton (or Automata), and its quite an old, yet intriguing algorithm.
The core algorithm is simple: randomly generate a series of 1s and 0s (1s representing Walls, 0s representing ground tiles), and over a series of iterations, determine which neighbors are “most like each other” to essentially get a very organic cave-like system. A simple definition means that a tile becomes a wall if it has 5 of its 8 neighbors as walls (well, the majority of its neighbors are walls). By repeating this process a few times (but not a lot of times), we can get a fairly good shape structure created.
But a fantastic Unity-specific one was done by a guy named Sebastian Lague- if you’re interested in coding, especially with regards to Unity, you should definitely check him out. Here’s a link to his series.
This project, Fragment’s Moonrise, is using a heavily modified version of the central idea behind the Cellular Automata algorithm, to of course fit our needs as it pertains to the project. There’s a series of things we need to ensure and enact in order for the algorithm to actually work and maintain stability.
The first is absolute connectivity- our maps are guaranteed so that any “island’ is connected back into the mainland, so that all of the world is traversable. This is, of course, checked over to ensure there is no terrain that cannot be reached. We merely look for segments of unconnected land, and merge them into their nearest neighbor.
The next is assembling the series 2D sprites, and to do this one, we need a little bit of help from Unity to fully complete.
This is because a 2D map like this, especially one containing a 200x200 array of tiles, needs use of Unity’s Tilemap system in order to function and still achieve 60fps+. The Tilemap system essentially converts our array of tiles (GameObjects) into a single sheet, while still allowing it to be editable (which will be important when we begin adding in the Passageways). This single sheet genuinely converts thousands of GameObjects into pretty much just one, saving lots of memory, and greatly improving performance.
Link to basic Tilemap.
Link to a short tutorial.
The last modification is to pair our generated Map as to work with our Pathfinder. We’re currently using Aron Granberg’s A* Pathfinder as its an incredibly smooth and very fast algorithm. More info here.
To perform the pairing process, we just need to ensure that all our tiles denoted as a Wall are referenced by the Pathfinder so the Pathfinder understands that terrain cannot be traversed. This means, as part of the tilemapping process, we need to still have the impassable tiles denoted.
Those are the core algorithms we are using to assemble and use our Maps. We decided to go with the Cellular Automata algorithm to give us a fairly open, yet dense land, and, more importantly, to give us a system of randomization so that no two maps are the same. This is key, for the game will definitely feature a good amount of replayability. In addition, the straightforwardness of the map generator allows us for easy modification as we see fit. Maybe in the future we’ll develop a much more varied and significantly more seamless open world system (something that can be done without the use of nodes, alongside an altered map generation system), but for now, this seems like the best base for the game.
Now, let’s see how we’ll be tying our Map Generation in with our Node Generator to complete our Open World.
Given that we have full access unto our map generator and tile system, as part of the generation process, we now begin to spawn objects we’ll be calling Passageways.
Passageways have some unique characteristics about their generation- first and foremost, when looking at our Node Generator, you’ll notice that no Node is aligned to a grid: their all free-floating. This is critical to represent in the game world, as the Node Generator and the real Maps player will be exploring need to be in sync.
So, the first thing we must do is examine two connected Nodes, and determine the angle between them.
Using basic trigonometry, we can figure out the angle, in degrees, and utilize that degrees to determine our spawning location for the Passageway (its a simple "determine the angle between two points" sort of algorithm). We want to place the Passageway in the appropriate spot in the world so that it represents what the player will be seeing in the Node Generator.
By treating our Map as a Circle, we can utilize the angle adequately in order to spawn our Passageways. By simply treating the angle as a ‘vector’, and traveling along its path to the edge of the Map (noting that the ‘center of the circle’ is the center of the Map), we have now found our spawning location (and, upon placing it, we can carve out the part of the Map back to the first walkable tile)
Now, with our Passageways spawned, we can correctly hook them up. The logic is straightforward- when player gets near the Passageway, they are allowed to activate it, and upon doing so, they are brought to the next Map.
Now, why didn’t we just take the significantly easier approach and align everything to a grid? The answer is just that- then we’d have to align everything to a grid. This approach gives us a significantly more organic system that’s much more fluid for exploration. Its much more expansive, and lends itself to additional uncertainty and unknowingness from the player as they explore the world. Implementing a grid system and our node system would have likely taken equivalent amounts of time to complete, and our node system gives us a much, much better result both aesthetically and functionally.
This Passageway system allows for a nearly-seamless interpretation of an Open World while still retaining the core aspects of a Real-Time Strategy. The system is based around a series of Maps, but instead of loading them seamlessly (based on player position), we load them upon Player’s request. This gets us around the issue given that the player can have a multitude of Units, each going a different direction, and ensures stability with a solid framerate. There are some ideas we may play around with to get our system even more seamless, but as it currently stands, this is the best solution that still allows great performance.
Lastly, something we want to touch on briefly is our aspect of Time. Time is critical, as our Maps are loaded in a single state, and unloaded when not used. We need to ensure things still ‘function’ as they would in an Open World (notably, with Player’s Home Base) without the need for having things loaded at a given instance.
We can do this simply by timestamping things at specific moments, and retaining that across both Maps and also sessions (in which Player stops playing, shuts the game down, then restarts it at a later point).
In the next part, we’ll go over how this will be explicitly used in regards to Player’s Home Base (the area we want Time to be persistent in), alongside go over some of our convenience features, like world traversal and teleportation across the maps.
Thank you for viewing our post! Support and interest for the project has been rapidly growing ever since we began posting here, and we're incredibly grateful for all the wonderful feedback so far! We hope this project interests you as much as we love developing for it, and please look forward to more updates coming in the very near future!
If you’re brand new, consider checking out our trailer and overall description of the game here.