I'm try to rotate cube for 90 degrees by one axis, all work fine if any axes in 0 position. But if any axis in any position, like a 90 or -90, he starts spinning endlessly.
Video how it looks: https://youtu.be/PE4YH19ndCc
public Vector3 targetEulerAngles;
public float spd = 0.1f;
GameObject mainCube;
public void Awake()
{
mainCube = GameObject.FindGameObjectWithTag("mainCube");
}
public void FixedUpdate()
{
mainCube.transform.eulerAngles = Vector3.Lerp(mainCube.transform.eulerAngles, targetEulerAngles, spd);
}
public void RotateToLeft()
{
targetEulerAngles = new Vector3(targetEulerAngles.x + 90f, targetEulerAngles.y, targetEulerAngles.z);
}
public void RotateToRight()
{
targetEulerAngles = new Vector3(targetEulerAngles.x - 90f, targetEulerAngles.y, targetEulerAngles.z);
}
public void RotateToUp()
{
targetEulerAngles = new Vector3(targetEulerAngles.x, targetEulerAngles.y, targetEulerAngles.z - 90f);
}
public void RotateToDown()
{
targetEulerAngles = new Vector3(targetEulerAngles.x, targetEulerAngles.y, targetEulerAngles.z + 90f);
}
What #joel64 said in his answer is definitely true, but it is not the problem here.
The problem is that you use Lerp incorrectly. The last argument to Lerp(a, b, c) is NOT SPEED, it is progress from 0 to 1, where c <= 0 means result = a, c >= 1 means result = b, and all other values are in between. Your code constantly rotates cube and never stops, just over time it rotates less and less.
With euler angles it is very visible because of conversion errors, using Quaternions it will be less visible, but still - rotation never stops.
To do the stuff correctly, you need to write some more code. You need some variable to hold current progress of rotation, and you have to remember where the rotation started. Then rotate cube from start position to target position given progress and increase progress each frame.
Oh, and don't forget about deltaTime if you want your rotation to be the same on any FPS.
Final code will be:
public Vector3 targetEulerAngles;
public float spd = 0.1f;
GameObject mainCube;
private Quaternion startRotation;
private float progress = 0;
public void Awake() {
mainCube = GameObject.FindGameObjectWithTag("mainCube");
}
public void FixedUpdate() {
if (progress == 0) startRotation = mainCube.transform.rotation;
mainCube.transform.rotation = Quaternion.Lerp(startRotation, Quaternion.Euler(targetEulerAngles), progress);
progress += spd*Time.fixedDeltaTime;
}
public void RotateToLeft() {
targetEulerAngles = new Vector3(targetEulerAngles.x + 90f, targetEulerAngles.y, targetEulerAngles.z);
progress = 0;
}
public void RotateToRight() {
targetEulerAngles = new Vector3(targetEulerAngles.x - 90f, targetEulerAngles.y, targetEulerAngles.z);
progress = 0;
}
public void RotateToUp() {
targetEulerAngles = new Vector3(targetEulerAngles.x, targetEulerAngles.y, targetEulerAngles.z - 90f);
progress = 0;
}
public void RotateToDown() {
targetEulerAngles = new Vector3(targetEulerAngles.x, targetEulerAngles.y, targetEulerAngles.z + 90f);
progress = 0;
}
sometimes even I get weird results when using Euler angles so I use quaternions instead
maybe try this in your fixed update
mainCube.transform.rotation = Quaternion.Lerp(mainCube.transform.rotation, Quaternion.Euler(targetEulerAngles), spd);
Related
I am creating a building system in unity and when a key is pressed this function is run.
In this function I want to rotate the buildModel 90 degrees slowly over time.
Here is the function and what I tried:
public void RotateBuild()
{
//buildModel.transform.Rotate(0, 0, 90.0f);
Vector3 newDirection = Vector3.RotateTowards(buildModel.transform.eulerAngles, buildModel.transform.localEulerAngles + new Vector3(0, 90, 0), Time.fixedDeltaTime, 0);
buildModel.transform.rotation = Quaternion.LookRotation(newDirection);
}
I want to rotate it on the y axis, just like in Fortnite.
Unfortunately, it doesn't work now, so does anyone know a solution?
private float _rotationSpeed = 180f;
private Quaternion _targetRotation;
void Start(){
_targetRotation = buildModel.transform.rotation;
}
public void RotateBuilding(){
_targetRotation = buildModel.transform.rotation * Quaternion.AngleAxis(90f, Vector3.up);
}
public void Update(){
buildModel.transform.rotation = Quaternion.RotateTowards(buildModel.transform.rotation, _targetRotation, Time.deltaTime * _rotationSpeed);
}
For this setup in Unity, there is a circular dial. The values of the rotation are being used to scale the model. The actual scaling code is removed because there is a bug in the way zAngle value is calculated. Ideally it should be in steady increments in all directions, but here, the values jump across horizontal and vertical axes but they grow slowly when the dial is dragged in other directions.
Is there a recommended practice for such cases when using the screen drag position to calculate the rotation value?
zAngle is the final angle value used to rotate the dial and would be also used after some clamping to scale the model, but that is not the requirement here.
public class CircularScaleDial : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
RectTransform rectTransform;
public GameObject model;
[SerializeField] Vector2 delta = Vector2.one;
[SerializeField] CanvasScaler canvasScaler;
private Vector3 startPosition;
[SerializeField] int zAngle;
Vector3 gd1 = Vector3.zero;
private void Awake()
{
rectTransform = GetComponent<RectTransform>();
}
public void OnBeginDrag(PointerEventData eventData)
{
startPosition = GetDelta_3(eventData);
}
int GetAngleFromPosition(Vector2 pos) {
var angleRadians = Mathf.Atan2(pos.y, pos.x);
var angleDegrees = angleRadians * Mathf.Rad2Deg;
return (int)angleDegrees;
}
Vector3 GetDelta_3(PointerEventData data)
{
Vector3 pos = new Vector3(data.position.x, data.position.y, 0);
Vector3 s2vp = Camera.main.ScreenToViewportPoint(pos);
return s2vp;
}
public void OnDrag(PointerEventData data)
{
if (data.dragging)
{
gd1 = GetDelta_3(data);
delta.x = gd1.x;
delta.y = gd1.y;
zAngle = GetAngleFromPosition(delta);
rectTransform.localRotation = Quaternion.Euler(new Vector3(0, 0, zAngle));
}
}
}
It was sort of fixed with a simpler code. Made other tweaks but this was the main change. The essence here is to resolve the delta along y. For this case I just take the y component but ideally we would take a dot product of two values to resolve one along the other.
public void OnBeginDrag(PointerEventData eventData)
{
startV2 = eventData.position;
}
public void OnDrag(PointerEventData data)
{
if (data.dragging)
{
endV2 = data.position;
Vector2 deltaV2 = endV2 - startV2;
deltaV2 = deltaV2.normalized;
rectTransform.Rotate(new Vector3(0, 0, -deltaV2.y), Space.Self);
startV2 = endV2;
}
}
I am trying to get the width difference between the canvas and a image which is scaled by CanvasScaler in order to create translation between the image and his border.
Illustration:
How get the size of the red arrow?
[EDIT 1]
The code snippet bellow give me a possible correct result
var dist = (canvasRectTransform.rect.width - image.sprite.rect.width) / 2;
But It seems to be incorrect:
public class Background : Monobehaviour
{
private float dist;
private float _percentage;
private float _currentLerpTime;`
private readonly Dictionary<LerpDirection, Func<Vector3>> _lerpDirectionActions;
public float lerpTime;
void Awake()
{
var image = GetComponent<Image>();
var canvasRectTransform = GetComponentInParent<RectTransform>();
dist = (canvasRectTransform.rect.width - image.sprite.rect.width) / 2;
_lerpDirectionActions = new Dictionary<LerpDirection, Func<Vector3>>
{
[LerpDirection.Left] = LerpToLeft,
[LerpDirection.Right] = LerpToRight
};
}
private Vector3 Lerp()
{
return Vector3.Lerp(
transform.position,
_lerpDirectionActions[lerpDirection].Invoke(), // will call LerpToRight or LerpToLeft
_percentage
);
}
private float LerpX => Lerp().x;
private Vector3 LerpToRight()
{
return new Vector3(transform.position.x - dist, transform.position.y);
}
private Vector3 LerpToLeft()
{
return new Vector3(transform.position.x + dist , transform.position.y);
}
void Update()
{
_currentLerpTime += Time.deltaTime;
_percentage = _currentLerpTime / lerpTime;
var localPositionX = tranform.position.x;
var mustGoRight = localPositionX <= 0 && lerpDirection == LerpDirection.Right;
var mustGoLeft = localPositionX >= dist && lerpDirection == LerpDirection.Left;
if (mustGoLeft || mustGoRight)
{
direction = direction.Invert(); // invert direction
Reset();
}
tranform.position = new Vector3(LerpX, tranform.position.y)
}
}
The Background script is applied to the Background GameObject.
_lerpDirectionActions[lerpDirection].Invoke()
This code above will invoke the right function for lerping on left or on right.
Illustration:
The translation change his direction but not when the canvas is on the border on the image.
The value you are searching for would be
var difference = (canvas.GetComponent<RectTransform>().rect.width - image.GetComponent<RectTransform>().rect.width) / 2f;
Your script looks quite complicated to be honest.
Why not simply put it all into one single Coroutine using Mathf.PingPong which does exactly what you are currently controlling with your direction flags and actions
PingPongs the value t, so that it is never larger than length and never smaller than 0.
The returned value will move back and forth between 0 and length.
public Canvas canvas;
public Image image;
// Time in seconds to finish the movement from one extreme to the other
public float duration = 1;
private void Start()
{
StartCoroutine (LerpForthAndBack());
}
private IEnumerator LerpForthAndBack()
{
var difference = (canvas.GetComponent<RectTransform>().rect.width - image.GetComponent<RectTransform>().rect.width) / 2f;
var maxPosition = Vector3.right * difference;
var minPosition = Vector3.left * difference;
// Hugh? :O
// -> This is actually totally fine in a Coroutine
// as long as you yield somewhere within it
while(true)
{
image.transform.position = Vector3.Lerp(minPosition, maxPosition, Mathf.PingPong(Time.time / duration, 1));
// "Pause" the routine, render the frame and
// and continue from here in the next frame
yield return null;
}
}
ofcourse you could also do the same still in Update
private Vector3 minPosition;
private Vector3 maxPosition;
private void Start()
{
var difference = (canvas.GetComponent<RectTransform>().rect.width - image.GetComponent<RectTransform>().rect.width) / 2f;
maxPosition = Vector3.right * difference;
minPosition = Vector3.left * difference;
}
private void Update()
{
image.transform.position = Vector3.Lerp(minPosition, maxPosition, Mathf.PingPong(Time.time / duration, 1));
}
This was probably asked a dozen of times, but I can't seem to find the answer anywhere.
I have a dedicated server for a space game written in C# console application. The problems that I'm facing is the synchronisation of GameObject rotations. I'm suspecting the issue being related to gimbal lock, but I am not sure.
Here I have my player movement/rotation controller:
public class PlayerMovement : MonoBehaviour {
[SerializeField] float maxSpeed = 40f;
[SerializeField] float shipRotationSpeed = 60f;
Transform characterTransform;
void Awake()
{
characterTransform = transform;
}
void Update()
{
Turn();
Thrust();
}
float Speed() {
return maxSpeed;
}
void Turn() {
float rotX = shipRotationSpeed * Time.deltaTime * CrossPlatformInputManager.GetAxis("Vertical");
float rotY = shipRotationSpeed * Time.deltaTime * CrossPlatformInputManager.GetAxis("Horizontal");
characterTransform.Rotate(-rotX, rotY, 0);
}
void Thrust() {
if (CrossPlatformInputManager.GetAxis("Move") > 0) {
characterTransform.position += shipTransform.forward * Speed() * Time.deltaTime * CrossPlatformInputManager.GetAxis("Move");
}
}
}
This script is applied to my character object which is a ship. Note that the character object has a child object which is the ship itself and has fixed rotation and position that do not change. When character has moved/rotated I send the following to the server: position(x, y, z) and rotation(x, y, z, w).
Now here is the actual script that receives network packet information and updates the other players in game:
public class CharacterObject : MonoBehaviour {
[SerializeField] GameObject shipModel;
public int guid;
public int characterId;
public string name;
public int shipId;
Vector3 realPosition;
Quaternion realRotation;
public void Awake() {
}
public int Guid { get { return guid; } }
public int CharacterId { get { return characterId; } }
void Start () {
realPosition = transform.position;
realRotation = transform.rotation;
}
void Update () {
// Do nothing
}
internal void LoadCharacter(SNCharacterUpdatePacket cuPacket) {
guid = cuPacket.CharacterGuid;
characterId = cuPacket.CharacterId;
name = cuPacket.CharacterName;
shipId = cuPacket.ShipId;
realPosition = new Vector3(cuPacket.ShipPosX, cuPacket.ShipPosY, cuPacket.ShipPosZ);
realRotation = new Quaternion(cuPacket.ShipRotX, cuPacket.ShipRotY, cuPacket.ShipRotZ, cuPacket.ShipRotW);
UpdateTransform();
Instantiate(Resources.Load("Ships/Ship1/Ship1"), shipModel.transform);
}
internal void UpdateCharacter(SNCharacterUpdatePacket cuPacket) {
realPosition = new Vector3(cuPacket.ShipPosX, cuPacket.ShipPosY, cuPacket.ShipPosZ);
realRotation = new Quaternion(cuPacket.ShipRotX, cuPacket.ShipRotY, cuPacket.ShipRotZ, cuPacket.ShipRotW);
UpdateTransform();
}
void UpdateTransform() {
transform.position = Vector3.Lerp(transform.position, realPosition, 0.1f);
transform.rotation = Quaternion.Lerp(transform.rotation, realRotation, 0.5f);
}
}
Do you see anything wrong with the code ?
My experience with 2 players in game is the following:
When I start the game with two players they spawn at the same location (0,0,0) and same rotation (0,0,0).
Lets say that The other player is rotating continuously around the X-axis only. What I am experiencing is:
First 90 deg. of rotation it renders fine.
Next 180 deg. of rotation the object stays in place
The last 90 deg. of rotation renders fine
In the first version I did not send the 'w' value of Quaternion, but then added it in the second version and it did not help. Note that there is no restrictions in rotation and users can rotate endlessly in all directions.
Btw the positioning works fine.
I might not understand Quaternions fully, but I thought that they had a purpose in avoiding the gimbal lock issue.
Any help would be appreciated.
To answer my own question. I used transform.localEulerAngles instead of transform.rotation, and everything seems to work just fine.
Now the only thing that I am unsure is if gimbal lock could happen with this change.
I am new to unity and don't know a lot of stuff. I've been watching tutorials and I saw one in which the guy created a replica of famous 'Chilly Snow'. The game is complete but the movement of ball isn't like the one in chilly snow. The ball starts orbiting continuously when I press mouse button. I wanted to know how to create that kind of movement, so that the ball turns left and right in a curve but doesn't go in to an orbit. I googled a lot but wasn't able to find my required result. I would really appreciate if anyone could point me in the right direction. Images are attached.Chilly Snow | Movement of my ball
public class movement : MonoBehaviour {
private float points;
public float playerSpeed;
private float rotationSpeed;
public Text score;
private bool isMovingLeft;
public GameObject player;
public bool isDead;
void Start () {
Time.timeScale = 0;
isDead = false;
isMovingLeft = true;
points = 0;
}
void Update ()
{
if (isDead == false)
{
points += Time.deltaTime;
}
transform.Translate (Vector3.down * playerSpeed * Time.deltaTime);
if (Input.GetMouseButtonDown (0))
{
Time.timeScale = 1;
isMovingLeft = !isMovingLeft;
rotationSpeed += 0.5f * Time.deltaTime;
}
if (Input.GetMouseButton (0))
{
rotationSpeed = 1f;
}
if (isMovingLeft) {
rotationSpeed += 1.5f * Time.deltaTime;
transform.Rotate(0,0,rotationSpeed);
} else
transform.Rotate(0,0, - rotationSpeed);
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Obstacle") {
Die ();
}
}
public void Die()
{
playerSpeed = 0f;
isDead = true;
Invoke ("Restart", 2f);
}
void Restart(){
SceneManager.LoadScene ("Ski_scene_1");
}
void FixedUpdate()
{
score.GetComponent<Text>().text = points.ToString("0");
}
}
Here is how I would approach it without doing a rotation... using your code.
public class movement : MonoBehaviour {
private float points;
public Text score;
public GameObject player;
public bool isDead;
private float currentXSpeed;
private float targetSpeed;
public float maxXSpeed;
public float speedChange;
void Start () {
Time.timeScale = 0;
isDead = false;
isMovingLeft = true;
points = 0;
targetSpeed = maxXSpeed;
}
void Update ()
{
if (isDead == false)
{
points += Time.deltaTime;
}
if(Input.GetMouseButtonDown(0))
{
Time.timeScale = 1;
targetSpeed = -targetSpeed;
}
currentSpeed = mathf.MoveTowards(currentSpeed, targetSpeed, speedChange * Time.deltaTime);
Vector3 movementDirection = new Vector3(currentSpeed, Vector3.down.y * playerSpeed, 0.0f);
transform.Translate (movementDirection * Time.deltaTime);
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Obstacle") {
Die ();
}
}
public void Die()
{
playerSpeed = 0f;
isDead = true;
Invoke ("Restart", 2f);
}
void Restart(){
SceneManager.LoadScene ("Ski_scene_1");
}
void FixedUpdate()
{
score.GetComponent<Text>().text = points.ToString("0");
}
}
You need something like sinusoidal movement or any other graph you fancy.
An example would be for this is like;
gameObject.transform.Translate(Vector3.right * Time.deltaTime*cubeSpeed);
gameObject.transform.position += transform.up * Mathf.Sin (Time.fixedTime * 3.0f ) * 0.1f;
Above pseudo is for 2D graph simulation, can be adapted to your situation.
The object is always moving to right and going up and down while making a sinusoidal movement. Because the up and down speed is not fixed hence you get the sinusoidal or like sinusoidal movement.
In your case, while the object is always going down it will make the sinusoidal movement to left and right.
Your movement is based on the rotation so, if you give this sinusoidal speed as your rotation speed, you can achieve this.
Another aproach can be lerp or slerp
Lerp allows you to make kinda smooth transactions between 2 vectors.
Like moving from pointA to pointB in X seconds.
For rotation you will need Quaternion.Lerp There is a great answer on Unity Answers you can check that if you haven't before.
Hope this helps! Cheers!