First Weapon Fire Pattern and Impact Cleanup¶
A weapon is a pipeline, not a single Fire node. Keep input, validation, spawning/tracing, damage, presentation, and cleanup in the objects that own those jobs.
The one-minute version¶
- A first weapon flow has seven jobs: input, can-fire checks, aim/muzzle transform, hit or projectile creation, damage, feedback, and cleanup.
- Put player input on the local
PlayerControlleror possessed pawn. Put reusable weapon rules in a weapon actor or weapon component when the project has more than one weapon. - Cooldown and ammo are gameplay state. Store them with the weapon/pawn that owns firing, not inside an impact effect or UI widget.
- A hitscan weapon traces now. A projectile weapon spawns an actor now and handles impact later inside the projectile.
- Impact feedback is presentation: muzzle flash, sound, decal, Niagara, camera shake, hit marker. It is not proof that damage was accepted.
- Cleanup belongs where the lifetime exists: projectile destroys itself, impact effects auto-destroy or use lifespan, melee hit windows clear their already-hit set, timers clear their handles.
- In multiplayer, local cosmetics can run immediately, but the server should own the trusted fire check, hit validation, projectile spawn, damage, ammo, and replicated health.
The complete first weapon loop¶
The loop looks like this:
flowchart LR
Input[Input Action: Fire] --> Check[Can fire?]
Check --> Aim[Aim point and muzzle transform]
Aim --> HitModel{Hit model}
HitModel --> Trace[Trace now]
HitModel --> Projectile[Spawn projectile]
Trace --> Impact[Impact result]
Projectile --> LaterImpact[Projectile hit / stop later]
Impact --> Damage[Apply Damage family]
LaterImpact --> Damage
Damage --> Feedback[Sound / VFX / decal / UI feedback]
Feedback --> Cleanup[Cooldown, lifespan, destroy, reset state]
Do not start by asking "which node fires a gun?" Start by choosing the weapon's hit model:
| Weapon behavior | Fire-time work | Impact-time work |
|---|---|---|
| Hitscan rifle | Trace, filter hit, apply point damage, play muzzle feedback. | Usually only impact feedback from the trace result. |
| Projectile launcher | Spawn projectile and start cooldown. | Projectile detects hit, applies damage, plays impact feedback, destroys itself. |
| Shotgun | Run several traces or one/more multi traces, filter and de-duplicate. | Apply damage once per valid target or per pellet by design. |
| Melee swing | Open a hit window from an animation notify or input state. | Trace/overlap during the window, de-duplicate targets, close/clear on end. |
| Grenade / rocket explosion | Spawn or detonate area source. | Apply radial damage, spawn area feedback, destroy/expire source actor. |
The quick path is putting every job in the character's input event. That works for a prototype, but the concrete cost is that ammo, cooldown, impact logic, and UI updates get copied when you add a second weapon. A first project can keep the graph in the character, but draw the boundaries as separate functions so the later move to a weapon component is straightforward.
Who should own each job?¶
Use the object that already owns the state.
| Job | Good first owner | Why |
|---|---|---|
| Read local input | PlayerController or possessed pawn/character |
Input belongs to the local player and changes when possession changes. |
| Can-fire gate | Weapon actor/component, or the character if there is only one weapon | It owns cooldown, ammo, fire mode, reload state, and current weapon data. |
| Muzzle transform | Weapon mesh/socket, muzzle scene component, or character camera helper | The fire source should be explicit and debuggable. |
| Hitscan trace | Weapon/character fire function, server-side in multiplayer | The authoritative query should use the same owner/team/range rules as damage. |
| Projectile travel | Projectile actor | The projectile owns its movement, collision, hit event, lifespan, and impact cleanup. |
| Damage response | Target actor or health component | The damaged actor owns health, death, resistance, UI broadcasts, and replication of health. |
| Impact VFX/audio/decal | Weapon/projectile/impact helper, often cosmetic-only on clients | Feedback is presentation and may not need to be authoritative state. |
| Ammo UI | Widget listening to weapon/pawn state | UI should display state changes, not own ammo rules. |
For a tiny single-player prototype, this is acceptable:
Character Input Fire
-> FireWeapon function
-> LineTrace or SpawnProjectile
As soon as you have more than one weapon, prefer:
Character Input Fire
-> CurrentWeaponComponent.TryFire(AimData)
That function name matters. TryFire implies "may fail cleanly" because ammo,
cooldown, reload, team, pause, and authority can all block the request.
The can-fire check¶
Do checks before spawning actors or playing gameplay-changing effects.
TryFire
-> Branch bIsReloading false
-> Branch AmmoInClip > 0
-> Branch bCanFire true
-> Branch weapon is not blocked by menu/pause/death state
-> consume ammo
-> set bCanFire false
-> Set Timer by Event ResetCanFire after FireInterval
-> perform trace or spawn projectile
Keep the failed path boring:
| Failed check | Normal result |
|---|---|
| No ammo | Play dry-fire click or start reload, but do not spawn damage. |
| Cooldown active | Do nothing or buffer input only if your fire mode needs it. |
| Reloading | Do nothing or cancel reload by explicit design. |
| Dead/stunned/paused/menu open | Do nothing, or route input to UI. |
| Invalid muzzle/current weapon | Print a development-only warning and stop. |
| Not authority in multiplayer | Ask the server or wait for the server-approved path. |
Avoid reducing ammo after an unreliable impact event. Ammo was spent when the weapon fired, not when a projectile eventually hit.
Aim point and muzzle transform¶
For first-person or over-the-shoulder weapons, the camera and muzzle answer different questions:
Camera trace:
What is under the crosshair?
Muzzle transform:
Where does the shot physically start?
A common first-person shape:
CameraLocation, CameraForward
-> LineTrace camera to MaxRange
-> if hit:
AimPoint = Hit.ImpactPoint
else:
AimPoint = CameraLocation + CameraForward * MaxRange
MuzzleLocation
-> Direction = Normalize(AimPoint - MuzzleLocation)
-> Find Look at Rotation or Make Rot from X
-> trace/projectile from the muzzle using that direction
This prevents the crosshair from lying while still letting the muzzle be blocked by nearby walls if you add a muzzle trace. Debug both the camera trace and muzzle direction before tuning spread or projectile speed.
Hitscan fire pattern¶
Hitscan resolves impact during the fire call:
Input Action Fire Triggered
-> TryFire
-> Build camera aim point
-> Build muzzle start/end
-> Line Trace By Channel or Sphere Trace By Channel
-> Branch hit
-> Break Hit Result
-> filter hit actor: valid, not self, not same team, damageable
-> Apply Point Damage
-> Spawn impact sound/VFX/decal from Impact Point, Impact Normal, Surface Type
-> update ammo/cooldown UI from state change
Use Apply Point Damage because the trace already produced hit data. The
receiver can use bone name, hit component, impact location, and shot direction.
For a shotgun, do not apply damage while blindly looping every returned component:
For each pellet direction
-> trace
-> if hit actor valid:
add pellet result to per-target map
After traces
-> For each target:
calculate target damage
Apply Point Damage once or per pellet by design
The "once or per pellet" choice is game design. The important part is that it is deliberate and debug-visible.
What each boundary gives you back¶
A weapon graph crosses several APIs. Each one reports success differently.
| Boundary | What you get back | What failure looks like |
|---|---|---|
| Input Action event | Execution and an input value/state. | The event never fires because context, possession, focus, input mode, or trigger state is wrong. |
| TryFire function | Your own result, usually a Boolean or enum reason if you make one. | Branch returns false/no-op because ammo, cooldown, reload, pause, death, or authority blocked fire. |
| Line/shape trace | Boolean plus FHitResult, or an array for multi traces. |
Boolean false, empty/unhelpful hit result, wrong actor/component, or self-hit. |
| Spawn Actor from Class | Actor reference, or None. |
Spawn collision, invalid class, invalid transform, authority, or construction failure leaves no projectile. |
| Projectile impact event | Event pins and usually an FHitResult. |
No event fires, repeated events fire, or collision reports the wrong component pair. |
| Apply Damage / Point Damage | Numeric accepted damage. | 0.0 when no damage was accepted or the target rejects the damage path. |
| Apply Radial Damage | Boolean for whether at least one actor was damaged. | false when no actor accepted radial damage. |
| Set Timer by Event | Timer handle. | Timer never fires because it was cleared, replaced, paused, or set with an invalid interval. |
Do not collapse all of those into "the gun worked." Print or watch the boundary that answers the question you are debugging.
Projectile fire pattern¶
Projectile fire splits fire time from impact time.
At fire time:
Input Action Fire Triggered
-> TryFire
-> MuzzleComponent.GetWorldTransform
-> Spawn Actor from Class BP_Projectile
Spawn Transform = Muzzle transform or aimed rotation
Instigator = firing pawn
Owner = weapon/character only if your rules need owner relationship
-> Is Valid Return Value
-> configure returned projectile if needed
-> play muzzle flash/sound/recoil
Inside the projectile:
BP_Projectile
-> root collision component
-> Projectile Movement Component
-> Initial Life Span = miss cleanup time
OnComponentHit or OnProjectileStop
-> filter Other Actor
-> Apply Point Damage or Apply Damage
-> Spawn impact feedback
-> Destroy Actor
The projectile owns miss cleanup because the projectile is the object that may
live forever if it never hits. Use Initial Life Span or Set Life Span for a
simple miss timeout.
Do not leave a looping trail, attached audio component, timer, or collision enabled after the projectile should be gone. Either let the effect auto-destroy or explicitly stop/destroy it in the same impact path.
Impact cleanup¶
Impact cleanup is usually four separate decisions:
| Thing | Cleanup rule |
|---|---|
| Gameplay projectile actor | Destroy on accepted impact, stop/bounce if designed, lifespan on miss. |
| Impact VFX | Spawn fire-and-forget if safe, or keep returned component only when you need to stop it later. |
| Impact decal | Give it a lifespan/fade policy; do not leave permanent decals from rapid fire unless intended. |
| Audio | Use one-shot sounds for impacts; store/stop attached looping sounds. |
| Cooldown timer | Store and clear/replace the timer handle if weapon state can reset. |
| Melee hit set | Clear at the start/end of each swing so old actors do not block future hits. |
Damage acceptance and cleanup are related but not identical. A projectile can
hit a wall, spawn dust, and destroy itself without damaging an actor. A bullet
can hit an invulnerable enemy, spawn impact feedback, and still have damage
Return Value 0.0.
Multiplayer authority¶
A responsive weapon can still keep the server in charge.
Owning client input
-> local muzzle flash / recoil / predicted UI if desired
-> ServerFire RPC
ServerFire
-> verify owning connection, equipped weapon, ammo, cooldown, range, team
-> consume authoritative ammo
-> perform authoritative trace or spawn replicated projectile
-> apply damage on server
-> replicate health/ammo/projectile state
-> multicast or replicated state drives impact feedback
Separate the three categories:
| Work | Usually runs where |
|---|---|
| Immediate feel: muzzle flash, local sound, recoil, crosshair animation | Owning client. |
| Trusted gameplay: ammo, cooldown, line-of-sight, projectile spawn, damage | Server. |
| Shared presentation: replicated projectile, impact VFX/sound, hit marker, death state | Server-approved state sent to clients. |
Do not trust a client-only hit result as damage. The concrete bug is not just "cheating"; it is also normal desync: the client and server may disagree about position, collision, relevance, cooldown, ammo, or whether the target still exists.
When the pattern fails¶
Common failures have specific owners:
| Symptom | Likely owner to inspect |
|---|---|
| Fire input never runs | Mapping context, input mode, possession, or UI focus. |
| First shot works, later shots stop | Cooldown timer never resets or handle was replaced/cleared. |
| Ammo goes negative | Can-fire check and ammo consumption are not in one authoritative path. |
| Projectile flies sideways | Muzzle rotation, local X+ direction, projectile movement settings, visual mesh axis. |
| Projectile hits shooter | Spawn location, ignored owner/instigator, collision response, or self-filter. |
| Trace hits the wrong thing | Start/end math, trace channel, ignored actors, debug draw, camera/muzzle mismatch. |
| Damage event never fires | Hit actor is invalid, target rejects damage, damage called on wrong machine, or wrong damage node. |
| Impact VFX appears but health does not change | Presentation ran locally, but server damage did not happen or was rejected. |
| Multiplayer client sees projectile but server does not | Gameplay projectile was spawned only on the client. |
| Impact actors/effects pile up | Missing destroy/lifespan/auto-destroy policy. |
Debug from the boundary inward. Print the input owner and authority, draw the trace, print the hit actor/component, print the damage Return Value, and print the health change on the target.
What a first weapon pattern does not do¶
- It does not require a full inventory or equipment system. Start with one current weapon reference or component.
- It does not make UI the owner of ammo. UI displays ammo changes.
- It does not make a projectile automatically apply damage. The projectile still needs impact filtering and a damage call.
- It does not make local client cosmetics authoritative.
- It does not guarantee one hit per attack. Multi traces, overlaps, and component hits need de-duplication.
- It does not clean up misses unless you add lifespan, distance, or timeout rules.
- It does not solve fire modes, reload animation, recoil curves, spread, save data, or equipment switching. Those build on this pipeline.
Lookalikes - which one do I want next?¶
| Need | Read |
|---|---|
| Choose trace vs projectile vs overlap | Line Trace vs projectile vs overlap weapon hits |
| Build a moving projectile actor | Projectile Movement Component and first projectile actor |
| Give traces thickness or several hits | Sphere Trace, Capsule Trace, and Multi Trace basics |
| Send exact hit data into damage | Apply Point Damage / Apply Radial Damage / Damage Type |
| Make a cooldown | Set Timer by Event / Clear Timer by Handle |
| Keep multiplayer authority straight | Replication basics |
Going deeper¶
- Input Actions & Input Mapping Contexts - mapping the Fire action onto the local player.
- Started, Triggered, Ongoing, Canceled, Completed - choosing whether Fire happens on press, hold, repeat, release, or cancel.
- Spawn Actor from Class - projectile
spawn references, Owner, Instigator, and
Nonereturns. - Get Forward Vector & Find Look at Rotation - muzzle direction and aim-point math.
- Event AnyDamage / Apply Damage - the generic sender/receiver damage flow.
- Spawn Niagara System at Location / Attached, Spawn Decal at Location and impact feedback, Play Sound 2D / Play Sound at Location / Spawn Sound Attached, and Physical Materials for surface-specific feedback - impact presentation after the hit is known.
- Remote Procedure Calls - official overview for Server and Multicast RPC behavior.
- Official docs: Enhanced Input, Gameplay Timers, Remote Procedure Calls, and Implementing Projectiles.
- Engine source (requires engine access - we do not reproduce it here):
UEnhancedInputComponent::BindActioninEngine/Plugins/EnhancedInput/Source/EnhancedInput/Public/EnhancedInputComponent.h;UEnhancedInputLocalPlayerSubsystem::AddMappingContextinEngine/Plugins/EnhancedInput/Source/EnhancedInput/Public/EnhancedInputSubsystems.hand the corresponding private plugin source;FTimerManager::SetTimerandFTimerManager::TickinEngine/Source/Runtime/Engine/Private/TimerManager.cpp;UProjectileMovementComponent::TickComponentinEngine/Source/Runtime/Engine/Private/ProjectileMovementComponent.cpp; Blueprint trace wrappers such asUKismetSystemLibrary::LineTraceSingleandUKismetSystemLibrary::SphereTraceSingleinEngine/Source/Runtime/Engine/Private/KismetSystemLibrary.cpp;UGameplayStatics::ApplyDamageandUGameplayStatics::ApplyPointDamageinEngine/Source/Runtime/Engine/Private/GameplayStatics.cpp;AActor::CallRemoteFunctioninEngine/Source/Runtime/Engine/Private/Actor.cppandUNetDriver::ProcessRemoteFunctioninEngine/Source/Runtime/Engine/Private/NetDriver.cpp.