Class AbilityApplyHitscanAction
Action that resolves an instantaneous ray from the ability object and runs the ability's OnHit events for everything it passes through — the hitscan half of the ability system.
[Serializable]
public class AbilityApplyHitscanAction : BaseAction, IAction
- Inheritance
-
AbilityApplyHitscanAction
- Implements
- Inherited Members
Remarks
This is what makes Bullet and Beam abilities possible, and they are the same action wired to different events. Nothing else about the two differs: both resolve a ray from the ability object's pose, in ray order, capped at MaxHitsValue.
| Bullet | An instant ability with this action on its OnSpawn event. The object exists for one tick, fires once, and its lifetime expires. Nothing travels, so there is no projectile to dodge and no lead to aim — the shot lands on the tick it was fired. |
| Beam |
A channelled ability (one carrying AbilityController.ChanneledTemplate) with
this action on its OnSpawn event. SpawnChanneledAbility already spawns one
object per tick for the life of the channel, each at the caster's CURRENT aim, so the beam
tracks where the player is looking and re-resolves its hits every tick. Damage per tick is
the ability's damage; hold duration is the channel's.
|
Why a per-tick respawn rather than one long-lived object with this on OnTick. An ability object's pose is fixed at spawn and advanced by a closed form (AbilityMoveTransformAction), so a single object re-raycasting each tick would keep firing along the heading it was born with — a beam welded to the direction you were facing when you pressed the button. The channel path re-resolves the aim per tick because it genuinely re-spawns, which is also why every peer can reproduce it: each spawn is broadcast with its own pose. Putting this on OnTick is still legal and is the right choice for a fixed emplacement — a turret, a trap, a laser fence — where not tracking is the point.
Server only, like every other hit-resolving action. A physics query is not
reproducible across peers, so it must run exactly once, where hits are authoritative; clients
learn the outcome through the usual paths (damage via the resource broadcast, the visual via
AbilityActivatedBroadcast reproducing the spawn). The owner still sees its own damage
immediately, because ApplyDamageAction predicts on the caster's client and
PredictedCombatEvents greys out anything the server does not confirm.
Lag compensated, and it is the shape that needs it most. A ray is infinitely thin, so unlike a blast radius it has no tolerance to absorb the gap between where the shooter saw a target and where the server holds it — at 300 ms that gap is metres and the shot is simply a miss. RaycastNearest(EventData, GameObject, Vector3, Vector3, float, LayerMask, int, bool, List<CompensatedHit>, Transform) rewinds every character in the scene to the tick the caster's client was rendering, runs the ray there, and orders and caps the result inside the same scope.
Fields
BlockedByScenery
Whether scenery stops the shot.
[Tooltip("When true the ray is blocked by scenery, so cover works. When false it passes through the world and only characters count.")]
public bool BlockedByScenery
Field Value
Remarks
The one authored decision this action has that the area version does not, and it is a real gameplay choice rather than an implementation detail. True — the default — means the ray is blocked by the first thing it meets that is not a character, so a target behind a wall is safe and cover works. False means only characters are considered at all and the ray passes through the world, which is what a debug weapon or a deliberately wall-piercing ability wants.
It is expressed as "does scenery count" rather than left to the layer mask because the mask defaults to every layer: an author who never touches it would otherwise get a shot that spends its entire pierce count on terrain and hits nobody.
MaxHitsValue
The value provider that determines how many distinct characters the ray may hit.
[Tooltip("How many distinct characters the ray may hit. 1 stops at the first; 0 or less pierces everything.")]
[SerializeReference]
public IIntValueProvider MaxHitsValue
Field Value
Remarks
The pierce count. One is a shot that stops at the first target; zero or less is a beam that passes through everything on its line, matching CappedCount(int, int) and every selector.
RangeValue
The value provider that determines how far the ray reaches, in metres.
[Tooltip("The value provider that determines how far the ray reaches, in metres.")]
[SerializeReference]
public IFloatValueProvider RangeValue
Field Value
Remarks
Authored rather than taken from Ability.Range, which is Speed * LifeTime and
therefore zero for anything that does not travel — every hitscan ability by definition.
TargetLayerMask
Layers the ray tests against.
[Tooltip("Layers the ray tests against.")]
public LayerMask TargetLayerMask
Field Value
- LayerMask
Methods
Execute(ICharacter, EventData)
Resolves the ray and runs the ability's OnHit events for each character it found.
public override void Execute(ICharacter initiator, EventData eventData)
Parameters
initiatorICharacterThe character that cast the ability.
eventDataEventDataEvent data carrying the ability object.