Frontline Command | Dev Log #1
Frontline Command (Dev Log #1)
I have always been interested in game development and have tinkered with Unity on and off in the past. Now that I have just finished my Bachelor’s in Computer Science, I wanted to keep building things on the side. Growing up, I played a wide range of games like FPS, MMO RPGs, turn-based strategy games like Civilization, RTS, and more. That said, I have always found myself gravitating toward real-time strategy games.
Because of that, Frontline Command (working title, probably going to be renamed) is the side project I’m experimenting with. The idea is a simple RTS defense game where waves of monsters attack your base. The goal is to gather resources (ideally through automation), build infrastructure and defensive structures, and survive for as long as possible. At this stage, the concept is still very loose, and I’m not entirely sure what the final game will look like.
While I’ve explored Unity before, I don’t have real experience in full-scale game development. For now, this project is mostly an experiment, a way to learn Unity, game development concepts, and software architecture. Game development is interesting because it forces you to confront architectural problems that don’t come up very often in web development. In web dev, frameworks and libraries abstract away many of these ideas, which makes it harder to explore and apply design patterns in a meaningful way. This project is my excuse to do exactly that.
In any case, this dev log is about an interesting (and horrible) piece of spaghetti code I’ve built so far.
From the video, you can see that multiple units can be selected by holding left-click and dragging over them. Group movement is controlled using Boids, a technique for simulating “artificial life,” inspired by games like StarCraft II and other RTS titles (see Boids from Standford). Getting the movement to feel right required a lot of parameter tweaking.On top of that, each unit casts spheres in front of itself to detect obstacles and performs local steering to avoid them. The avoidance logic simply pushes the unit away from whatever it hits, scaled by distance:
avoidance += hit.normal * (1f - hit.distance / length);
The avoidance vector is updated every frame. hit.normal gives the direction perpendicular to the surface hit, and its influence is scaled by how close the obstacle is relative to the cast length.
For group movement, I’m also using A* with a distance-based obstacle penalty so the FormationLeader avoids paths that are too close to walls or obstacles. This works by building a navigation grid, detecting obstacles via physics casts, and marking those nodes as non-walkable. From there, I run a BFS to calculate each node’s distance from nearby obstacles and assign a penalty accordingly. This encourages the pathfinder to choose routes that keep a reasonable distance from obstacles, depending on how intense the penalty is. When calculating the paths, we also considered the number of selected units as clearancePenalty . This means that the size of the selected units will contribute of which path the FormationLeader will take.
Meanwhile, FormationUnits follow and position themselves relative to the leader using Boids behaviour. As with everything else, achieving the movement you see in the video required a lot of tweaking.
To keep things short, the smooth RTS-style movement is currently achieved by doing:
- A* pathfinding to move from point A to point B
- Boids-based formations for swarm-like movement inspired by classic RTS games
- Local steering avoidance so units can navigate around obstacles instead of getting stuck, which greatly improves the feel when moving large groups
The Nightmare
Functionality-wise, I’m really happy with how it works. Architecturally… it’s a disaster.
Each unit has a long list of MonoBehaviour scripts attached in the inspector. This raises a lot of questions: How do I cleanly stop a unit from moving? What if a unit needs to move and attack at the same time? How do we manage unit state in a sane way? With the current setup, none of this is easy to answer.
Sure, I could keep layering more logic on top, but that would only make things worse. It wouldn’t scale, and it would be a nightmare to maintain. The solution is to separate responsibilities and introduce a state machine, where each state is responsible for specific behaviours and nothing more. This creates clear boundaries and makes the system much easier to extend and reason about.
To get there, I’m currently researching the State Design Pattern, with the help of internet resources and the all-knowing LLM agents (GPT). I’ll share what I learn and how I refactor this mess in the next dev log.