How-To: Make a Cast-Time Ability
Goal: an ability that winds up for a duration, can be interrupted, only pays its cost when the cast completes, and delivers its effect authoritatively on the server.
1. Subclass the cast-time base
Right-click -> Blueprint Class -> `CrimsonGameplayAbility_WithCastTime`.
2. Set the cast time and commit point
ECrimsonAbilityCommitTime decides when the cost is actually paid.
| Value | Cost is paid |
|---|---|
On Activation | Immediately, before the cast runs. Interrupting still costs the player. |
On Cast Completed | Only if the cast finishes. Interruption is free. |
On Cast Completed and on Interrupted | On completion, and also when interrupted. |
BlueprintNativeEvent) to compute the duration from attributes - haste, a stack count, whatever. Read the resolved value back with Get Calculated Cast Time.3. Deliver the effect on the server
The base class fires each lifecycle point twice: a (Cosmetic) event that runs on the server AND on the predicting client, and an (Authority) event that runs only where HasAuthority is true. Spawning a projectile or applying a gameplay effect belongs in the authority event.
| Event | Runs on | Use for |
|---|---|---|
On Cast Started (Cosmetic) | Server and predicting client | Cast bar, wind-up VFX, sound. |
On Cast Completed (Cosmetic) | Server and predicting client | Release VFX, camera shake. |
On Cast Interrupted (Cosmetic) | Server and predicting client | Fizzle VFX, failure sound. |
On Cast Started (Authority) | Server only | Reserve a target, spawn a wind-up actor. |
On Cast Completed (Authority) | Server only | Spawn the projectile, apply the gameplay effect. |
On Cast Interrupted (Authority) | Server only | Refund a resource, clean up server state. |
On Cast Completed (Cosmetic) runs on both machines. Spawning an actor there produces two of them on a listen server and a client-only ghost on a remote client. Anything that mutates replicated state belongs in the (Authority) event.Right-click in the ability graph -> Add Event -> On Cast Completed (Authority), then Spawn Actor from Class (Class = your projectile; Transform = Get Avatar Actor from Actor Info -> GetActorTransform).
Put the muzzle flash and sound on On Cast Completed (Cosmetic) instead, so the caster sees them without waiting for the server.
On Cast Completed commit modes, and when the ability ends. The client's timer drives the cosmetic events only, so a client cannot shorten or cancel its own cast on the server.4. Handle interruption
The plugin tags casting state for you: Ability.State.Casting while the cast runs, and Ability.State.Interrupted when it breaks. Watch those to react to whether a cast is happening - dimming a reticle, blocking another input, playing a stagger. They carry no duration, so they are not what a progress bar reads; step 5 covers that.
Use Wait Gameplay Tag Add / Wait Gameplay Tag Remove on Ability.State.Casting to run cosmetics for as long as any cast is up.
On Cast Completed, no cost was paid.5. Drive a cast bar
The base class fires FCrimsonAbilityProgressMessage through the ASC's OnAbilityProgress event (native OnAbilityProgressNative, channel Message.Ability.Progress) when a timed segment starts and ends. A HUD widget binds it on its own player's ASC.
| Field | Meaning |
|---|---|
Instigator | The avatar casting. Filter on this - a split-screen partner shares the process. |
AbilitySpecHandle | Which ability. Key your active bar on it. |
AbilityIdentifierTag | Stable identity, for per-ability styling or an icon lookup. |
StartWorldTime | World time the cast began. Interpolate against this. |
Duration | Total seconds. Zero or less means indeterminate - an infinite channel. Never divide by it. |
Phase | Started, Completed, or Interrupted. |
Kind | Cast or Channel, so one widget can style both. |
ModifiedCastTime is computed once in ActivateAbility and cannot change mid-cast, so interpolating locally from StartWorldTime against the world clock is not an approximation - it is exactly correct, and smoother than any broadcast rate would be.Fill =
(World Time - StartWorldTime) / Duration, clamped to 0..1.That failure is invisible on a listen-server host, whose HUD shares a process with the server. Always check enemy-facing UI from a remote client. Showing someone else's cast needs replicated cast state on the avatar.
In your HUD widget: Event Construct -> Get Ability System Component (owning player pawn) -> Cast To Crimson Ability System Component -> Bind Event to On Ability Progress. In the event, branch on the message's Phase: Started stores StartWorldTime and Duration and shows the bar; Completed and Interrupted hide it. On Tick, set the fill from (Get Game Time In Seconds - StartWorldTime) / Duration.
Ignore a Completed or Interrupted whose AbilitySpecHandle does not match the one you stored - otherwise a channel ending clears a cast that started after it.
UCrimsonCoreCastBarWidget with this already built, plus a finish-animation state machine - the bar stays visible after the cast ends so an interrupted animation can play, and a new cast preempts it. Study it if you have the suite; the code above is the standalone equivalent.See also
- How-To: Use Toggle, Charges and Channeling
- Concept: Ability Subclasses