From ZDoom Wiki
Jump to navigation Jump to search
For more information on this article, visit the Projectile page on the Doom Wiki.

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, but they only really require MISSILE. Any actor without the MISSILE flag is not a projectile.


By default projectiles utilize only one pointer: target. Counter-intuitively, the pointer doesn't point to the actor that the projectiles' shooter is targeting, 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 need extra pointers to be able to deal damage, because their behavior is based on collision: they will damage whatever actor they collide with.

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.


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()
		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()
		if (GetAge() > 35 && !spedup) //check the age and if it has been sped up yet
			A_StartSound(seesound); //play the seesound
			vel = vel.unit() * 60; //set velocity to 60
			spedup = true; //record that it has been sped up