Valve's Code on Projectiles - A how-to on making entities come out of the barrel!
5 replies, posted
Hi! Just thought I'd share a tidbit of information on projectile ejection. The following code is a 1:1 Lua translation of Valve's C++ Source SDK code for the weapon_frag and weapon_rpg, respectively.
[b]The snippets below show you how to throw and launch entities from a predesignated barrel location. This helps add an extra, although small, still realistic factor to your weapons.[/b]
[b]There are screen shots below which depict the snippet's effects.[/b]
[i]The following code can be also found within the latest revision of [url=http://code.google.com/p/swep-bases/]The SWEP Bases Project.[/url][/i]
[i]swep_frag[/i]
[lua]//-----------------------------------------------------------------------------
// Purpose:
// Input : *pPlayer -
//-----------------------------------------------------------------------------
function SWEP:ThrowGrenade( pPlayer )
if ( self.m_bRedraw ) then
return;
end
if ( !CLIENT ) then
local vecEye = pPlayer:EyePos();
local vForward, vRight;
vForward = pPlayer:GetForward();
vRight = pPlayer:GetRight();
local vecSrc = vecEye + vForward * 18.0 + vRight * 8.0;
self:CheckThrowPosition( pPlayer, vecEye, vecSrc );
// vForward.x = vForward.x + 0.1;
// vForward.y = vForward.y + 0.1;
local vecThrow;
vecThrow = pPlayer:GetVelocity();
vecThrow = vecThrow + vForward * 1200;
local pGrenade = ents.Create( self.Primary.AmmoType );
pGrenade:SetPos( vecSrc );
pGrenade:SetAngles( vec3_angle );
pGrenade:SetOwner( pPlayer );
pGrenade:Fire( "SetTimer", GRENADE_TIMER );
pGrenade:Spawn()
pGrenade:GetPhysicsObject():SetVelocity( vecThrow );
pGrenade:GetPhysicsObject():AddAngleVelocity( Angle(600,math.random(-1200,1200),0) );
if ( pGrenade ) then
if ( pPlayer && !pPlayer:Alive() ) then
vecThrow = pPlayer:GetVelocity();
local pPhysicsObject = pGrenade:GetPhysicsObject();
if ( pPhysicsObject ) then
vecThrow = pPhysicsObject:SetVelocity();
end
end
pGrenade.m_flDamage = self.Primary.Damage;
pGrenade.m_DmgRadius = GRENADE_DAMAGE_RADIUS;
end
end
self.m_bRedraw = true;
self.Weapon:EmitSound( self.Primary.Sound );
// player "shoot" animation
pPlayer:SetAnimation( PLAYER_ATTACK1 );
end
[/lua]
[b]Positioning equation:[/b]
[lua] local vecEye = pPlayer:EyePos();
local vForward, vRight;
vForward = pPlayer:GetForward();
vRight = pPlayer:GetRight();
local vecSrc = vecEye + vForward * 18.0 + vRight * 8.0;
[/lua]
[b]Explanation:[/b]
Since normally, we shoot entities out of the GetShootPos return value, which happens to be a player's eyes, we don't visually see the entity fly out of the barrel. By getting the forward normalized vector of a player (the direction they're facing), and the right normalized vector (your right-hand side), we can multiply the normalized vectors to position the entity closer to the viewmodel's barrel, therefore making it seem as if the entity has shot out of the gun.
[b]Media:[/b]
[media]http://img.photobucket.com/albums/v308/flaming-gummy-bear/gm_construct0011.jpg[/media]
[lua]//-----------------------------------------------------------------------------
// Purpose:
// Input : *pPlayer -
//-----------------------------------------------------------------------------
function SWEP:LobGrenade( pPlayer )
if ( self.m_bRedraw ) then
return;
end
if ( !CLIENT ) then
local vecEye = pPlayer:EyePos();
local vForward, vRight;
vForward = pPlayer:GetForward();
vRight = pPlayer:GetRight();
local vecSrc = vecEye + vForward * 18.0 + vRight * 8.0 + Vector( 0, 0, -8 );
self:CheckThrowPosition( pPlayer, vecEye, vecSrc );
local vecThrow;
vecThrow = pPlayer:GetVelocity();
vecThrow = vecThrow + vForward * 350 + Vector( 0, 0, 50 );
local pGrenade = ents.Create( self.Primary.AmmoType );
pGrenade:SetPos( vecSrc );
pGrenade:SetAngles( vec3_angle );
pGrenade:SetOwner( pPlayer );
pGrenade:Fire( "SetTimer", GRENADE_TIMER );
pGrenade:Spawn()
pGrenade:GetPhysicsObject():SetVelocity( vecThrow );
pGrenade:GetPhysicsObject():AddAngleVelocity( Angle(200,math.random(-600,600),0) );
if ( pGrenade ) then
pGrenade.m_flDamage = self.Primary.Damage;
pGrenade.m_DmgRadius = GRENADE_DAMAGE_RADIUS;
end
end
self.Weapon:EmitSound( self.Secondary.Sound );
// player "shoot" animation
pPlayer:SetAnimation( PLAYER_ATTACK1 );
self.m_bRedraw = true;
end
[/lua]
[b]Positioning equation:[/b]
[lua] local vecEye = pPlayer:EyePos();
local vForward, vRight;
vForward = pPlayer:GetForward();
vRight = pPlayer:GetRight();
local vecSrc = vecEye + vForward * 18.0 + vRight * 8.0 + Vector( 0, 0, -8 );
[/lua]
[b]Explanation:[/b]
The decrease in Z position notes that the entity will be thrown slightly lower. In this case, it matches up nicely with the hand position during the weapon's animation.
[b]Media:[/b]
[media]http://img.photobucket.com/albums/v308/flaming-gummy-bear/gm_construct0005-1.jpg[/media]
[i]swep_rpg[/i]
[lua]/*---------------------------------------------------------
Name: SWEP:PrimaryAttack( )
Desc: +attack1 has been pressed
---------------------------------------------------------*/
function SWEP:PrimaryAttack()
// Only the player fires this way so we can cast
local pPlayer = self.Owner;
if (!pPlayer) then
return;
end
if (self.m_bNeedReload) then
return;
end
if ( self:Ammo1() <= 0 ) then
self.Weapon:EmitSound( self.Primary.Empty );
self.Weapon:SetNextPrimaryFire( CurTime() + self.Primary.Delay );
return
end
if ( self.m_bIsUnderwater && !self.m_bFiresUnderwater ) then
self.Weapon:EmitSound( self.Primary.Empty );
self.Weapon:SetNextPrimaryFire( CurTime() + 0.2 );
return;
end
// Can't have an active missile out
if ( VERSION >= 72 ) then
if ( self.dt.Missile != NULL ) then
return;
end
elseif ( self.Weapon:GetNetworkedEntity( "Missile" ) != NULL ) then
return;
end
// Can't be reloading
if ( self.Weapon:GetActivity() == ACT_VM_RELOAD || self.m_bInReload ) then
return;
end
local vecOrigin;
local vecForward;
self.Weapon:SetNextPrimaryFire( CurTime() + 0.5 );
local pOwner = self.Owner;
if ( pOwner == NULL ) then
return;
end
local vForward, vRight, vUp;
vForward = pOwner:GetForward();
vRight = pOwner:GetRight();
vUp = pOwner:GetUp();
local muzzlePoint = pOwner:GetShootPos() + vForward * 12.0 + vRight * 6.0 + vUp * -3.0;
if ( !CLIENT ) then
local vecAngles;
vecAngles = pOwner:GetAimVector():Angle();
local pMissile = ents.Create( self.Primary.AmmoType );
pMissile:SetPos( muzzlePoint );
pMissile:SetAngles( vecAngles );
pMissile:SetOwner( self.Owner );
pMissile:Spawn();
// If the shot is clear to the player, give the missile a grace period
local tr;
local vecEye = pOwner:EyePos();
tr = {}
tr.startpos = vecEye
tr.endpos = vecEye + vForward * 128
tr.mask = MASK_SHOT
tr.filter = self.Weapon
tr.collision = COLLISION_GROUP_NONE
tr = util.TraceLine( tr );
if ( tr.Fraction == 1.0 ) then
pMissile.GracePeriod = 0.3;
end
pMissile.Damage = self.Primary.Damage;
if ( VERSION >= 72 ) then
self.dt.Missile = pMissile
else
self.Weapon:SetNetworkedEntity( "Missile", pMissile );
end
self.m_hMissile = pMissile;
end
self:DecrementAmmo( self.Owner );
self.Weapon:SendWeaponAnim( ACT_VM_PRIMARYATTACK );
if ( !self.Owner:IsNPC() ) then
self.Weapon:EmitSound( self.Primary.Sound );
else
self.Weapon:EmitSound( self.Primary.SoundNPC );
end
// player "shoot" animation
pPlayer:SetAnimation( PLAYER_ATTACK1 );
self.m_bNeedReload = true;
end
[/lua]
[b]Positioning equation:[/b]
[lua] local vForward, vRight, vUp;
vForward = pOwner:GetForward();
vRight = pOwner:GetRight();
vUp = pOwner:GetUp();
local muzzlePoint = pOwner:GetShootPos() + vForward * 12.0 + vRight * 6.0 + vUp * -3.0;
[/lua]
[b]Explanation:[/b]
As you can see, for different weapons, you may have to retrieve all directions to properly place the entity, rather than just the player's forward and right-hand directions. Here we also move the entity down
Looks like it works perfectly, I'll give it a shot next time I make some kind of projectile weapon
Rated useful.
Thanks, I made a projectile weapon a week or 2 ago but I didn't know how to position it so it was just spawning in front of your face.
[code]
local muzzle, vm, att, direction_here, rocket, ...
vm = self.Owner:GetViewModel( )
muzzle = vm:LookupAttachment( "0" )
att = vm:GetAttachment( muzzle )
...
rocket:SetPos( att.Pos )
rocket:SetAngles( direction_here )
...
[/code]
direction_here would probably be a normal from a trace's hit pos and the attachment position.
This only really works with most models, given the hand grenade model and probably some others don't have any attachments, but most of the time this is a simpler way to do it when it's available.
Your code is quite useful, though, and I don't mean to say it's not.
I figured this out myself already, but still a nice tutorial. Useful'd :buddy:
Nice tutorial!
Sorry, you need to Log In to post a reply to this thread.