projects > Senior Project Update


Senior Project Update

Published at 2024-3-29

godot
game dev
senior project

Hello there! I Have been hard at work on my senior project for the past quarter, and I’ve decided that I’m at a point where I’d like to share a little sneaky-peaky of where I’m at and what’s soon to come! This project has been a thought bubble for a few years now that has refused to leave my brain, and I’m so glad to be working on it now! It has been a super fun and rewarding experience so far, and I’ve learned so much about procedural environment generation and game development in Godot.


So first off, you all are probably wondering: “Now Jacob, what the heck is this silly little project that you’re working on?” Well, I’m so glad you asked! My senior project is…


a game 👾


Let me elaborate a little bit further. This game revolves around backpacking between backcountry campsites on a trail through a mountainous, procedurally generated environment. Along the trail, you will run into NPC characters that share their stories of their time on the trail. The twist, though, is that each of these NPC characters’ dialog will be powered by generative AI, creating unique backstories for each NPC the player meets. This will create an immersive experience that makes the game replayable and enjoyable each time.



Terrain


Most of my time has been spent on making sure the terrain system is optimized and works properly. While there are a few terrain systems already implemented in Godot, I decided to create my own in order to ensure that it fits my use case. I found a couple really good videos that walk through some methods for infinite terrain (devmar’s video on clipmap terrain and SimonDev’s video on quadtree terrain). After lots of consideration of the two options, I decided to go with the wandering clipmap method, as it was easier to implement without too large of a performance hit. As this is my first journey into game development, getting familiar with Godot and reading up on the documentation took quite a while. However, Godot’s node-based scene system made it pretty easy to find my bearings and cook up a pretty good terrain system.


In my clipmap-based system, the terrain is "infinite", and is generated based on simplex noise. Why is infinite in quotes, you ask? Well, the clipmap terrain method relies on a ton of smoke and mirrors to create the illusion of infinite terrain. In reality all of the "terrain" is a single subdivided plane that is parented to the player character. This plane is used in conjunction with a vertex shader (using the aforementioned simplex noise as a heightmap) to adjust the y values of the plane's vertices, based on the location of each vertex on the heightmap. If I lost you there, take a look at this little animation:


Each vertex has its own y-value, based on the heightmap. This y-value is multiplied by an amplitude value to get the full height. Godot also has a very nifty trick that allows the generated noise to also be used as the normalmap for the plane, so I don’t have to worry about shadows not being cast correctly. All of this together forms the foundation of the terrain system, and with a little bit of fragment shader code, we get some pretty nice looking mountainous terrain.



Trails


Next, I worked on trail generation. This is pretty simple, using the A-Star algorithm to generate a path between two points (campsites). Godot has a pretty well optimized implementation of the algorithm already. However, I needed to extend the class to ensure that the cost function is based on the height of the heightmap. The cost function was heavily based off the function discussed in Rune Skovbo Johansen’s blog post: Creating natural paths on terrains using pathfinding (he has some wonderful blog posts, highly recommend checking him out). Essentially, the cost function ensures that trail doesn’t change slope too drastically all at once, favoring gradual slopes. This, in addition to providing a limiting slope value, results in pretty natural looking trails that contour to the slopes of the terrain. This also has the added benefit of generating switchbacks, which are commonly found on real life trails! The generated trail is saved as a pathmap, which is used when texturing the terrain to create a dirt ground wherever the trail goes.


I had a little bit of trouble in making sure that the trails were actually flat, and not just texturing the slopes of the mountains. My solution to this was to modify the heightmap itself, creating flat terrain that follows the path the trail takes. This makes it so wherever the trail goes, there is a little bit of leeway for the trail to run. This gives the added result of digging into the terrain a little bit, creating trails that integrate well with the terrain.



Nature


The nature elements of the game are incredibly important, in order to facilitate player immersion. All of the trees, rocks, and physical elements are handled by one single instancer script.


The instancer script creates and manages a large number of instanced 3D objects that are distributed across a grid. A multitude of different parameters allow for customizing the mesh’s, scale, rotation, and position randomization of the instances. The instances can optionally have collision shapes, which are positioned for only nearby objects to optimize performance. The script uses a MultiMeshInstance node to efficiently render many copies of the same mesh. It samples the heightmap to position the instances vertically based on the terrain elevation. The number of instances, spacing, scale ranges, rotation ranges, randomization amounts, and whether to generate colliders are all configurable via exported variables. On each frame, it updates the instance positions and transforms to be centered around the player while maintaining their randomized properties. Essentially, the instancer provides a way to dynamically populate the 3D environment with scattered instanced objects.


The non-physical elements of the terrain (grass and flowers), are handled by a particle shader that is run on the GPU. This works pretty much the same as the instancer. The foliage shader is parented to the player character, and generates and renders foliage around the player. The shader has several different parameters for positioning, scaling, rotation, culling, and distribution. It calculates particle positions based on an indexed grid with randomization, samples heightmaps and normalmaps to conform particles to the terrain’s elevation and slopes, culls particles outside specified altitude ranges or on steep slopes, creates clear paths by removing particles near a pathmap, implements clumping based on a clump texture, applies randomized scaling and rotation with optional terrain normal orientation, and finalizes each particle’s transform with the calculated position, scale, and rotation before rendering, allowing for highly customizable foliage that integrates seamlessly with the terrain geometry.



All together now!


With all of these combined, the whole game starts to really come together! Check out this little video!



So yeah, that’s where I’m at in terms of the terrain and environment! I’m super proud of where I’m at, considering this is my first true game development experience. I’m still stoked on the game, and by no means am burnt out. I’m excited to work on the AI part of the game throughout this next quarter!



Other odds and ends


In addition to the terrain system, I also worked on some other things, related to UI elements and scene logic. I spent some time on cooking up a main menu, loading screen, and a scene manager for loading different scenes.



I really like how the main menu screen turned out, and especially like how the title screen contains motion. Oftentimes, I feel like title screens can be a little bit dull. In my eyes, the title screen should play a role in making sure the player is excited to play the game! I think having a live title screen helps make the player excited and eager to play the game. I also really like how I was able to highlight the “AI” in the title to emphasize it. It definitely came as a spur of the moment type thing, but I’ll just say it was all planned out. 😉 For loading, I used some of the knowledge that I obtained from my operating systems course regarding threads to do all of the trail generation on another thread while displaying a loading screen, making it so the game doesn’t freeze while loading.



Outro


I have a ton of work to do if I want to finish this up by the end of the upcoming quarter. I’m excited, but it’s definitely a nervous excitement. Not so much nervousness about not getting it done, but more so a nervousness about getting burnt out because of an approaching deadline. I really don’t want the impending due-date to prevent me from getting to things that I want to implement in my game. I also have to wrap my head around the generative AI part of my project. I’ve been learning a ton through HuggingFace courses, but I definitely know that there’s going to be a learning curve to that, just like there was a learning curve to learning Godot.


However, at the end of the day, I’m incredibly proud of myself. I decided to do a senior project on not just one, but two areas of computer science that I’m unfamiliar with. As a result, I’m broadening my computer science knowledge, and having an absolute blast learning about game development and AI/ML. I definitely will do some more fun stuff with Godot in the future, and who knows? Maybe this little silly senior project will end up being something that I continue to work on afterwards, eventually resulting in a full-on game!


Anyways, it’s 11:37 at night, (still got the post in today though, told ya, Kassi 🤓) and I need to go to bed! Farewell!