I have a Cube(Player) in my game scene. I have written a C# script to constrain the movement(using Mathf.Clamp()) of the Cube so it does not leave the screen.
Below is my FixedUpdate() method from the Script
private void FixedUpdate()
{
float moveHorizontal = Input.GetAxis("Horizontal");
float moveVertical = Input.GetAxis("Vertical");
Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical);
rb.velocity = movement * speed;
rb.position = new Vector3(
Mathf.Clamp (rb.position.x, x_min, x_max),
0.5f,
Mathf.Clamp(rb.position.z, z_min, z_max)
);
}
Values of x_min, x_max, z_min, z_max are -3, 3, -2, 8 respectively inputted via the unity inspector.
PROBLEM
The script is working fine but my player(Cube) can move up to -3.1 units in negative X-AXIS (if I keep pressing the left arrow button) which is 0.1 units more in negative X-AXIS(This behavior is true for other axes too). It obviously clamps -3.1 to -3 when I stop pressing the button.
Why is this happening? Why doesn't it(Mathf.Clamp()) restrict the Cube to -3 units in first place?
Why am I getting that extra 0.1 units?
And Why is this value 0.1 units only? Why not more or less?
Your issue could be caused because you are setting velocity and then position, but before the next frame unity adds the velocity to your objects position. Which is why it ends up 0.1 units off.
To fix this, try resetting the velocity of the object to zero if it's about to leave your boundaries.
This is happening because the internal physics update moves the cube after you have put it in its clamped position. Mathf.Clamp() does restrict the cube to the expected position in the first place, as you can verify by putting a Debug.Log directly after you assign the position
You are getting the extra .1 units because you give the object a speed that gets applied after you clamp
It is 0.1 units because of the speed and the setting for Fixed Timestep (in Project Settings > Time). If you would set a higher speed it would be more. If you would lower the Fixed Timestep it would also be a bit more.
You already got the reason why this happens.
In order to fix the problem, you can use this code to prevent the cube to go outside your boundaries:
private void FixedUpdate() {
Vector3 movement = new Vector3(Input.GetAxis("Horizontal"), 0f, Input.GetAxis("Vertical"));
// Get new position in advance
float xPos = rb.position.x + movement.x * speed * Time.fixedDeltaTime;
float zPos = rb.position.z + movement.z * speed * Time.fixedDeltaTime;
// Check if new position goes over boundaries and if true clamp it
if (xPos > x_max || xPos < x_min) {
if (xPos > x_max)
rb.position = new Vector3(x_max, 0, rb.position.z);
else
rb.position = new Vector3(x_min, 0, rb.position.z);
movement.x = 0;
}
if (zPos > z_max || zPos < z_min) {
if (zPos > z_max)
rb.position = new Vector3(rb.position.x, 0, z_max);
else
rb.position = new Vector3(rb.position.x, 0, z_min);
movement.z = 0;
}
rb.velocity = movement * speed;
}
Related
I have been searching for an answer for hours, and I cant find the solution. I have a script that rotates a steering wheel when the car is turning. It has maximum rotation of 120 and a minimum rotation of -120. I have been trying to rotate with RotateTowards and other Quaternion methods but I can't figure them out. How can I make it when the turning angle is not zero and turning keys (a and d) are not pressed, it goes to its original rotation of 0, 0, 0, at a certain speed?
Here's my script for the steering wheel:
if (horizontalInput > 0)
{
wheel.transform.Rotate(Vector3.forward*Time.deltaTime*wheelspeed);
rotations -= wheelspeed;
}
else if (horizontalInput < 0)
{
wheel.transform.Rotate(-Vector3.forward * Time.deltaTime * wheelspeed);
rotations += wheelspeed;
}
And here is my (very bad) script for the minimum and maximum rotation of the steering wheel:
angle += Input.GetAxis("Horizontal") * Time.deltaTime*10;
angle = Mathf.Clamp(-120, angle, 120);
wheel.transform.localRotation = Quaternion.AngleAxis(angle, Vector3.forward);
angle += Input.GetAxis("Horizontal") * Time.deltaTime * 400;
angle = Mathf.Clamp(120, 0, angle);
wheel.transform.localRotation = Quaternion.AngleAxis(angle, Vector3.forward);
You should do something like this for the steering wheel script:
float rotateBack = 0f;
float angle = 0f;
void Update(){
angle += Input.GetAxis(“Horizontal”) * Time.deltaTime * 10;
angle = Mathf.Clamp(angle, -120, 120);
if (Input.GetAxis(“Horizontal”) == 0 && angle != 0f){
angle += rotateBack * Time.deltaTime;
if (angle > 0){
rotateBack = -10f;
}
else{
rotateBack = 10f;
}
}
transform.rotation = Quaternion.Euler(0f, 0f, angle);
}
What this does is setting a variable of angle to the input times a value to increase turning speed. Then it clamps if so it doesn’t go over 120, or under -120. Then, if there is no input and the angle isn’t zero: it will change the variable by how much to rotate back. The rotate back variable is set to either positive or negative based on which direction the steering wheel needs to be turned. The only problem with this script that I could see is the angle not being exactly 0, continuing the if statement. If this error affects your game, use the Mathf.Round(); function.
(If there are any errors, comment on this post.)
I would do what #ken is doing, but refactor the speed factors and other constants into fields so they are more easily changed and also use Mathf.MoveTowardsAngle to move the angle back to its neutral position.
[SerializeField] float rotateBackSpeed = 3f; // degrees per second
[SerializeField] float rotateSpeed = 10f; // degrees per second
[SerializeField] float angle = 0f; // degrees
[SerializeField] float minAngle = -120f; // degrees
[SerializeField] float maxAngle = 120f; // degrees
[SerializeField] float neutralAngle = 0f; // degrees
void Update()
{
angle = Mathf.Clamp(angle + Input.GetAxis(“Horizontal”) * rotateSpeed
* Time.deltaTime, minAngle, maxAngle);
if (Mathf.Approximately(0f, Input.GetAxis(“Horizontal”)))
{
angle = Mathf.MoveTowardsAngle(angle, neutralAngle,
rotateBackSpeed * Time.deltaTime);
}
transform.eulerAngles = angle * Vector3.forward;
}
I'm trying to create a character controller that allows the movement speed to be affected by how far the player is pressing the left stick. When I can get that much to work, the other issue I encounter is the players speed decreasing while moving diagonally.
Most of the information I've found online deal with the opposite issue where diagonal movement is faster (movement vector goes above 1). The solution there would be to clamp the magnitude so pressing the stick into a corner doesn't go above 1. I've tried clamping to see if that would work, however, I don't believe that's the solution to my problem.
The code here allows the player to move in relation to the camera and the speed is influenced by input vector.
Vector3 input = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
Vector3 inputDir = input.normalized;
void Move(Vector3 inputDir, Vector3 input)
{
running = Input.GetKey(KeyCode.Joystick1Button1); // B button on xbox one controller
// Rotation stuff
if (inputDir != Vector3.zero) {
float targetRotation = Mathf.Atan2(inputDir.x, inputDir.z) * Mathf.Rad2Deg + cameraTransform.eulerAngles.y;
transform.eulerAngles = Vector3.up * Mathf.SmoothDampAngle(transform.eulerAngles.y, targetRotation, ref turnSmoothVelocity, GetModifiedSmoothTime(speedSmoothTime));
}
// Pretty sure this is where things are breaking
float targetSpeed = ((running) ? runSpeed : walkSpeed) * inputDir.magnitude;
currentSpeed = Mathf.SmoothDamp(currentSpeed, targetSpeed, ref speedSmoothVelocity, GetModifiedSmoothTime(speedSmoothTime));
currentSpeed *= input.magnitude;
vel = transform.forward * currentSpeed + Vector3.up * velocityY;
currentSpeed = new Vector3(_controller.velocity.x, 0, _controller.velocity.z).magnitude;
}
void ExecuteMovement()
{
_controller.Move(vel * Time.deltaTime);
}
I have a pendulum in my game and try to play a sound when the rotation of the pendulum is (0,0,0) & Time.timeScale!=0 .The script works fine but if i increase the rotation speed of the pendulum the sound stops playing or is skipped multiple time(If in TimeManager I set time scale to 0.1 the sound clip is played).
Pendulum Code
Quaternion qStart, qEnd;
float angle = 20.0f;
public float speed;
Public AudioSource Sound;
void Start () {
qStart = Quaternion.AngleAxis ( angle, Vector3.forward);
qEnd = Quaternion.AngleAxis (-angle, Vector3.forward);
}
void Update () {
//rotation code
transform.rotation = Quaternion.Lerp (qStart, qEnd, (Mathf.Sin(Time.time * speed) + 1.0f) / 2.0f);
//Playing Sound
if (transform.rotation == Quaternion.Euler (0, 0, 0) && Time.timeScale!=0)
Sound.Play ();
}
I think what is happening is that the rotation is going straight from positive to negative, or vice versa, without ever hitting (0,0,0) in between. If the angle is -0.1 at one moment and 0.1 at the next, it will never equal 0 and your sound will never play.
Instead of testing whether or not transform.rotation is (0,0,0), try watching when Mathf.Sin(Time.time * speed) goes from positive to negative or vice versa.
Because a real pendulum is in continuous, smooth motion, but your simulated pendulum moves in discrete time slices with each Unity update frame, unless the time slices are very small (and they get bigger as timeScale increases) your rotation will skip from a positive angle position to negative angle (and vice versa) without reaching exactly 0; the trick then is to detect the transitional frame and play the sound then. Given that the mid-point occurs when your Lerp factor is 0.5, you can simplify the check to be "for the current Lerp factor and the previous, if one greater than or equal to 0.5 and the other isn't":
Quaternion qStart, qEnd;
float angle = 20.0f;
float previousLerpFactor;
public float speed;
public AudioSource Sound;
void Start () {
qStart = Quaternion.AngleAxis ( angle, Vector3.forward);
qEnd = Quaternion.AngleAxis (-angle, Vector3.forward);
}
void Update () {
//Lerp factor
float lerpFactor = (Mathf.Sin(Time.time * speed) + 1.0f) / 2.0f;
//rotation code
transform.rotation = Quaternion.Lerp (qStart, qEnd, lerpFactor);
//Playing Sound
if (((lerpFactor >= 0.5f) ^ (previousLerpFactor >= 0.5f)) && Time.timeScale!=0)
Sound.Play ();
previousLerpFactor = lerpFactor;
}
I have a zoom function on my camera that works by moving it on the Z axis when I pinch my fingers on the screen. However, when the camera moves into any value greater than zero, it has adverse effects on the rest of my camera code (movement, orbiting).
I put in a place a bool that would stop my camera moving once it got to a certain value, but it makes it very jumpy. And if your pinching, the camera will still move past the value until you let go.
So what I'm trying to do now, is use Mathf.Clamp to limit the range it can move, but I'm unsure if I'm using it correctly. This is my method right now:
void CameraZoom()
{
// if fingers moved certain distance apart, zoom
float dot = Vector2.Dot(Input.GetTouch(0).deltaPosition.normalized, Input.GetTouch(1).deltaPosition.normalized);
if (dot < fingerDistance)
{
// Store both touches.
Touch touchZero = Input.GetTouch(0);
Touch touchOne = Input.GetTouch(1);
Vector2 touchZeroPrevPos = touchZero.position - touchZero.deltaPosition;
Vector2 touchOnePrevPos = touchOne.position - touchOne.deltaPosition;
float prevTouchDeltaMag = (touchZeroPrevPos - touchOnePrevPos).magnitude;
float touchDeltaMag = (touchZero.position - touchOne.position).magnitude;
float deltaMagnitudeDiff = prevTouchDeltaMag - touchDeltaMag;
// apply zoom to camera
transform.Translate(0, 0, -deltaMagnitudeDiff * perspectiveZoomSpeed);
// clamp movement
transform.position = new Vector3(transform.position.x, transform.position.y, Mathf.Clamp(0, 0, maxDistance));
}
}
}
I only want this to affect the Z axis, but when I zoom, my whole rig gets moved. What is it that I'm doing wrong?
Update
Ive changed my code to the following, but now when I zoom, it just jumps between two points and I can no longer zoom.
New code:
transform.Translate(0, 0, -deltaMagnitudeDiff * perspectiveZoomSpeed);
Vector3 pos = transform.position;
pos.z = Mathf.Clamp(transform.position.z, 0.0f, -200.0f);
transform.position = pos;
Check the documentation for the Mathf.Clamp. You are using the parameters in the wrong order, which breaks the internal implementation of the function. The min value should be the second and the max value should be the third. So changing your line to this should stop the odd behavior:
pos.z = Mathf.Clamp(transform.position.z, -200.0f, 0.0f);
Or rather I should say, "EFFECTIVELY disabling diagonal movement".
There are plenty of Q/As online about this, but I keep encountering the same problem: When moving horizontally (left, for example), I can override the current direction and start moving vertically (by pushing up), which is what I want. But it does not work the other way around! Vertical movement can not override horizontal movement.
void Update () {
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
ManageMovement(h, v);
}
void ManageMovement(float horizontal,float vertical) {
if (vertical != 0f) {
horizontal = 0f;
Vector3 movement = new Vector3 (horizontal, vertical, 0);
GetComponent<Rigidbody2D> ().velocity = movement * speed;
return;
}
if (horizontal != 0f) {
vertical = 0f;
Vector3 movement = new Vector3 (horizontal, vertical, 0);
GetComponent<Rigidbody2D> ().velocity = movement * speed;
return;
} else {
Vector3 noMovement = new Vector3 (0, 0, 0);
GetComponent<Rigidbody2D> ().velocity = noMovement;
}
}
If I reverse the order of these if() statements, it reverses the problem. So, that's a clue. But I'm not a great detective. I would love some help!
try to add else statement to ManageMovement method:
void ManageMovement(float horizontal,float vertical) {
if (vertical != 0f) {
horizontal = 0f;
Vector3 movement = new Vector3 (horizontal, vertical, 0);
GetComponent<Rigidbody2D> ().velocity = movement * speed;
return;
}
else if (horizontal != 0f) {
vertical = 0f;
Vector3 movement = new Vector3 (horizontal, vertical, 0);
GetComponent<Rigidbody2D> ().velocity = movement * speed;
return;
} else {
Vector3 noMovement = new Vector3 (0, 0, 0);
GetComponent<Rigidbody2D> ().velocity = noMovement;
}
}
Use Input.GetAxisRaw instead of GetAxis.
GetAxis returns a float on a scale from -1 to 1. How quickly it returns to 0 depends on the axis settings. If you set gravity to a very high number, it will return to 0 more quickly. If you set sensitivity to a very high number, it will go to -1 or 1 more quickly.
So depending on these settings your function ManageMovement will be called multiple times with gradually changing values for horizontal and vertical.
The inputs over times could look something like this:
Update #1: ManageMovement(0.2, 1.0)
Update #2: ManageMovement(0.3, 0.9)
...
Update #N: ManageMovement(1.0, 0.0)
So when you check if vertical != 0, it will keep being non-zero until it has actually reached 0, and then for all those updates before that you set horizontal to 0.
GetAxisRaw is not smoothed in this way.