I am developing a 2D run 'n' gun game in Unity and am having an issue getting the aiming to "feel right".
Some background:
- The player can aim in 6 directions when running (left, right, and the four diagonals)
- The player can aim in 8 directions when standing still and holding a lock button (left, right, up, down, and the four diagonals)
For digital input like a keyboard or D-pad, aiming is pretty easy. I do it with conditionals like
if (left_key.pressed && up_key.pressed) { aim_diagonal_up();}
But I have yet to find a satisfying way to deal w/ analog input from the thumbstick, should the player be using that instead of the D-pad.
The first thing I tried was a naïve implementation where I treated crossing various deadzone thresholds as, essentially, digital button presses, and then used conditionals like the above to set the aim (I'm using the popular Rewired extension to handle input in Unity, which makes this a breeze).
But even permuting all the different parameters in Rewired (for example, deadzone threshold, radial deadzone versus axial deadzone, sensitivity, and so on), I couldn't find a configuration that "felt good".
Either aiming w/ the joystick felt sluggish and unresponsive, or was too sensitive so that it was easy to accidentally shoot diagonally when you wanted to shoot forward. Some permutations felt better than others for aiming, but resulted in character movement itself feeling slugging (since any deadzones set on the thumbstick for aiming are also used for movement of the character left and right).
The second thing I tried was polling the thumbstick in my code to get its current position. If the length of the vector is above some custom deadzone I specify (say, 0.3), I aim according to the angle that vector makes with Vector2.right
, looking at which sector of the unit circle the angle falls in (where there are 8 sectors, each spanning 45 degrees)
Here's a snippet:
// aim
Vector2 input_vector = Vector2.zero;
input_vector.x = player.GetAxis("HorizontalAxis");
input_vector.y = player.GetAxis("VerticalAxis");
float length = input_vector.magnitude;
input_vector = input_vector.normalized;
float angle;
angle = Utility.GetAngle(input_vector, Vector2.right);
if (CustomInputManager.instance.IsJoystickConnected())
{
if (length > 0.3f)
{
// right forward
if (-22.5f < angle && angle <= 22.5f)
{
upper_animator.Play("upper_shoot_forward");
firing_point = forward_point.position;
}
// right forward up
else if (-67.5f < angle && angle <= -22.5f)
{
upper_animator.Play("upper_shoot_diagonal_up");
firing_point = diagonal_up_point.position;
}
// up
else if (-112.5f < angle && angle <= -67.5f)
{
upper_animator.Play("upper_shoot_up");
firing_point = up_point.position;
}
You get the idea.
But even this felt off. I was still running into the problem of accidentally aiming diagonal when trying to aim forward (especially in frenetic situations), or accidentally aiming upwards or downwards when I was trying to aim diagonal.
So the next think I did was started studying run 'n' guns like Mummy Demastered, Blazing Chrome, and Valfaris (all games whose game feel I am trying to emulate). Then I noticed that it seems like they are doing something a bit more nuanced than just mapping thumbstick angle to equivalently-sized sectors of the unit circle.
For example, in Mummy Demastered, if you push the thumbstick directly up, once you pass the deadzone, the player will first aim diagonal up, then straight up, suggesting aiming in these directions is entirely based on the vertical component of the axis, rather than both vertical and horizontal. Aiming down, however, does not work like this in the game.
As another example, in Blazing Chrome & Valfaris, it seems like the sector for aiming diagonal up is not simply -22.5 to -67.5. It seems to start later than you'd expect, closer to -45, and extends closed to -90. The sector for aiming directly up actually feels quite small. And this feels good in game.
Similarly, the sector for aiming diagonal down starts earlier than you might expect, and this also feels good.
My question is not whether these games are actually using these techniques (I know that's off topic), but rather, What are the different techniques you can use for implementing 8-way aiming in a 2D sidescrolling run 'n' gun (are there other nuanced techniques like those I think I've identified above)?
And what are efficient ways of actually implementing those techniques? (For example, I am currently using inequalities to capture the notion of sectors of a unit circle, when I'd like a more flexible representation that would allow me to easily tweak sector sizes as I balance the game feel)
Some references
- Mark Venturelli's Everything I Learned About Dual-Stick Shooter Controls
- Josh Sutphon's Doing Thumbstick Deadzones Right