Think Ahead


Why did I do this project?

The goal behind this wasn't to make a commercially successful game or even to release it. The aim was to take a project from nothing to completion, practice planning and designing my code base/project, and finally, to gamify spatial reasoning skills.

Spatial Reasoning

Spatial reasoning is the ability to visualize transformations of an object in 3D space. This ability is crucial in computer graphics, so this game is a way to flex those muscles. In Think Ahead, the player must select all their moves upfront, forcing them to visualize the pig’s movement before executing. A graphics programmer’s ability to use spatial reasoning is valuable because it lets them write code that translates to a visual thing while picturing what they are doing in their head. It provides a visual method of debugging, which is a powerful tool alongside traditional code debuggers. Being able to see a bug displayed and backtrack it in your mind can save a lot of time. This skill comes with experience, but practicing never hurts.

Abstraction

Think Ahead, being one of my earlier projects, lacks a clear code architecture or pattern, but I do follow rules that I wanted to practice. Starting my journey into graphics APIs, I noticed abstraction is a huge and important part of programming. The code base for Think Ahead focuses on abstracting out complicated functionality into reusable classes and functions, which also inherently satisfies the DRY principles. An example is the Grid generation system. The grid can be generated in the UE editor by simply setting rows, columns, and selecting the “generate” button. This is an abstraction of what I’ve done behind the scenes, where in reality I’m using procedural meshes to generate each tile, find the next vertices, and do it again to create a resizable dynamic grid.

        void AGridTile::OnConstruction(const FTransform& Transform)
        {
          Super::OnConstruction(Transform);
        
          Width = TileSize;
          Height = TileSize;
        
          TArray VertsBuffer;
          TArray TrisBuffer;
        
          GenerateTileVerts(VertsBuffer);
          GenerateTrianles(TrisBuffer);
           Verticies = VertsBuffer;
           Triangles = TrisBuffer;
        
          TileMesh->CreateMeshSection_LinearColor(0, Verticies, Triangles, Normals, Uvs, VertexColors, Tangents, true);
        }

Mesh Generation

One of the things I was very set on using in this project was procedural meshes. I wanted to learn how to draw vertices, connect them with tris, and then fill those tris with a face. Starting with a plane seemed like the best way to learn how these parts connect. Doing it this way introduced me to concepts like normals, backface culling, and handedness of a 3D system. In the tile OnConstruction, I broke down what can be seen as a ‘simple plane’ into all of its more complicated parts.

What I Learned

Think Ahead taught me the importance of abstracted code separated into units of responsibility. I saw the value of having smaller, abstracted code that I could reuse, not just because it's easier to write but also because it's easier to revisit. In my future projects, I aim to implement more of this organization and focus on decoupling code further to minimize dependencies as much as possible.