Project Oregon Devlog 1 - 13/10/21

Back to Main Page

As I move into my third year of university, I need to work with my team to produce a final year game project. My team decided early on that we wanted to do something in VR, and we had an idea for a roguelike set in space, with spacechips that automatically randomly generated around you, and a system tht would allow you to fully customise the gun by picking up parts such as new barrels or triggers, which would have different effects on the function, such as increasing accuracy or fire rate. With this idea in mind, I started making some prototypes, and had a working system for the customisable gun mechanic working in a day or so, and even started doing a little worldbuilding for the setting.

Then, as I was offline at a family gathering, the entire team decided that they would be abandoning the idea completely, and insted opting for a setting the game in a forest, fighting cryptid monsters, but keeping the scifi customisable gun mechanic. Fine. Sometimes this kind of thing happens in team-based development and you just have to roll with it (I'm still somewhat annoyed that my idea was rejected though, as I thought the concept was super cool).

The problem that immediately presented itself was random generation of a forest. The standard method for an indie game developer to mkae a random forest is to use Perlin Noise to make vaguely undulating ground, then sprinkle trees around randomly, maybe using Poisson Disc Sampling if you feel fancy, but honestly I just didn't want to do that. There is no challenge in making a system like that, and you also don't get a whole load of say in how the terrain is structured, as it is fully random.

So after some thinking, I came up with my own system. Instead of creating one huge area completely randomly, we get the artists to make individual tiles of convincing looking forest, with rocks, trees, moss etc, and then randomly place the tiles around the world. That way, the look of the forest can be tailored reasonably easily, but we can still generate forests quickly and easily. One of the designers also told me that he wanted to have specific "interesting" areas, such as a radio tower, or a shack, where events could happen, but he didn't want them placed as often as the default tiles, so I created a system that could handle that too.

As this game was being made in Unity (my engine of choice), I had access to jazzy features like Scriptable Objects. After being introducted to them properly in my first year by one of my lecturers, I have grown a soft spot for them, and use them pretty often in my work. Scriptable Objects are similar to normal scripts, but exist separately from a gameobject, and are used to store data. You could, for example, create a WeaponData scriptable object, and store the damage dealt, accuracy, name, description and model, then just have a generic weapon script on the player that takes in a WeaponData object. You can then store all the scriptable objects separately, and easily use them between other scripts. They might seem somewhat redundant in this example, but once you get used to using them, they can be a huge help.

So for my tile system, I created a Tile scriptable object, which takes in a prefab of the tile itself (including things like scenery, enemy spawners, sound effects), as well as what kind of terrain was on each edge (for example Clear, ConiferousForest, DeciduousForest), and it also internally calculated how many trees are in the area. The edge types were another scriptable object, this time being used as a kind of glorified enum.

Now that I had a representation of all my tiles, I could generate a world with them. I do this in a spiral formation, rather than just doing the rows and columns of a rectangle, because that way it becomes less obvious that the world is made of tiles, since the terrain would only ever depend on the tile down and left of the current tile. It also makes it easier to generate the world infinitely (if that is ever needed), since I can just place the generator on the tile the player is on, and generate again. I thought I would try something new and use an IEnumerator to get the next position to generate a tile at. IEnumerators in C# are a cool feature that lets you have methods that can keep returning data, and be paused each time they return, but will still keep their internal state.

The youtube channel Aarthifical has done an excellent video on these, better than I can explain

What my IEnumerator does is constantly yield return the next position to generate a tile at, following the spiral. Then, I have another function that will continuously get the next postion, check if it is occupied, and if it isn't, will analyse all the tiles around it to check which tiles have compatible edges. Every time a tile is generated, it is added to a dictionary, where the key is the position and the value is the tile type itself (The scriptable object mentioned earlier). That way, it can prevent generating tiles on top of each other, and it is what is used to check what the edges of the adjacent tiles are.

I also added a feature that the tiles calculate how many trees are in them, which allows the potential tiles to be ordered by density, and an option to bias them towards being more dense. This is what it looks like at the default value:

Sparse terrain generation

And with the bias turned almost all the way up:

Dense terrain generation

At this stage of development I am still operating on programmer models and environments, so it doesn't look amazing, but once I start receiving some proper artwork, this will start to look a lot better

Once I had that basic system working (and some bugs fixed, as usual), I started work on the "Interesting" tile system that the designer had requested. This wasn't too hard, I simply made more tiles similar to how the tiles were being generated before, but assigned them to a different group of tiles (yes, Tile Group was another scriptable object I made) than the basic filler tiles. Then, I could pick some small amount of random interesting tiles, randomly place them in the world (in the same position that a boring tile would be placed), and add the them to the same dictionary that I was already adding the basic tiles to. That way, they original generation script picked up that the space was already occupied, and skipped it, and also picked up on the edge configuration for the tiles around it.

This concluded the basic world generation system, and now I am just waiting on assets from the environment artist to flesh out my janky programmer art tiles, at which point I will probably need to update the system to fix some esoteric bug that has popped up, or some new feature request. Until then, I decided to move on to some basic enemy AI. The unity navigation system relies on a navigation mesh ("navmesh") being generated, telling the agents what areas are walkable, and allows them to use pathfinding to find the optimal route to any destination. However, by default the navmesh baking system can only be done in the editor, which doesnt work for my procedurally generated levels. Luckily, unity is working on a new navmesh system, which is currently available as a preview package, and this system allows for navmesh baking at runtime.

Usually, using preview packages in an important project is discouraged, however I don't have much option, since I while I enjoy a programming challenge, coding an entire navigation and pathfinding system is curently a bit beyond me. It is also quite slow at large sizes, so I will have to investigate how the system works properly, so that I can optimise the tiles and generation as much as possible.

Now that I had a working navmesh, I had to start working on enemy pathfinding. Unfortunately, I had an issue with the agents, as they could be created before the navmesh was finished generating. While this should not be a problem in final gameplay, since the enemies will not be spawned until the player gets close (so save some processing on pointless enemies), I still wanted to try and fix it. To do this, I disabled the agent component on the enemies by default, and enabled it 0.1 seconds after it was spawned, to give it a chance to find everything. I also implemented some basic object pooling, to help try to keep object destruction and creation to a minimum, improving processing and hopefully keeping microlags to a minimum

Once the navmesh was all set up, I could start directing the enemies around. This is currently done using a waypoint system that I made. It's mainly just a list of vector3s that the enemy goes through, and once it gets within a certain threshold of its target waypoint, it will switch to the next one. What I'm proud of is having the editor display the waypoints before the game is played. Unity has the OnDrawGizmos() and OnDrawGizmosSelected() methods, which are called when the editor view wants to draw Gizmos, which is unity's word for the editor graphics that are used for things like the arrows you drag things around with. In this case I am just using it to draw lines between the vector3s that make up the route. The route is shown here in the bright green line:

Waypoints shown when the object is selected

There will not be a download for this entry, since the game is still in development, but when it is finished I will almost cetainly add an itch.io link or similar.

POST UNIVERSITY UPDATE: So this project didnt really go as I had hoped it would. For example, this whole terrain system that I enjoyed making was trashed by the designers despite me explaining how it should work to them. In the end, this game that I had high hopes for became something that I really hated, and I don't plan on writing up a second part of this devlog.

Back to Main Page