I have the model representing the player's ship gradually leaning when the player strafes. For instance, here's the code that leans the ship right:
In Update() of the Game class:
if (ship.rightTurnProgress < 1 && (currentKeyState.IsKeyDown(Keys.D)))
{
ship.rightTurnProgress += (float)gameTime.ElapsedGameTime.TotalSeconds * 30;
}
In Update() of the Ship class:
if (currentKeyState.IsKeyDown(Keys.D))
{
Velocity += Vector3.Right * VelocityScale * 10.0f;
RotationMatrix = Matrix.CreateRotationX(MathHelper.PiOver2) *
Matrix.CreateRotationY(0.4f * rightTurnProgress);
}
This is what I'm attempting to do to make it ease back out of the lean when it stops strafing:
In Update() of the Game class:
if (ship.rightTurnProgress > 0 && currentKeyState.IsKeyUp(Keys.D))
{
ship.rightTurnProgress -= (float)gameTime.ElapsedGameTime.TotalSeconds * 30;
}
In Update() of the Ship class:
if (currentKeyState.IsKeyUp(Keys.D) && rightTurnProgress > 0)
{
RotationMatrix = Matrix.CreateRotationX(MathHelper.PiOver2) *
Matrix.CreateRotationY(-0.4f * rightTurnProgress);
}
Since easing into the lean works no problem, I thought easing out of the lean would be a simple matter of reversing the process. However, it tends to not go all the way back to the default position after a long strafe. If you tap the key, it snaps all the way back to the full lean of the -opposite- direction. This isn't what I expected at all. What am I missing here?
I suggest you represent the rotation of you ship as a quaternion. That way you can use an interpolation function such as slerp. Simply have a second quaternion that represents you targeted lean angle and the ship will smoothly rotate until it achieves the targeted angle.
Here's a good tutorial on quaternions. If you want to avoid quaternions use MathHelper.Lerp to smoothly transition from the current value to the target.
if (currentKeyState.IsKeyDown(Keys.D))
{
ship.TurnProgress = MathHelper.Lerp(ship.TurnProgress, 1, somefloat * timeDelta);
}
else if (currentKeyState.IsKeyDown(Keys.a))
{
ship.TurnProgress = MathHelper.Lerp(ship.TurnProgress, -1, somefloat * timeDelta);
}
else (currentKeyState.IsKeyDown(Keys.D))
{
ship.TurnProgress = MathHelper.Lerp(ship.TurnProgress, 0, somefloat * timeDelta);
}
Edit: Also there is a GameDev stack overflow so check it out if you have more questions.
Unless you know how long the turn is or you have some kind of acceleration vector you will have to wait until the turn is stopped before returning the sprite angle to neutral, then what happens when the player turns left before the sprite has reached its neutral position? I assume that when you turn right using RightTurnProgress you also have a LeftTurnProgress I suggest you combine them into one variable to keep it smooth and avoid the snapping effect you are getting.
You are creating an 'absolute' rotation matrix so you don't need to flip the sign to -0.4f. Why not just have a variable called ship.lean and calculate the rotation matrix every update. Then you just need logic to ease ship.lean between -1 (left lean) and 1 (right lean) or 0 for no lean.
Related
I've been told that Rigidbody.MoveRotation is the best way in Unity 3D to rotate the player between fixed positions while still detecting hits. However, while I can move smoothly from fixed position to position with:
if (Vector3.Distance(player.position, targetPos) > 0.0455f) //FIXES JITTER
{
var direction = targetPos - rb.transform.position;
rb.MovePosition(transform.position + direction.normalized * playerSpeed * Time.fixedDeltaTime);
}
I can't find out how to rotate smoothly between fixed positions. I can rotate to the angle I want instantly using Rigidbody.MoveRotation(Vector3 target);, but I can't seem to find a way to do the above as a rotation.
Note: Vector3.Distance is the only thing stopping jitter. Has anyone got any ideas?
First of all MoveRotation doesn't take a Vector3 but rather a Quaternion.
Then in general your jitter might come from overshooting - you might be moving further than the distance between your player and target actually is.
You can avoid that bit by using Vector3.MoveTowards which prevents any overshooting of the target position like e.g.
Rigidbody rb;
float playerSpeed;
Vector3 targetPos;
// in general ONLY g through the Rigidbody as soon as dealing wit Physics
// do NOT go through transform at all
var currentPosition = rb.position;
// This moves with linear speed towards the target WITHOUT overshooting
// Note: It is recommended to always use "Time.deltaTime". It is correct also during "FixedUpdate"
var newPosition = Vector3.MoveTowards(currentPosition, targetPos, playerSpeed * Time.deltaTime);
rb.MovePosition(newPosition);
// [optionally]
// Note: Vector3 == Vector3 uses approximation with a precision of 1e-5
if(rb.position == targetPos)
{
Debug.Log("Arrived at target!");
}
Then you can simply apply this same concept also to rotation by going through the equivalent Quaternion.RotateTowards basically just the same approach
Rigidbody rb;
float anglePerSecond;
Quaternion targetRotation;
var currentRotation = rb.rotation;
var newRotation = Quaternion.RotateTowards(currentRotation, targetRotation, anglePerSecond * Time.deltaTime);
rb.MoveRotation(newRotation);
// [optionally]
// tests whether dot product is close to 1
if(rb.rotation == targetRotation)
{
Debug.Log("Arrived at rotation!");
}
You can go one step further and use a tweeting library to tween between rotations.
DOTween
With that you can call it like this:
rigidbody.DoRotate(target, 1f) to rotate to target in 1 second.
Or even add callbacks.
rigidbody.DoRotate(target, 1f).OnComplete(//any method or lambda you want)
If at some point you want to cancel the tween yuou can save it on a variable and then call tween.Kill();
So, you want to animate the rotation value over time until it reaches a certain value.
Inside the Update method, you can use the Lerp method to keep rotating the object to a point, but you will never really reach this point if you use Lerp. It will keep rotating forever (always closer to the point).
You can use the following:
private bool rotating = true;
public void Update()
{
if (rotating)
{
Vector3 to = new Vector3(20, 20, 20);
if (Vector3.Distance(transform.eulerAngles, to) > 0.01f)
{
transform.eulerAngles = Vector3.Lerp(transform.rotation.eulerAngles, to, Time.deltaTime);
}
else
{
transform.eulerAngles = to;
rotating = false;
}
}
}
So, if the distance between the current object angle and the desired angle is greater than 0.01f, it jumps right to the desired position and stop executing the Lerp method.
I have the following code that rotates an object towards a target point at a smooth rate.
public bool RotatedTowards(Vector3 lookAtPoint, float deltaTime)
{
flatDirPath = Vector3.Scale((lookAtPoint - transform.position), new Vector3(1, 0, 1));
targetRotation = Quaternion.LookRotation(flatDirPath, Vector3.up);
transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, deltaTime * basicProperties.turnRate);
if (Vector3.Dot(flatDirPath.normalized, transform.forward) >= 0.9f)
{
return true;
}
else
{
return false;
}
}
It works well enough, the problem is if I right click around a point to move to really fast (telling this object to rotate toward a very similar but different lookAtPoint at a very high frequency), the object shakes very slightly as its constantly making small rotational changes (I believe that's whats happening).
I believe the best solution may be to only do the transform.rotation if the target point is over a threshold from the front of the object, say at least 1 degree to the left or the right of the direction the object is already facing. How could I test this? If it's under 1 degree, no point rotating toward it, and that should remove the shaking from clicking around the same point really fast. I hope.
Any help is appreciated. Thanks
you can use this: https://docs.unity3d.com/ScriptReference/Quaternion.Angle.html
in your code:
public bool RotatedTowards(Vector3 lookAtPoint, float deltaTime)
{
flatDirPath = Vector3.Scale((lookAtPoint - transform.position), new Vector3(1, 0, 1));
targetRotation = Quaternion.LookRotation(flatDirPath, Vector3.up);
float angle = Quaternion.Angle(transform.rotation, targetRotation);
float threshold = 1;
if(Mathf.Abs(angle) > threshold)
transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, deltaTime * basicProperties.turnRate);
return (Vector3.Dot(flatDirPath.normalized, transform.forward) >= 0.9f);
}
if you don't like how this threshold behaves, you can try another approach:
use Queue<Vector3> to store the last few flatDirPath vectors (last 10, for example), then add a new vector each time and remove the oldest vector each time - then compute the average vector from all vectors in the Queue and use that in Quaternion.LookRotation - you should get a more smooth behavior. the rotation will lag behind a bit with more vectors you store in the Queue, so use 4-6 vectors for a faster response or 10-20 vectors for a more smooth response.
This question already has answers here:
Rotate GameObject over time
(2 answers)
Closed 5 years ago.
So I am trying to rotate an object on key press (if I press "a" rotate left by 90 degree, if I press "d" rotate right by 90 degree).
I can't make it to work, I have tried a few things but I ended up not solving the problem. As you will see in the next example I only figure out how to rotate TO 90 degree, not more or less, I actually want to rotate the object BY 90 degrees, from whichever degree was currently on. For example:
from 90 degree to 180 degree
from 23 degree to 113 degree and so on...
here is my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ModuleBehaviour : MonoBehaviour {
public bool rotating = false;
private void Update()
{
if (rotating)
{
RotatePlatform();
}
}
private void RotatePlatform()
{
Vector3 target = new Vector3(0,0,90);
if (transform.eulerAngles.z > 90)
{
transform.rotation = Quaternion.Euler(target);
}
transform.Rotate(target * Time.deltaTime);
}
}
(Don't bother about the rotating variable, I am changing it in another script, it get's true whenever I press the "d" key. I only need to know how to rotate in the right direction, after that I will figure it out how to apply for left rotation)
Thank you;
This will do what you want:
private IEnumerator RotatePlatform(float dir) {
Vector3 target = new Vector3(0, 0, (transform.eulerAngles.z + dir + 360) % 360);
while(Mathf.Abs(transform.eulerAngles.z - target.z) >= Mathf.Abs(dir * Time.deltaTime * 2)) {
transform.Rotate(new Vector3(0,0,dir) * Time.deltaTime);
yield return null;
}
transform.eulerAngles = target;
rotating = false;
}
The key things to take away from this is that I encapsulated the "make it animate" code into a coroutine so that the object rotates to the desired direction from any starting rotation to any rotation in either direction. The reason this works is that the coroutine's local variables hold their state between iterations (the Unity scheduled task system will halt executing the code on a yield instruction and resume at a later time: in this case, null means it will resume on the next frame). This way we can store the starting rotation and target rotation without having to do anything crazy.
It does this by getting the current rotation, adding the rotation amount, then making that value lie within 0-360: transform.eulerAngles.z + dir + 360) % 360. transform.eulerAngles.z will already be [0,360], subtracting 90 from that would leave [-90,270], then by adding 360 we insure that the value will always be greater than 0 (without affecting the resulting angle), then % 360 effectively subtracts off any excess quantities of 360.
The Mathf.Abs(transform.eulerAngles.z - target.z) >= Mathf.Abs(dir * Time.deltaTime * 2) statement checks to see if the "distance we have yet to rotate" is "greater than the amount we're going to rotate by" with some buffer: we don't want to overshoot and have our "how far do we need to go" be 358 degrees!
The other important change was that we want to use new Vector3(0,0,dir) rather than target as our value to rotate by so that the speed remains constant.
The last 2 instructions make sure the rotation actually achieves the desired value and, of course, toggle back the bool tracking whether or not pressing keys does anything (we only want 1 instance of our coroutine).
Then here's the Update() method I used to control it:
private void Update() {
if(!rotating) {
if(Input.GetKeyDown(KeyCode.D)) {
rotating = true;
StartCoroutine(RotatePlatform(90));
}
if(Input.GetKeyDown(KeyCode.A)) {
rotating = true;
StartCoroutine(RotatePlatform(-90));
}
}
}
To rotate in the other direction, subtract 90.
void Update()
{
if (Input.GetKeyDown(KeyCode.D))
transform.Rotate(0, 0, transform.rotation.z + 90);
}
I have a simple waypoint system. It uses a transform of arrays that act as the bucket what holds the waypoint values.
I use this waypoint system to move a camera throughout a scene by moving towards one point to another. The scene is not big - so everything is close to each other.
The camera moves from one position to another by button click/press. This works fine.
void Start()
{
//Sets the Camera to the first point
Camera = GameObject.Find("Main Camera");
Camera.transform.position = patrolPoints[0].position
currentPoint = 0;
}
//Fixed Update seems to work better for LookAt
void FixedUpdate()
{
//Looks at initial Target
Camera.transform.LookAt(TargetPoints[0]);
if (click == true)
{
Camera.transform.position = Vector3.MoveTowards(Camera.transform.position, patrolPoints[currentPoint].position, moveSpeed * Time.deltaTime);
//Camera.transform.rotation = Quaternion.Slerp(Camera.transform.rotation, patrolPoints[currentPoint].transform.rotation, Time.deltaTime);
Camera.transform.LookAt(TargetPoints[currentPoint]);
}
}
public void onNextClick()
{
if (currentPoint >= patrolPoints.Length)
{
currentPoint = 0;
}
if (Camera.transform.position == patrolPoints[currentPoint].position)
{
currentPoint++;
click = true;
}
}
I am having difficulty with one aspect of the transform that I haven't talked about yet. That is the rotation.
I have used lookAt for setting up the target of the first look at point and that works. However when it runs to the next target in the look at array - the change is sudden.
I have tried an Slerp in the shot as well - and this works when the waypoint has been placed in the appropriate rotation value - and the value Slerps from one position to the next. However, the timing isn't quite aligning up yet. Some position transitions get there quicker - meaning the rotation is trying to get caught up / while others are lagging behind.
I have tried getting the distance between the current waypoint and the next waypoint and treating that as an overall percentage in the update relative to the current position of the camera - I believe this could help work out how far the rotation should be orientated given the position update.
However, I am somewhat lost on it. Many examples suggest looking at iTween - and I wouldn't imagine this would work - however, I want to get into the math a bit.
Can anyone put me in the right direction?
Looks like the Lerp for Position and a Slerp for Rotation done the trick.
MoveTowards wasn't playing ball with a Slerp on rotation - so I believe the timings weren't aligning.
if (click == true)
{
Camera.transform.position = Vector3.MoveTowards(Camera.transform.position, patrolPoints[currentPoint].position, moveSpeed * Time.deltaTime);
Camera.transform.rotation = Quaternion.Lerp(Camera.transform.rotation, patrolPoints[currentPoint].rotation, moveSpeed * Time.deltaTime);
I've been led to believe the lerp values work like a percentage of such - so the same input value works for it.
Finally I used a range on the distance between current position and update on the click - this helped speed up the button click.
if (Vector3.Distance(Camera.transform.position, patrolPoints[currentPoint].position) < PositionThreshold)
{
currentPoint++;
click = true;
}
Thank you for your time.
I have been searching and racking my brains for a while now to try and get this code working, to no avail. Hopefully you guys can help.
I have a simple set up with a cube that moves position every frame. I need the cube to go to an x position when it reaches a different location.
Example: Cube starts at position 0, moves forward in the x axis until it gets to position 15, then reverts back to 0 and stops.
Vector3 startingPosition;
void Start ()
{
startingPosition = gameObject.transform.position;
}
void Update ()
{
if (gameObject.transform.position.x == 15) {
gameObject.transform.position = startingPosition;
} else {
float translation = Time.deltaTime * 2;
transform.Translate (0, 0, translation);
transform.Translate (Vector3.forward * translation);
}
}
}
Currently the cube continuously moves (no stopping point), it's x position having no affect on the positioning.
Change your == to >= and see if that makes a difference. My guess is that position.x is never exactly equal to 15, either due to floating-point precision errors or due to your translation logic skipping over 15 from one frame to the next.
I have had an issue with a animation not working correctly and feel like I have scoured the internet for days trying to get it to work, so this was very helpful. In my situation, the animations' GameObject was at a position of y=1 and should have moved to y=9, but would not update unless I clicked the transition in the animator. I changed the code from x == 15 to y <= 9 and Vector3.forward to Vector3.up and it works perfectly now. Hope this might help someone else with the same issue. using Unity v 2017.1.2