Thursday 19 July 2012

Top-Down Shooters and Recoil

Any shooter fan who's played even a couple games knows how important recoil is for gun play. It's one of the primary attributes when balancing weapons, for example handicapping a weapon that deals high damage per shot with lots of recoil. Additionally, it can aid immersion, making the player feel even more like they are the soldier/person holding the weapon they're firing.

In this blog post, I'm going to be discussing the potential applications of recoil for a top-down shooter, how they might be implemented and what the advantages and disadvantages of each are.

Spread

Spread is the most common system used for simulating recoil in contemporary shooters. It revolves around the concept that the point on screen that the user is aiming at is the centre of a circle and that the bullets fired from the gun will land anywhere within that circle. Obviously the larger the circle, or "spread", for that weapon then the less likely the bullets will land exactly where the user is aiming.

Usually this circle tends to grow as the player continuously fires their weapon and shrink when they're not, encouraging users to use controlled bursts (as one would do when firing a real firearm) instead of prolonged spraying.


In 3D games, incorporating spread when firing would be accomplished by randomly selecting an angle between 0 and 2π radians and then adding a random offset less than the current amount of spread to the firing direction. For a 2D shooter, the spread is more of a line instead of a circle, but the concept is still the same. The difference is that you only pick from 2 directions, left and right.

Pros:
- Relatively simple to implement.
- Common mechanic that's easily understood.

Cons:
- No ability to "manage" recoil.
- Not very immersive.

For the purposes of simplicity, we'll assume that the player class keeps track of the total recoil, allowing it to persist when changing weapons or performing actions. This value will be incremented when the weapon is fired and decreases gradually over time using the player's Update() method. When we project the shots into our world, we simply adjust the firing angle by a random amount lower than the total recoil.
// Determine which direction to spread in.
float direction = random.NextDouble() > 0.5 ? 1 : -1;

// Determine amount of spread.
float spread = direction * (random.NextDouble() * recoil);

// Construct ray direction.
Vector2 aiming = Vector2.Transform(Vector2.UnitX,
    Matrix.CreateRotationZ(rotation + spread);

aiming.Normalize();
Now the direction is ready for raycasting into the world. It's been slightly offset from the player's rotation by an amount which is less than the total amount of spread in either direction. Obviously, you can combine multiple lines to shorten the implementation and save memory allocation.

View Kick

View kick is also relatively prevelant in modern 3D shooters, though it's usually used more as a slight immersive effect rather than the core mechanic. The idea behind it is that when the user fires their weapon, a slight offset or "kick" is applied to the player's view to mimic the gun recoiling in their hands. Larger calibre weapons would obviously add a greater offset than smaller ones.

Like spread, the view could gradually reset back to its previous position when not firing. Some players find it more difficult to engage multiple targets when the aim is resetting though, so some games simply leave the view where it ends up to avoid this issue.


Incorporating view kick in a 3D shooter is done by selecting a direction which is mostly up, using a similar approach to spread, and adjusting the player's view by an appropriate amount for the weapon held. Once again, for 2D shooters you would only select from two directions (left or right), though you would also add a small amount of noise to the offset to compensate for the lack of a second axis.

Pros:
- More immersive than other approaches.
- Allows user to manage recoil by adjusting aim.

Cons:
- Large amounts of kick can be jarring.

View kick is slightly simpler to implement than spread, as it just involves adjusting the player's rotation. However, the recoil value needs to be incremented both positively and negatively when firing to make it kick in both directions. You would then override the player's rotation property to include this value, such that every time it is updated it "kicks" the player's view.
// Determine which direction to kick in.
float direction = random.NextDouble() > 0.5 ? 1 : -1;

// Calculate random noise.
float noise = (random.NextDouble() > 0.5 ? 1 : -1)
    * (random.NextDouble() * MaxNoise);

// Determine amount of kick.
float kick = (direction * recoilRate) + noise;

// Add recoil.
recoil = MathHelper.Clamp(recoil + kick, -0.25f, 0.25f);
The above implementation assumes that there is a gradual reset and clamps the rotation to stop the player spinning, though you could omit both to permanently alter the player's aim and avoid the issues with resetting. Raycasting proceeds as normal since the offset is applied directly to the player's rotation value, not the aiming angle.

Crosshair Kick

Crosshair kick is a slightly different take on view kick which is more common in simulator-style shooters where head movement can be controlled seperately from weapon aim. It works by applying kick to the weapon and/or crosshair, rather than the entire character. Like view kick, more offset is added for larger calibre weapons.

Unlike view kick, it would be impossible to omit resetting the offset as the crosshair could potentially get stuck at a limit or otherwise scroll off screen. As such, the crosshair must gradually reset when not firing to avoid this problem, similar to how spread reduces when idle.


Crosshair kick is performed using an offset similar to that in view kick. The difference is that rather than immediately applying it to the player's view, the offset is applied to the aiming direction when firing. It might also be necessary to update both the crosshair and the player's arms/weapon to point in the direction of the offset so that the player has a rough idea of where they're aiming.

Pros:
- Allows user to manage recoil.
- Not as jarring as view kick.

Cons:
- Slightly more complicated to implement.

Crosshair kick is implemented in a similar manner to view kick. The difference is that rather than updating the player's rotation, this value is used when adjusting the aim. I prefer to adjust the vector, rather than the angle, as that allows us to more easily determine where the crosshair is on screen (so that we can draw it later).
// Calculate firing angle.
Vector2 aiming = Vector2.Transform(new Vector2(1, recoil),
    Matrix.CreateRotationZ(rotation));

aiming.Normalize();
This way the recoil value can be directly used when drawing the crosshair to place it exactly where the bullets would go. You simply scale the value by how far away the crosshair is from the player. (i.e. If the crosshair is 480 pixels away from the player than you use 480 * recoil.)

No comments:

Post a Comment