- Home /
How to Add Limits to Rotating Wheel?
I have a wheel that the player can click on with their mouse, and drag in a circular motion to rotate it on its Z axis. With the help of 2 different scripts I've found online and combined in my game, I also have it so that the continuous Z rotation is stored, rather than simply 0 - 360, then wrapping around and restarting.
This is all working well, except for the limits. I'm trying to limit the wheel's Z rotation between -2000 and 25. What I have works for the actual rotation of the wheel, but once the wheel reaches and stops at one of the limits, I'm still able to keep rotating the value with the mouse. Because of this, if I keep moving my mouse around the wheel even after it has stopped, the continuous rotation keeps going, so if I had hit, say, the -2000 limit, but had kept rotating, I would have to 'undo' all the extra rotation by rotating back in the opposite direction until I passed -2000 again, before the wheel would actually rotate again.
How can I stop all the rotations at the limit, so even if I keep dragging my mouse around the wheel after it has reached the limits, it doesn't keep adding on rotation?
I hope I was able to explain this well. I'm also wondering how I would go about lerping the rotation, so the wheel doesn't immediately rotate with the exact movement of the mouse, but has a little bit of a drag to it? The behavior of the wheels in "Amnesia: Justine" are what I'm aiming for.
This is what I have so far:
using UnityEngine;
using System.Collections;
public class SpinWheel : MonoBehaviour {
public float yAngle;
private Camera myCam;
private Vector3 screenPos;
private float angleOffset;
public bool inZone = false;
public bool looking = false;
float newRotationValue;
float oldRotationValue;
void Start () {
myCam=Camera.main;
yAngle = transform.eulerAngles.y;
}
void Update () {
if (inZone == true && looking == true)
{
//This fires only on the frame the button is clicked
if (Input.GetMouseButtonDown(0))
{
screenPos = myCam.WorldToScreenPoint (transform.position);
Vector3 v3 = Input.mousePosition - screenPos;
angleOffset = (Mathf.Atan2(transform.right.y, transform.right.x) - Mathf.Atan2(v3.y, v3.x)) * Mathf.Rad2Deg;
GetComponent<AudioSource>().Play();
}
//This fires while the button is pressed down
if (Input.GetMouseButton(0))
{
Vector3 v3 = Input.mousePosition - screenPos;
float angle = Mathf.Atan2(v3.y, v3.x) * Mathf.Rad2Deg;
newRotationValue = angle+angleOffset;
while (newRotationValue < oldRotationValue - 180f) newRotationValue += 360f;
while (newRotationValue > oldRotationValue + 180f) newRotationValue -= 360f;
oldRotationValue = newRotationValue;
if (oldRotationValue > -2000 && oldRotationValue < 25)
{
transform.eulerAngles = new Vector3(0,yAngle,angle+angleOffset);
}
}
}
}
}
I normally use JS, so I'm not the best with C#.
I also could've sworn I had it so the wheel wasn't snapping into a certain rotation when the mouse button was clicked, but now it is. I'm wondering if this has to do with the fact that I have the cursor locked, until a second script unlocks it when the player clicks on the wheel, and locks it again when they release it.
Bumping this, because I can't continue my game without this script working properly. Does anybody else have any ideas?
All right, even without Lerping, is there any way to just apply limits that actually stop the wheel correctly, as I explained?
Answer by josehzz112 · Jul 31, 2016 at 01:20 AM
I think you need just a few changes, here's my solution:
First define a float value that going to store the velocity of the lerp rotation.
public float rotationSpeed = 5f;
Then change the code of where you detect the mouse is being pressed down to this.
//This fires while the button is pressed down
if (Input.GetMouseButton(0))
{
Vector3 v3 = Input.mousePosition - screenPos;
float angle = Mathf.Atan2(v3.y, v3.x) * Mathf.Rad2Deg;
newRotationValue = angle+angleOffset;
while (newRotationValue < oldRotationValue - 180f) newRotationValue += 360f;
while (newRotationValue > oldRotationValue + 180f) newRotationValue -= 360f;
//oldRotationValue = newRotationValue; //This is making oldRotation update even when the new rotation is not being applied.
if (newRotationValue > -2000 && newRotationValue < 25) //Checks if newRotation is valid
{
//Goes from the currentAngle to the target Angle with velocity = rotationSpeed
//Using Time.delta to make it independable from frame rate
transform.eulerAngles = Vector3.Lerp(transform.eulerAngles, new Vector3(0,yAngle,angle+angleOffset), rotationSpeed * Time.deltaTime);
//After updating make the oldRotation the newRotation so in the next frame represents the last valid rotation made.
oldRotationValue = newRotationValue;
}
}
I haven't tested it because I can't reproduce the setup, but I think this will work.
Thank you for trying to help. I tried this, but the Z rotation now only wants to rotate downwards, I can't rotate it the other way at all. Even when rotating downwards, it rotates then stops, rotates then stops, intermittently, and every now and then does a random, crazy, unwanted rotation.
I definitely understand this being difficult to solve not being able to reproduce it. It's a bit complicated. I just can't think of any other way to explain it. Experience has shown I'm not very good at explaining things. :/
Thank you again for trying to help, though. I appreciate your effort!
Answer by gouthammannuru · Aug 02, 2016 at 01:38 PM
the simplest answer would be to Animating it.
Yes, but I don't see how I could control that with the circular motion of the mouse.
Answer by Fair-Strides · Aug 06, 2016 at 03:02 AM
If I'm understanding what you're saying, @TheGoliath, you do the following and get the following behavior:
Click on the wheel with the mouse.
Drag the mouse either 25 degrees one way, or constantly around to -2000 degrees the other way.
The wheel hits the limit and stops physically rotating.
You're still able to move the mouse around and the rotation value you're storing still increases.
Is that correct?
If that is the case, then I recommend changing only a small snippet of your code. I believe I'm reading your code right when I say that you
"check if newRotationValue is trying to wrap around the 0-360 range and correct this. Then see if the rotation is between -2000 and 25. If it is, apply the rotation to the wheel."
In that case, I recommend moving the code around a bit, but I can't visualize how this would affect your math except to hopefully fix your current issue.
newRotationValue = angle+angleOffset;
if (oldRotationValue > -2000 && oldRotationValue < 25)
{
// I changed these from while to an if-else if, but I don't know your math,
// so this could need to be changed back. Just looked to me that the while
// statement would keep on going until it goes on, but that could be the
// wrap-prevention...
if (newRotationValue < (oldRotationValue - 180f))
{ newRotationValue += 360f; }
else if (newRotationValue > (oldRotationValue + 180f))
{ newRotationValue -= 360f; }
oldRotationValue = newRotationValue;
transform.eulerAngles = new Vector3(0,yAngle,angle+angleOffset);
}
Yes, what you described is exactly what I'm trying to do.
However, I tried using this arrangement, but now the wheel won't move at all.
This is the script, starting at Update ():
void Update () {
if (inZone == true && looking == true)
{
//This fires only on the frame the button is clicked
if (Input.Get$$anonymous$$ouseButtonDown(0))
{
screenPos = myCam.WorldToScreenPoint (transform.position);
Vector3 v3 = Input.mousePosition - screenPos;
angleOffset = ($$anonymous$$athf.Atan2(transform.right.y, transform.right.x) - $$anonymous$$athf.Atan2(v3.y, v3.x)) * $$anonymous$$athf.Rad2Deg;
GetComponent<AudioSource>().Play();
}
//This fires while the button is pressed down
if (Input.Get$$anonymous$$ouseButton(0))
{
Vector3 v3 = Input.mousePosition - screenPos;
float angle = $$anonymous$$athf.Atan2(v3.y, v3.x) * $$anonymous$$athf.Rad2Deg;
newRotationValue = angle+angleOffset;
if (oldRotationValue > -2000 && oldRotationValue < 25)
{
// I changed these from while to an if-else if, but I don't know your math,
// so this could need to be changed back. Just looked to me that the while
// statement would keep on going until it goes on, but that could be the
// wrap-prevention...
if (newRotationValue < (oldRotationValue - 180f))
{
newRotationValue += 360f;
}
else if (newRotationValue > (oldRotationValue + 180f))
{
newRotationValue -= 360f;
}
oldRotationValue = newRotationValue;
transform.eulerAngles = new Vector3(0,yAngle,angle+angleOffset);
}
}
}
}
If I remove the line:
newRotationValue = angle+angleOffset;
Then the wheel continues rotating without ever stopping.
If I'm completely missing something, or making a stupid mistake, I apologize. Rotation has never been a strong point of $$anonymous$$e, and I'm not the best with C#. Thank you for trying to help, by the way.
Okay, then the first thing I would recommend is trying to change the if-else if back to while statements.
And since we're just trying to normalize the value after it keeps increasing, we have two options:
Stop the value from increasing after the limit in the first place.
Post-process and take the difference off of the value so it's back to -2000 or 25.
Toward that end, what if we do a check on newRotationValue, since it's possible for that to be over the -2000-25 range?
Change this:
newRotationValue = angle+angleOffset;
to this, and let's see what happens:
newRotationValue = angle+angleOffset;
// Let's use the $$anonymous$$athf.Clamp to control the value after we update it.
newRotationValue = $$anonymous$$athf.Clamp(newRotationValue, -2000, 25);
For reference, $$anonymous$$athf.Clamp's job is to take a value, a $$anonymous$$imum, and a maximum and contain the value between. Since you're able to stop the wheel physically rotating already, I think this will be enough to help the number from increasing afterwards.
http://docs.unity3d.com/ScriptReference/$$anonymous$$athf.Clamp.html
If this still doesn't work, try moving your "if(newRotationValue <" stuff back outside below the newRotationValue = angle + angleOffset; line, but before the $$anonymous$$athf.Clamp line.
Okay, Unity is really starting to confuse me now. I tried your final suggestion, but the wheel started doing what it had been doing at one point before; rotating, then stopping at its limit, but continuing to rotate if you continue trying to rotate it with the mouse. I had already tried $$anonymous$$athf.Clamp before, but it didn't seem to work.
So, I tried putting the $$anonymous$$athf.Clamp line below all the code (right under the if statement that actually rotates the wheel while "oldRotationValue" is within the limits), and the wheel stops at its limits. However, some unknown rotation value keeps rotating if I keep rotating the mouse after the limit is reached, so I still need to 'undo' any 'over'-rotation if I decide to rotate it in the opposite direction.
To troubleshoot this, I made a public float that updates to be the same value as "newRotationValue" (right under where newRotationValue is clamped), to keep track of it in the inspector. To my confusion, newRotationValue is actually clamping correctly, but some rotation value, somewhere, is still being updated past the limits.
Now I'm really stumped. Jeez, Unity's rotation is a headache.
Okay, I found out that it was "oldRotationValue" that was still being updated. I clamped this, and it stops with the wheel at its limits. However, if I keep rotating the mouse what seems to be another 180 degrees, it resets. If it was stopped at its limit of 25, then after 180 more degrees with the mouse, it resets to about -150. If it was stopped at -2000, the number it resets to is about -1800.
This causes the wheel to do what it was doing before; stopping, then starting, then stopping, then starting, again and again, because something is somehow overriding the $$anonymous$$athf.Clamp. It doesn't matter where I put it, the wheel does not want to obey the limits.
You wouldn't happen to have any more ideas, would you? I've expended all $$anonymous$$e... :/
Given how many comments we have on this above, if you want to move this into Skype or email, we could perhaps discuss things in a faster environment. Should you want them, they are fairstrides2 (skype) and tristongoucher (gmail).
If I'm understanding it correctly with your current changes, the wheel has a sort of stutter to its motion, making it jerky and haphazard and not obeying the limits. We've gotten it to clamp and stop moving, except if the angle of rotation goes over 180, so we're halfway there...
Besides checking if oldRotationValue == -2000 or oldRotationValue == 25 before you ever do any of the code in the block, what you could try is this:
$$anonymous$$ake a private variable (int or float) called RotationDelta and set it to 0.
In Update, compare differences between oldRotationValue and NewRotationValue to add to RotationDelta.
$$anonymous$$ove the transform.eulerAngles bit to FixedUpdate and apply the RotationDelta to the rotation.
Set oldRotationValue to newRotationValue and reset RotationDelta to 0 so we can start this all again.
The only pitfall that I can see you could encounter is if the user moves the mouse too fast around and creates a high RotationDelta, but if my logic checks out, doing the changes above should allow for the wheel to move at a constant time/speed versus a calculation every frame.
Answer by Anhvuive · Aug 08, 2016 at 02:09 PM
I has a project about magicwheel on git, all of you can try it, and if you like, develop it with me :D git clone https://anhvuive91@bitbucket.org/anhvuive91/magicwheel.git
What I'm working with is a 3D wheel, which I have functioning already. I just need it to stop its limits.