I'm working with this tutorial on using a mouse to test a VR game, and for the most part I got it working, except it seems to "reset" the X or Y position every time I switch which axis I'm moving on. For example, if I move the mouse x axis, it rotates the view, but then if I move the y axis, the x-position (.localRotation in the code) goes back to 0 (looking straight ahead in the original position). Then if I move the x axis again, the y-position goes back to zero, but the x position picks up where I left it last time.
I have a feeling it isn't this code that's doing it, but I'll paste here just in case:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class MouseLook : MonoBehaviour
{
public InputActionReference horizontalLook;
public InputActionReference verticalLook;
public float lookSpeed = 0.25f;
public Transform cameraTransform;
float pitch;
float yaw;
// Start is called before the first frame update
void Start()
{
Cursor.lockState = CursorLockMode.Locked;
horizontalLook.action.performed += HandleHorizontalLookChange;
verticalLook.action.performed += HandleVerticalLookChange;
}
void HandleHorizontalLookChange(InputAction.CallbackContext obj)
{
yaw += obj.ReadValue<float>();
transform.localRotation = Quaternion.AngleAxis(yaw * lookSpeed, Vector3.up);
}
void HandleVerticalLookChange(InputAction.CallbackContext obj)
{
pitch += obj.ReadValue<float>();
transform.localRotation = Quaternion.AngleAxis(pitch * lookSpeed, Vector3.right);
}
}
You set the rotation to explicit values based on a sum of mouse position or delta (isn't clear without seeing the Input Asset, but I assume delta), thus your rotation gets "reset" to the exact rotations only given by the current axis everytime.
Solution 1
Add the new rotation to your existing rotation by multiplying it to the left of your current rotation, e.g. localRotation = Quaternion.AngleAxis(...) * localRotation;
Solution 2 (recommended)
Since Quaternions are not as intuitively understood as angles, Unity provides a convenience function called Rotate(axis, angle) on every Transform object
=> transform.Rotate(<Vector3 as axis>, <your angle value>);
In both approaches, however, the angle value should be calculated from mouse delta and not absolute screen position nor a sum of deltas.
Make sure to read through the Public Methods section on this manual page for a thorough understandung of how to manipulate transforms in Unity: https://docs.unity3d.com/ScriptReference/Transform.html
Related
So I have a script for a day/night cycle attached to the directional light in unity. It slowly rotates the light which creates an effective day/night cycle. There's an event that I want to call once every sunset, or more specifically when the x rotation of the light is at 200 degrees. The problem is my script rotates a little bit each frame, according to Time.deltatime which is obviously not perfectly consistent. Because of this, I might be at a rotation just below 199 and then at the next frame, I might be at a rotation just above 200 degrees, overshooting it so that it's never actually 200 degrees. I tried to get around this by checking if the x rotation is above 200 AND the x rotation - my rotate amount in that frame is below 200, then calling the event. That was the idea but it didn't work for some reason. It never calls the event. Here's my script.
using UnityEngine;
using UnityEngine.Events;
public class DayNightCycle : MonoBehaviour
{
public TerrainGenerator terrainGenerator;
public float dayLength = 3;
float rotationSpeed;
public UnityEvent night;
public float timeNightStarts = 200;
// Start is called before the first frame update
void Start()
{
rotationSpeed = 360 / (dayLength * 60);
}
// Update is called once per frame
void Update()
{
if (terrainGenerator.mapLoaded)
{
Vector3 rotateAmount = Vector3.right * rotationSpeed * Time.deltaTime;
transform.Rotate(rotateAmount, Space.World);
float xRotation = transform.eulerAngles.x;
if (xRotation >= timeNightStarts && xRotation - rotateAmount.x < timeNightStarts)
{
night.Invoke();
}
}
}
}
The problem you are facing is expected since Unity uses Quaternions under the hood and quaternion to euler conversions are not stable.
Quote from Unity docs:
When you read the .eulerAngles property, Unity converts the
Quaternion's internal representation of the rotation to Euler angles.
Because, there is more than one way to represent any given rotation
using Euler angles, the values you read back out may be quite
different from the values you assigned. This can cause confusion if
you are trying to gradually increment the values to produce animation.
To avoid these kinds of problems, the recommended way to work with
rotations is to avoid relying on consistent results when reading
.eulerAngles particularly when attempting to gradually increment a
rotation to produce animation. For better ways to achieve this, see
the Quaternion * operator.
If you want to avoid Quaternions, you can represent the eulerX angle as a float variable in your code. Increment its value, always set the transform.euler.x from it, but never read it back from the transform. If no other script or physics affects your transform (which should be the case for sun) you will be fine.
I am trying to achieve an helical movement with min/max limiation :
positive rotation > negative transform
negative rotation > positive transform
Both positive & negative rotation would be done with the MRTK hands interactions provided script, so this is not my problem.
I (think) that I am struggling with the logic.
tl;dr : I want to screw something using Unity, MRTK and virtual hands.
I ended up solving my problem, after days of scratching my head really, really hard.
To be honest, I probably wouldn't have succeeded on my own, because of a logic problem (this is very often the case, isn't it?).
Initial Setup
In my case, the gameObject to interact with, is a screw.
I wanted it to perform a positive translation when a positive rotation occurs, vice-versa.
On this gameObject are attached some components :
a collider (mesh, box, capsule)
a rigidbody
a PointerHandler.cs (MRTK)
a ObjectManipulator.cs (MRTK)
a NearInteractionGrabbable.cs (MRTK)
the TwistingRotation.cs found below
The rigidbody, PointerHandler.cs, ObjectManipulator.cs and the TwistingRotation.cs configurations can be found here :
The magic appear with the PointerHandler.cs that allow us to detect when the near interaction happen with the screw, thx to the OnPointerDragged event.
TwistingRotation.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// Translation of the object while rotating it when grabbed using the MRTK.ObjectManipulator
/// Boundaries and axis restrictions ongoing
/// </summary>
public class TwistingRotation : MonoBehaviour
{
/*******CACHE REFERENCE*******/
private Transform _myTransform;
[SerializeField] private float translationFactor = 90f;
private Vector3 _minPosition;
private Vector3 _maxPosition;
private Vector3 _previousVector;
private Rigidbody _rb;
private void Start()
{
// Cache reference
_myTransform = gameObject.transform;
_rb = gameObject.GetComponent<Rigidbody>();
// Reference for the previous rotation vector
_previousVector = _myTransform.up;
// Default position is the maximum transform.position (unscrewed)
_maxPosition = _myTransform.position;
// Minimum position is default transform.position + 1unit in local space direction
_minPosition = _maxPosition + Vector3.forward;
}
/// <summary>
/// Move the object according to the rotation angle value
/// A positive rotation leads to a positive translation, and vice-versa
/// </summary>
public void TranslateRotation()
{
// Retrieve the angle on a defined local axis when the rotation occur
var currentVector = _myTransform.up;
// Compute the angle between the previous and the current vector on a defined local axis
// Difference between the previous rotation vector, and the actual, on a global axis
var angle = Vector3.SignedAngle(_previousVector, currentVector, Vector3.forward);
// Move object proportional to its rotation
var translation = Vector3.forward * (angle / translationFactor);
_myTransform.Translate(translation, Space.Self);
// Get the GO current position
var currentPosition = _myTransform.position;
// Clamp for each axis between _minPosition and _maxPosition (the default spawn position)
// Doing a Mathf.Min/Max inside the Mathf.Clamp to insure that the min and max values are correct
var x = Mathf.Clamp(currentPosition.x, Mathf.Min(_minPosition.x, _maxPosition.x), Mathf.Max(_minPosition.x, _maxPosition.x));
var y = Mathf.Clamp(currentPosition.y, Mathf.Min(_minPosition.y, _maxPosition.y), Mathf.Max(_minPosition.y, _maxPosition.y));
var z = Mathf.Clamp(currentPosition.z, Mathf.Min(_minPosition.z, _maxPosition.z), Mathf.Max(_minPosition.z, _maxPosition.z));
// Compute the new position while taking the boundaries into consideration
var newPosition = new Vector3(x, y, z);
_myTransform.position = newPosition;
// Save position for the next frame
_previousVector = currentVector;
}
}
What is happening ?
Quite simple : the screw is translating over 1 (unity) unit while rotating.
The value of the rotation depend on the value of the translationFactor variable.
In my case, the value is 360 so a complete rotation over a 1 (unity) unit translation.
Result
It is far from being perfect, probably very much "meh" but hey, it is working (not as intended tho, but still) and it allowed me to move forward and I made my presentation.
I have a simple cannon I am trying to program to shoot a projectile. I have 4 game objects:
The tank object
A Pivot Object (child of tank)
A Cannon Object (child of pivot)
An empty GameObject called Tip which sits just above the cannon (child of cannon)
My code for the cannon object is below:
public class cannon: MonoBehaviour
{
public float power = 1.0f;
public Transform projectile;
void Update()
{
if (Input.GetButtonDown("Fire1"))
{
Transform bullet;
Vector2 pos = transform.GetChild(0).position;
bullet = Instantiate(projectile, pos, Quaternion.identity);
Rigidbody2D bullet_rb = bullet.GetComponent<Rigidbody2D>();
bullet_rb.AddForce(pos * power);
}
}
}
Everything seems to work okay, until I looked at the trajectory of the projectiles when the cannon is aimed directly along the x-axis. There is still a small y component to the applied force, which I didn't expect and do not desire.
Here's a gif:
What could be causing this behavior?
The force you're adding is pos (times a scalar power)... The position of your cannon is above zero on the y axis, so that's why it launches with a y offset. I'm assuming it has an x offset too, just less noticeable, because the base (tank) is centered at x while it's above center in the y. Try moving the whole tank setup off away from the scene root; you'll probably see a huge spike in the force of the projectile, because of this error of using pos.
What you want is a vector representing a pure direction instead. One that is also normalized (magnitude of one). In this case, either right (forward in 2d) or up, from the perspective of the rotating tip or cannon.
I am trying to create a third person spaceship movement.
The spaceship rotates about all axes at its position, and has a throttle control to move in forward direction. There is a camera which is always behind it. I am not making the camera a child because I want the camera to NOT follow the rotation about z axes.
The camera has a script, which keeps its position a fixed distance behind the spaceship, and then calls transform.LookAt(spaceShipTarget).
The problem is that as I rotate the ship around global x axes 90 degrees, the y axis of camera suddenly does a 180 degree rotation. The camera control script is below:
using UnityEngine;
namespace UnityStandardAssets.Utility
{
public class FollowBehind : MonoBehaviour
{
public Transform target;
public float distance;
public float delay;
private Vector3 velocity = Vector3.zero;
private void LateUpdate()
{
Vector3 offset = target.transform.TransformVector(0, 0, distance);
Vector3 currentPosition = transform.position;
Vector3 finalPosition = target.position + offset;
transform.position = Vector3.SmoothDamp(currentPosition,
finalPosition, ref velocity, delay);
transform.LookAt(target);
}
}
}
Why would that happen and how can I fix it?
The problem you have with the rotation of the camera is probably caused by the script you use to make the camera follow the spaceship, probably because when you rotate the spaceship the rotation (and probably the position) of the camera are affected.
What you could do instead is make both the spaceship and camera child of another object, and then add a script to this parent object. Now you can put some code in the script of the parent to move the parent itself (this way both camera and spaceship will move together, and you don't need to keep them together manually) and also in the script of the parent you can put some code to rotate the spaceship and camera individually or together based on specific inputs.
I have a sphere gameobject with 5 cubes placed on different points on the surface of the sphere. When a key is pressed, i would like the sphere to spin for a few seconds and then slowly stops on the first cube point on the cube and always keeping the same direction in rotation. The issue i am facing now is that, Quaternion.Slerp always takes the shortest path to the next cube which means sometimes the rotation direction is changed. Any ideas?
Thanks
You can easily handle the rotation as a Vector3 of Euler angles.
This way you can Linearly Interpolate the angles to the correct value. And you can use coterminal angles so that you're always interpolating to a higher value ( so no backwards rotations would occur ). After every rotational step you might want to normalize the angles to the 0, 360 range with this approach though.
Example Code :
using UnityEngine;
using System.Collections;
public class Rotation : MonoBehaviour {
public Transform firstPosition;
public Transform secondPosition;
public float rotationDuration = 3;
void Start () {
transform.rotation = firstPosition.rotation;
StartCoroutine(Rotate());
}
IEnumerator Rotate() {
var deltaTime = 0.0f;
var distance = firstPosition.rotation.eulerAngles - secondPosition.rotation.eulerAngles;
while(deltaTime < rotationDuration) {
var rotation = transform.rotation.eulerAngles;
rotation = firstPosition.rotation.eulerAngles + deltaTime/rotationDuration*distance;
transform.rotation = Quaternion.Euler(rotation);
deltaTime += Time.deltaTime;
yield return null;
}
transform.rotation = secondPosition.rotation;
}
}