Projectiles

From Valve Developer Community
Revision as of 20:18, 25 April 2012 by Artfunkel (talk | contribs) (Prediction)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Projectiles are entities which emerge from weapons and usually explode either on contact or on a timer. Most inherit from either CBaseGrenade (for VPhysics) or CBaseAnimating (for QPhysics).

Although perfectly ordinary entities in a technical sense, projectiles do require some special design considerations. This article will cover them.

Collisions and ownership

One annoyance with projectiles is their tendency to immediately bump into and potentially explode on the player who fired them. This happens either because the projectile spawned inside the player (in which case it will become stuck there) or because the player is moving fast enough to collide with it from behind while it's still close.

Ownership is used to record which player fired a projectile and in theory can be used to filter out owner collisions. But unfortunately an object never collides with its owner. This total lack of owner collision is bad because if a player is teleported or respawns into the path of an owned projectile it ought to hit him like it would anyone else.

One way of solving these problems follows. See Owner#Collisions with owner for details on adding support for FSOLID_COLLIDE_WITH_OWNER.

void CMyProjectile::Spawn()
{
	// If you don't want to implement FSOLID_COLLIDE_WITH_OWNER, store the owner in a private pointer.
	// DON'T try to store ownership recursively via the weapon, since a weapon may be removed from the world.
	AddSolidFlags( FSOLID_NOT_SOLID | FSOLID_TRIGGER | FSOLID_COLLIDE_WITH_OWNER );

	bGracePeriod = true;

	// Thinking will disable the grace period. Adjust the delay to suit the speed of the projectile.
	SetNextThink( gpGlobals->curtime + 0.2 );

	/* ... */
}

void CMyProjectile::Touch( CBaseEntity* pOther )
{
	if ( bGracePeriod && pOther == GetOwnerEntity() )
		return;

	/* ... */
}

void CMyProjectile::Think()
{
	bGracePeriod = false;
	BaseClass::Think();
}
  1. FSOLID_NOT_SOLID allows the entity to pass through others.
  2. FSOLID_TRIGGER makes it call Touch() when it does so, even though there is no collision.
  3. FSOLID_COLLIDE_WITH_OWNER makes the entity behave toward its owner in the same way as it would any other entity.

If you need more precision in when the grace period ends consider testing for the owner showing up as pOther in EndTouch(). This may lead to problems with re-entry if the player is moving close to the projectile at a similar velocity, however.

Tip:You may (or may not) also want to call SetCollisionGroup(COLLISION_GROUP_PROJECTILE), which stops the entity from colliding with debris, weapons, or other projectiles.

Persistence

To do: How to best attribute kills, detect team ownership, etc. if the owner has disconnected.

Prediction

Prediction and lag compensation can be passed off for hitscan weapons because they are instant and invisible. This is not so with projectiles. To predict a rocket would mean the server spawning it a long way away from a lagged firer, potentially right in a surprised target's face: a very visible inconsistency that would also hurt gameplay, since players expect to be able to see projectiles coming.

The viewmodel is a good candidate for prediction, as is the depletion of the player's ammo stock, but not the projectiles.

Interpolation

A projectile will hang in the air for a moment before moving and will disappear a moment before it explodes. Both of these things happen because an entity's position in the world is subject to interpolation, but its creation and destruction are not.

In Team Fortress 2, Valve worked around this limitation by turning interpolation off for certain projectiles (grenades and arrows), then spawning them on the client behind the player who fired at a distance determined by the current interp period ("lerp"). Projectiles treated like this pass through the barrel of the weapon at the right moment without any pause but spawn in an incorrect location. If an observer's lerp is much higher than the default 100ms things start to look really bizarre!

Another possible solution is creating a single manager entity that spawns along with the world. This would take a lot of implementation but could also be used to reduce network overhead by reducing the frequency at which updates for simple "straight line" projectiles are broadcast.

Precaching

Because projectiles generally don't exist until after a map spawns they aren't automatically precached. To overcome this use the precache functions of weapons that fire them to call UTIL_PrecacheOther(string classname).

See also