How software developer tried game development

Right

I have several years of experience in IT, but I’ve never worked in game development. That didn’t stop me from participating in the Tech Jam by Facepunch for the yet-to-be-released s&box. In this article, I’ll share my experience—along with all the failures along the way.

Preface

This article was originally posted on the s&box website. Here you can find a slightly extended version — in the original post I stripped descriptions of me and the s&box platform. I thought it's nice to restore the cut content, so now you can read the full article.

About Me

I occasionally write a blog about OOP and static analysis, mostly focusing on compiler-related topics. As you might have noticed, game development is not on that list. My professional experience is limited to web development and static analysis.

That said, I have some experience with game development. I started with the Warcraft 3 editor and spent quite a bit of time working on add-ons for Garry’s Mod. Not to mention the many small games I made using Love2D that never saw the light of day.

An unreleased game I was making a year ago

Still, I wouldn’t call myself particularly competent in this area, which made this adventure all the more eventful.

About s&box

If you haven’t heard, Garry’s Mod recently made headlines as the best-selling PC-exclusive game of all time. Now, almost 20 years later, its spiritual successor—s&box—is on the way. It takes things further than its predecessor, positioning itself somewhere between the original Gmod, Roblox, and, surprisingly, Unity.

One crucial aspect of s&box is that it includes a development platform built on top of Source 2. It features its own game editor, the Source 2 level editor (Hammer Editor), an asset database, and a C# API—meaning it has a lot in common with Unity.

It’s hard to spot a difference at the first glance

After years of closed testing, s&box became available to the public in mid-2024. Anyone could request access to the game from the website, and it remains in semi-open testing today. Many features are still missing, and the API is quite unstable.

So far, the developers have hosted two game jams, and now they’ve announced a technical jam. I tried participating in the previous one, but I couldn’t quite handle the learning curve. After working on a puzzle game for a while, I decided to give it another shot and participate in the tech jam.

The Idea

Every game jam starts with a solid idea. At first, I wanted to create a simplified version of a space simulation engine—something between Kerbal Space Program and Space Engineers. Fortunately, I quickly abandoned that idea due to its complexity.

Ironically, someone else ended up submitting a project that looked like a mini Space Engine.

So, I started thinking about what I could realistically accomplish. Being a fan of a well-known survival game series featuring anomalies and artifact hunting, I came up with a simple idea: create AI bots that could surround the player. In the game, this is usually accompanied by a memorable battle cry: “Sboku, sboku zahodi!”

Thus, the name SbokuBot was born. To this day, I still wonder if it was a mistake not to call it s&boku bot to take advantage of the obvious pun.

I thought this was a good choice since game AI is often based on graph structures—something much closer to my professional skills (I even wrote an article about it recently). The initial concept was as follows:

  • The bots should be able to surround the player, actively using cover.
  • They should hide while reloading and move between cover spots while teammates provide distractions.

At that point, I started figuring out how to implement it. s&box already had a NavMesh with an API for pathfinding. The tricky part was the cover system. How should I determine cover locations? Manually? By scanning and memorizing the environment? Extracting them from geometry?

Initially, I wanted to do this using a weighted NavMesh, but when I first opened the API reference, I was surprised at how minimalistic it was. The only thing I could do was find a path from point A to point B. That meant I had to refine my idea based on a very broad task description.

NavMesh display in s&box

Development Begins

The next question was: where do I start? s&box is very much like Unity in the sense that everything—including the character controller—has to be built from scratch. So, basing my project on an existing system was rather a necessity.

Facepunch had made their own shooter, Nicked, which I originally wanted to fork. Thankfully, its source code was open.

Counter-Strike like game mode

However, after reading discussions about Nicked, I decided against it. It was too bulky, and I needed something simpler. That led me to Simple Weapon Base (SWB), a weapon pack with a built-in FFA deathmatch mode—exactly what I needed.

Screenshot from the official page

I was thrilled to see that SWB already had dummy bots included, as if everything was ready for me to just add AI. Or so I thought.

Simple Weapon Base

While digging through SWB’s internals, I was surprised to find this in the PlayerBase class:

[Sync] public bool IsBot { get; set; }  

This meant bots weren’t separate entities—they were just flagged with a bool. The entire system relied on if statements to determine behavior. That’s when I first suspected things would be more complicated than I expected.

And indeed, SWB wasn’t designed for bots at all. In the weapon class, for example, reloading was handled like this:

else if (Input.Down(InputButtonHelper.Reload))  

This meant weapon actions were directly tied to human input. The same applied to shooting. The funniest part? If you gave a bot a gun and held down the left mouse button, both you and the bot would start firing—because the weapon component responded to your keyboard inputs.

While I solved the issue with the property instead of the class by creating my own controller that implemented a common interface, this time I had to dive into the project’s code, refactor it, and extend that common interface.

Just for fun, I decided to use a human avatar as the enemy and let the player control Terry. This turned the game into a battle where the old avatar takes down the new one.

Fixing the bot logic took me almost a week. The sheer amount of new information slowed me down significantly. Even something as simple as rotating the bot’s camera towards a target was daunting, since I’m not perfect with linear algebra. When I first encountered quaternions, I probably gained a few gray hairs.

Nonetheless, after about four attempts, I managed to write a new bot class, and things started moving forward.

Experiments with NavMesh

With the basics set up, it was time to (finally) work on AI. s&box provided a NavMeshAgent component that could find paths and avoid collisions, but I encountered a funny problem—bots kept sliding around like they were on ice.

It feels like there’s not enough friction

After searching in Discord for advice, I ended up creating my own agent from scratch, directly interfacing with the NavMesh API. The task was simple: fetch a path using GetSimplePath and store it until an update was needed. I had to sacrifice crowd control features, but it worked.

A day later, I had an AI bot that could follow the player and shoot. By then, it was already January 8.

Finally, the bot feels like a bot
One more note—I didn’t dive in headfirst. Instead, I decided to start with something simpler. I thought about mimicking NPC behavior from Half-Life 2—they never seemed particularly smart to me, but they also weren’t just standing around like statues. They’d either move around randomly or take cover. So this became both my first step and my "bare minimum" approach.

Finite-State Machine

A bit of theory

A week into development, I finally tackled AI logic. The big question was: how should I implement it?

A quick search led me to three possible approaches:

  1. Finite-State Machine (FSM) – The bot switches between different states, each defining a unique behavior.
  2. Behavior Tree.
  3. Decision Tree.

I didn't even describe the last two—because I didn’t understand them :). While FSM resembled a familiar state pattern, the tree-based approaches seemed way more complex. I couldn’t grasp their benefits for my specific use case in the short timeframe. So maybe next time.

FSM, however, had their own challenges. For example, let’s say a bot has the state "moving from point A to B" and another state "attacking the target." What if I wanted the bot to both move and attack at the same time?

That’s where Hierarchical Finite-State Machines (HFSMs) come in. However, I misunderstood them and accidentally invented my own version instead. I suspected this for a while, but I only confirmed it now.

Anyway, here’s what my NPC’s state system looked like:

  • Movement States: idle, chasing, combat maneuvering (strategic movements, like taking cover).
  • Combat States: Idle, shooting, reloading.

Clearly, there were two parallel layers of states (movement and combat), so I split them into separate subtypes:

Simplified class diagram

Additionally, I introduced conditions (an ICondition interface). This prevented redundant checks across multiple states. For instance, if the bot lost its weapon, it shouldn’t try to shoot or reload. Here’s a class structure of condtions:

There’s not many, but it’s better that copying code

Their usage was as follows:

  1. The FSM runs every set period of time:
  2. Check conditions – If needed, change states.
  3. Execute logic – Run the behavior of the current state (handled by Think).

How I did it

Each state had a straightforward implementation:

  • Idle – both combat and action states wait for the right conditions to change the state.
  • Attacking and reloading – If the bot has ammo and a clear shot, it fires (IsShooting = true). Otherwise, it reloads.
  • Chasing – Updates target coordinates and requests a path from NavMesh. The bot continues chasing until it's close enough to enter combat maneuvering.
  • Combat Maneuvering – Is more interesting. AI finds a nearby cover and moves behind it. If no cover is available, the bot picks a random movement pattern. Below is a flowchart of the algorithm (where "tick" refers to a Think call):

flowchart of the algorithm

Once this work was done, I had an AI bot that resembled a basic Half-Life 2 NPC. Before refining it further, I wanted to showcase its existing features.

Making a Game in One Day

A week before the deadline, I had a great idea: create a simple combat mode where I could test the AI and showcase it to others.

The concept was simple: a wave-based arena mode where you fight off waves of enemies. Between rounds, you level up, but so do your enemies.

I planned to build it in one day (optimistic, I know). I only needed:

  • Three menus: character upgrades, weapon selection, and the game over screen.
  • A spawner for enemy bots.
  • A round manager to handle progression.

Long story short: I didn’t.

Another one, another one, another one

Things couldn’t go completely smoothly
If I had to pinpoint what went wrong the most, it was definitely poor planning. Altogether, I spent about 2–3 full workdays just on the arena. On top of that, the end of the holidays hit my motivation pretty hard.
But the shortcomings in the platform also had an impact. Here’s what went wrong:

  • Project Naming Bug – When setting up my s&box project, I accidentally used uppercase letters in the package ident. This caused issues, I forgot which exactly. Later, when I added a period (".") to the ident, all my components disappeared. It took me an hour to realize the project name was the culprit.
  • Editor Glitches – I followed the official docs to add an in-editor tool… but nothing happened. After 30 minutes of restarting the editor, the tool suddenly appeared. Also a redundant prefab spawns on the scene each time I open the editor.
  • NavMesh Broke – A week before submission, the NavMesh suddenly stopped working. After debugging for days, I found out it wasn’t my fault—many people had the same issue. The fix? Regenerate the NavMesh.
  • Networking Bugs – In the final days, duplicate player instances started spawning randomly, leading to such amusing bugs:

Players get extra guns. I noticed it when I fixed a bug in networking, but the problem remained. Co-op mode is still unfinished.

Despite these and other setbacks, I pushed forward. There were more straightforward situations too—like when I was debugging multiplayer and couldn’t figure out why the IsProxy property was returning the wrong result. Who would’ve known that you need to call it after OnStart, not OnAwake, especially when the documentation doesn’t mention it at all (just a reminder, I’m an amateur in game dev).

Change of concept

At this point, I realized it was time to change the idea because all I had ready were just interfaces. And submitting a jam entry with a library that only had this to offer:

public interface ISbokuState  
{  
    public void Think();  
    public void OnSet();  
    public void OnUnset();  
}  

Well, that seemed... questionable. It became clear that I probably wouldn’t be able to pull off that “smart” AI in time, so I decided to focus on two things:

  1. Finish the arena.
  2. Make the existing logic portable to any weapon base.

The second point was necessary because during development, I had tied everything to SWB that I didn’t even consider it might backfire.

To fix this, I had to turn SbokuBase into an abstract class and detach it from SWB, then inherit BotAdapter from it. During development, these were two unrelated components, both nailed to the weapon pack (the first was my implementation of the NavMesh agent, and the second was my implementation of the controller for SWB). On top of that, the states were also tightly bound to SWB. So, I decided to settle on the “minimum” version. With the new plan, I could at least submit something functional.

In soviet Russia, deadlines hit you

With three days left, I still didn’t have a proper arena (despite my "one-day" plan). It was crunch time.

The game mode

There’s not much to say here—I just had to sit down, get it done, test it, and repeat.

Surprisingly, s&box uses Razor markup for UI (borrowed from Blazor). It even has built-in SCSS support. My web development experience finally came in handy.

Like in old good days

Creating the Map

Until now, I had been testing on a rough prototype—a bunch of resized cubes. But I needed a real map.

Straightforward and to the point.

Fortunately, I had experience with Hammer Editor (from Counter-Strike: Source map-making). Unfortunately, Source 2’s Hammer is completely different.

Jokes aside, the editor has really leveled up. Now it’s more like Blender.

The new editor is great, but it has one downside—there's not enough guides online. Learning the new tools took time, but I eventually built a spectacular box with slight verticality.

Finally, my skills were good for something.

Adding some props and fog made it look halfway decent.

I’m happy with the result. There’s definitely room to grow, though.

Final Steps: Modular AI Library

With the game mode finished, I returned to my original goal—creating a reusable AI framework. Overall, I already described my goal in the "Change of Concept" section. And it turned out to be a bit easier than I expected. So, the last day for me was spent in relaxed tweaks and refactoring.

But there’s one more interesting remark I’ll make here. One limitation of s&box—it doesn’t support NuGet. Instead, it uses a custom library system that loads dependencies directly from its website. It’s supposedly safer (only whitelisted namespaces work), but it also means libraries can’t depend on each other yet.

Submission & Release

In the end, I submitted my entry on the last day—alongside 99 other participants. Some submissions were really impressive, and some of them are even in the same field as mine.

My game mode, Sboku Arena, is available here, and the AI library, available here. They share the same repo on GitHub. Feel free to improve it if you want—there’s definitely room for enhancement.

And a huge shoutout to the SWB creators—without their work, this game mode wouldn’t have been possible.

Final Thoughts

This was a fun, albeit chaotic, experience. Jumping into an unfamiliar field like game development was both exciting and frustrating. But I learned a lot, and I’m happy to see my work alongside other great submissions. I especially enjoyed (It’s just my background, what can I say) a Lua interpreter ported from C++. Check out the article about it, it’s awesome.

As for s&box, I have mixed feelings. It has huge potential, and compared to Unity, it feels refreshing. However, the ecosystem is still rough—tons of roadblocks and breaking updates discourage me from using it for now. So I’ll focus on Sboku Arena and pause the puzzle game development I mentioned.

That’s it for now! See you next time!