I ran into an issue with box-casts while developing a 3D game for mobile.
I want to check the path between my player and his target, to avoid him passing through environmental objects (he doesn't have a rigidbody attached and movement is only possible between specific points).
This is the code that I used to check:
private bool CheckPath (Vector3 position, Vector3 target)
{
Vector3 center = Vector3.Lerp(transform.position, target, 0.5f);
Vector3 halfExtents = new Vector3(1, 1, (transform.position - target).magnitude) / 2;
Quaternion rotation = Quaternion.LookRotation((transform.position - target).normalized);
RaycastHit[] rhit = Physics.BoxCastAll(center, halfExtents, (transform.position - target).normalized, rotation);
bool result = rhit.All(r => r.collider.tag != "Environment");
DebugUtilities.BoxCastDebug.DrawBox(center, halfExtents, rotation, result ? Color.green : Color.red, 3);
return result;
}
This code works for the most situations:
But fails, for example for this situation:
To visualize the box-cast I used the script from this Unity Answers link.
I am not sure where the problem lies, although the most likely cause would be a flaw in the debugging script mentioned above, which made me believe that my box-cast call was correct.
I am grateful for every solution, although a simple one would be more appreciated.
Some further information (if needed):
The objects that I want to make impassable are marked with a Environment tag.
I was not the one that wrote the debugging script, it seemed to work at first, so I was fine with it.
Three problems in your code
1.The position variable from the unction parameter is not used. You are instead using transform.position which means that the starting point may be wrong.
Replace all your transform.position with position.
2.You are performing the raycast backwards. It should not be transform.position - target. That should be target - transform.position.
3.Your Physics.BoxCastAll will not work properly since there is no ending to the raycast. Objects behind the starting will be detected by the raycast. Now, if you fix problem #2, the problem will reverse. Now, all the objects in behind the target will also be detected since you did not provide the raycast distance. You can provide the distance with Vector3.Distance(position, target) in the last parameter of the Physics.BoxCastAll function.
Fixed function:
private bool CheckPath(Vector3 position, Vector3 target)
{
Vector3 halfExtents = Vector3.one;
Quaternion rotation = Quaternion.LookRotation(target - position);
Vector3 direction = target - position;
float distance = Vector3.Distance(position, target);
RaycastHit[] rhit = Physics.BoxCastAll(position, halfExtents, direction, rotation, distance);
bool result = rhit.All(r => r.collider.tag != "Environment");
Vector3 center = Vector3.Lerp(position, target, 0.5f);
halfExtents = new Vector3(1, 1, (target - position).magnitude) / 2;
DebugUtilities.DrawBox(center, halfExtents, rotation, result ? Color.green : Color.red);
// Debug.DrawRay(position, direction, result ? Color.green : Color.red);
return result;
}
It's worth setting up 3 objects (Cubes) in order to easily test this function. The first one is from Object (Player). The middle one should be the obstacle with the "Environment" tag. The last one should be the target Object where player is moving to.
Then you can use the script below to test it. Run it and move the obstacle away between the player and the target and it should work as expected.
public GameObject playerObject;
public GameObject targetObject;
void Update()
{
Debug.Log("Path cleared: " + CheckPath(playerObject.transform.position, targetObject.transform.position));
}
You will get the similar result below:
Related
This is what I have tried so far:
I create a raycast and if it hits an object on layer 8 (the layer in which objects need to be launched to the player), I call the SlerpToHand() function.
private void Update()
{
if(Physics.Raycast(transform.position, transform.forward * raycastLength, out hit))
{
if(hit.collider.gameObject.layer == 8)
{
// Launch object to player
SlerpToHand(hit.collider.transform);
}
}
}
Inside of SlerpToHand(), I set the object's position to Vector3.Slerp(), that vector being created from values in the hit object.
private void SlerpToHand(Transform hitObj)
{
Vector3 hitObjVector = new Vector3(hitObj.transform.position.x, hitObj.transform.position.y, hitObj.transform.position.z);
hitObj.position = Vector3.Slerp(hitObjVector, transform.position, speed);
}
But the result of this is all wrong, the object just gets teleported to the player's hands. Is Vector3.Slerp() not a good way to curve an object to the player? For context I am trying to recreate Half-Life: Alyx's grabbity gloves. There is still some work to do with the hand gestures but I am just trying to get the object curve down. Help is much appreciated, let me know if more info is needed.
See unity docs:
public static Vector3 Slerp(Vector3 a, Vector3 b, float t);
Here, t is a normalized position between two input values. It means, if t = 0, result will be exactly first value. If t = 1, result will be exactly second value. If t = 0.5, result will be the middle between two values.
So, usually, you need to call Slerp every Update, step by step increasing t from 0 to 1. For this, usually Time.deltaTime used (which equals the time between updates). For speed control, multiply your speed by Time.deltaTime.
Update()
{
if (t < 1)
{
t += Time.deltaTime * speed;
hitObj.position = Vector3.Slerp(startPosition, endPosition, t);
}
}
...and in this case, for start moving, you just need to set t = 0. Probably, you have to implement your own logic here, but this should show the idea.
In addition:
Slerp used to interpolate between vector directions, for positions use Lerp.
Consider use DOTween plugin - its free and powerful for such cases.
I was looking for a way to set the Quaternions (x, y, z, w) through the inspector window. We get all these variables when we click on "Debug" mode in Unity. Through Unity docs, I got to know that these values are between 0-1. So how do we set for angles such as 90,-90,180,-180,270,.... MAIN THING here is that I want to set the target rotations in the script of this game object so that the gameObject moves from initial rotation to target rotation.
For example in "Normal" window, if I set the target rotation of x as 180 (shown as -5.008956e-06 in the inspector window), the gameObject moves from 0 to -180, instead of +180. That is the reason I moved to "Debug" window thinking it helps here to set it. But the values here range between 0-1, so does anyone have an idea of how to calculate this?
Moreover, for rotation I am using this one line:
transform.localRotation = Quaternion.Slerp(transform.localRotation, targetRotation, Time.deltaTime * RotationSpeed);
It sounds like you want to be able to adjust it via a Vector3 just how Unity does it in the Transform Inspector. You could do something like
public Vector3 targetRotationV3;
private Quaternion targetRotation;
private void Start()
{
targetRotation = Quaternion.Euler(targetRotationV3);
}
or if you need to be more flexible
private void Update()
{
targetRotation = Quaternion.Euler(targetRotationV3);
...
}
Then for my comment what I mean is that Slerp interpolates a value between the first and the second argument using the factor between 0 and 1.
Since you every frame use a new value as start point, namely the current rotation, this will get slower and slower to the end and depending on your given speed never reach the target rotation.
It makes little sense to use Time.deltaTime here which just divides your speed by about 60 (for 60 FPS). Usually you rather want a constant interpolation factor between 0 and 1. If the frame-rate goes up it might even rotate back since in this case the Time.deltaTime would get smaller!
So you either rather want a constant interpolation factor
[Range(0f,1f)] private float slerpFactor = 0.5f;
private void Update()
{
targetRotation = Quaternion.Euler(targetRotationV3);
transform.localRotation = Quaternion.Slerp(transform.localRotation, targetRotation, slerpFactor);
}
or if you want to rotate with a constant speed instead use Quaternion.RotateTowards
private void Update()
{
targetRotation = Quaternion.Euler(targetRotationV3);
transform.localRotation = Quaternion.RotateTowards(transform.localRotation, targetRotation, Time.deltaTime * RotationSpeed);
}
where your RotationSpeed is now in ° / second
As I said in my comment, don't set Quaternion directly ever, unless you are really confident in your understanding of them, as pointed out in the unity docs (emphasis mine).
They are based on complex numbers and are not easy to understand intuitively. You almost never access or modify individual Quaternion components (x,y,z,w); most often you would just take existing rotations (e.g. from the Transform) and use them to construct new rotations (e.g. to smoothly interpolate between two rotations). The Quaternion functions that you use 99% of the time are: Quaternion.LookRotation, Quaternion.Angle, Quaternion.Euler, Quaternion.Slerp, Quaternion.FromToRotation, and Quaternion.identity. (The other functions are only for exotic uses.)
Rather what you want to do is set the initial and target rotations as Vector3 (Eulerangles) from the inspector and use the build in Quaternion.Euler(); method to let Unity figure out the transformation from Eulerangles to Quaternions.
This would look something like this (Note that I am doing this in an update for the example, and using a float time that I update from the inspector to change the rotation, this is just done for ease of example and not the best way to do implement the t parameter of Quaternion.Slerp):
public Vector3 initialrotation;
public Vector3 targetRotation;
public float time;
void Update()
{
// Let Unity figure out what the appropriate Quaternions are for the given Eulerangles
// Note that this can better be done in Start if initialRotation and targetRotation never change. Just put it here for simplicity
var initialQuaternion = Quaternion.Euler(initialrotation);
var targetQuaternion = Quaternion.Euler(targetRotation);
var slerp = Quaternion.Slerp(initialQuaternion, targetQuaternion, time);
transform.rotation = slerp;
}
Wasted many hours trying to figure out the rotations and many hours looking for answers, but wasn't able to find anything that fits my problem. I need to rotate an entire gameObject to a specific direction rather than rotating in y axis:
1) How the object is currently rotated while given a direction inside Quaternion.LookRotation or by Atan2.
2,3) Examples of how it should rotate. The red ot simbolizes the pivot point from which the rotation happens
Not much code to show as there is not much to it besides gameObject transformations which are rotated and a direction in which to rotate gameObject.
As requested
[System.Serializable]
public class ToRotate
{
//Object which will be rotated by the angle
public GameObject gameObject;
//Object last known position of this object. The object is rotated towards it's last global position
private Vector3 lastPosition;
//Initializes starting world position values to avoid a strange jump at the start.
public void Init()
{
if (gameObject == null)
return;
lastPosition = gameObject.transform.position;
}
//Method which updates the rotation
public void UpdateRotation()
{
//In order to avoid errors when object given is null.
if (gameObject == null)
return;
//If the objects didn't move last frame, no point in recalculating and having a direction of 0,0,0
if (lastPosition == gameObject.transform.position)
return;
//direction the rotation must face
Vector3 direction = (lastPosition - gameObject.transform.position).normalized;
/* Code that modifies the rotation angle is written here */
//y angle
float angle = Mathf.Rad2Deg * Mathf.Atan2(direction.x, direction.z);
Quaternion rotation = Quaternion.Euler(0, angle, 0);
gameObject.transform.rotation = rotation;
lastPosition = gameObject.transform.position;
}
}
Since you want the object's local down to point in a calculated direction, while keeping the object's local right unchanged as possible, I would use Vector3.Cross to find the cross product of that down and right to determine the direction the object's local forward should face, then use Quaternion.LookRotation to get the corresponding rotation:
//Method which updates the rotation
public void UpdateRotation()
{
//In order to avoid errors when object given is null.
if (gameObject == null)
return;
//If the objects didn't move last frame, no point in recalculating and having a direction of 0,0,0
if (lastPosition == transform.position)
return;
//direction object's local down should face
Vector3 downDir = (lastPosition - transform.position).normalized;
// direction object's local right should face
Vector3 rightDir = transform.right;
// direction object's local forward should face
Vector3 forwardDir = Vector3.Cross(downDir, rightDir);
transform.rotation = Quaternion.LookRotation(forwardDir, -downDir);
lastPosition = transform.position;
}
I'm learning unity and c#, and want to make my movement to be camera relative movement instead of world relative movement. How do I do that?
I'm learning unity and c#, my unity version is 2018.3.12f1. I would be happy for help.
just to let know, instead of moving the cam I'm rotating the player.
void Update()
{
float AxisY = Player.transform.eulerAngles.y;
/* Movement starts here */
Vector3 Movement = new Vector3 (Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) { //running code
Player.transform.position += Movement * running_speed * Time.deltaTime;
} else {
Player.transform.position += Movement * speed * Time.deltaTime;
}
/*Movement ends here */
/* Rotation controller starts here */
Quaternion target = Quaternion.Euler(Player.transform.eulerAngles.x, Player.transform.eulerAngles.y, Player.transform.eulerAngles.z);
/*if (Player.transform.eulerAngles.x != 0 || Player.transform.eulerAngles.z != 0 || Player.transform.eulerAngles.y != 0) {
Player.transform.rotation = Quaternion.Euler(0,0,0);
}*/
if (Input.GetKey(KeyCode.E))
{
Debug.Log("E got pressed");
//float AxisYPositive = Player.transform.eulerAngles.y;
AxisY = AxisY+1;
Player.transform.rotation = Quaternion.Euler(0, AxisY, 0);
} else if (Input.GetKey(KeyCode.Q))
{
Debug.Log("Q got pressed");
//float AxisYNegetive = Player.transform.eulerAngles.y;
AxisY=AxisY-1;
Player.transform.rotation = Quaternion.Euler(0, AxisY, 0);
}
}
}
The player's movement is world relative, how to make the movement camera relative?
If you want to make the movements relative to the gameObject, call the method Transform.Rotate() on the transform of the gameObject you want to rotate rather than modifying its Quaternion directly. Just make sure the final argument is set to Space.Self.
if (Input.GetKey(KeyCode.E))
{
Debug.Log("E got pressed");
//float AxisYPositive = Player.transform.eulerAngles.y;
AxisY = AxisY+1;
Player.transform.Rotate(Quaternion.Euler(0, AxisY, 0), Space.Self);
}
In general you don't want to directly mess with objects transform.rotation, at least not unless you at least somewhat understand quaternions (I don't!).
I can see a few issues with your code, but the common thread seems to be that you don't really understand how transforms work. Specifically, you might want to look into World/Local space.
The usual way to control a player goes roughly like this:
void DoMovement(Transform player)
{
//If you move first your controls might feel 'drifty', especially at low FPS.
Turn(player);
Move(player);
}
void Turn(Transform player)
{
float yaw = Input.GetAxis("Yaw") * time.deltaTime; //Aka turn left/right
player.Rotate(0, yaw, 0, Space.Self);
// Space.Self is the default value, but I put it here for clarity.
//That means the player will rotate relative to themselves,
//...instead of relative to the world-axis, like in your code.
}
You didn't ask about movement, but as-is your character will always move relative to the world. The below should make it move relative to the camera.
Transform _cameraTransform; //Assumes this is set druing Start()
void Move(Transform player)
{
var forwardMove = _cameraTransform.Forward; //Get whatever direction is 'forward' for camera
forwardMove.Y = 0; //Don't want movement up and down.
forwardMove = forwardMove.normalized; //Normalize sets the 'power' of the vector to 1.
//If you set Y to 0 and don't normalize you'll go slower when camera looks down
//...than when camera is flat along the plane
player.position += forwardMove * Input.GetAxis("Vertical") * time.deltaTime;
//Here you could do the same for strafe/side to side movement.
//Would be same as above, but using the transform.right and Horizontal axis
}
Now, I'm making some assumptions here since you haven't specified what kind of game it is and what kind of controls you want. I'm assuming you have a character running around on a mostly flat plane (no aircraft/spaceship controls), and that the camera is attached to the player. This might not not actually be the case.
In any case I advice you to check out the tutorials, especially the Roll-a-Ball tutorial which I have found is good for beginners to get a grasp on basic players controls that are not just world-relative. The other tutorials, too, are pretty good if you think they're interesting.
Aside from the official Unity tuts a ton of decent to amazing tutorials out there, including video tutorials, so for something like this you could just search for <game type> tutorial and pick whatever seems good to you. While getting started I advice you to avoid the shortest videos, as you will likely benefit greatly from explanation that only fits in longer videos. Of course, that doesn't mean you should pick the longest videos either.
In case someone needs to move an object and don't care about colliders, you can use transform.Translate and assign to his second parameter relativeTo your camera (or any transform) to automatically calculate the translation relative to the object assigned.
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.