Welcome to our eighteenth blog post!
With the advent of our open world system beginning its integration, we thought it would be a good idea to discuss how this system will work, as we’ve previously never gone over such a topic. As always, if you’d like to know more detail of how this was integrated, go ahead and send us a message.
The core world aspect behind our project, Fragment’s Moonrise, is establishing an open-world setting while still emphasizing our real-time strategy based gameplay. There’s quite a bit of techniques and design philosophies that go into it to ensure playability is still at the forefront. In this short series, we’ll be going over how our Open World system will be integrated, and how it will work programmatically.
We need to first start with how we’ll be basing our world- we want randomness, so each playthrough is unique and different, as exploration is key.
Introducing our node-generation algorithm: it works quite simply, and allows us to essentially create a network of nodes that will be the basis for our open world. You can think of each node as a map- strung together via their connections.
One thing to keep in mind while we discuss this algorithm is that our engine is Unity, and therefore we can utilize quite a bit of the engine’s features in order to further help us acquire this goal. Doing this purely mathematically can get incredibly complicated very quickly, so in order to ease our desired goal, and give us something Unity can understand, we need to base our code around our engine.
We begin by placing nodes randomly. This serves as a base- we want X amount of nodes to represent the X maps player will be allowed to explore.
Then, we can begin the connection process. What we want is a clean set of nodes- we definitely do not want the connections to be like a spider-web, meaning we don’t want anything to overlap.
Thankfully, the engine can help us out quite a bit.
We begin at our first node in our List, and enact a raycast determining all nearby nodes that we can hit with said raycast. We aren’t worrying about overlaps at this point- we’re just worrying about how many other nodes this particular node sees. We repeat this step for each node spawned. Bear in mind: we’re spawning physical gameobjects of the connections as a black line with a collider attached.
Then, we begin to eliminate connections. Remember: our goal is a clean series of nodes. Taking both collision and length (distance) into account, we go through and check out which connections overlap (collide) with one another. When we find a collision, we look at the two connections, and pick the longer the two to eliminate. We do this because we only want the closest two nodes to be our connection, so the node system primarily considers its immediate neighbors first. We repeat this until we have no more colliding connections.
This gives us a fairly clean series of connections, but, there’s still one more critical step: ensuring all are connected.
Starting at any node (can be random, or can just pick the first one) we traverse across its connection(s) (think of this like Breadth-first search). As we traverse, we mark all discovered nodes, and, when we’re done with the traversal process, we merely check to make sure all nodes were discovered. If they aren’t, we can attempt to see which node(s) have not been discovered, and attempt to force a connection nearby, then run another check to ensure all are discoverable.
While we could just check our nodes for if they have a connection or not, this could give us ‘islands’ in which some nodes are only connected with each other and not the mainland, and that is something we definitely do not want. We want our premise to be on total discoverability, and ensuring such a thing takes place.
That’s just the algorithm for one particular set of nodes- this set being referred to as our “environment”. We now need to cross-connect them with other environments, to ensure traversal across the world as a whole.
Our particular game is currently only utilizing 4 environments (with a few extras we’ll add later on). This makes cross-environment traversal much more simple, as all can be assembled and shaped like a square. Utilizing the same tricks of generating connections as before, we generate cross-environment connections to fully integrate the map system, and ensure connectivity across the world.
You may notice the barrier between the environments- this ensures all environments are first generated unto themselves, and then when we are ready for cross-generation, we simply drop (ignore the collision of) the barriers.
Finally, we can do all our other miscellaneous things, like add in our Sub-Environments to serve as unique territories, usually containing stronger enemies and bosses. There will, of course, be the necessary balance integration, to ensure players with weaker armies can get past those areas until their ready. But that will come much later.
This was just step 1 of our Open World system- in the next blog post, we’ll go over how our map generation works, and how that will be integrated with our node generation matrix. We’ll also discuss time as it persists in the world, and how that can play a key role in ensuring things still function even while not loaded. We hope you look forward to it!
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.