How to restrict Quaternion.RotateTowards() to clockwise-only rotation - c#

I made a speedometer script for rotaing the needle in the speedometer
public class Speedometer : MonoBehaviour
{
public float zeroRate = 3f;
[SerializeField] PlayerController vehicle;
void Update()
{
if (vehicle.IsOnGround())
{
float zRot = ((vehicle.speed * 2.237f) / vehicle.maxSpeed) * 180;
transform.rotation = Quaternion.Euler(0, 0, -zRot);
}
else
{
transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.identity, zeroRate * Time.deltaTime);
}
}
}
I'm facing the problem that the needle sometimes does a full 360 to rotate towards Quanternion.identity. I could use an if condition to check if the value goes beyond 180, subtract the twice the difference but the condition would check for this in every Update() call and would just clutter code with a 1-time-use-only if-condition. Is there a function that restricts the rotate in clockwise/anti-clockwise direction?

Alternatively you could probably do something like store the last zRot value and over time reduce it and go
private float zRot;
void Update()
{
if (vehicle.IsOnGround())
{
zRot = ((vehicle.speed * 2.237f) / vehicle.maxSpeed) * 180;
transform.rotation = Quaternion.Euler(0, 0, -zRot);
}
else
{
if(zRot > 0)
{
zRot -= zeroRate * Time.deltaTime;
// clamp to 0
zRot = Mathf.Max(0, zRot);
transform.rotation = Quaternion.Euler(0, 0, -zRot);
}
}
}
Just that now zeroRate would be in °/second instead of radians/second in your version.

Related

Drone Controller in Unity: movement issues with the unity inputSystem

I'm trying to build a controller for a unity gameobject to move it similary to a normel quadrotor drone. So the drone should be able to ascend and descend by mobing the left stick up and down, turn around by moving the left stick sidewards and move horizontally by using the right stick.
I tried implementing it with the unity inputSystem, but unfortunately, it doesn't really work the way it is supposed to.
Movements are mostly not smooth and the rotation causes the horizonzal movement to move in wrong directions.
Here is my code:
public void OnMove(InputValue inputValue)
{
//horizontal movement
Debug.Log(inputValue.Get());
Vector3 move = new Vector3(inputValue.Get<Vector2>().x * 10 * Time.deltaTime, 0, inputValue.Get<Vector2>().y * 1 * Time.deltaTime);
move = Quaternion.Euler(0, rotation, 0) * move;
//playerDrone.transform.position += new Vector3(inputValue.Get<Vector2>().x * 10 * Time.deltaTime, 0, inputValue.Get<Vector2>().y * 10 * Time.deltaTime);
playerDrone.transform.Translate(move, Space.World);
}
public void OnClockwiseRotation()
{
//rotation of drone clockwise
playerCam.transform.Rotate(0, 0.5f, 0, Space.World);
rotation += 0.5f;
}
public void OnCounterclockwiseRotation()
{
//rotation of drone counterclockwise
Debug.Log("Rotation");
playerCam.transform.Rotate(0, -0.5f, 0, Space.World);
rotation += 0.5f;
}
public void OnAscend1()
{
//ascend drone
playerDrone.transform.position += new Vector3(0, 0.1f, 0);
Debug.Log("Ascend");
}
public void OnDescend()
{
//descend drone
Debug.Log("Descend");
playerDrone.transform.position += new Vector3(0, -0.1f, 0);
}
Does anyone know why the movement is so problematic with that implementation?
Thanks in advance
There are a few mistakes I'd say
Your rotation and your ascending are frame-rate dependent. Here you didn't use Time.deltaTime
In both OnClockwiseRotation and OnCounterclockwiseRotation you do
rotation += 0.5f;
You are using once playerDrone and another time playerCam .. you should probably stick to one as it sounds like you are only rotating a camera but not the drone.
In general I would not hardcode the values into your code but rather expose some class fields so you can adjust the velocities vis the Inspector without having to recompile
You probably would rather do something like
// Adjust these in the Inspector!
// in angle per second
public float rotationSpeed = 45;
// in meter per second
public float ascendingSpeed = 1;
public float forwardSpeed = 1;
public float sidewardsSpeed = 1;
public void OnMove(InputValue inputValue)
{
// In this case probably not a big deal but in general use getters only once
var input = inputValue.Get();
var move = new Vector3(input.x * sidewardsSpeed * Time.deltaTime, 0, input.y * forwardSpeed * Time.deltaTime);
move = Quaternion.Euler(0, rotation, 0) * move;
playerDrone.transform.Translate(move, Space.World);
}
public void OnClockwiseRotation()
{
var angle = rotationSpeed * Time.deltaTime;
playerDrone.transform.Rotate(0, angle, 0, Space.World);
rotation += angle;
}
public void OnCounterclockwiseRotation()
{
var angle = rotationSpeed * Time.deltaTime;
playerDrone.transform.Rotate(0, -angle, 0, Space.World);
rotation -= angle;
}
public void OnAscend1()
{
playerDrone.transform.position += Vector3.up * ascendingSpeed * Time.deltaTime;
}

Stop my first person character controller going through the wall in Unity using C#?

As seen in the video here: https://i.gyazo.com/ad45ef9e231fd2f9ec6d4cf76889aece.mp4
My code:
MouseLook.cs:
using UnityEngine;
using System.Collections;
public class MouseLook : MonoBehaviour
{
public enum RotationAxes { MouseXAndY = 0, MouseX = 1, MouseY = 2 }
public RotationAxes axes = RotationAxes.MouseXAndY;
public float sensitivityX = 3F;
public float sensitivityY = 3F;
public Camera playerCamera;
public float minimumX = -360F;
public float maximumX = 360F;
public float minimumY = -60F;
public float maximumY = 60F;
private float rotationX = 0F;
private float rotationY = 0F;
private Quaternion originalRotation;
void Update()
{
if (axes == RotationAxes.MouseXAndY)
{
rotationX += Input.GetAxis("Mouse X") * sensitivityX;
rotationY += Input.GetAxis("Mouse Y") * sensitivityY;
rotationX = ClampAngle(rotationX, minimumX, maximumX);
rotationY = ClampAngle(rotationY, minimumY, maximumY);
Quaternion xQuaternion = Quaternion.AngleAxis(rotationX, Vector3.up);
Quaternion yQuaternion = Quaternion.AngleAxis(rotationY, -Vector3.right);
transform.localRotation = originalRotation * xQuaternion * yQuaternion;
}
if (axes == RotationAxes.MouseX)
{
rotationX += Input.GetAxis("Mouse X") * sensitivityX;
rotationX = ClampAngle(rotationX, minimumX, maximumX);
Quaternion xQuaternion = Quaternion.AngleAxis(rotationX, Vector3.up);
transform.localRotation = originalRotation * xQuaternion;
}
if (axes == RotationAxes.MouseY || playerCamera != null)
{
rotationY += Input.GetAxis("Mouse Y") * sensitivityY;
rotationY = ClampAngle(rotationY, minimumY, maximumY);
Quaternion yQuaternion = Quaternion.AngleAxis(-rotationY, Vector3.right);
if (playerCamera != null)
{
playerCamera.transform.localRotation = originalRotation * yQuaternion;
}
else
{
transform.localRotation = originalRotation * yQuaternion;
}
}
}
void Start()
{
/*
if (gameObject.GetComponent<Rigidbody>())
{
gameObject.GetComponent<Rigidbody>().freezeRotation = true;
}
*/
originalRotation = transform.localRotation;
}
public static float ClampAngle(float angle, float min, float max)
{
if (angle < -360F)
{
angle += 360F;
}
if (angle > 360F)
{
angle -= 360F;
}
return Mathf.Clamp(angle, min, max);
}
}
FirstPersonController.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
using Cursor = UnityEngine.Cursor;
public class FirstPersonController : MonoBehaviour
{
private float speed = 5;
private float jumpPower = 4;
Rigidbody rb;
CapsuleCollider col;
public GameObject crossHair;
bool isActive;
float HorizontalInput;
float VerticalInput;
void Start()
{
Cursor.visible = false;
Cursor.lockState = CursorLockMode.Locked;
rb = GetComponent<Rigidbody>();
col = GetComponent<CapsuleCollider>();
crossHair = GameObject.FindWithTag("CrossHair");
}
void Update()
{
HorizontalInput = Input.GetAxisRaw("Horizontal");
VerticalInput = Input.GetAxisRaw("Vertical");
if (Input.GetKeyDown("escape"))
{
Cursor.lockState = CursorLockMode.None;
}
if (Input.GetButtonDown("Sprint"))
{
speed = 15;
}
if (Input.GetButtonUp("Sprint"))
{
speed = 5;
}
if (Input.GetKeyDown(KeyCode.H))
{
isActive = !isActive;
}
if (isActive)
{
crossHair.SetActive(true);
}
else
{
crossHair.SetActive(false);
}
}
void FixedUpdate()
{
Vector3 xMovement = transform.right * speed * HorizontalInput * Time.deltaTime;
Vector3 zMovement = transform.forward * speed * VerticalInput * Time.deltaTime;
rb.velocity = new Vector3(HorizontalInput, 0, VerticalInput) * speed;
if (isGrounded() && Input.GetButtonDown("Jump"))
{
rb.AddForce(Vector3.up * jumpPower, ForceMode.Impulse);
}
}
private bool isGrounded()
{
return Physics.Raycast(transform.position, Vector3.down, col.bounds.extents.y + 0.1f);
}
}
Is there anything wrong I am doing in this code, if so how do I fix it?
Entire project can be downloaded here: https://github.com/Some-T/FirstPersonController-CSharp
Project has relevant colliders and rigidbodies set up!
Someone has advised me to use shapecast, but I believe that may incorrect? I can't see how that would work as my player does not have character controller component added to it?
Overall how do I stop my first person character controller going through the wall like in the initial video specified above?
Upon further research I have discovered the following:
The answer is to use:
https://docs.unity3d.com/ScriptReference/Rigidbody.AddForce.html as a quick fix.
But definitively for flawlessness use:
https://docs.unity3d.com/ScriptReference/Rigidbody-velocity.html
As to how in C# I am not so sure, but here is a screen shot I did in bolt asset.
Currently I have movmement working with velocity but it does not work properly, not sure as to why? So overall my question now is how do I get movement working using velocity? I added a line in FirstPersonController.cs that moves the character using velocity of rb.velocity = new Vector3(HorizontalInput, 0, VerticalInput) * speed; so my only question and issue now is my player does not move in the direction the camera on my player is facing so I am not sure how to fix this specific thing overall?
In your project code is different from one that you provided here.
Try to enable rigidbody`s rotation constraint - freeze X and Z rotation, leave only Y. When you rotate your capsule collider (as it works in your project), it can "climb" on a wall.
Do the isGrounded check when you move, and lock movement, if not grounded.
Try to increase Collider.contactOffset
If above does not helps, try to use Rigidbody.velocity instead of Rigidbody.MovePosition.
You can also reduce Rigidbody.drag
In general, pretty good technique is to use NavMesh for movement - in this way, you able to explicitly lock player from movement outside of navmesh surface. But, of course, it doesn`t fit to many gameplays.
Hope, it helps.
upd
You move your player like this:
void FixedUpdate()
{
Vector3 xMovement = transform.right * speed * HorizontalInput * Time.deltaTime;
Vector3 zMovement = transform.forward * speed * VerticalInput * Time.deltaTime;
}
Note, that you not even apply it to rigidbody;
But you get your input this way:
float HorizontalInput = Input.GetAxis("Horizontal");
float VerticalInput = Input.GetAxis("Vertical");
in Update, so input just stays inside Update, and does not applied at all. You declared your variables twice, once on class top, another in Update().

Unity Mathf.Lerp only executes ones

void Fire(float firingRate)
{
TimePerFrame += Time.deltaTime;
if(TimePerFrame >= firingRate)
{
Vector3 ProjectileDistance = new Vector3(0, 30, 0); //distance between center of the campion and it's head
GameObject beam = Instantiate(projectile, transform.position + ProjectileDistance, Quaternion.identity) as GameObject;
beam.GetComponent<Rigidbody2D>().velocity = new Vector3(0, projectileSpeed, 0);
// AudioSource.PlayClipAtPoint(fireSound, transform.position);
TimePerFrame = 0;
}
}
void Update ()
{
if (freezePosition == false)
{
Fire(firingRate);
PositionChaning();
firingRate = Mathf.Lerp(minFiringRate, maxFiringRate, 0.1f);
Debug.Log(firingRate);
}
}
I want my firerate to be flexible, i want it to start by shooting fast and let it automatically lower it's fire rate. (the bigger the firingRate float is the slower the speed is)
The problem is that firingRate = Mathf.Lerp(minFiringRate, maxFiringRate, 0.1f);
triggers once and only once. It doesn't seem to change it's value every frame.
Debug.Log(firingRate); tells the value every frame but it seems to remain a constant.
Why does this happen?
The update triggers every Frame, and so does your Mathf.Lerp However you are not changing the interpolation, which in your case is defined as 0.1f.
By changing this interpolation, you will be able to achieve the 'shifting' of fire rate.
In your case you could define a variable t outside the scope of your update, and update it inside the Update() through t += 0.5f * Time.deltaTime;
The Mathf.Lerp documentation has a pretty good sample of how todo so as well
void Update()
{
// animate the position of the game object...
transform.position = new Vector3(Mathf.Lerp(minimum, maximum, t), 0, 0);
// .. and increate the t interpolater
t += 0.5f * Time.deltaTime;
// now check if the interpolator has reached 1.0
// and swap maximum and minimum so game object moves
// in the opposite direction.
if (t > 1.0f)
{
float temp = maximum;
maximum = minimum;
minimum = temp;
t = 0.0f;
}
}
Your problem is here:
firingRate = Mathf.Lerp(minFiringRate, maxFiringRate, 0.1f);
As you can see here your t has to be calculated every frame.
public static float Lerp(float a, float b, float t);
You can change it like this:
private float fireTimer = 1.0f;
public float fireLimiter = 0.05f;
void Fire(float firingRate)
{
TimePerFrame += Time.deltaTime;
if(TimePerFrame >= firingRate)
{
Vector3 ProjectileDistance = new Vector3(0, 30, 0); //distance between center of the campion and it's head
GameObject beam = Instantiate(projectile, transform.position + ProjectileDistance, Quaternion.identity) as GameObject;
beam.GetComponent<Rigidbody2D>().velocity = new Vector3(0, projectileSpeed, 0);
// AudioSource.PlayClipAtPoint(fireSound, transform.position);
TimePerFrame = 0;
}
}
void Update ()
{
if (freezePosition == false)
{
if(fireTimer > 0.0f){
fireTimer -= Time.deltaTime * fireLimiter;
}
Fire(firingRate);
PositionChaning();
firingRate = Mathf.Lerp(minFiringRate, maxFiringRate, fireTimer);
Debug.Log(firingRate);
}
}
Do not forget to reset fireTimer to 1.0f after shooting!
The delay of the firerate can be controlled by fireLimiter

How can i rotate a spaceship back to it's original rotation to face the original position?

The spaceship start moving from point A. The spaceship is facing the moving direction.
Now when i click one on the L key i want that the spaceship will rotate and will face to the original position it was start moving from. But even if the spaceship is now rotated by axis Z or Y or X to rotate it first to the regular rotation values and to face to the start moving position.
using UnityEngine;
using System.Collections;
public class Control : MonoBehaviour
{
public int rotationSpeed = 75;
public int movementspeed = 10;
public int thrust = 10;
private bool isPKeyDown = false;
private float acceleration = .0f;
private Vector3 previousPosition = Vector3.zero;
private Rigidbody _rigidbody;
private Vector3 originalPosition;
private Quaternion originalRotation;
// Use this for initialization
void Start()
{
originalPosition = transform.position;
originalRotation = transform.rotation;
_rigidbody = GetComponent<Rigidbody>();
Debug.Log("Acc Speed: " + thrust);
}
// Update is called once per frame
void Update()
{
var v3 = new Vector3(Input.GetAxis("Vertical"), Input.GetAxis("Horizontal"), 0.0f);
transform.Rotate(v3 * rotationSpeed * Time.deltaTime);
transform.position += transform.forward * Time.deltaTime * movementspeed;
if (Input.GetKey(KeyCode.Z))
transform.Rotate(Vector3.forward * rotationSpeed * Time.deltaTime);
if (Input.GetKey("p"))
{
isPKeyDown = Input.GetKey("p");
float distance = Vector3.Distance(previousPosition, transform.position);
acceleration = distance / Mathf.Pow(Time.deltaTime, 2);
previousPosition = transform.position;
_rigidbody.AddRelativeForce(0f, 0f, thrust, ForceMode.Acceleration);
}
if (Input.GetKey("l"))
{
transform.rotation = Quaternion.Slerp(transform.rotation, originalRotation, 0);
//StartCoroutine(TurnShip(transform, transform., originalRotation.eulerAngles, 1));
//transform.position += transform.forward * Time.deltaTime * movementspeed;
}
}
IEnumerator TurnShip(Transform ship, Vector3 startAngle, Vector3 endAngle, float smooth)
{
float lerpSpeed = 0;
while (lerpSpeed < 1)
{
ship.eulerAngles = Vector3.Lerp(startAngle, endAngle, lerpSpeed);
lerpSpeed += Time.deltaTime * smooth;
yield return null;
}
}
void OnGUI()
{
if (isPKeyDown)
{
GUI.Label(new Rect(100, 100, 200, 200), "Acc Speed: " + acceleration);
}
}
}
This is where i click the L button but i tried some things but can't yet find how to do it.
The main goal is if i click once on L the spaceship should automatic rotate if needed and move back to the original position and then land on ground. L stand for landing that's the main goal.
Add a variable on top -
...
private Vector3 originalPosition;
private Quaternion originalRotation;
private bool landShip = false;
...
And use following code in update function -
if (Input.GetKey("l"))
{
landShip = true;
//StartCoroutine(TurnShip(transform, transform., originalRotation.eulerAngles, 1));
//transform.position += transform.forward * Time.deltaTime * movementspeed;
}
if(landShip){
transform.rotation = Quaternion.Slerp(transform.rotation, originalRotation, 0.5f);
}
Once the spaceship lands, set the landShip value back to false.

Unity2D Asteroids style game

I am trying to build an asteroids style game in Unity and could really use some help. I believe all my math is correct as far as the ship movement but I am having trouble getting it to work inside Unity. I am having a couple different problems.
The ship does not update with velocity ( if you start moving and then let go, it will stand still)
I am unsure in Unity how to set the ships rotation to my specific angle.
Any help would be greatly appreciated.
public class playerController : MonoBehaviour {
public static float timer;
public static bool timeStarted = false;
Vector2 accel;
Vector2 velocity;
float direction;
float angle;
float shotCooldown;
float speed;
const float pi = 3.141592f;
const float maxSpeed = 300;
const float maxAccel = 500;
void Start () {
timeStarted = true;
}
void Update () {
if (timeStarted == true) {
timer += Time.deltaTime;
}
shotCooldown -= (timer%60);
angle = direction * pi / 180;
if (Input.GetKey(KeyCode.W)) {
accel.y = -Mathf.Cos(angle) * maxAccel;
accel.x = Mathf.Sin(angle) * maxAccel;
velocity += accel * Time.deltaTime;
}
if (Input.GetKey(KeyCode.S)) {
accel.y = -Mathf.Cos(angle) * maxAccel;
accel.x = Mathf.Sin(angle) * maxAccel;
velocity -= accel * Time.deltaTime;
}
if (Input.GetKey(KeyCode.Space)) {
if (shotCooldown <= 0)
{
// Create new shot by instantiating a bullet with current position and angle
shotCooldown = .25f;
}
}
if (Input.GetKey(KeyCode.D)) {
direction += 360 * Time.deltaTime;
}
if (Input.GetKey(KeyCode.A)) {
direction -= 360 * Time.deltaTime;
}
/*
if (this.transform.position.x >= Screen.width && velocity.x > 0) {
this.transform.position.x = 0;
}*/
while (direction < 0) {
direction += 360;
}
while (direction > 360) {
direction -= 360;
}
speed = Mathf.Sqrt( (velocity.x * velocity.x) + (velocity.y * velocity.y));
if (speed > maxSpeed) {
Vector2 normalizedVector = velocity;
normalizedVector.x = normalizedVector.x / speed;
normalizedVector.y = normalizedVector.y / speed;
velocity = normalizedVector * maxSpeed;
}
this.transform.position = velocity * Time.deltaTime;
transform.rotation = Quaternion.AngleAxis(0, new Vector3(0,0,angle));
}
}
It's usually a bad idea to set the position the way you are, because you're not actually using any physics. The way you're doing it, velocity is a new position for the ship, not a speed. Once you let go of the keys, it stops calculating new positions, and thus stops moving.
There are a couple of alternatives which would make for a better result:
1) One way this can be done is by calling: transform.Translate(Vector3.forward * speed * Time.deltaTime) Vector3.forward should correspond to the direction you consider as "forward", but if not, you can change it to whichever works (eg Vector3.up). This means you only really need to calculate a speed and let unity hangle the rest.
2) If you're using a rigidbody on your ship, you could simply do:
rigidbody.AddForce(Vector3.forward * speed * Time.deltaTime) which will automatically accelerate the ship in the given direction by whatever speed you give it.
As for rotation, perhaps try something like this:
if (Input.GetKey(KeyCode.D))
{
Vector3 newRotation = transform.rotation.eulerAngles;
newRotation.z += 10;
transform.rotation = Quaternion.Euler (newRotation);
}
else if (Input.GetKey(KeyCode.A))
{
Vector3 newRotation = transform.rotation.eulerAngles;
newRotation.z -= 10;
transform.rotation = Quaternion.Euler (newRotation);
}

Categories