Rotating Pickups

From Valve Developer Community
Jump to: navigation, search

In this tutorial we'll create a rotating health pickup entity, like the ones in TF2. When a player walks over it they will gain some health and the item will disappear until a respawn timer completes.

Get the code here. You should be familiar with creating entities, so only the interesting parts will be picked out.


Initialise the entity as a trigger
Ensure that the item floats above any walkable surfaces
Create a decal to mark the item's position when it is invisible
Handle touches from players (give health, play a respawn sound)
Rotate the model
Handle model visibility



  • Note how some of CItem's changes have to be undone (it's not designed with impossible floating objects in mind). Source is a big and mature engine, and it's always worth checking what your base classes are doing.
  • We are setting MdlTop before fiddling about with the bounding box. This will be used when repositioning the entity; if we just used the origin, then if/when a larger model is used it might reach so high that it obscures the player's view.


  • Here we trace down to our desired height, adding to the entity's Z position if we come up short. This is done in Activate() instead of Spawn() in case we spawned before the entity beneath us.
  • This is also where we fire a decal onto the ground. This might look bad in some cases, so a flag to bypass the call is provided.


  • We need to get the return value from UTIL_PrecacheDecal(), but Precace() is called statically (i.e. directly, not through any instance of the class). Storing the value in a global variable is the only sensible solution.



  • C_RotatingPickup::ClientRotAng is used to rotate the model, not CBaseEntity's built-in variable. This is because even though the latter has been excluded from this entity's datatable, it is still zeroed when the entity materialises (I suspect that CItem is resetting the entity's location through some sort of back door). Not calling GetAbsAngles() also provides a small performance boost.


  • If you have looked into receive proxies, you may be wondering why we are testing against a cached value when we could simply hook into the receipt of an updated one. Two reasons:
    1. You can't have proxies on RecvPropBool().
    2. Proxies are static and should never, ever be used to run entity logic. Receive proxies are called not only when receiving entity updates, but also when validating predicted values. It's quite possible for a proxy to be re-used by another entity too.