Unity Quaternion rotation with rigidbody is influences rotations it shouldn't - c#

Hey fellow programmers,
I have a small problem with Quaternion (who doesn't). I make an airship that is flying using rigidbody. Making it fly forward and turning was not a problem, but now I try to make it lean again the direction of the turn (like a person driving a motor leaning into when doing a turn). The
problem is that the ship is making rotations around the x-axis, while i didn't program anything in that would influence it. I checked rotationSpeed, but that only influence y and z-axis. It goes wrong around deltaRotation, where the x-axis is influenced. I tried forcing it to be 0 with deltaRotation.x = 0.0f;, but the ship still goes down without me changing the x-axis. Does anyone know what is happenning, and how to fix it?
if (Input.GetKey(KeyCode.W))
{
if (speed < maxSpeed)
{
speed += maxSpeed / incrementsSpeed;
}
}
if (Input.GetKey(KeyCode.S))
{
if (speed > 0)
{
speed -= maxSpeed / incrementsSpeed;
}
else
{
speed = 0;
}
}
if (Input.GetKey(KeyCode.D))
{
if (rotationSpeed.y < maxRotationSpeed)
{
rotationSpeed.y += maxRotationSpeed / incrementsRotationSpeed;
}
}
if (Input.GetKey(KeyCode.A))
{
if (rotationSpeed.y > -maxRotationSpeed)
{
rotationSpeed.y -= maxRotationSpeed / incrementsRotationSpeed;
}
}
forwardPower = transform.forward * speed;
//Calculate lean value of airship, depending on the speed and turn angle of the ship
float currentLeaningValue = rotationSpeed.y * speed;
float maxValue = maxRotationSpeed * maxSpeed;
float currentValue = ExtensionMethod.Mapz(currentLeaningValue, -maxValue, maxValue, -maxLeaningAngle, maxLeaningAngle);
//map the value of rotation to inspector rotation
float realRot = ExtensionMethod.Mapz(transform.eulerAngles.z, 0f, 360f, -180f, 180f);
realRot = 180 - Mathf.Abs(realRot);
if (currentValue < 0.0f && realRot > 0)
{
realRot *= -1f;
}
//Calculate force of leaning
float leanRotationSpeed = (currentValue - realRot) / incrementsRotationSpeed;
//Add lean force to turn rotation speed
rotationSpeed = new Vector3(0, rotationSpeed.y, -leanRotationSpeed);
//Convert to Quaternion, here something goes wrong
Quaternion deltaRotation = Quaternion.Euler(rotationSpeed);
//Did this to see if I manually put the x rotation to 0, it would work. Sadly it didn't
deltaRotation.x = 0.0f;
//Add forces to actual movement of airship
rb.MoveRotation(rb.rotation * deltaRotation);
rb.MovePosition(transform.position + forwardPower);

Related

Moving gameobject based on direction but the movement gets effected by distance of cursor

Im trying to make a peice of code that instantiates a gameobject, set its rotation to face the direction of the cursor from the player character at that moment and move towards that direction with constant speed for 2 seconds then stop. However my piece of coded is moving the gameobject towards the cursor direction but the speed gets changed depending on how far my cursor is from the player character.
private IEnumerator Rake()
{
Vector3 relativepos =
Camera.main.ScreenToWorldPoint(Input.mousePosition) - transform.position;
Quaternion rotation = Quaternion.LookRotation(relativepos, Vector3.up);
float timepassed = 0;
GameObject WcastRB =
Instantiate(Wcast, gameObject.transform.position, rotation);
Rigidbody2D rg;
rg = WcastRB.GetComponent<Rigidbody2D>();
while (timepassed < 2)
{
timepassed += Time.deltaTime;
rg.velocity = WcastRB.transform.forward * 1000 * Time.deltaTime;
if (timepassed >= 2)
{
rg.velocity = WcastRB.transform.forward * 0;
}
yield return null;
}
}
this is what I have made.
Try this out, I found that because transform.forward was dependent on the object's rotation(and subsequently the original click position).
When the click was too close to the object the transform.forward Vector2 had a magnitude(length) of less than one, causing the speed to slow down.
By increasing the magnitude of the velocity to exactly 1f it should go consistent speeds in all directions
IEnumerator Rake()
{
Vector3 relativepos = Camera.main.ScreenToWorldPoint(Input.mousePosition) - transform.position;
Quaternion rotation = Quaternion.LookRotation(relativepos, Vector3.up);
float timepassed = 0;
GameObject WcastRB = Instantiate(Wcast, gameObject.transform.position, rotation);
Rigidbody2D rg;
rg = WcastRB.GetComponent<Rigidbody2D>();
Vector2 velocity = rg.transform.forward;
velocity.Normalize();
while (timepassed < 2)
{
timepassed += Time.deltaTime;
rg.velocity = velocity * 1000 * Time.deltaTime;
if (timepassed >= 2)
{
rg.velocity = new Vector2();
}
yield return null;
}
}
Edit:
Removed self-implemented .Normalize() because I forgot that i also used the built in .Normalize() literally right before it.

How can I rotate a steering wheel to its original rotation in Unity3D?

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;
}

Character controller get stuck

I am making a game with Unity. For some reason the character controller gets stuck.
Here is a video of that
This is the player controller code:
private CharacterController CharacterController;
public float Speed = 12;
private Vector3 Velocity;
private GameObject GroundCheck;
private bool IsGrounded;
public float CheckDistance = 0.4f;
public LayerMask GroundMask;
public float JumpHeight = 3;
public float Gravity = -9.81f;
private void Start()
{
GroundCheck = GameObject.Find("GroundCheck");
CharacterController = GetComponent<CharacterController>();
}
private void Update()
{
IsGrounded = Physics.CheckSphere(GroundCheck.transform.position, CheckDistance, GroundMask);
float X = Input.GetAxis("Horizontal");
float Y = Input.GetAxis("Jump");
float Z = Input.GetAxis("Vertical");
Vector3 Move = transform.right * X + transform.forward * Z;
CharacterController.Move(Move * Speed * Time.deltaTime);
if (Y == 1 && IsGrounded)
{
Velocity.y = Mathf.Sqrt(JumpHeight * -2 * Gravity);
}
if (IsGrounded && Velocity.y < 0)
{
Velocity.y = -2;
}
Velocity.y += Gravity * Time.deltaTime;
CharacterController.Move(Velocity * Time.deltaTime);
}
Happened to me too, judging by the code we watched the same youtube tutorial. The problem here is that you (and that tutorial guy) are making two calls to the Move function inside the Update function, this can cause the jitter you show on the video among other unexpected behaviors. You should only call the Move function once in the Update function and everything will be fine. So you need to combine X, Y, and Z movement in a single Vector3:
private void Update()
{
IsGrounded = Physics.CheckSphere(GroundCheck.transform.position, CheckDistance, GroundMask);
float X = Input.GetAxis("Horizontal");
float Y = Input.GetAxis("Jump");
float Z = Input.GetAxis("Vertical");
if (Y == 1 && IsGrounded)
{
Velocity.y = Mathf.Sqrt(JumpHeight * -2 * Gravity);
}
if (IsGrounded && Velocity.y < 0)
{
Velocity.y = -2;
}
Vector3 Move = transform.right * X * speed
+ transform.forward * Z * speed
+ transform.up * Velocity.y;
CharacterController.Move(Move * Time.deltaTime);
}
Note that now we are multiplying by the speed before, just the X and Z components, because we don't want the vertical velocity affected by the movement speed.
Although it should work now, there are a few details worth noting:
· You should name your variables in lower camel case: "move", not "Move".
· You don't really need a Vector3 for the vertical velocity, it could be just a float, since you are just using it for one axis. Note that you are reading Velocity.y, but never Velocity.x or Velocity.z. AFAIK this is because you copied the script from the mentioned Youtube tutorial, happened to me too until I realized.
· I don't really know why you are using "float Y = Input.GetAxis("Jump");" since the Jump key should not be an axis, if you run into problems try this instead:
private void Update()
{
IsGrounded = Physics.CheckSphere(GroundCheck.transform.position, CheckDistance, GroundMask);
if (IsGrounded && Velocity.y < 0)
{
Velocity.y = -2f;
}
float X = Input.GetAxis("Horizontal");
float Z = Input.GetAxis("Vertical");
if (Input.GetButtonDown("Jump") && IsGrounded)
{
Velocity.y = Mathf.Sqrt(JumpHeight * -2f * Gravity);
}
Vector3 Move = transform.right * X * speed
+ transform.forward * Z * speed
+ transform.up * Velocity.y;
CharacterController.Move(Move * Time.deltaTime);
}
Does it get stuck as in "It vibrates near the cube trying to climb up" or in "I can't move the character as soon as it touches the cube"?
My guess would be that your character tries to climb up the cube. The cube is approximately 2 meters high (at least if I can correctly get that from the video), your step offset is only 1, though.
Another thing to consider would be your character's skin width. From the Unity Manual:
It’s good practice to keep your Skin Width at least greater than 0.01 and more than 10% of the Radius.
But yours seems to have a good value.
If nothing helps, I would recommend actually going back to a CharacterController with a size of 2 meters. Citing the Unity Manual again:
You can modify the Height and Radius to fit your Character’s mesh
. It is recommended to always use around 2 meters for a human-like character.
One more thing: Start naming your variables after the C#-conventions, which would be camelCase. For example, your Velocity would become velocity, your GroundMask would become groundMask etc.

How can i prevent from moving the camera view under the terrain?

I'm using this mouseorbit script attached to a camera.
The problem is when i move the camera with the mouse and rotating it so the camera is under the terrain.
I want that when it get to the terrain height then stop don't move down i mean don't get to this view under the character maximum to be in the terrain height..
To stop on terrain height i mean something like that when it's getting to this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MouseOrbit : MonoBehaviour
{
/* These variables are what tell the camera how its going to function by
* setting the viewing target, collision layers, and other properties
* such as distance and viewing angles */
public Transform viewTarget;
public LayerMask collisionLayers;
public float distance = 6.0f;
public float distanceSpeed = 150.0f;
public float collisionOffset = 0.3f;
public float minDistance = 4.0f;
public float maxDistance = 12.0f;
public float height = 1.5f;
public float horizontalRotationSpeed = 250.0f;
public float verticalRotationSpeed = 150.0f;
public float rotationDampening = 0.75f;
public float minVerticalAngle = -60.0f;
public float maxVerticalAngle = 60.0f;
public bool useRMBToAim = false;
/* These variables are meant to store values given by the script and
* not the user */
private float h, v, smoothDistance;
private Vector3 newPosition;
private Quaternion newRotation, smoothRotation;
private Transform cameraTransform;
/* This is where we initialize our script */
void Start()
{
Initialize();
}
/* This is where we set our private variables, check for null errors,
* and anything else that needs to be called once during startup */
void Initialize()
{
h = this.transform.eulerAngles.x;
v = this.transform.eulerAngles.y;
cameraTransform = this.transform;
smoothDistance = distance;
NullErrorCheck();
}
/* We check for null errors or warnings and notify the user to fix them */
void NullErrorCheck()
{
if (!viewTarget)
{
Debug.LogError("Please make sure to assign a view target!");
Debug.Break();
}
if (collisionLayers == 0)
{
Debug.LogWarning("Make sure to set the collision layers to the layers the camera should collide with!");
}
}
/* This is where we do all our camera updates. This is where the camera
* gets all of its functionality. From setting the position and rotation,
* to adjusting the camera to avoid geometry clipping */
void LateUpdate()
{
if (!viewTarget)
return;
/* We check for right mouse button functionality, set the rotation
* angles, and lock the mouse cursor */
if (!useRMBToAim)
{
/* Check to make sure the game isn't paused and lock the mouse cursor*/
if (Time.timeScale > 0.0f)
Cursor.lockState = CursorLockMode.Locked;
h += Input.GetAxis("Mouse X") * horizontalRotationSpeed * Time.deltaTime;
v -= Input.GetAxis("Mouse Y") * verticalRotationSpeed * Time.deltaTime;
h = ClampAngle(h, -360.0f, 360.0f);
v = ClampAngle(v, minVerticalAngle, maxVerticalAngle);
newRotation = Quaternion.Euler(v, h, 0.0f);
}
else
{
if (Input.GetMouseButton(1))
{
/* Check to make sure the game isn't paused and lock the mouse cursor */
if (Time.timeScale > 0.0f)
Cursor.lockState = CursorLockMode.Locked;
h += Input.GetAxis("Mouse X") * horizontalRotationSpeed * Time.deltaTime;
v -= Input.GetAxis("Mouse Y") * verticalRotationSpeed * Time.deltaTime;
h = ClampAngle(h, -360.0f, 360.0f);
v = ClampAngle(v, minVerticalAngle, maxVerticalAngle);
newRotation = Quaternion.Euler(v, h, 0.0f);
}
else
{
Cursor.lockState = CursorLockMode.Confined;
}
}
/* We set the distance by moving the mouse wheel and use a custom
* growth function as the time value for linear interpolation */
distance = Mathf.Clamp(distance - Input.GetAxis("Mouse ScrollWheel") * 10, minDistance, maxDistance);
smoothDistance = Mathf.Lerp(smoothDistance, distance, TimeSignature(distanceSpeed));
/*We give the rotation some smoothing for a nicer effect */
smoothRotation = Quaternion.Slerp(smoothRotation, newRotation, TimeSignature((1 / rotationDampening) * 100.0f));
newPosition = viewTarget.position;
newPosition += smoothRotation * new Vector3(0.0f, height, -smoothDistance);
/* Calls the function to adjust the camera position to avoid clipping */
CheckSphere();
smoothRotation.eulerAngles = new Vector3(smoothRotation.eulerAngles.x, smoothRotation.eulerAngles.y, 0.0f);
cameraTransform.position = newPosition;
cameraTransform.rotation = smoothRotation;
}
/* This is where the camera checks for a collsion hit within a specified radius,
* and then moves the camera above the location it hit with an offset value */
void CheckSphere()
{
/* Add height to our spherecast origin */
Vector3 tmpVect = viewTarget.position;
tmpVect.y += height;
RaycastHit hit;
/* Get the direction from the camera position to the origin */
Vector3 dir = (newPosition - tmpVect).normalized;
/* Check a radius for collision hits and then set the new position for
* the camera */
if (Physics.SphereCast(tmpVect, 0.3f, dir, out hit, distance, collisionLayers))
{
newPosition = hit.point + (hit.normal * collisionOffset);
}
}
/* Keeps the angles values within their specificed minimum and maximum
* inputs while at the same time putting the values back to 0 if they
* go outside of the 360 degree range */
private float ClampAngle(float angle, float min, float max)
{
if (angle < -360)
angle += 360;
if (angle > 360)
angle -= 360;
return Mathf.Clamp(angle, min, max);
}
/* This is our custom logistic growth time signature with speed as input */
private float TimeSignature(float speed)
{
return 1.0f / (1.0f + 80.0f * Mathf.Exp(-speed * 0.02f));
}
}
It looks like the script i grabbed already handles terrain collision... i just need to make sure to set the collision layers on it to include the terrain. But not sure how to do it.
What i tried:
I added now a new layer in the inspector called it Terrain.
Then in the hierarchy on the Terrain i change it's layer to terrain.
Also in the script i selected Terrain. But it's still not working.
In the screenshot the top is the inspector of the camera with the script selected in Collision Layers Terrain.
In the bottom the terrain inspector selected Terrain as :Layer
Many answers to this question.
But here's my take on it since I am fairly certain this solution is interesting compared to all solutions you will find online related to unity 3rd person controllers.
Abstract
In Unity, you could take the usual approach of having an empty object and having that be the parent of the camera, etc... But what people in the industry would typically do is use a mathematical formula called Spherical coordinates.
This, by far, is the best approach for 3rd person controllers from my experience and its the most elegant and beautiful approach. All you need to do after implementing this approach, is shoot a ray from the camera down a meter, and if it detects anything, then change a single value in your spherical coordinates formula which would make the sphere around the player smaller which gives the effect of collision
Implementation
Here's a snippet of code which shows a single function needed to create the spherical coordinates formula.
private Vector3 sphericalCoordinates()
{
float m = distanceFromPlayer;
v = mouseY;
h = mouseX;
x = m * Mathf.Sin(v) * Mathf.Cos(h);
z = m * Mathf.Sin(v) * Mathf.Sin(h);
y = m * Mathf.Cos(v);
Vector3 pos = new Vector3(x, y, z);
return pos;
}
m = magnitude.
You can think of this as the radius from the sphere, this is what you change to give the collision effect.
I later use this snippet to bind the player with the camera in trigger.
[NOTE: very unclean code, I just wanted to show you a basic concept, definitely make it cleaner later]
Vector3 getCamRotWORLD()
{
Vector3 camRotWORLD = new Vector3(0, transform.rotation.eulerAngles.y, 0);
return camRotWORLD;
}
void adjustPlayerRot()
{
if (Input.GetKey(KeyCode.W))
{
player.transform.rotation = Quaternion.Euler(player.transform.rotation.x, getCamRotWORLD().y, player.transform.rotation.z);
player.transform.Translate(0, 0, playerScript.speed * Time.deltaTime);
}
if (Input.GetKey(KeyCode.A))
{
player.transform.rotation = Quaternion.Euler(player.transform.rotation.x, getCamRotWORLD().y - 90, player.transform.rotation.z);
player.transform.Translate(0, 0, playerScript.speed * Time.deltaTime);
}
if (Input.GetKey(KeyCode.S))
{
player.transform.rotation = Quaternion.Euler(player.transform.rotation.x, getCamRotWORLD().y - 180, player.transform.rotation.z);
player.transform.Translate(0, 0, playerScript.speed * Time.deltaTime);
}
if (Input.GetKey(KeyCode.D))
{
player.transform.rotation = Quaternion.Euler(player.transform.rotation.x, getCamRotWORLD().y + 90, player.transform.rotation.z);
player.transform.Translate(0, 0, playerScript.speed * Time.deltaTime);
}
}
Here's how my update looks like in case you really would like to snatch the implementation instead of trying to experiment with it yourself.
private void Update()
{
mouseX -= Input.GetAxis("Mouse X") * (sensitivity * Time.deltaTime);
mouseY -= Input.GetAxis("Mouse Y") * (sensitivity * Time.deltaTime);
playerCam.transform.LookAt(player.transform);
playerCam.transform.position = new Vector3(
sphericalCoordinates().x + player.transform.position.x,
sphericalCoordinates().y,
sphericalCoordinates().z + player.transform.position.z
);
}

Unity avoiding obstacles with pathfinding c#

I have a simple project which includes pathfinding with obstacle avoidance. Now, I have void Steer which steers the object around since the path is not straight. then I have void AvoidObstacles which has raycast and the obstacle avoidance part basically.
Whenever the object steers, it calls the AvoidObstacles function. The problem now is, at the start, whenever it hasn't call in the Steer function yet, because it is a straigh line, it passes through the object not avoiding it.
Here are some of the codes used:
public Vector3 Steer(Vector3 target, bool bFinalPoint = false)
{
//Calculate the directional vector from the current position towards the target point
Vector3 desiredVelocity = (target - transform.position);
float dist = desiredVelocity.magnitude;
AvoidObstacles(ref desiredVelocity);
//Normalise the desired Velocity
desiredVelocity.Normalize();
//Calculate the velocity according to the speed
if (bFinalPoint && dist < 10.0f)
desiredVelocity *= (curSpeed * (dist / 10.0f));
else
desiredVelocity *= curSpeed;
//Calculate the force Vector
Vector3 steeringForce = desiredVelocity - velocity;
Vector3 acceleration = steeringForce / mass;
return acceleration;
}
here is the other one
public void AvoidObstacles(ref Vector3 desiredVelocity)
{
RaycastHit hit;
Vector3 leftRay = transform.position;
Vector3 rightRay = transform.position;
//leftRay.x -= 2;
//rightRay.x += 2;
Debug.DrawLine(transform.position,(transform.forward * 5) + transform.position,Color.green);
if(Physics.Raycast(transform.position, transform.forward,out hit, minimumDistToAvoid))
{
Debug.DrawLine(transform.position,(transform.forward * 10) + transform.position,Color.red);
if(hit.transform != transform)
{
//dir += hit.normal * 50;
//Get the normal of the hit point to calculate the new direction
Vector3 hitNormal = hit.normal;
hitNormal.y = 0.0f; //Don't want to move in Y-Space
//Get the new directional vector by adding force to vehicle's current forward vector
desiredVelocity = transform.forward + hitNormal * force;
}
}
}
and here is my update
void Update ()
{
//Unify the speed
curSpeed = speed * Time.deltaTime;
targetPoint = path.GetPoint(curPathIndex);
//If reach the radius within the path then move to next point in the path
if(Vector3.Distance(transform.position, targetPoint) < path.Radius)
{
//Don't move the vehicle if path is finished
if (curPathIndex < pathLength - 1)
curPathIndex ++;
else if (isLooping)
curPathIndex = 0;
else
return;
}
//Move the vehicle until the end point is reached in the path
if (curPathIndex >= pathLength )
return;
//Calculate the next Velocity towards the path
if(curPathIndex >= pathLength - 1 && !isLooping)
velocity += Steer(targetPoint, true);
else
velocity += Steer(targetPoint);
transform.position += velocity; //Move the vehicle according to the velocity
transform.rotation = Quaternion.LookRotation(velocity); //Rotate the vehicle towards the desired Velocity
//AvoidObstacles(ref Vector3 desiredVelocity);
dir.Normalize();
}
Maybe someone can help me out buy letting me know how can I call the avoidobstacle function outside of steer? Maybe in update or start perhaps.
I can't really use other algorithms, just this one right here. TIA

Categories

Resources