# Working but flawed bullet spread (vector math)

I'm trying to implement random bullet spread into my game, and I've managed something that works, but is consistently flawed.
The spread doesn't vary depending on the distance the mouse is from the player, it does however vary depending on the combined mouse position, aka top left is the maximum amount of spread (20 degrees I think, as I have my GetSpread( ) return 10) and top right is no spread at all.

void Player::StartShooting( float mouse_x, float mouse_y )
{
for( unsigned int i = 0; i < activeFirearm.bullet.size( ); i++ )
{
if( activeFirearm.bullet[i].GetActive( ) == false )
{
activeFirearm.bullet[i].SetOrigin( GetPosition( ).x + GetHitbox( ).x / 2,
GetPosition( ).y + GetHitbox( ).y / 2 );
sf::Vector2f projEndNorm = vectorNormal( { mouse_x - activeFirearm.bullet[i].GetOrigin( ).x,
mouse_y - activeFirearm.bullet[i].GetOrigin( ).y } );
float ratio = tan( activeFirearm.GetSpread( ) * 3.141592 / 180 );
float random = static_cast<float>( randomNumberGenerator( static_cast< int >( ratio * -100000 ),
static_cast< int >( ratio * 100000 ) ) ) / 100000.f;

//projEndNorm.x += random;
//projEndNorm.y += random;
projEndNorm = vectorNormal( { projEndNorm.x + random,
projEndNorm.y - random } );
if( projEndNorm.x == 0 &&
projEndNorm.y == 0 )
{
if( mouse_x < GetPosition( ).x + GetHitbox( ).x / 2 &&
mouse_y == GetPosition( ).y + GetHitbox( ).y / 2 )
{
projEndNorm.x = -0.999f;
projEndNorm.y = -0.001f;
}
else if( mouse_x > GetPosition( ).x + GetHitbox( ).x / 2 &&
mouse_y == GetPosition( ).y + GetHitbox( ).y / 2 )
{
projEndNorm.x = 0.999f;
projEndNorm.y = 0.001f;
}
else if( mouse_x == GetPosition( ).x + GetHitbox( ).x / 2 &&
mouse_y < GetPosition( ).y + GetHitbox( ).y / 2 )
{
projEndNorm.x = -0.001f;
projEndNorm.y = -0.999f;
}
else if( mouse_x == GetPosition( ).x + GetHitbox( ).x / 2 &&
mouse_y > GetPosition( ).y + GetHitbox( ).y / 2 )
{
projEndNorm.x = 0.001f;
projEndNorm.y = 0.999f;
}
}
activeFirearm.bullet[i].SetPosition( activeFirearm.bullet[i].GetOrigin( ) + projEndNorm );
activeFirearm.bullet[i].SetVelocity( vectorNormal( activeFirearm.bullet[i].GetPosition( ) - activeFirearm.bullet[i].GetOrigin( ) ).x * activeFirearm.GetInitialVelocity( ),
vectorNormal( activeFirearm.bullet[i].GetPosition( ) - activeFirearm.bullet[i].GetOrigin( ) ).y * activeFirearm.GetInitialVelocity( ) );
activeFirearm.bullet[i].SetActive( true );
activeFirearm.bullet[i].SetStep( 0 );
break;
}
}
}


I'm not sure what I should do to make it work.

• It is not clear to me what is the problem you are facing, and what is the result you're expecting, and what are the images. Jun 6 '15 at 23:13

First thing you should do is to refactor your code, to have a clear view about what's being done. So get out of thee loop the values that won't change, and cache any re-used value.

Below is some pseudo-code that should get you in the right direction.

The principle is : get the aimAngle, the angle between player and target (mouse).
Then randomly pick an angle aimAngle +- the spread.
Then build the vector having weaponSpeed as norm and this random angle as direction.

There's just a little trick here, to avoid numerical issues,( causing almost random direction ) , that is to re-use the last valid aimAngle in case the mouse is too near from the player ( making atan useless with too small y/x figures ).

void Player::StartShooting( float mouse_x, float mouse_y )
{
// compute the start point of all bullets once
var startPoint = GetPosition( ) + GetHitbox( ) / 2;
// compute player to mouse distance
var distance = ... you guess
var aimAngle = 0;
if (distance < minDistance ) {
// if the mouse is too close from the player, the angle cannot be accurate.
// lastAimAngle is a class property init. to zero.
aimAngle = lastAimAngle;
} else {
// get angle
aimAngle = atan2 ( delta y , delta x) ;
lastAimAngle = angle
}
// retrieve weapon data
var bulletVelocity = activeFirearm.GetInitialVelocity( );
//
var bullets = activeFirearm.bullet ;
for( unsigned int i = 0; i < bullets.size( ); i++ )
{
var bullet = activeFirearm.bullet[i];
if( bullet.GetActive( ) == true ) continue;
// pick a random angle between aimAngle - weaponSpread and  aimAngle + weaponSpread
var bulletAngle = aimAngle + weaponSpread * ( random() - 0.5 ) ;
var speedVector = { bulletVelocity * cos ( bulletAngle ) ,
bulletVelocity * sin ( bulletAngle )  } ;
// setup the  bullet
bullet.SetOrigin( startPoint );
bullet.SetPosition( startPoint );
bullet.SetVelocity( speedVector );
bullet.SetActive( true );
bullet.SetStep( 0 );
break;
}
}

• Your version is certainly easier to read, but wouldn't it impact the performance, creating variables and a new bullet object for each time you shoot? Jun 7 '15 at 10:41
• This code is not creating any object : it uses variables to hold reference to the various object. So yes, there are more variables, but just to take the bullet example, factorising it in the code above trades 1 local var for ... 6 indirection and 6 array access if you keep using activeFirearm.bullet[i]. No doubt it will be faster. And remember, after compilation, a local var will most likely create ZERO overhead -they are just using a CPU register-. Jun 7 '15 at 10:49
• Oh I did not know that, thought variables was slightly expensive when declared, but if they don't have any impact at all I should be breaking up my code a lot more. Jun 7 '15 at 10:55
• Also, at the very end, should I assign my array bullet to be the local bullet? Otherwise I can't see how I should manipulate and update the local bullet further past the StartShooting( ) function. Jun 7 '15 at 11:00
• Yes, only when you are using a lot (>16 or 32, depending on CPU) of variables at the same time (for instance : in the same loop) then the compiler will have to swap registers. Ever did that ? I never did, even in quite complex algorithm. Concretely, you can forget about sparing your vars, and always factorize your code. The extra bonus being, like in this code above, that we always know what we are dealing with. For instance the 'var bullet = ...' line makes obvious we're iterating the bullet array to process each bullet. Jun 7 '15 at 11:02