Looking for Guidance on How to Implement Mouse Cursor Rotational Direction Control

Hi,
Forgive me if this question is previously asked - I have an effect that I’m going for, but I don’t know what I don’t know, so I can only think to bluntly throw up a forum post with my ask.
I’m working on a fighting game, but the perspective is top-down, so ability direction is indicated with mouse position. The animations happen about the player’s model as a reference point, and the animations might be rotating about an axis through that point, so I want to program the ability to initiliate the animation going in either direction. I’ll share a gif below to demonstrate this.
abililityRotationDirectionDemo

Refering to the gif:

  • the ball in the center of the screen is the “player”
  • the little white boxes appearing near the players are hitboxes created by the character’s ability.
  • In the gif, I think all but the first hitbox are generated from the same cast, so the white box that you see are all the same hitbox and they have the same dimensions, but they appear at different positions about the player because the mouse is positioned differently.
  • It may be hard to tell in the gif, but - the hitbox trajectory is sometimes clockwise, and sometimes counterclockwise, as desired.

So, here is the crux of my post: I want to program the input such that the direction of movement of the hitbox is clockwise or counterclockwise, as determined by the movement of the player’s mouse when they initiate the cast.

The current implementation is as follows:

  • initialization of hitbox animation, with respect to boolean rotatingClockwise
Hit h = Instantiate(hitEvent.hit);
h.Initialize(caster, caster.transform, !rotatingClockwise);
hits.Add(h);
  • Update of the boolean rotatingClockwise
var direction = new Vector3(cursorWorldPosition.x-playerPosition.x, 0, cursorWorldPosition.z-playerPosition.z).normalized;
Quaternion newRotation = Quaternion.FromToRotation(Vector3.forward, direction);
float yRotationDiff = newRotation.y - transform.rotation.y;

if (yRotationDiff != 0) {
    RotatingClockwise = (yRotationDiff>0);
}

So those are the snippets relevant to this question. In brief - the player’s look direction is directly at the mouse (and to be more specific, it’s the intersection of the mouse’s raycast through a horizontal plane through the center of the player). Each frame, I look at the of the player from the previous frame to the next frame, check the difference in the angle, and update rotatingClockwise accordingly.

I think this implementation works perfectly, barring one thing - given that the degree of rotation is calculated between two game logic frames (@60Hz), the rotation angle is subject to any sort of jittering of the mouse position. So, I might feel that my mouse cursor is spinning my character clockwise, but if my hand movement isn’t smooth and the mouse position twitches in the opposite direction during a frame, the direction of rotation will be opposite to what might be the user’s “percieved” direction of rotation.

So, how can I reflect the model rotation direction to more closely mirror a user’s percieved rotation direction, as determined by their real mouse position? I have some conjectures as to what might work, but maybe there are some standard approaches to this sort of problem.

Approach off the top of my head:

  • create a stack of n previous mouse positions or mouse rotations, and calculate the direction of rotation as the average rotation
  • though, then, what should n be? maybe that would just be determined by the player?
  • Would the performance of this be okay? Maybe I could do some sort of moving/decaying averaging to speed it up?

and, a bonus adjacent question, if I may - while working on this, I came across the following issue: The object rotation angle is calculated in radians. Given that the value is periodic, there’s a point in the object’s rotation where the rotation angle skips from the top of the range to the bottom of the range. I still haven’t written out the logic to account for this because, while I was working on it, I found the following:

  • the rotation of Vector3.forward seems to be 0u - gorgeous, makes sense
  • rotation of Vector3.back seems to be 1u - great
  • rotating clockwise from Vector3.forward to Vector3.back increases the angle from 0 to 1 - as expected
  • rotating counterclockwise from Vector3.forward to Vector3.back starts decreasing from 0 to negative numbers, but somewhere in Q3 (around -.86), the values are suddenly positive!

What the heck is that about? I would expect the jump to happen from 1 to -1, but the values in the range I’d expect to be ~[-.86, -1] are all positive. I see that the .86 value seems to be about sqrt(3)/2, and it visually looks as if the jump happens at 45deg from the axes. Also, to be clear - the angle of rotation isn’t something that I’m calculating - I’m pulling the value directly from transform.rotation, so I don’t think the wonky value has anything to do with the logic in my project.

Aaah yes, the brain-exploding confusion that comes from accessing the internals of a quaternion… I remember it well myself. It’s kind of a rite of passage. :slight_smile:

These values you are accessing:

are NOT angles, not in radians, not in degrees, not in nothing that is any use to any of us gamedevs.

Those are the internal four parameters of a Quaternion. Consider them black magic.

You may be able to get what you want from using newRotation.eulerAngles.y and transform.rotation.eulerAngles.y, but as you note, those too are subject to wrapping at 0 and 360

However, if you use Mathf.DeltaAngle() you may be able to properly compare these values regardless of wrapping.

Here’s more relevant reading:

All about Euler angles and rotations, by StarManta:

https://starmanta.gitbooks.io/unitytipsredux/content/second-question.html

Quaternion dumper if you absolutely insist on peeking inside the ugly parts:

1 Like

This is immensely helpful, thank you! I am pretty new to this stuff, and I vaguely remember reading those same points on considering Quaternions “black magic” haha. I’ll get to studying about them at some point.

Given that the part about rotation angles wasn’t the primary ask of the post, I won’t mark this as a solution, but this is definitely a piece of my puzzle.

If I understand, you want the “sweep” of the mouse as it passes the player to impart a rotation to the spell, either left or right, correct?

If so just use Mathf.Atan2() to get the difference between:

  • player to first mouse sample
  • player to last mouse sample

Atan2() give you radians so convert with Mathf.Rad2Deg, then use Mathf.DeltaAngle() again to determine positive or negative, and spin accordingly.

If I understand, you want the “sweep” of the mouse as it passes the player to impart a rotation to the spell, either left or right, correct?

Correct - however, I do currently have it implemented as you’ve described, but if I measure the DeltaAngle over the timespan of two input frames (where input is measured at 60Hz), the measurement is very sensitive. See the OP, where I describe:

[…] if my hand movement isn’t smooth and the mouse position twitches in the opposite direction during a frame, […]

Input can be noisy like that… I would use more than 2 subsequent frames… use from start to current frame, or keep a running list of all inputs and when you have enough of them, generate an input signal.

This means you’d have to change your game so the dragging could be happening and no signal has been generated yet, eg, you haven’t dragged far enough.

You can also smooth vectors over time by summing and averaging…

Hmm, I’ve thought about it more, and I’ve also come up with the following two solutions:

  • Calculate a moving average, with some amount of decay:
// each frame, recalculate a moving average rotation
Quaternion newRotation = Quaternion.FromToRotation(Vector3.forward, direction);
float yRotationDiff = Mathf.DeltaAngle(newRotation.eulerAngles.y, transform.rotation.eulerAngles.y);

if (!Mathf.Approximately(yRotationDiff, 0f)) {
    // where  averageRotationDecay in (0, 1]
    MovingAverageRotation = averageRotationDecay*yRotationDiff + (1f-averageRotationDecay)*MovingAverageRotation;
    if (!Mathf.Approximately(MovingAverageRotation, 0f)) {
        RotatingClockwise = (MovingAverageRotation<0);
    }
}
  • and while trying to get the above implementation working, I also figured I could just identify some minimum rotation angle threshold:
Quaternion newRotation = Quaternion.FromToRotation(Vector3.forward, direction);
float yRotationDiff = Mathf.DeltaAngle(newRotation.eulerAngles.y, transform.rotation.eulerAngles.y);

if (Mathf.Abs(yRotationDiff)>rotationThreshold) // I still haven't decided a default rotationThreshold...
  RotatingClockwise = (yRotationDiff>0);

I’ll keep messing around with these. Thanks, @Kurt-Dekker !

1 Like

Don’t forget about the cheesy case of using Lerp as a poor-mans low-pass filter to smooth things out:

In the case of input smoothing (implementing your own input filtering):

1 Like