I want to rotate an object with the left arrow key and I want to bound the rotation to 30 deg. My code is:
if (Input.GetKey(KeyCode.LeftArrow) && transform.localEulerAngles.z <= 30)
transform.Rotate(0,0,1);
Why rotation stops to 31 deg?
Obviously my problem is more complex than this, I have different bounds and I need precision. The reason of this example is to simply say that rotations are not precise if managed in this way.
I think the reason is that Unity3D internally uses quaternions and acting on degrees is just an approximation. I'm right? In this last case how can I cope to this?
For example, how can I use quaternions to bound of 30 degs a rotation on an axis?
By the way if the problem is not this, do you have other solutions?
I don't know how unity manage the rotation, but here your problem seem more simple.
In your if you use the '<=' comparison, so when your object is at 30 degree, you enter a last time in the if and rotate 1 more degree, use a '<' to stop at the right moment
Get the current rotation in the Start() function then use it to find an offset that will be used to perform the if statement. This should do it:
public GameObject gameObjectToRotate;
Vector3 defaultAngle;
float minRot = 30f;
float maxRot = 30f;
// Use this for initialization
void Start()
{
defaultAngle = gameObjectToRotate.transform.eulerAngles;
}
// Update is called once per frame
void Update()
{
if (Input.GetKey(KeyCode.LeftArrow))
{
float offset = defaultAngle.z - gameObjectToRotate.transform.eulerAngles.z;
//Check if we are within min, max rot
if (offset < minRot && offset > -maxRot)
{
gameObjectToRotate.transform.Rotate(0, 0, 1);
Debug.Log("Rotating!");
}
}
}
The precision is 30.016. This is much more better than what you are getting now.
Related
I wanted to a cube do not rotate more than 60 degrees so it dosn't rotate to more
I tried to use this
if(gameObject.GetComponent<Transform>().rotation.x >= 60 || gameObject.GetComponent<Transform>().rotation.x <= -60 || gameObject.GetComponent<Transform>().rotation.z >= 60 || gameObject.GetComponent<Transform>().rotation.z <= -60)
{
gameObject.GetComponent<Transform>().rotation = new Quaternion(0, 0, 0, 0);
}
to check the rotation.
It didn't work so I print what rotation is getting in the x coordinate
and it said cordinates like:
5.98324, 7.39482, -1.983495
and I was just moving the x position not the rotation and It change it.
So how do I get the x rotation that it sais in the component transform?
I think the way you approached is fine but not suitable for the scenario. Euler Angles should be handled in order to control the Transformation.
simple understanding be like:
// declare your cube rotation
float moveSpeed = 5f // speed in which cube rotates
float rotatecube = moveSpeed* Time.deltaTime* 10;
//Your condition be like
if(transform.eulerAngles.z < (.....declare the requirement.....))
transform.Rotate(vector3.forward * rotateCube);
If you still face any issue related to angles console.log() is the better way for self understandings.
Hope it clarifies..
You are interested in the euler angles of the rotation, not the quaternion components of the rotation. You should start by referring to transform.eulerAngles.x instead of transform.rotation.x or transform.eulerAngles.z instead of transform.rotation.z.
By the way, it's best to call GetComponent as few times as you can get away with as it's an expensive operation. You should rather call it once and assign the result to a variable such as this:
Transform cubeTransform = gameObject.GetComponent<Transform>();
if(cubeTransform.eulerAngles.x ...)
{
...
}
Additionally, you don't even need to use GetComponent to access the transform of the gameObject the script is attached to. Instead, you can just use transform, e.g.:
if ( transform.eulerAngles.x >= 60 || transform.eulerAngles.x <= -60
|| transform.eulerAngles.z >= 60 || transform.eulerAngles.z <= -60)
{
transform.rotation = Quaternion.identity;
}
Basicaly, you want to make limit of rotation of any axis - x,y, or z.
if yes, then here is the solution :
Program:
Rotating A cube not more that 60 degree in x and z only.
var rotValue += Time.deltaTime;
Cube.transform.rotaion = Quaternion.Eular(Mathf.Clamp(rotaValue,0,60), 0, Mathf.Clamp(rotaValue,0,60));
you can use Mathf.clamp for Clamping any Float value Between min and max.
hope, problem solved.
Skip all the above comments. I found a way to get the exact rotation values as in INSPECTOR. I am showing for angle (x). The code is as follows:
public Transform GameObject;
void Update()
{
float Rotation;
if(GameObject.eulerAngles.x <= 180f)
{
Rotation = GameObject.eulerAngles.x;
}
else
{
Rotation = GameObject.eulerAngles.x - 360f;
}
}
I've been programming an ability for a Hack n Slash which needs to check Units within a pie slice (or inbetween two angles with max length). But I'm stuck on how to check whether an unit is within the arc.
Scenario (Not enough, rep for an image sorry im new)
I currently use Physics2D.OverlapSphere() to get all of the objects within the maximum range. I then loop through all of the found objects to see whether they are within the two angles I specify. Yet this has janky results, probably because angles don't like negative values and value above 360.
How could I make this work or is there a better way to do this?
I probably need to change the way I check whether the angle is within the bounds.
Thanks in advance guys! I might respond with some delay as I won't be at my laptop for a couple hours.
Here is the code snippet:
public static List<EntityBase> GetEntitiesInArc(Vector2 startPosition, float angle, float angularWidth, float radius)
{
var colliders = Physics2D.OverlapCircleAll(startPosition, radius, 1 << LayerMask.NameToLayer("Entity"));
var targetList = new List<EntityBase>();
var left = angle - angularWidth / 2f;
var right = angle + angularWidth / 2f;
foreach (var possibleTarget in colliders)
{
if (possibleTarget.GetComponent<EntityBase>())
{
var possibleTargetAngle = Vector2.Angle(startPosition, possibleTarget.transform.position);
if (possibleTargetAngle >= left && possibleTargetAngle <= right)
{
targetList.Add(possibleTarget.GetComponent<EntityBase>());
}
}
}
return targetList;
}
Vector2.Angle(startPosition, possibleTarget.transform.position);
This is wrong. Imagine a line from the scene origin (0,0) to startPosition and a line to the transform.position. Vector2.Angle is giving you the angle between those two lines, which is not what you want to measure.
What you actually want is to give GetEntitiesInArc a forward vector then get the vector from the origin to the target position (var directionToTarget = startPosition - possibleTarget.transform.position) and measure Vector2.Angle(forward, directionToTarget).
im trying to move a object in unity between 2 points, and at the moment it kinda works strange, i read the documentation and it says that the object begin point is (0,0,0) so my object goes under my other mesh that i have there, and the end point i can actually control, in my case it is 10, i want the object to move between 1.5 and 10(not 0 to 10)
i have this
void Update () {
transform.position = new Vector3(transform.position.x,Mathf.PingPong(Time.time,10.0f), transform.position.z);
}
when i try to put speed on the ball doing this:
void Update () {
transform.position = new Vector3(transform.position.x,Mathf.PingPong(Time.time,10.0f) * 10, transform.position.z);
}
the object does not colide and goes back at the end point it just stop looping and never came back how can i correct this 2 problems?
If your object has a collider, I suggest you move it via its Rigidbody rather than its Transform, to avoid potential collision issues. Try this:
public float MinY = 1.5f; // y position of start point
public float MaxY = 10f; // y position of end point
public float PingPongTime = 1f; // how much time to wait before reverse
public Rigidbody rb; // reference to the rigidbody
void Update()
{
//get a value between 0 and 1
float normalizedTime = Mathf.PingPong(Time.time, PingPongTime) / PingPongTime;
//then multiply it by the delta between start and end point, and add start point to the result
float yPosition = normalizedTime * (MaxY - MinY) + MinY;
//finally update position using rigidbody
rb.MovePosition(new Vector3(rb.position.x, yPosition, rb.position.z));
}
Here you have a better control on the distance to travel, and the speed.
Actually I didn't get exactly what are the problem you faced. But don't forget here and in your try, that you are directly modifying the position of the object, not adding forces or else.
Hope that helps you.
I think you simply misunderstood how the Mathf.PingPong method works :
first argument t is the value you want to "clamp" between 0 and the given length : this is were you want to put the Time.time as you did since this value will increase over time and therefore perpetually oscillate. If you want to increase/decrease the oscillation speed you have to multiply it.
second argument length is the max value of the "clamp" : if you want to increase/decrease the distance (in your case) you have either set it to 0 and multiply the whole Mathf.PingPong(...) by a value or directly give it the wanted value (both implementations will have a different effect.
Mathf.PingPong(Time.time * speed, 1.0f) * value : speed will affect the oscillation speed / value will affect the max value reached AND the speed / time to complete the oscillation (back and forth) will remain the same as value changes and decrease as speed increases
Mathf.PingPong(Time.time * speed, value) : speed will affect the oscillation speed / value will affect the max value reached BUT NOT the speed / time to complete the oscillation (back and forth) will increase as value increases and decrease as speed increases
About your other problems :
If you want to move your object between 1.5 and 10 you have to write something like this :
transform.position = new Vector3(transform.position.x, 1.5f + Mathf.PingPong(Time.time, 10.0f - 1.5f), transform.position.z);.
Also if you want to detect collision, avoid setting position manually as it will mess up with Physics and cause weird behaviors. Best way to move your object while keeping physic working is to do as #Heldap said using Rigidbody.MovePosition.
I'm working on an isometric game (diamond grid) and I've stumbled across a minor problem regarding a character movement.
I'm using A* to find a path between 2 points and then I want to move my character from point A to point B going through all the tiles which form the path but I can't find a way to do this , I mean a simpler and accurate method.
So far I've scrapped this piece of code but it's kinda "rusty"
public void Destination(tile destination)
{
for (int i = 0; i < 8; i++)
{
if (AdjacentTile[i] == destination)
{
characterDirection = i;
}
}
animation.changeSpriteDirection(characterDirection); //After I found which adjacent tile is the next destination I change the character direction based on it's position (1 = North , 2 = Nort Est etc) .. so the Y of the Animation_sourceRectangle it's changed//
Vector2 Position;
Position.X = current_characterTile.X - destination.X;
Position.Y = current_characterTile.Y - destination.Y;
rotation = (float)Math.Atan2(-Position.X, Position.Y);
moveVector = (Vector2.Transform(new Vector2(0, -1), Matrix.CreateRotationZ(rotation))) * characterSpeed;
movingCommand = 1; // the character is supposed to be moving..
Move(); //this function moves the sprite until the *tile.i and tile.j* of the character is the same as tile.j and tile.i of the destination
//something like this
if ( characterTile.i == destination.i && characterTile.j == destination.j)
movingCommand = 0 //stop
else
character_Position += moveVector;
}
If anyone could give me a hint on what to do or help me I'll be very grateful.
Thank You.
Possibilities:
At each tile, determine the character's speed vector and also determine how much time it will take for the character to move to next tile. When that time elapses, immediately begin moving to the next tile. (This is what I implemented below.)
At each tile, determine the character's speed vector. Then, when the character is sufficiently close to the next tile (say, the difference between their X and Y coordinates is less than 2 pixels?), snap it to the tile and begin moving to the next tile. This will causes artifacts and be in general less precise.
A solution:
Let's assume you already ran your pathfinding algorithm and found a linked list of a tiles that you must go through to arrive at target. Let's also assume those tiles cannot become blocked partway through the movement (it is simple to modify the algorithm if they can, though).
I usually do something like this to handle this problem:
Run the pathfinding algorithm, which returns a List, if a path
exists.
character.Path = theListThatAStarReturned;
character.beginMovingToTarget(character.Path[0]);
character.Path.RemoveAt(0);
The beginMovingToTarget() method will determine the velocity vector and also determine the the time needed to arrive at the tile. When the time is reached, we immediately go to the next tile, until the Path is empty. Let's call this time variable character.timeToArrival.
Update():
if (!character.Moving) return; // Or just don't execute the rest of this code.
character.position += character.speed * elapsedSeconds;
character.timeToArrival -= elapsedSeconds;
// Did the character arrive in a tile?
if (character.timeToArrival <= 0)
{
// This will ensure the character is precisely in the tile, not a few pixels veered off.
character.position = character.movingToTile.position;
if (character.Path.Count == 0)
{
character.Moving = false;
// We are at final destination.
}
else
{
character.beginMovingToTarget(character.Path[0]);
character.Path.RemoveAt(0);
}
}
And the beginMovingToTarget(targetTile) function:
this.movingToTile = targetTile;
Vector2 direction;
direction = targetTile.position - this.position;
this.timeToArrival = direction.Length() / this.speedPerSeconds;
direction.Normalize();
direction *= this.speedPerSeconds;
this.speed = direction;
// Here, you may also want to change the character's animation, if you want to, or you may do that directly in the Draw() method based on its speed vector.
Make sure the division is in floats, not integers.
My problem is I want to be able to rotate a cylinder 9 times. 360/9 is 40 so I all I should need to do is rotate by 40 degrees 9 times. This doesn’t work however as when I rotate the cylinder for the first time instead of by 40 degrees it rotates by 39.99 degrees. This happens at other rotations as well.
I’m rotating by doing this.
if(Input.GetKeyUp("i"))
transform.GetChild(m_Section).Rotate(0,40.0f,0);
I have unity version 3.4 it is not pro and I’m coding in C#.
Any help appreciated as I have just started trying to learn unity.
Unity3D stores rotations in a pretty abstract mathematical representation called quaternion. Tranformation from and to Euler angles (what you see in Unity editor) involves a cascade of trigonometric functions and thus is prone to rounding errors especially for the simple float type.
To get around this problem in your case I recommend storing the initial Quaternion object before starting to rotate and set it at the end of the process. Some pseudo-code:
public class RotationController : MonoBehaviour {
Quaternion rotationAtStart;
int numOfRotations = 0;
void rotate () {
numOfRotations++;
if (numOfRotations == 1) {
rotationAtStart = transform.rotation;
} else if (numOfRotations < 9) {
transform.Rotate (new Vector3 (0,40.0f,0));
} else if (numOfRotations == 9) {
transform.rotation = rotationAtStart;
}
}
void Update () {
if (numOfRotations < 9) {
rotate ();
}
}
}
The special situation of 360° makes this approach stable. For less than 360° you have to live with small rounding errors. For this case I would recommend calculating the target quaternion Quaternion.RotateTowards and set it in the last step analog to the 360 case.
Another useful thing for you are Animations. You can define an animation as smooth or in discrete steps and just call GameObject.animation.Play("MyRotation") if "i" is pressed. Use an AnimationEvent at the end to get informed when the animation is terminated.
And at last Mathf contains a function Approximately that deals with the problem of floating point imprecision.
Have a look at the answer of Kungfooman in this post, he descripes the problem with the rotation over 90 degree or at 90 degree as well as 270 degree. He provides an Extension which will always calculate the correct pendant of the Quaternion for the value you want to set. Have a look here:
using UnityEngine;
using System.Collections;
public class ExtensionVector3 : MonoBehaviour {
public static float CalculateEulerSafeX(float x){
if( x > -90 && x <= 90 ){
return x;
}
if( x > 0 ){
x -= 180;
} else {
x += 180;
}
return x;
}
public static Vector3 EulerSafeX(Vector3 eulerAngles){
eulerAngles.x = CalculateEulerSafeX(eulerAngles.x);
return eulerAngles;
}
}
And I used it like this:
Quaternion newQuat = m_directionalLight.transform.rotation;
Vector3 nL = new Vector3(ExtensionVector3.CalculateEulerSafeX(xNewValueLight),
0,
0);
newQuat.eulerAngles = nL;
m_directionalLight.transform.rotation =
Quaternion.Lerp(m_directionalLight.transform.rotation,
newQuat,
Time.deltaTime);