Projectile
A projectile is an actor that moves at a constant speed each tic (unless A_ScaleVelocity or similar function is used to accelerate or decelerate it). For very fast projectiles, the normal collision code does not work and the actor should instead inherit from FastProjectile. On impact with a SHOOTABLE actor, the projectile will deal damage according to its Damage property. If the projectile has the RIPPER flag, it will pass through the actor; otherwise it will stop there. No projectiles can pass through walls.
When a projectile collides, it explodes and enters its death state, although technically it is not killed. If the Crash and XDeath states are defined, they may also be entered instead of the Death state, according to the collision type:
Collision | State entered |
---|---|
Wall | Death |
Bleeding actor | XDeath |
Non-bleeding actor | Crash |
A non-bleeding actor is an actor with the NOBLOOD flag.
Projectiles generally have the Projectile combo to be a fully-fledged projectile (i.e. to be able to activate projectile line specials, enter its Death state sequence when hitting something, etc.), but the defining flag that makes an actor a projectile is MISSILE. Any actor without the MISSILE flag is not a projectile.
Note that the MISSILE flag only defines two explicit behaviors:
- An actor with this flag will stop (its velocity will be set to 0) and enter Death (or another suitable state sequence) upon hitting something.
- This flag is also required to use the Bouncetype property or any of the related flags (DOOMBOUNCE, HEXENBOUNCE, etc.). Actors without the MISSILE flag cannot bounce, regardless of any other properties or flags.
The full Projectile combo implies the following flags:
- NOBLOCKMAP - Excludes the actor from blockmap, so that it can't be passively collided with.
- NOGRAVITY - Disables gravity. Note, this flag has to be unset after the combo if you want to make a gravity-affected projectile.
- DROPOFF - The actor can cross ledges.
- MISSILE - The actor stops and enters Death sequence when hitting something. Also allows the actor to recognize bouncing properties and flags.
- ACTIVATEIMPACT - The actor activates "projectile hits" line specials.
- ACTIVATEPCROSS - The actor activates "projectile crosses" line specials.
- NOTELEPORT - The actor can't activate teleport specials.
It is acceptable to use the MISSILE flag on non-projectile actors (such as bouncing debris or gibs) to enable bouncing; the Projectile combo should not be used in this case, since the other behaviors it implies are normally undesirable for bouncing FX actors.
Pointers
By default projectiles utilize only one pointer: target. Counter-intuitively, the pointer doesn't point to the actor that the projectiles' shooter is attacking but rather to whoever shot the projectile (i.e. a monster that fired it with A_SpawnProjectile or a player pawn whose weapon fired it with A_FireProjectile). Projectiles don't get any pointers to their potential victims because they don't need to: they simply damage any actor they collide with while moving.
Seeker projectiles (ones using the SEEKERMISSILE flag and seeking functions) store the actor they are tracking to their tracer pointer (the target pointer however is still used like above).
Having a proper target pointer is important for various reasons:
- It lets the shooter get a credit for the kill or damage, which makes sure a proper obituary is printed, the damaged victim will start hunting the correct actor and other little things.
- It ensures proper collision: a projectile can't collide with its shooter, because otherwise it would explode immediately upon firing (since projectiles are actually spawned inside whoever shot them)
- If the target pointer is not set correctly, A_Explode will always hurt the shooter, even if the XF_HURTSOURCE flag is not set.
As such, spawning a projectile in a way that doesn't set the target pointer is not recommended. If, for example, A_SpawnItemEx is used to spawn a projectile, the SXF_SETTARGET flag should be added to set the target correctly. If a projectile spawns more projectiles mid-flight (e.g. if it's some kind of a cluster bomb), the extra projectiles must have the target pointer set correctly (again, in case of A_SpawnItemEx the SXF_TRANSFERPOINTERS flag should be used).
(Note: the summon <classname> console command has special handling for projectiles, where it'll automatically assign the player who used the command as the target of the projectile.)
Projectiles do get a victim pointer to the actor they're colliding with (or flying through), but that pointer only exists within the context of the SpecialMissileHit() virtual function. It can be utilized to create a custom "ripper" projectile that conditionally damages specific actors.
Speed
A projectile's speed property defines 2 things:
- How many units away from the center of the actor the projectile will spawn (no more than the actor's radius however: projectiles need to spawn within their shooter to make sure they don't spawn inside geometry or other actors)
- The initial velocity it receives upon spawning
Once a projectile is fired, the speed property is no longer used, and modifying it (directly in ZScript or via A_SetSpeed) will have no effect. At this point the projectile is flying with a constant momentum and will do so until it collides with something.
If you wish to change a projectile's velocity mid-flight, you can either use A_ScaleVelocity, or multiply its vel vector in ZScript.
For example, this variation of the rocket will continuously increase its velocity until it reaches 60 (this is done to avoid potential collision issues, since non-FastProjectiles should not use speed above 60):
class SpeedingRocket : Rocket
{
override void Tick()
{
super.Tick();
if (vel.length() < 60)
{
vel *= 1.05;
}
}
}
This variation changes its velocity to 60 after 35 tics (1 second) in flight (and also plays its seesound, the same sound it plays when fired).
class ExtraFuelRocket : Rocket
{
bool spedup; // store if the rocket was sped up
override void Tick()
{
super.Tick();
if (GetAge() > 35 && !spedup) //check the age and if it has been sped up yet
{
A_StartSound(seesound); //play the [[Actor_properties#Sound|seesound]]
vel = vel.unit() * 60; //set velocity to 60
spedup = true; //record that it has been sped up
}
}
}
Virtual methods
int SpecialMissileHit(Actor victim)
This virtual function is called when a projectile hits an actor. This is called after all other collision checks. Returning one of the following values will determine what the missile does next:
- 1: The projectile ignores the actor (no collision happens, the projectile keeps moving past the actor)
- 0: The projectile hits but does no special behavior (e.g. won't deal damage, explode, etc.)
- -1: Uses standard projectile hit behavior (i.e. it'll die by default, or rip through the actor if it uses the RIPPER flag
victim - The actor that was hit by the projectile. This pointer only exists within the context of this virtual function. Note, this pointer points to whatever the projectile collides with, whether it can be damaged or not.