This is a question we get asked a lot, usually by other indie devs who are deep in Blueprint spaghetti and wondering if there's a better way. The short answer is: C++ first is almost always the right call for anything you expect to ship. The longer answer requires understanding what Blueprints are actually for.

Blueprints are a prototyping and extension layer. They're Unreal's way of letting non-programmers script behaviour, and they're excellent at that job. But they're not a replacement for a well-architected C++ foundation — and treating them like one is the thing that kills most indie UE5 projects before they reach content lock.

What Goes Wrong With Blueprint-First

The typical pattern we've seen (and experienced ourselves in earlier projects): you start in Blueprints because it's fast. The game loop comes together in days. You add features. The graph gets more complex. You start using casts everywhere. Variables multiply. By the time you have a working vertical slice, you have a Blueprint that nobody — including you — can read six weeks later.

Worse, you can't easily unit test it. You can't profile it properly. Refactoring it means untangling visual wire spaghetti. And the moment you need a second developer to touch that system, you've lost half a sprint to orientation.

The thing Blueprint-first optimises for is the first two weeks of development. C++-first optimises for everything after that. For a shipping title, the maths is obvious.

How We Use Blueprints

Blueprints aren't banned at Ashenex — they're just scoped correctly. We use them for three things: rapid prototyping of mechanics that may not survive the cut, Designer-owned configuration of C++ classes (setting values, wiring events, specifying asset references), and one-off level scripting that doesn't belong in a reusable system.

In Starbud Station, every ACharacter, AActor, and UActorComponent that ships has its core logic in C++. The Blueprint child class exists to expose tunable properties to the editor and assign art assets. That's it. A designer can tune a customer NPC's archetype weights, price sensitivity, or morale thresholds without touching a .cpp file. A programmer can refactor the underlying scoring system without breaking any Blueprint graphs.

The C++ Patterns We Actually Use

Unreal's C++ has a reputation for being intimidating — the macro system, the garbage collector, the reflection system. It is a steep curve initially. But the patterns we rely on most are actually quite narrow. In Starbud Station, almost everything flows through four structures: UGameInstanceSubsystem for persistent game-wide managers (the strain registry, the market state), UActorComponent for per-actor behaviour (the shop level component, worker stat tracking), UPrimaryDataAsset for all tunable data (recipes, strain definitions, customer archetypes, unlock tables), and delegates for loosely coupled communication between systems.

The delegate pattern in particular is worth emphasising. Unreal's DECLARE_DYNAMIC_MULTICAST_DELEGATE macros let systems broadcast events that any interested party can bind to, without the broadcaster needing to know anything about the listeners. The processing pipeline manager fires OnProductionCompleted — and the inventory system, UI, analytics tracker, and shop level XP system all bind to it independently. The pipeline doesn't know any of those systems exist. Clean separation, easy to test, easy to extend without touching the broadcaster.

The Performance Argument

Beyond architecture, there's a practical performance ceiling with Blueprint-heavy games on lower-end hardware. Blueprint VM overhead is real — not catastrophic for simple logic, but it adds up fast in systems that run frequently. Our economy simulation, customer scoring, and grow-cycle logic all run in C++. On a mid-tier machine, those systems are essentially free. In Blueprint, we'd be watching the profiler much more carefully.

For a studio at our scale — one core developer — the time saved debugging and profiling Blueprint performance problems is time better spent building features. C++ upfront is the cheaper option in aggregate, even accounting for the longer initial setup time.

A Real Example: The Shop Level System

Starbud Station has a shop progression system — players earn XP from sales, contracts, and discoveries, and level up their shop to unlock new equipment, apps, customer tiers, and automation. Every unlock in the game is gated through this system.

The entire thing runs through a UShopLevelComponent attached to the GameState. Unlocks are defined in a UShopLevelUnlockData DataAsset — a table mapping level numbers to Gameplay Tags. Any system that needs to check if something is unlocked makes a single call: IsUnlocked(FGameplayTag UnlockTag). The automation system doesn't know about levels. The customer NPC spawner doesn't know about levels. They just check their unlock tag and act accordingly.

This is the C++ pattern in practice. The logic is in one place, well-tested, server-authoritative. The data is in a DataAsset that can be tuned freely. The rest of the game queries it through a clean interface. Trying to build this in Blueprints — with the level state shared across sessions, replicated to clients, and queried by a dozen different systems — would have been a maintenance nightmare by week three.

The Bottom Line

We're not dogmatic about it. If you're building a jam game or a solo prototype with no intention of shipping, Blueprint-first is completely sensible. But if you're building a studio, building a product, building something you want to maintain and extend over 18 months of development — write the systems in C++. Use Blueprints as the configuration layer on top. You'll thank yourself by the time you reach beta.

That's the philosophy Ashenex was built on, and it's what Starbud Station is being built with. We'll keep documenting how it plays out in practice as development continues.