Need to rotate object in specific ways - Stuck with Gimbal Lock? - unity3d

I am working on a small mini-game that requires the rotation of a cube 90 degrees in the appropriate direction based on the direction you swipe. So you could swipe up and it would rotate up 90 degrees, and them immediately after, swipe left, and it would swipe 90 degrees to the left from your current rotation (so it would stay rotated up 90 degrees as well). I feel like this should be really simple, but it's giving me a ton of trouble.
I would like to use Lerp/Slerp so that the rotation looks nice, though it isn't entirely necessary. The way I currently have it implemented, each time I call my "SlerpRotateLeft()" function for example, it only rotates to the exact same exact rotation relative to the world each time (instead of the current rotation + 90 degrees in the correct direction).
I have been reading up on Quaternions and Euler angles all day, but I'm still not entirely sure what my problem is.
I am currently using states to determine when the object is currently rotating and in what direction, though I feel like I may be overcomplicating it. Any possible solution to this problem (where you can swipe in a particular direction, in any order, in succession, to rotate a cube 90 degrees in that particular direction). Previously, I attempted to use coroutines, but those didn't have the desired effect either (and I was unable to reset them).
Here is my class. It works and you can test it by dropping the script into any cube object in-editor, but it doesn't work as intended. You will see what my problem is by testing it (I recommend placing an image on the cube's front face to track which one it is). I'm not sure if I explained my problem properly, so please let me know if any more information is needed.
****UPDATE: I have accepted #Draco18s's answer as correct, because their solution worked. However, I did not completely understand the solution, or how to store the value. I found an answer to a similar question that also used Transform.Rotate, and stored the value, which helped clear the solution up. The key seemed to be storing it in a GameObject instead of in a Quaternion like I originally thought. I thought I should provide this code in case anyone stumbles upon this and is equally confused, though you may not need the swipe detection:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Rotater : MonoBehaviour
{
private GameObject endRotation;
//SWIPE VARIABLES
public Vector2 touchStart = new Vector2(0, 0);
public Vector2 touchEnd = new Vector2(0, 0);
public Vector2 currentSwipe = new Vector2(0, 0);
public Vector2 currentSwipeNormal = new Vector2(0, 0);
// Use this for initialization
void Start()
{
endRotation = new GameObject();
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
touchStart = new Vector2(Input.mousePosition.x, Input.mousePosition.y);
//Debug.Log("Touched at: " + touchStart);
}
if (Input.GetMouseButtonUp(0))
{
touchEnd = new Vector2(Input.mousePosition.x, Input.mousePosition.y);
//Get Swipe Vector information
currentSwipe = new Vector2(touchEnd.x - touchStart.x, touchEnd.y - touchStart.y);
//Normalize Swipe Vector
currentSwipeNormal = currentSwipe;
currentSwipeNormal.Normalize();
//Swipe up
if (currentSwipeNormal.y > 0 && currentSwipeNormal.x > -0.5 && currentSwipeNormal.x < 0.5)
{
endRotation.transform.Rotate(-Vector3.left, 90, Space.World);
}
//Swipe down
if (currentSwipeNormal.y < 0 && currentSwipeNormal.x > -0.5 && currentSwipeNormal.x < 0.5)
{
endRotation.transform.Rotate(Vector3.left, 90, Space.World);
}
//Swipe left
if (currentSwipeNormal.x < 0 && currentSwipeNormal.y > -0.5 && currentSwipeNormal.y < 0.5)
{
endRotation.transform.Rotate(Vector3.up, 90, Space.World);
}
//Swipe right
if (currentSwipeNormal.x > 0 && currentSwipeNormal.y > -0.5 && currentSwipeNormal.y < 0.5)
{
endRotation.transform.Rotate(-Vector3.up, 90, Space.World);
}
}
LerpRotate();
}
void LerpRotate()
{
transform.rotation = Quaternion.Lerp(transform.rotation, endRotation.transform.rotation, Time.deltaTime * 10);
}
}

Use Transform.RotateAround
You're encountering an issue where you take the current Euler angles and try and add/subtract 90, which does not necessarily correlate to the desired position, due to the rotated nature of the rotated reference frame.
But using RotateAround, you can pass in the global Up, Left, and Forward vectors, which is what you're trying to do.

Related

Unity: How to find a empty hole in a wall for player?

I'm trying to figure out simple custom 2D physics for platformer. At the moment I'm using ray casts to figure out collision between the player and map. However using ray cast has some problems. For example, if the player is falling (has somewhat high downwards velocity) its unable to pick up holes in the wall because it moves past them due to going too far down to be detected as empty space.
One solution is to move to tile based system instead of using ray casts but I would preferably not to do so.
So I'm wondering is there some kinda solution to figure out empty holes in wall, even small edge ones without huge performance impact.
High quality drawn illustration, assume leftwards velocity:
Physics2D.BoxCast is how I would tackle this. It does what you expect, instead of a ray it calculates as if a full box was traversing the distance.
Usage is very similar to raycasting. Example:
public Collider2D mainCollider;
public Vector2 velocity;
void Update() {
Vector2 origin = new Vector2(transform.position.x, transform.position.y);
Vector2 size = mainCollider.bounds.size;
Vector2 direction = velocity * Time.deltaTime;
float maxDist = velocity.magnitude;
var hit = Physics2D.BoxCast(origin, size, 0, direction, maxDepth);
if (hit == null) {
transform.position += direction;
} else {
transform.position += direction.normalized * hit.distance;
}
}
If boxes are not your taste, there's also a Physics2D.CapsuleCast and Physics2D.CircleCast.

Moving Rigidbody using player input

Im trying to move an object in a 3d world using a controller, but think I am missing something cus it just clips away as soon as I give any input:
private void Update()
{
float h = Input.GetAxisRaw("Horizontal");
float v = Input.GetAxisRaw("Vertical");
Vector3 movement = new Vector3(h, 0, v).normalized * Time.deltaTime * speed;
if(h != 0 || v != 0)
_rigidBody.MovePosition(movement);
}
Works fine using _rigidbody.velocity, but as I understand it that should be avoided for these types of things.
Rigidbody.MovePosition sets the position of the rigidbody with interpolation. It looks like you want to offset the position by movement, so you should probably set the velocity. If you do still want to use MovePosition, you should do _rigidBody.MovePosition(transform.position + movement);.
"using a controller"
Do you mean Character Controller Component? You have 2 options and they are both well explained in Unity Documentation.
Second is by Rigidbody Component.

Reverse movement done with transform.RotateAround()

I am making a third person mobile game with the help of joysticks. I have it set up where joystick.vertical moves the character forward or backwards depending on where he is looking and joystick.horizontal turns the character. Since the camera is parented to the character the camera always stays behind the character.
Swiping across the screen rotates the camera around the player with a touch panel using Camera.main.transform.RotateAround() function and the transform.LookAt() ensures I am looking at my character always.
My issue: I would like when the swipe is let go the camera to return to its original position behind the character but in a smooth motion or at a set speed moving around the player.
My Solution 1: To make an empty gameObject parented to the character and place it in the position where I want the camera to revert back to and call this position when the mouse is let go.
float spanCamera = -Joystick.Horizontal;
Camera.main.transform.LookAt(rb.transform.position);
if (spanCamera != 0)
Camera.main.transform.RotateAround(rb.position, Vector3.up, spanCamera * Time.fixedDeltaTime * spanSpeed);
else if (Input.touchCount <= 1)
{
float var6 = var5 * Time.deltaTime;
Camera.main.transform.position = camPos.transform.position;
Camera.main.transform.LookAt(camLookAt.transform.position);
}
This piece of code moves the camera back to the start position immediately without smoothing.
Solution 2: use a Vector3.MoveTowards() instead
// Camera.main.transform.position = camPos.transform.position;
Camera.main.transform.position = Vector3.MoveTowards(Camera.main.transform.position, camPos.transform.position, var6);
This code allows me to smoothly move to the start position in a straight line. I want it to go around the player.
So I tried a different method where I assign a float value 1 if the camera turns right and check if the rotate button is let go and if the value is 1 within an if block.
public float axisDir;
...
else if (spanCamera == 0 && axisDir == 1)
{
Camera.main.transform.RotateAround(rb.position, Vector3.up, -1 * Time.fixedDeltaTime * spanSpeed);
}
But this results in an infinite spin because I do not know how to check if the desired position has been reached.
I hope someone can help. This is quite a long post. :(
You could have an dummy gameObject as a child of the player, and the camera as a child of the dummy.
That way, the camera is looking at the centre of the gameObject: where the player is. The camera would also rotate with the player, so it would always be behind the player.
Now, you know that when Mathf.Approximately(dummy.transform.localEulerAngles.y, 0.0f), the camera is behind the player. [1][2]
Knowing that, you can check when the player stopped swiping and start slowly rotating it back.
// If the player let go and camera is not behind the player
if (!playerIsSwiping && !Mathf.Approximately(dummy.transform.localEulerAngles.y, 0.0f))
{
// Slowly rotate until the camera is behind the player
dummy.transform.RotateAround(Vector3.zero, Vector3.up, rotationSpeed * Time.deltaTime);
}
try this buddy :
// Maximum turn rate in degrees per second.
public float turningRate = 30f;
// Rotation we should blend towards.
private Quaternion _targetRotation = Quaternion.identity;
// Call this when you want to turn the object smoothly.
public void SetBlendedEulerAngles(Vector3 angles)
{
_targetRotation = Quaternion.Euler(angles);
}
private void Update()
{
// Turn towards our target rotation.
transform.rotation = Quaternion.RotateTowards(transform.rotation, _targetRotation, turningRate * Time.deltaTime);
}
i found it here
from my own experience using LERP works quite well, you lookin for a smooth transition, or between two points, known as interpolation, in this case linear interpolation.

How to move an object by a certain angle over a period of time in a circle [duplicate]

I a new here and i try to start working with Unity Engine.
Could somebody explain me, how works Quaternion.Slerp? Because I want to rotate some object in different angles 90, 180 and 270. My code you can see below. Unfortunately when I add 180 degrees, object make crazy things and than put rotation to (0, 180, 180) for this game object. I would like to get (180,0,0)
public float speed = 0.1F;
private float rotation_x;
void Update()
{
if (Input.GetButtonDown("Fire1"))
{
rotation_x = transform.rotation.eulerAngles.x;
rotation_x += 180;
}
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.Euler(rotation_x, transform.eulerAngles.y, transform.eulerAngles.z), Time.time * speed);
}
Most examples out there including Unity examples from their official website are using Lerp in the wrong way. They didn't even bother to describe how it works in the API documentation. They just starch it in the Update() function and call it a day.
Mathf.Lerp, Vector3.Lerp, and Quaternion.Slerp work by changing from one position/rotation to another with the t value(last parameter) being passed in.That t value is also know as time.
The min of the t value is 0f and the max is 1f.
I will explain this with Mathf.Lerp to make it easier to understand. The Lerp functions are all the-same for both Mathf.Lerp, Vector and Quaternion.
Remember that Lerp takes two values and returns values between them. If we have a value of 1 and 10 and we do Lerp on them:
float x = Mathf.Lerp(1f, 10f, 0f); will return 1.
float x = Mathf.Lerp(1f, 10f, 0.5f); will return 5.5
float x = Mathf.Lerp(1f, 10f, 1f); will return 10
As you can see, the t(0) returns the min of the number passed in, t(1) returns the max value passed in and t(0.5) will return mid point between the min and the max value. You are doing it wrong when you pass any t value that is < 0 or > 1. That code in you Update() function is doing just that. Time.time will increase every second and will be > 1 in a second, so you have problems with that.
It recommended to use Lerp in another function/Coroutine instead of the Updated function.
Note:
Using Lerp has a bad side of it when it comes to rotation. Lerp does not know how to rotate Object with the shortest path. So bear that in mind. For example, you have an Object with 0,0,90 position. Lets say you want to move the rotation from that to 0,0,120 Lerp can sometimes rotate left instead of right to reach that new position which means it take longer to reach that distance.
Let's say we want to make the rotation (0,0,90) from whatever the current rotation is. The code below will change the rotation to 0,0,90 in 3 seconds.
ROTATION OVER TIME:
void Start()
{
Quaternion rotation2 = Quaternion.Euler(new Vector3(0, 0, 90));
StartCoroutine(rotateObject(objectToRotate, rotation2, 3f));
}
bool rotating = false;
public GameObject objectToRotate;
IEnumerator rotateObject(GameObject gameObjectToMove, Quaternion newRot, float duration)
{
if (rotating)
{
yield break;
}
rotating = true;
Quaternion currentRot = gameObjectToMove.transform.rotation;
float counter = 0;
while (counter < duration)
{
counter += Time.deltaTime;
gameObjectToMove.transform.rotation = Quaternion.Lerp(currentRot, newRot, counter / duration);
yield return null;
}
rotating = false;
}
INCREMENTAL ANGULAR ROTATION OVER TIME:
And to just rotate the Object to 90 in z axis, the code below is a great example of that. Please understand there is a difference between moving Object to new rotational point and just rotating it.
void Start()
{
StartCoroutine(rotateObject(objectToRotate, new Vector3(0, 0, 90), 3f));
}
bool rotating = false;
public GameObject objectToRotate;
IEnumerator rotateObject(GameObject gameObjectToMove, Vector3 eulerAngles, float duration)
{
if (rotating)
{
yield break;
}
rotating = true;
Vector3 newRot = gameObjectToMove.transform.eulerAngles + eulerAngles;
Vector3 currentRot = gameObjectToMove.transform.eulerAngles;
float counter = 0;
while (counter < duration)
{
counter += Time.deltaTime;
gameObjectToMove.transform.eulerAngles = Vector3.Lerp(currentRot, newRot, counter / duration);
yield return null;
}
rotating = false;
}
All my examples are based on frame-rate of the device. You can use real-time by replacing Time.deltaTime with Time.delta but more calculation is required.
Before anything, you can't add 180 on euler angles like that, and that's mainly what is causing your problem. You'd better use quaternion directly instead, or work on the transform itself.
You can think of a quaternion as an orientation in space. In contrary to what have been said, I do recommend learning how to use them if you can. However, I don't recommend using euler angles at all... as they're suject to different writing conventions, and will fail sometimes. You can look at 'gimbal lock' if you want details about that.
Simply a slerp or lerp (standing for spherical linear interpolation, or linear interpolation respectively) is a way to interpolate (go from one orientation to another, by increasing t from 0 to 1, in a coroutine or anywhere else) between orientation A and B. The difference between the two is that the slerp is giving you the shortest path from A to B.
In the end, when t = 1, lerp(A,B,t) and slerp(A,B,t) will give you B.
In your case, if you want to instantly rotate an object in space to a specific orientation, I suggest you use Quaternion.AngleAxis which is the most forward way to describe mathematically a quaternion.
If you want to add a rotation, say 90° to you actual orientation (without animation between the two), you can do something like this :
transform.rotation *= Quaternion.AngleAxis(axis_of_rotation, angle)
or use transform.rotate (depending on the parameters, it can be a right multiply, or left : local, or world transform).
Programmers' answer is detailling how to animate your transform. But I do suggest you to investigate quaternion themselves, as it will give you global understanding of space transforms.

How to limit where a sprite can move in Unity?

I am fairly new to Unity so please bear with me I have tried looking for the answer everywhere, but have had no luck.
Basically I am using onMouseDrag to move a sprite around the background for a classroom (1366x768) that has a table. However, I want to limit where the sprite can go so that it does not end up off screen or off of the table on my background.
My sprite has the 2d box collider and rigidbody components attached (gravity is set to zero and it is at a fixed angle). I thought that by placing four 2d box colliders around the area I want to keep the sprite in it would be enough to contain it but the sprite simply goes straight through them.
I also read up about using Mathf.Clamp to restrict the area but I do not really understand how to use it from the examples I have seen.
Below is my code for moving the sprite:
using UnityEngine;
using System.Collections;
public class MovementScript : MonoBehaviour {
float x;
float y;
void Update() {
x = Input.mousePosition.x;
y = Input.mousePosition.y;
}
public void OnMouseDrag() {
transform.position = Camera.main.ScreenToWorldPoint (new Vector3 (x, y, 1.0f));
}
}
Any help would be greatly appreciated!
Moving an object using its transform is not the same as moving it.
When you use the transform, the object doesn't "move", it teleports every Update by a small amount. Unfortunately, the Rigidbody can't detect this change in position as movement and thus does not react with any colliders. Regardless, that's probably the more complicated way to do this. Using Clamp is definitely easier.
Clamp is a pretty straightforward function. it takes three args: a value, a min, and a max. If the value is less than min or greater than max, it returns that boundary. Otherwise, it returns the value itself.
For instance,
Mathf.Clamp(5, 1, 3); //returns 3
Mathf.Clamp(2, 1, 3); //returns 2
Mathf.Clamp(-2, 1, 3); //returns 1
This is simply a convenience function for something like this:
if(val > max) {
return max;
} else if(val < min) {
return min;
} else {
return val;
}
So using Clamp, you can restrict the values of your x and y coordinates:
//to avoid confusion, I'm referring to your x and y variables
//as inputX and inputY. they represent the mouse position.
public void OnMouseDrag() {
Vector3 pos = Vector3.zero;
pos.x = Mathf.Clamp(inputX, minX, maxX);
pos.y = Mathf.Clamp(inputY, minY, maxY);
pos.z = 1.0;
transform.position = Camera.main.ScreenToWorldPoint (pos);
}
Mathf.Clamp will keep the X and Y coordinates of your Transform within the range (minX, maxX) and (minY, maxY) respectively. You can create these variables as inspector variables so you can change them on the fly.