Accelerate rotation from 0 to x angle in x seconds in Unity - c#

This is more of a math question than a coding question. I would like to reach for example an rotation angle of 90 in 1 second while speed is accelerating at constant value. My current version takes 1.4 seconds to reach the desired rotation angle, and it should reach it in 1 second. I believe that the reason for that is that it currently accelerates to speed of 90 in 1 second and not to rotation angle of 90. Since I am not that good in math, I have no idea how I need to adjust the acceleration calculation. I am unable to find any solution to this.
NOTE: I need to adjust the rotation angles manually, I am not able to use any existing functions, like for example transform.Rotate(), since in my complete version the rotation direction can change at any time and the rotation also has deceleration value.
This is a very simplified version of what I have (it only rotates the z axis to one direction and runs once on start):
private float accelerationInSeconds = 1;
private float targetAngle = 90f;
private float speed = 0;
private float axis = 1;
private bool rotate = true;
private float acceleration;
void Start() {
// Calculate acceleration (this calculation should be changed)
acceleration = targetAngle / accelerationInSeconds;
}
void Update() {
if (rotate) {
// Accelerate
speed += axis * (acceleration * Time.deltaTime);
// Calculate next rotation position
Vector3 rotationVector = transform.rotation.eulerAngles;
rotationVector.z += speed * Time.deltaTime;
// Rotate object
transform.rotation = Quaternion.Euler(rotationVector);
// Check if rotation has gone over the target angle
if (rotationVector.z >= targetAngle) {
rotationVector.z = targetAngle;
speed = 0;
rotate = false;
}
}
}
Thanks in advance for anyone who can help!
EDIT: Modified code to be more efficient. I can't use RotateTowards() since in my complete code I need to clamp the rotation between targetAngle and negative targetAngle. Hopefully this code is more efficient and performance friendly. But I still have not found a solution for my original math related question, which was the whole point of this question.
private float accelerationInSeconds = 1;
private float targetAngle = 90f;
private float speed = 0;
private float angle = 0;
private float axis = 1;
private bool rotate = true;
private float acceleration;
void Start() {
// Calculate acceleration (this calculation should be changed)
acceleration = targetAngle / accelerationInSeconds;
}
void Update() {
if (rotate) {
// Accelerate
speed += axis * (acceleration * Time.deltaTime);
// Calculate next rotation position
angle += speed * Time.deltaTime;
// Check if rotation has gone over the target angle
if (angle >= targetAngle) {
angle = targetAngle;
speed = 0;
rotate = false;
}
// Rotate object
transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
}
}

I finally figured it out, thanks to Math section in StackExchange.
So the simple answer is this:
acceleration = 2 * targetAngle / Mathf.Pow(accelerationInSeconds, 2);

As was suggested before I would use a Coroutine. Coroutines are like temporary Update methods and often easier to control and maintain than doing stuff directly in Update.
// Flag to avoid concurrent routines
private bool isRotating;
public void Rotate(float targetAngle, float duration)
{
if(! isRotating) StartCoroutine (RotateRoutine(targetAngle, duration));
}
private IEnumerator RotateRoutine (float targetAngle, float duration)
{
// Just to be sure
if(isRotating) yield break;
// block concurrent routines
isRotating = true;
// Pre-calculate the start and end rotation
var start = transform.rotation;
var end = Quaternion.Euler(0, 0, targetAngle);
var timePassed = 0f;
while(timePassed < duration)
{
// This value will grow linear from 0 to 1 in exactly "duration" seconds
var x = timePassed / duration;
// TODO!
var y = MAGIC;
// Interpolate between the start and end rotation using given factor "y"
transform.rotation = Quaternion.Lerp(start, end, y);
// "pause" the routine here, render this frame
// and continue from here in the next frame
yield return null;
// Increase by the time passed since last frame
timePassed += Time.deltaTime;
}
// To be sure to end with clean values
transform.rotation = end;
// Allow next routine
isRotating = false;
}
So what do we have to fill in for MAGIC?
Basically it can be any mathematical function that maps given input 0 to 1 to 0 to 1.
There are multiple possibilities.
What you currently ask for is a linear growing speed. That means the resulting movement shall be quadratic! So we already know the Formular
var y = a * x * x + b;
We further know from your code that speed always starts from 0 -> b = 0. And the last step is pretty straight forward:
What value do we have to fill in so y goes from 0 to 1 at the same time that x goes from 0 to 1?
1 = a * 1 * 1 + 0;
=> a = 1!
So in your case it is simply
var y = x * x;
If you also want ease-out you could also simply use Mathf.Smoothstep which automatically adds ease-in and ease-out
var y = Mathf.SmoothStep(0, 1, x);
To make it even easier to control you could use an AnimationCurve and adjust the movement curve exactly to your needs in the Inspector
[SerializeField] private AnimationCurve curve;
The curve editor already comes with some preset curves like e.g. linear, logarithmic, exponential and eased-in/-out grow from 0 to 1!
And then use AnimationCurve.Evaluate to get the value (y) in the routine for a given input time (x).
var y = curve.Evaluate(x);

Related

Rotate object in Unity 2D

Help to understand the management of objects. At the moment, there is a rotation of the object. I want the arrow to rotate, and the angle of rotation depends on the current X and Y coordinates. Unity2D.
Now it is left (does not rotate), but it needs to be right (it always rotates and looks in one direction), but I don’t know how to calculate the degrees for rotation.
`
public float angle = 0; // угол
public float radius = 0.5f; // радиус
public bool isCircle = false; // условие движения по кругу
public float speed = 5f;
// Update is called once per frame
void Update()
{
angle += Time.deltaTime; // меняется значение угла
var x = Mathf.Cos(angle * speed) * radius + parent.position.x;
var y = Mathf.Sin(angle * speed) * radius + parent.position.y;
transform.position = new Vector3(x, y,0);
//transform.Rotate(0, 0, a);
}
`
Help me, how to calculate angle?
You need Mathf.Atan2, it will return a radian, then you need to multiply a Mathf.Rad2Deg to get the Euler angle.

Movement Code Affected by Fps in Unity 2D

My current player movement code provides 0.32 translation on each key click.
If it is clicked 2 times very quickly, it should be 0.64.
I provide this with coroutine, but it is affected by fps.
How do I ensure that it is not affected by fps?
I know Time.deltaTime, but how do I get exactly 0.32 if I multiply by Time.deltaTime?
My current move code:
public float move = 0.32f; //movement limitation
public float repeat = 4; // I perform 0.32 in 4 parts to be more smooth.
void Update()
{
if (Input.GetKeyDown(KeyCode.LeftArrow) //Exapmle key. I have 4 ways to move
{
StartCoroutine("Left");
}
}
IEnumerator Left()
{
for (int i = 1; i <= repeat; i++)// as I said, I perform 0.32 in 4 parts to be more smooth.
{
transform.position = transform.position + new Vector3(-move / repeat, 0, 0);
yield return null;
}
}
If you wanted to be smooth rather use a fixed speed and do e.g.
// distance to move
public float move = 0.32f;
// speed in Unity units per second
public float speed = 1f;
void Update()
{
if (Input.GetKeyDown(KeyCode.LeftArrow)
{
StartCoroutine(Left);
}
}
IEnumerator Left()
{
// Pre-calculate the target position
// This is the huge advantage of IEnumerators -> you can store local variables over multiple frames
var targetPosition = transform.position + Vector3.left * move;
// track how far you have already moved
var moved = 0f;
// Loop until you moved enough
while(moved < move)
{
// the step is now frame-rate independent and moves the object with a fixed value per second
var step = speed * Time.deltaTime;
// every frame move one such step towards the target without overshooting
transform.position = Vector3.MoveTowards(transform.position, targetPosition, step);
// keep track of the movement
moved += step;
yield return null;
}
// and to end up with a clean value in the end
transform.position = targetPosition;
}
However
If it is clicked 2 times very quickly, it should be 0.64
this wouldn't be covered. In order to do this I would actually use a completely different approach without Coroutines at all and rather simply use e.g.
// distance to move
public float move = 0.32f;
// speed in Unity units per second
public float speed = 1f;
Vector3 targetPosition;
void Start()
{
targetPosition = transform.position;
}
void Update()
{
if (Input.GetKeyDown(KeyCode.LeftArrow)
{
targetPosition += Vector3.left;
}
...
transform.position = Vector3.MoveTowards(transform.position, targetPosition, speed * Time.deltaTime);
}
so now you can smash your move keys in any direction as much as you like and it will simply sum them all up into the one targetPosition and always keep moving towards it smoothly but with a fixed velocity.
or another alternative would be to interpolate and rather make the object move faster if it is further away from the target but slower the closer it comes
// 5 here is an arbitrary number based on experience -> tweak it according to your needs
public float interpolation = 5f;
...
transform.position = Vector3.Lerp(transform.position, targetPosition, interpolation * Time.deltaTime);

Ease-in and ease-out with Vector2.MoveTowards

I've seen a lot of examples of easing with Vector2.Lerp.
But I want to use Vector2.MoveTowards because I am randomizing the distance my NPC is traveling, and I want the speed of the NPC to always be constant regardless of the distance traveled. Ideally, would like to control the easing granularly with an AnimationCurve if that's possible, but a smooth step function would be ok too. Here is the simplified code that I'm using now (using Behavior Designer so the methods are a bit different):
private readonly float Speed = 1.5f;
public SharedVector2 BugDirection;
public override void OnStart()
{
BugDirection.Value = new Vector2(Random.Range(0.1f, 0.5f), transform.position.y);
}
public override TaskStatus OnUpdate()
{
if (Vector2.Distance(transform.position, BugDirection.Value) == 0)
{
return TaskStatus.Success;
}
transform.position =
Vector2.MoveTowards(transform.position, BugDirection.Value, Speed * Time.deltaTime);
return TaskStatus.Running;
}
Okey so there is one first issue: Never use == for comparing two float values!
Even a situation like 5f * 0.2f / 10f == 1f might fail because due to floating point precision it might actually be 0.9999999 or 1.0000001.
Instead you usually rather check against a certain range like e.g.
if(Mathf.Abs(a - b) <= someThreshold)
Unity for this also offers Mathf.Approximately and using
if(Mathf.Approximately(a, b))
basically equals using
if(Mathf.Abs(a - b) <= Mathf.Epsilon)
where Epsilon is
The smallest value that a float can have different from zero.
So much for your current stop condition.
I just assume for now this is happening somewhere outside of a MonoBehaviour so a Coroutine would not be an option.
However, what you basically want to achieve is having a value that continously grows from 0 to 1 linear (the time) mapped onto an eased-in and eased-out movement curve.
So assuming that the target position is not updated between the start and the ending of a movement I would do it like e.g.
private readonly float Speed = 1.5f;
private Vector2 target;
private Vector2 start;
private float timePassed;
private float duration;
public override void OnStart()
{
target = new Vector2(Random.Range(0.1f, 0.5f), transform.position.y);
start = transform.position;
duration = Vector2.Distance(start, target) / speed;
timePassed = 0f;
}
public override TaskStatus OnUpdate()
{
if (timePassed >= duration)
{
transform.position = target;
return TaskStatus.Success;
}
var factor = timePassed / duration;
//MAGIC
transform.position = Vector2.Lerp(start, target, factor);
timePassed += Time.deltaTime;
return TaskStatus.Running;
}
So far this will be a linear movement just as before ;)
So let's fill in the MAGIC.
One very simple option for ease-in and ease-out is using Mathf.SmoothStep like e.g.
factor = Mathf.SmoothStep(0, 1, factor);
or you could give the AnimationCurve another shot now using
factor = yourAnimationCurve.Evaluate(factor);
which gives you complete control over the smoothing of the movement and yet results in the total average velocity of speed as if you would have applied it linear.

2D Projectile Trajectory Prediction(unity 2D)

using (unity 2019.3.7f1) 2d.
I have a player that moves around using a pullback mechanic and has a max power(like in Angry Birds).
I'm trying to draw a line(using a line renderer) that shows the exact path the player will go. I'm trying to make the line curve just like the player's path will. so far I've only managed to make a straight line in a pretty scuffed way.
The known variables are the Jump Power and the player's position, there is no friction. and I believe gravity is a constant(-9.81). Also, I would like to have a variable that allows me to control the line's length. And, if possible, the line will not go through objects and would act as if it has a collider.
// Edit
This is my current code. I changed The function so it would return the list's points because I wanted to be able to access it in Update() so it would only draw while I hold my mouse button.
My problem is that the trajectory line doesn't seem to curve, it goes in the right angle but it's straight. the line draws in the right direction and angle, but my initial issue of the line not curving remains unchanged. If you could please come back to me with an answer I would appreciate it.
enter code here
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TrajectoryShower : MonoBehaviour
{
LineRenderer lr;
public int Points;
public GameObject Player;
private float collisionCheckRadius = 0.1f;
public float TimeOfSimulation;
private void Awake()
{
lr = GetComponent<LineRenderer>();
lr.startColor = Color.white;
}
// Update is called once per frame
void Update()
{
if (Input.GetButton("Fire1"))
{
lr.positionCount = SimulateArc().Count;
for (int a = 0; a < lr.positionCount;a++)
{
lr.SetPosition(a, SimulateArc()[a]);
}
}
if (Input.GetButtonUp("Fire1"))
{
lr.positionCount = 0;
}
}
private List<Vector2> SimulateArc()
{
float simulateForDuration = TimeOfSimulation;
float simulationStep = 0.1f;//Will add a point every 0.1 secs.
int steps = (int)(simulateForDuration / simulationStep);
List<Vector2> lineRendererPoints = new List<Vector2>();
Vector2 calculatedPosition;
Vector2 directionVector = Player.GetComponent<DragAndShoot>().Direction;// The direction it should go
Vector2 launchPosition = transform.position;//Position where you launch from
float launchSpeed = 5f;//The initial power applied on the player
for (int i = 0; i < steps; ++i)
{
calculatedPosition = launchPosition + (directionVector * ( launchSpeed * i * simulationStep));
//Calculate gravity
calculatedPosition.y += Physics2D.gravity.y * (i * simulationStep);
lineRendererPoints.Add(calculatedPosition);
if (CheckForCollision(calculatedPosition))//if you hit something
{
break;//stop adding positions
}
}
return lineRendererPoints;
}
private bool CheckForCollision(Vector2 position)
{
Collider2D[] hits = Physics2D.OverlapCircleAll(position, collisionCheckRadius);
if (hits.Length > 0)
{
for (int x = 0;x < hits.Length;x++)
{
if (hits[x].tag != "Player" && hits[x].tag != "Floor")
{
return true;
}
}
}
return false;
}
}
Here's a simple way to visualize this.
To create your line you want a bunch of points.
The points represents the player's positions after being fired after X amount of time.
The position of each point is going to be : DirectionVector * (launch speed * time elapse) + (GravityDirection * time elapse^2)
You can decide in advance how far you pre calculate the points by simulating X duration and choosing the simulation step(calculate a point every X amount of time)
To detect collision each time you calculate a point you can do a small circle cast at that location. If it hits something you can stop add new points.
private float collisionCheckRadius = 0.1f;
private void SimulateArc()
{
float simulateForDuration = 5f;//simulate for 5 secs in the furture
float simulationStep = 0.1f;//Will add a point every 0.1 secs.
int steps = (int)(simulateForDuration/simulationStep);//50 in this example
List<Vector2> lineRendererPoints = new List<Vector2>();
Vector2 calculatedPosition;
Vector2 directionVector = new Vector2(0.5f,0.5f);//You plug you own direction here this is just an example
Vector2 launchPosition = Vector2.zero;//Position where you launch from
float launchSpeed = 10f;//Example speed per secs.
for(int i = 0; i < steps; ++i)
{
calculatedPosition = launchPosition + ( directionVector * (launchSpeed * i * simulationStep));
//Calculate gravity
calculatedPosition.y += Physics2D.gravity.y * ( i * simulationStep) * ( i * simulationStep);
lineRendererPoints.Add(calculatedPosition);
if(CheckForCollision(calculatedPosition))//if you hit something
{
break;//stop adding positions
}
}
//Assign all the positions to the line renderer.
}
private bool CheckForCollision(Vector2 position)
{
Collider2D[] hits = Physics2D.OverlapCircleAll(position, collisionCheckRadius);
if(hits.Length > 0)
{
//We hit something
//check if its a wall or seomthing
//if its a valid hit then return true
return true;
}
return false;
}
This is basically a sum of 2 vectors along the time.
You have your initial position (x0, y0), initial speed vector (x, y) and gravity vector (0, -9.81) being added along the time. You can build a function that gives you the position over time:
f(t) = (x0 + x*t, y0 + y*t - 9.81t²/2)
translating to Unity:
Vector2 positionInTime(float time, Vector2 initialPosition, Vector2 initialSpeed){
return initialPosition +
new Vector2(initialSpeed.x * t, initialSpeed.y * time - 4.905 * (time * time);
}
Now, choose a little delta time, say dt = 0.25.
Time | Position
0) 0.00 | f(0.00) = (x0, y0)
1) 0.25 | f(0.25) = (x1, y1)
2) 0.50 | f(0.50) = (x2, y2)
3) 0.75 | f(0.75) = (x3, y3)
4) 1.00 | f(1.00) = (x4, y4)
... | ...
Over time, you have a lot of points where the line will cross. Choose a time interval (say 3 seconds), evaluate all the points between 0 and 3 seconds (using f) and put your line renderer to cover one by one.
The line renderer have properties like width, width over time, color, etc. This is up to you.

How to interpolate a camera that gets new position every update in Unity?

I have a physical rowing device that sends the remaining distance of its rowing race to Unity constantly (i.e. 350m remaining to complete the race). When I try to update the position of my player accordingly (Z-Axis only), the movement is choppy.
How can I interpolate this kind of movement so that it's smoother?
My first try looked like this:
void Update()
{
player.position = new Vector3(player.position.x, player.position.y,
pm5.Pm5_distance);
}
This resulted in choppy movement, probably because the player is simply teleported to the next position.
Next I tried to lerp to next position, but I'm not sure if I did it correctly or if this works at all since my player receives a new position every frame:
private float startTime;
private float journeyLength;
public float speed = 5.0F;
void Start()
{
startTime = Time.time;
}
void Update()
{
if (player.position != new Vector3(player.position.x,
player.position.y, pm5.Pm5_distance))
{
journeyLength = Vector3.Distance(player.position, new
Vector3(player.position.x, player.position.y, pm5.Pm5_distance));
float distCovered = (Time.time - startTime) * speed;
float fracJourney = distCovered / journeyLength;
transform.position = Vector3.Lerp(player.position, new
Vector3(player.position.x, player.position.y, pm5.Pm5_distance),
fracJourney);
}
}
Even though this does not throw errors, it doesn't seem to fix my problem either. Maybe the whole concept is wrong? I'd be glad if somebody could help me out here.
EDIT 25.04.19:
From trying around with my David's code I landed here:
void Update()
{
if (useLerpMovementModel)
{
float newDistance = pm5.Pm5_distance;
if (player.position.z != newDistance)
{
// Distance moved
float distCovered = journeyLength - pm5.Pm5_distance;
// Fraction of journey completed = current distance divided by total distance.
float fracJourney = distCovered / journeyLength;
// Set our position as a fraction of the distance between the markers.
transform.position = Vector3.Lerp(player.position, new Vector3(player.position.x, player.position.y, newDistance), fracJourney);
}
}
else
{
player.position = new Vector3(player.position.x, player.position.y, journeyLength - pm5.Pm5_distance);
}
}
I'm not sure if this does anything at all though in terms of smoothing the movement compared to player.position = new Vector3(player.position.x, player.position.y, journeyLength - pm5.Pm5_distance);
The following line is actually an incorrect calculation:
journeyLength = Vector3.Distance(player.position, new
Vector3(player.position.x, player.position.y, pm5.Pm5_distance));
You'll need to store the position at the start and at the end of the journey so that you can interpolate the player's position between them.
I will try to explain what is necessary by taking your code and adding comments. I have also included the changes that I recommended as well as others:
private float journeyLength;
private Vector3 startPosition;
private Vector3 endPosition;
void Start()
{
// The total length of the journey, let's imagine it's 100
// This assumes that when this Start() method is called that
// pm5.Pm5_distance is equal to the journey length
journeyLength = pm5.Pm5_distance;
// Store the player's starting position
// This will be used to interpolate between start/end
startPosition = player.position;
// Store the position that the player will be at when the journey is complete
// This will be used to interpolate between start/end
endPosition = startPosition + Vector3.forward * journeyLength;
}
void Update()
{
// I am assuming that 'pm5.Pm5_distance' is the remaining distance
if (pm5.Pm5_distance > 0f)
{
// Take the remaining distance from the total journey length to get
// the current distance travelled. Let's imagine the remaining distance is 50:
// This will end up being 100 - 50 = 50 units travelled into the journey
float distCovered = journeyLength - pm5.Pm5_distance;
// We now get the fraction of the journey that that distance equates to,
// which in this case will be 0.5
float fracJourney = distCovered / journeyLength;
// Interpolate the players position from where it originally started
// to the position at the end of the journey (the one we calculated in Start())
transform.position = Vector3.Lerp(startPosition, endPosition, fracJourney);
}
}
As a final note, it is important to understand how the Vector3.Lerp() function works:
It takes two vectors and interpolates between them by a value that is between 0 and 1.
If the value is 0, then the first vector is returned.
If the value is 1, then the second vector is returned.
If the value is 0.5 then a vector that lies between both is returned.
So you can see that in the example above, fracJourney is 0.5, which means that for this frame, the player's position will be exactly half way through the race.
But in the next frame, fracJourney will be recalculated and let's say something like 0.55.
In that frame, the new position is going to be calculated just slightly forward from where it was in the previous frame.
This continues on and on until eventually the distance to the end of the race is 0.

Categories