Neonix: a 2D arcade game in C++ and SFML

A top-down tank shooter built from scratch in C++ with SFML. Overview of the project, what it does, and what I learned building it.

Neonix is a 2D top-down shooter built using C++ and SFML. I built this game in my free-time and it’s been a good way to continue developing skills in C++ and game-design. The game is inspired by diep.io, which I used to play quite a bit when I was younger.

In the game, you control a tank, shoot destructible objects scattered around the map, level up, build up a score, and spend upgrade points on things like new guns, health, speed, bullet damage, and fire rate. There are enemy turrets that track and shoot at you, enemy tanks that chase you around, and small seeking enemies called pests which collide with you. It’s not a finished product by any stretch, but I’ve learned quite a lot in building it.

Early game — under attack with no upgrades

What’s in it

The feature list has grown a fair bit over time:

  • Multiple gun types and layouts. Guns can be arranged around the tank in a rotating pattern or lined up in parallel. Bullet types include regular, grenade, and seeking (which spawns a pest entity that chases things on its own).
  • An upgrade system. Destroying objects builds your score. Score thresholds trigger level-ups (gun path upgrades), and each level gives you a point to spend on your stats like health, speed, bullet damage, bullet speed, or fire rate. Upgrading your stats has visual impacts on your tank e.g. upgrading health makes your tank physically bigger, increased speed changes the intensity of your trail, and stronger bullets mean bigger bullets and bigger guns.
  • Enemy AI. Turrets sit in place and track you when you get close, rotating gradually toward you before firing. Enemy tanks extend that with movement, chasing you when you’re in range and wandering with smoothly interpolated direction changes when you’re not.
  • Visual effects. Bullet trails using triangle strips with additive blending, a gun “pop” animation on fire (the gun briefly scales up then lerps back), and bullets that fade and expand when they expire.
  • A minimap and HUD. Simple world-to-screen projection for the minimap, plus score, level, and health bar displays.
  • Auto-turret. The player can unlock an automatic turret that independently targets the nearest destructible object and fires on its own.

Mid-game — auto-turret and rotation gun layout

Fully upgraded rotation tank
Fully upgraded rotation tank
Parallel gun layout with auto-turret
Parallel gun layout with auto-turret

Why C++ and SFML

I work with Unreal Engine a lot, which is C++ under the hood, but Unreal’s architecture is its own thing. Macros, reflection, garbage collection. I wanted to write C++ where I’m responsible for more of it e.g. memory management, object ownership, the game loop etc. SFML is good for this because it’s a mid-level library and leaves you with plenty of control.

The codebase is about 35 source files now. No engine, no ECS framework, just an inheritance hierarchy for entities and subsystem objects for things like guns, health, and trails. It’s a mix of inheritance and composition, which I think is fine for a project like this, though I’d probably lean more toward components if I started over.

Things I’d do differently

The entity model is the obvious one. Everything inherits from Entity, with DestructableObject below it, then Turret, then EnemyTank. It works until you want something that’s destructible but not an entity in the traditional sense, or you want to mix and match behaviours. A component system would have been more flexible, but I also think you have to build the wrong thing once to understand why the right thing exists.

The GameServices class is basically a global service locator, which I added when I needed to spawn objects during the update loop without passing references through five layers of function calls. It solved the problem cleanly, though it’s the kind of shortcut I’d think twice about using in a larger codebase.

There’s also a fair amount of magic numbers scattered around, particularly in the spawning code. The constructors for turrets and enemies take about ten parameters each. A data-driven approach with config files would be better and is something I want to add in a future update.

What’s next

There’s a long to-do list of things I could add: more advanced AI players, portals, landmines, mystery boxes, predictive aim, main/game-over menus. Hopefully I can tackle a few of those in the future.

I’m planning to write a couple of deeper posts about specific systems in the game. The weapon system and the enemy AI (once I’ve improved it more) are probably good candidates for this.