This week Sonder Games participated in #ue4 Spring 2018 Game Jam, our second team game jam. We ended up merging classic bullet hell games with thatgamecompany's "flOw" in a underwater shooter we titled "Not Every Man's Ocean" (a play on Hello Games' "No Man's Sky").
It's a free download here.
One of the features we decided on was to allow the player to build "modules" around their cockpit in a infinitely expanding grid. This isn't by any means the most difficult or complex game programming challenge, but it was quite fun and I'm hoping it might benefit someone considering (or beginning) game programming.
Create an index system for 3d vectors expanding outwards indefinitely from a center "cockpit".
The first index in the array should start outside of a 2x2 grid.
Arrays are limited to 1 dimension.
All modules should be built from front to back to give a better aesthetic to the ship.
This is a game jam, so time is critical.
Step 1: Determine the desired output
I like to approach every problem by figuring out what I'm actually looking for. I can also recommend this step for non-programming problems, but gotta start somewhere...
For this I just grab a notepad and try to visualize the system i'm creating. It looks something like this.
Yep, most of it is nonsensical, but the sheet on the left is where I have the full visualization up to the 3rd 'level' (what I called the number of dimensions from the center). This is a critical step, especially for visual mediums like games.
This also makes it much easier to identify patterns that will simplify our code.
The key things I noticed after drawing it out:
The row width and column height both increase by 2 every level. [4,6,8,10...]
The total in a level increase by 8 every level. [12, 20, 28, 36...]
The left column is always even, right column is always odd. (bottom dependent on direction)
Every level starts at the square of a multiple of 2. [4, 16, 36, 64...]
Now I think a bit more abstract about the relations...
The width and height are one more than the level, doubled. (level + 1) * 2. [1→4, 2→6, 3→8...]
When you account for the center 4 spaces or first level, the index for a level goes from that (level * 2)^2 to (((level + 1) * 2)^2) - 1. [1→4|15, 2→16|35, 3→36|63...]
Since each level starts at a square of a multiple of 2 and spans two numbers, modulus math can be used to get the level. level = sqrt(index) - (sqrt(index % 2)).
The sides are a bit more tricky, to get them we need to figure out the index 'FromZero' in it's level...
From here I figure I have about enough to go to the next step.
Step 2: Pseudo-code for the use cases
The goal is that this step is more translation than anything, we're just taking the information we already have and structuring it closer to machine-readable code. We'll also assume we have the necessary variables and they work.
Looking at our problem, there appears to be 4 distinct cases.
The Index is on the top row.
The Index is on the bottom row.
The Index is on the left side.
The Index is on the right side.
Pretty simple, right?
So, lets figure out how to determine these.
Top Row: FromZero will be less than Width.
Bottom Row: Index will be greater than Width^2 - Width.
Left Side: None of the above AND Index is even.
Right Side: None of the above. (Index will be odd)
Boom! With all this prepped we're ready to put it together.
Step 3: Put it all together
First, we'll declare all the variables that we can use from Step 1.
As Input: [Index]
To simplify logic: [Width, Level, and FromZero]
As Output: [XPos, YPos]
Now we'll define the variables used to simplify logic. Since Width and FromZero both depend on Level, we'll solve that first. We stated before the equation to get Level, however the square roots increment by 2, so we need to divide by 2.
Then we can easily see Width increments linearly, twice per Level.
FromZero is Index minus the start Index of the Level.
All that's left is to put in our cases. Once you have everything else, the XPos and YPos are trivial to calculate. Keep in mind X goes left - right and Y goes up - down.
This will calculate XPos and YPos as 1-unit grid locations, since our project had modules be 1 square meter, we just needed to multiply by the spacing and add on the offset. In our project it was as simple as: