MARCIN RUSINOWSKI — ONLINE SINCE 90s MAC OS · C++ · UNREAL ENGINE EN · PL*
Dev — ← back to Dev

To GAS or not to GAS

2026-09-30

If you build combat in Unreal long enough, someone on the team will eventually ask: are we using GAS, or are we writing our own?

On day one both answers sound fine. GAS is what Epic used for Paragon and what Fortnite runs on. A custom system is smaller, fits your game, and everyone understands it because you wrote it last week. The difference only shows months later, when a designer asks for a poison that stacks five times, can't be applied to undead, gets cleansed by fire, shows a green icon with a counter, and has to work in multiplayer at 150 ms ping.

It's a question almost every Unreal team runs into, so I wanted to write down how I actually think about it. I'll skip the usual "GAS is great" versus "GAS is bloated" back and forth. What interests me more is what GAS is underneath, which problems it solves that are really hard, where it's known to hurt, and what you're signing up for when you say "we'll just write our own".

What you're actually choosing

"Plugin or custom code" is the wrong way to frame it. What you're picking is a model of gameplay state. Where a character's numbers live. Who's allowed to change them. How temporary changes stack and wear off. How you say "stunned characters can't cast". How all of it gets to other players.

Every combat game has that model, even if nobody ever wrote it down. In a small game it's a few if statements on the character. In a bigger one it turns into the backbone that abilities, items, talents, enemies and UI all hang off. Once that happens, changing it is expensive, because everything touches it.

So the questions I ask are simpler than they sound. Does Epic's model fit my game well enough that I can live inside it for a few years? Where it doesn't fit, can I bend it, or will I spend my time fighting it? And if I write my own, am I just going to solve the same problems later, with fewer people looking at the code?

That last one is the uncomfortable one. Custom ability systems tend to start small and clean. Then, one feature request at a time, they grow effect stacking, tag-like state flags, cooldown bookkeeping, and eventually some kind of prediction. At that point you own a worse copy of GAS that only your team knows.

GAS as an architecture

Most GAS tutorials open with a list of classes: UAbilitySystemComponent, UGameplayAbility, UGameplayEffect, UAttributeSet. For this decision I'd rather start one level up. Under the classes there are a handful of design ideas, and whether GAS feels right to you mostly comes down to whether you like those ideas.

The first one is that each actor has exactly one owner of its gameplay state. The Ability System Component holds the attributes, active effects, granted abilities and tags, and nothing else is supposed to touch health directly. If you've worked with a single store in a UI framework, it's the same instinct: one source of truth, all changes go through it, and anyone can listen.

The second is a hard line between logic and data. Abilities are code. They activate, wait, play montages, trace, spawn things. Effects are data. "Take 25 mana." "Grant Cooldown.Fireball for six seconds." "Deal damage every second for eight seconds." An effect doesn't do anything by itself. I think of it as a small transaction you apply to someone's state.

The third is easy to overlook. Every attribute has a base value and a current value. A fifteen second strength buff never touches the base; it sits on top and disappears when the effect ends. That one detail removes a whole family of bugs where a buff runs out and the stat comes back wrong.

The fourth is tags. Gameplay Tags are hierarchical labels like State.Debuff.Stun, and abilities and effects check them. "Can't cast while silenced", "immune to poison", "stun cancels channeling" stop being code and become settings on assets. Because tags are hierarchical, one rule can cover a whole family. Block on State.Debuff and every debuff blocks.

The fifth is keeping presentation on its own channel. Gameplay Cues handle the sparks and sounds. They're triggered by tags, replicated separately, and they're never allowed to change gameplay. The server doesn't care about them, clients can play them locally, and an artist can swap an effect without going near ability code.

And on top of all that sits the piece that's really painful to build yourself: client-side prediction for activations, costs, cooldowns and effects you apply to yourself, with rollback when the server says no.

Each of these has a price. The component is heavy. Some logic is awkward to express as data. You end up clamping values in two places. Tags turn into a mess if nobody owns them. Cues are one more concept to teach. Prediction comes with rules about what can and can't be predicted. If you read the ideas above and think "yes, that's roughly how I'd design it anyway", GAS is probably your answer. If half of it feels foreign to how your game works, keep reading.

What you get for free

The case for GAS fits in one sentence: it has already solved a bunch of problems that look tiny on a whiteboard and turn into months of work.

Prediction is the big one. With a LocalPredicted ability, the client activates right away. It pays the cost, starts the cooldown, plays the montage and applies effects to itself before the server has even answered. If the server rejects it, GAS rolls it all back using prediction keys. To do that yourself you need a prediction key system, a decision about what state you predict, and a way to handle every race between client and server. In a networked game this alone is the reason I'd pick GAS.

Then there's the effect lifecycle. Instant, timed, infinite and periodic effects. Stacking per source or per target, stack limits, refresh rules, removal by tag. Each of those is easy on its own. Put them together, add interactions and replication, and you've built a small engine.

Replication modes are another quiet win. With Mixed, the owning player gets full effect data while everyone else only gets tags and cues. With Minimal, AI sends almost nothing. Home-grown systems usually start by replicating everything and only get optimized once something is on fire.

A lot of combat rules also turn into configuration. Required tags, blocked tags, "cancel abilities with this tag", immunities through tag requirements. Designers can set them up without opening C++.

Levels come for free as well. Effects and abilities carry a level, and values can come from CurveTables, so talent ranks, item levels and character progression all use the same mechanism.

It's also been tested at a scale most of us will never reach. Fortnite runs on it with 100 players per server, and Paragon, a MOBA, was built on it. That doesn't make it perfect, but the main code paths get hammered hard, and Epic tends to find the bugs there before you do.

The last point isn't technical at all, and I think it gets underrated. At this point I'd call GAS the industry standard for ability systems in Unreal. When you hire a programmer, or bring in a technical designer or a contractor, there's a good chance they've already worked with it. They know what an ASC is, how effects stack, where to look when an ability won't fire. Tutorials, forum threads, Lyra and community docs all speak the same language. With a custom system, every new person starts from zero, and onboarding is exactly as good as your internal docs. I'll come back to that.

Where GAS hurts

This is the part most "should I use GAS?" threads skip, so I want to spend some time on it. GAS has rough edges that people who've shipped with it know well. None of them kills it on its own, but a few of them matter a lot for certain kinds of games.

The learning curve is a wall

There's a lot of it. ASC, abilities, effects, specs, contexts, attribute sets, executions, magnitude calculations, cues, tasks, target data, prediction keys, replication modes, instancing policies, net execution policies. Epic's docs explain the concepts, but a lot of the practical "how" lives in Lyra's source code and in community write-ups like tranek's GASDocumentation.

What this really costs you is time before someone feels confident. A developer new to GAS will fight it for a while, and the first things they write will probably get rewritten once the team gets the conventions. On a long project that's fine. On a short one it can eat a scary chunk of the schedule.

Attributes are floats, and they live in C++

Every attribute is an FGameplayAttributeData, and that's a float. No ints, no enums, no strings, no structs. So "arrows left", "current stance" or "weapon type" either get squeezed into a float or live somewhere outside GAS.

They're also declared in C++. A designer can't add a new stat from a data table; someone has to change code and rebuild. If your game invents new stats all the time, or gets them from mods or server data, you'll feel that.

Precision can bite too. A classic RPG with HP in the thousands will never notice. An idle game with numbers in the billions, or an economy that needs exact integer math, might.

Effects don't change once they're applied

An effect's modifiers are locked in when its spec is created. Want a buff that gets stronger the longer you channel? You'll be removing and reapplying it, stacking it, or moving the math into an MMC or into the ability. There's no clean way to say "change the strength of this active effect".

Stacking has opinions as well. Stacks share one duration and one set of rules. "Five poison stacks, each with its own timer" is something designers ask for all the time, and GAS doesn't do it directly. You end up with separate effect instances or your own bookkeeping.

Prediction stops at a certain point

Prediction is the best thing in GAS, and also where people get surprised. Some things just aren't predicted: removing effects, periodic ticks like DoTs and HoTs, damage and effects on other actors, and anything that happens after the prediction window closes, which is after WaitDelay or most other async tasks.

In practice your own actions feel instant, but hit reactions, enemy health bars and damage numbers still show up a round trip later. That's normal for a server-authoritative game. It only becomes a problem when the team expected GAS to make latency disappear, and it won't.

Movement is its own headache. GAS prediction and Character Movement Component prediction are two separate systems. A dash, leap or knockback that has to be both an ability and a movement needs extra work, usually root motion sources or custom movement modes, to avoid corrections.

Cooldowns and charges are clunky

In GAS a cooldown is an effect that grants a tag for some time. It's neat, right up until you need cooldown reduction, a UI that shows time remaining, or charges ("three charges, one comes back every eight seconds"). Cooldown reduction means modifying something that isn't an attribute. The UI has to query active effects by tag. Charges don't exist at all. Pretty much every project writes its own little cooldown layer.

The logic ends up spread everywhere

One fireball touches an ability Blueprint, a cost effect, a cooldown effect, a damage effect, an ExecutionCalculation in C++, a projectile actor, a Gameplay Cue, the tags config and the AttributeSet's PostGameplayEffectExecute. Every one of those pieces is simple. Figuring out why the fireball didn't do damage, across all of them, is not.

PostGameplayEffectExecute deserves a special mention. It's the only place that sees the final value, so damage, shields, death, XP, lifesteal and reflect all tend to end up there. Without some discipline it becomes the function nobody wants to open.

The tools help. showdebug abilitysystem, the Gameplay Debugger and activation failure tags all exist. It's still more jumping between files than debugging a plain function call.

Tags multiply

Tags are great because they're cheap to add, and that's exactly the problem. Leave them unowned for a year and you'll find State.Stunned, State.Debuff.Stun, Status.Stun and Effect.Stun all living side by side, each used by a different corner of the code. Tags looked up by string in C++ break at runtime instead of at compile time unless you declare them natively. Renames need redirects. None of it is hard. It just needs someone to own it.

Every actor pays for it

Each actor that takes part carries an ASC with its containers, attribute sets and replication. A player plus a few dozen enemies won't notice. Hundreds of simple AI units, destructible props, or projectiles that "just need HP" will, in memory, CPU and bandwidth. GASDocumentation mentions that Fortnite creates ASCs on world objects only when they're needed, which suggests Epic ran into this too.

It keeps changing

GAS is still moving. UE 5.3 split GameplayEffects into GameplayEffectComponents. UE 5.5 deprecated NonInstanced abilities. The old input binding approach gave way to per-project Enhanced Input layers like the one in Lyra. Every one of those changes makes sense, but on a long project each engine upgrade comes with a bit of GAS migration, and tutorials from a few years ago may show an editor you don't have anymore.

It only lives in Unreal

GAS sits in actors and components. It won't come with you to another engine, it's awkward to run without a world, and unit testing ability logic usually means spinning up a test world. If your rules also need to run on a backend, in a simulation service or on another platform, GAS can't be that shared core.

Some games barely use it

A turn-based RPG gets almost nothing out of prediction. A single-player game with ten abilities pays the full learning cost for a small slice of the benefit. A game where combat is mostly physics or movement doesn't need much from attribute modifiers. GAS was shaped by Paragon and Fortnite, and the further your game is from real-time networked action, the more of it you're carrying around for nothing.

What building your own really costs

I get why people want their own system. It fits the game exactly, the team understands every line, and there's no framework to argue with. The trap is judging it by its first version.

The first version really is easy. A component with a map of stats, a base ability class with CanActivate, Activate and End, a timer for cooldowns. A few days of work, and it feels nicer than GAS, because it does exactly what you need today and nothing else.

The bill arrives with the next requests, and they tend to come in roughly the same order.

First someone adds a buff, and the stat has to come back correctly when it ends, even if another buff started and ended in the meantime. Now you need base values, current values and a stack of modifiers. That's the GAS aggregator.

Then poison needs to tick. Then it needs a refresh rule, a max stack, and separate stacks per attacker. That's the effect lifecycle.

Then stuns have to block casting, silence only blocks spells, and one boss is immune to both. You either grow a pile of booleans or invent labels with a hierarchy. That's Gameplay Tags.

Then the artists want VFX to play on every client without the server caring. That's Gameplay Cues.

Then designers want to make effects without waiting for a programmer, so you build assets, editor tooling and validation. That's GameplayEffects as data.

Then multiplayer. The owning player needs details, other players only need what they can see, AI needs almost nothing. That's replication modes.

And finally someone says abilities feel sluggish at 100 ms. Now you're designing prediction keys, rollback and reconciliation, and this is where a weekend project turns into a senior engineer's next few months.

In a networked action RPG none of those steps is optional. You can do them differently, maybe even better for your particular game, but you'll do all of them. So when you estimate a custom system, estimate the whole list, plus the tooling and documentation GAS already has around it.

To be fair to the custom route: if your game is single-player or turn-based, the last two steps mostly don't exist. Those are the hardest two, and what's left is a few weeks of well understood work.

The risk nobody puts in the estimate

The technical comparison usually leaves out the biggest risk of a custom system. It's a people risk: it depends on the author, on the team around them, and on how much of it ever got written down.

It usually goes like this. One person is the architect. They design the structure, write the core and make the important calls, often quickly and well, because the whole model lives in their head. Everyone else adopts it. They build abilities on top, learn the patterns by copying what's there, and walk over to the architect's desk when something doesn't fit. It works, and for a while it works really well.

Then the architect leaves the project, or the company. From that day, carrying on means facing reality. The team has to understand the code and also the thinking behind it. Why effects are applied in this order. Why that flag exists. Which parts hold the building up and which are leftovers from a design nobody remembers. If none of that was written down, the team is reverse-engineering its own foundation while still shipping features on top of it.

Hiring makes the gap wider. When you look for someone to replace the architect, or just to grow the team, nobody out there knows your system. Every new programmer or technical designer starts from zero and learns it from the code and whatever docs exist. With GAS it's the opposite. Because it's become the industry standard for Unreal, you can hire people who already know it, and they're useful in their first week instead of their second month. That risk isn't zero, since your project still has its own conventions on top, but it's small and predictable.

GAS doesn't make the architect problem go away completely. Your wrapper layer and project conventions still have an author. But the core model is documented by Epic and the community, thousands of developers know it, and losing one person doesn't take the foundation with them.

Postmortem: the architect leaves

This is a made-up postmortem, a composite of how the story usually goes rather than a report on one real project.

What happened. All combat ran on a custom ability system built by one senior developer. Four months before release, they left the company. Combat work slowed to a crawl while the rest of the team rebuilt their understanding of the system, and several late bugs came from assumptions in the core that nobody had written down.

How it got there. Early in production the architect built the core fast. Decisions were made in conversations and never recorded, and "the code is the documentation" was the running joke. In full production the team adopted it. New abilities were made by copying old ones, and questions went straight to the architect, who answered so quickly that nobody bothered writing the answers down. Then came the resignation. A few weeks of notice turned into a rushed handover: two walkthrough sessions and a wiki page written in the last days. The first real change after that, a new stacking rule, broke two unrelated abilities. Nobody knew why the stacking code worked the way it did, so every change became slow and nervous. By late production a big part of combat time went to digging through old code instead of building features. Some parts got rewritten. Others were frozen with a "don't touch" comment.

Why it happened.

What would have saved it.

What it means for the GAS decision. When you compare GAS to your own system, put this story into the estimate. A custom system lasts only as long as its documentation and its second owner. If you can't commit to both, you're betting your game's combat on one person staying.

There's a middle road

It doesn't have to be all or nothing. The way I like to treat GAS is the way I'd treat physics or animation: an engine layer you build on, wrap, and occasionally route around.

The most useful habit is putting a thin layer of your own between gameplay code and GAS. A base ability class, a helper that builds damage specs, a small cooldown service, tags declared natively, an equipment component that grants and removes effects. Gameplay programmers call your API, and only that layer talks to GAS directly. When Epic changes something in the next engine version, you fix it in one place. Lyra is basically this idea at a much bigger scale.

You also don't have to use every feature. A setup I'd be happy with looks roughly like this:

Goes through GASStays outside GAS
Combat attributes (HP, mana, damage stats)Inventory, currency, quest state
Abilities with cost, cooldown and predictionDialogue, crafting, UI flows
Buffs, debuffs, DoTs, immunitiesInteger counters and enums (ammo, stance)
Status tags that block or cancel actionsSave data
Hit VFX and SFX through cuesProps that only care about "broken or not"

Not every barrel needs an ASC either. A tiny health component for props, with a bridge so GAS damage can reach it, keeps the heavy machinery for the characters that actually need it.

And if you do end up writing your own, steal the ideas anyway. Base and current values, modifiers as data, hierarchical tags, cosmetics on their own channel. They're good ideas regardless of whose code implements them, and they'll save you from finding the same problems the hard way.

One more pattern for when your rules have to run outside Unreal, on a server simulation, another engine or a retro port: write the pure rules, like the damage formula and the stacking math, as plain code with no engine types, and call them from an ExecutionCalculation on the Unreal side. GAS keeps doing the lifecycle and networking, and the math stays yours and stays portable.

How I decide

When the question comes up, I run through the same list. No single row settles it, but by the fifth or sixth one the answer is usually pretty obvious.

QuestionPoints to GASPoints to custom
Is combat real-time and networked?Yes, especially PvPSingle-player or turn-based
How many abilities and statuses will ship?Dozens to hundreds, interactingA handful, mostly independent
Will designers build effects without programmers?Yes, that's the planNo, programmers own gameplay
How long will the game live?Years, with live updatesA short project or a jam
Does anyone on the team know GAS?At least one personNobody, and the deadline is close
Will you hire or grow the team during production?Yes, and you want people productive fastNo, the team is small and stable
Who could own a custom core if its author left?Nobody, realisticallyAt least two people, with docs and tests
Do the rules need to run outside Unreal?NoYes, on a backend or another engine
Are stats mostly floats that get modified?YesMostly integers, enums, discrete states
How many actors need gameplay state?Players plus tens of enemiesHundreds of simple units

For a networked action RPG in Unreal my default is GAS, wrapped in a thin project layer, with clear rules about what stays outside it. Prediction and the effect lifecycle are worth the learning cost on their own, being able to hire people who already know the system is a big bonus, and the weak spots above are manageable once you know they're there.

For a single-player or turn-based RPG I'm less sure. If the team already knows GAS, I'd still use it for stacking and tag rules. If they don't, a well designed custom system that borrows GAS's ideas is a perfectly reasonable choice, and often a faster one, as long as it has a second owner and real documentation.

Closing thoughts

In the end "to GAS or not to GAS" is a question about where you want to spend your complexity. GAS makes you pay up front: a steep learning curve, conventions to absorb, a framework shaped by someone else's games. A custom system lets you pay later: an easy start, and then the hard problems show up one by one, on your schedule, with only your team to solve them and only your docs to explain them.

For networked, real-time combat I'd rather pay up front for something that has survived Fortnite than discover prediction and effect stacking one bug report at a time. For everything else it depends on the team, the timeline, and how much of GAS your game would actually use.

Whatever you pick, don't pick it out of habit. Figure out what model of gameplay state your game needs, hold it up against the one GAS gives you, and make the call with the weak spots, and the people, in view.

Sources