Camera approaching and distancing loop in Unity3D - c#

i can't understand how can i make it so that my camera will approach an object and after reaching a certain distance from it,it will go back to the initial position, and so on.
Here is my code:
public class Camera : MonoBehaviour
{
public GameObject cameraLook;
float speed = 10f;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
Vector3 distance = (transform.position - cameraLook.transform.position);
Debug.Log(distance);
transform.LookAt(cameraLook.transform.position);
if (distance.x > 30)
{
go();
}
if (distance.x < 0)
{
goBack();
}
}
void go()
{
transform.position += transform.forward * speed * Time.deltaTime;
}
void goBack()
{
transform.position-= transform.forward * speed * Time.deltaTime;
}
}

Update is called once every frame.
What currently happens in your code is
Frame 1
You reach a distance of x > 30
you call go once
Frame 2
Now your distance might be < 30 but still > 0 due to the movement last frame
You do nothing at all anymore
or the same with
Frame 1
You reach a distance of < 0
you call go once
Frame 2
Now your distance might be > 0 but still < 30 due to the movement last frame
You do nothing at all anymore
Then also it is very "dangerous" / unreliable to simply check the global X axis difference between your objects. This assumes that your camera is definitely moving along the X axis.
Instead I would rather use something like e.g.
public class Camera : MonoBehaviour
{
public GameObject cameraLook;
[Toolbox("The minimum distance the camera should stay away from the object when moving closer")]
[Min(0f)] public float minDistance = 0f;
[Toolbox("The maximum distance the camera should move away from the object")]
[Min(0f)] public float maxDistance = 30f;
[Toolbox("How fast shall the camera travel. If your target objects moves have in mind that this needs to be bigger then the movement speed of your target object ;)")]
[Min(0f)] public float moveUnitsPerSecond = 10f;
[Toolbox("If true starts moving towards otherwise starts moving away")]
public bool startMovingTowards = true;
private bool isMovingTowards;
private void Start()
{
isMovingTowards = startMovingTowards;
}
void Update()
{
// first look at the target object
transform.LookAt(cameraLook.transform.position);
// get the desired final distance based on the move direction flag
var targetDistance = isMovingTowards ? minDistance : maxDistance;
// get the desired final position based on the desired distance
// simply moving the distance backwards away from the object
var targetPosition = cameraLook.transform.position - transform.forward * targetDistance;
// Move smoothly with the given moveUnitsPerSecond to the desired final position
transform.position = Vector3.MoveTowards(transform.position, targetPosition, moveUnitsPerSecond * Time.deltaTime);
// is the desired final position reached (within a precision of 0.00001)
if(transform.position == targetPosition)
{
// then simply invert the move direction
isMovingTowards = !isMovingTowards;
}
}
}

Related

Swerve Control Unity C#

Hi I am creating a hyper casual game with unity, but I have encountered a problem with the swerve control (I have also seen many git hubs but even these do not work perfectly)
I've put this in my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
private float lastframeposx;
private float movefactorx;
public float MoveFactorX => movefactorx;
public Camera m_MainCam;
private float speed = 2.0f;
[SerializeField]
GameObject character;
[SerializeField] private float swerveSpeed = 0.5f;
[SerializeField] private float maxSwerveAmount = 1f;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
transform.position += Vector3.forward * speed * Time.deltaTime;
Cammina();
/*Vector3 destra = Camera.main.ScreenToWorldPointt(Input.touches[i].position);
transform.position += Vector3.zero destra;*/
}
void Cammina()
{
if(Input.GetMouseButtonDown(0))
{
lastframeposx = Input.mousePosition.x;
float swerveAmount = Time.deltaTime * swerveSpeed * MoveFactorX;
swerveAmount = Mathf.Clamp(swerveAmount, -maxSwerveAmount, maxSwerveAmount);
transform.Translate(swerveAmount, 0, 0);
}
else if (Input.GetMouseButton(0))
{
movefactorx = Input.mousePosition.x - lastframeposx;
lastframeposx = Input.mousePosition.x;
float swerveAmount = Time.deltaTime * swerveSpeed * MoveFactorX;
swerveAmount = Mathf.Clamp(swerveAmount, -maxSwerveAmount, maxSwerveAmount);
transform.Translate(swerveAmount, 0, 0);
}
else if(Input.GetMouseButtonUp(0))
{
movefactorx = 0f;
float swerveAmount = Time.deltaTime * swerveSpeed * MoveFactorX;
swerveAmount = Mathf.Clamp(swerveAmount, -maxSwerveAmount, maxSwerveAmount);
transform.Translate(swerveAmount, 0, 0);
}
}
/*void vaidoveschiacciato()
{
if (Input.touchCount > 0 && Input.touches[0].phase == TouchPhase.Began)
{
Ray ray = Camera.main.ScreenPointToRay(Input.touches[0].position);
RaycastHit hit;
if(Physics.Raycast(ray, out hit))
{
if(hit.collider != null)
{
transform.position += hit.collider.GetComponent<Transform.position> * speed * Time.deltaTime;
}
}
}
}*/
}
1 Problem: he don't go when the finger is
2 Problem: How do I eliminate the movement from right to left (Without making it go out of the path)
(Langauge: C#)
The problem: When you swerve, it swerves just in the direction, there is no limits on how far it goes.
How I would fix this: I would put the movement to change it through a function. This could clamp it, so the higher the distance to the center of the track, the less it swerves. Or, you can altogether check if the distance is a maximum and then stop swerving.
Note: you can use other functions to do this (they just have to flatten out the larger the input).
Smooth, good looking bell curve way
For example you could use a bell curve. Look one up if you've never seen one before. It is at it's highest possible output of one, at a zero input. When it gets hiher or lower, it outputs lower to zero.
the simplest equation is y = i-(x2). The lower i is (above 1), the wider the curve, or the larger the output is for a larger input. I made a graph here.
x can be the distance to the center of the track, so you should adjust i, so the maximum distance from the track is flat.
This output is what you should change swerveAmount to.
The flatter parts of the graoh is how much you will swerve when you are that distance from the center (x axis)
Alternatively
You could just check the distance, and if it passes a certain distance don't translate it.
Let me know in the omments if there are any problems! :)

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);

How to realistically reflect a 3d sphere in Unity with C#

I've been trying to realistically reflect a 3d sphere on the walls of a box for a while now in Unity. For some reason, the reflection is generally correct, but when the ball hits a wall in certain directions, the reflection is incorrect.
To illustrate what happens to the ball upon hitting a wall: T = top wall, R = right wall, L = left wall, and B = bottom wall. Let r = the ball comes/goes to the right, l = for the left, and s = the ball stops/slows down significantly. The instructions below take this format: Xyz, where X = the wall the ball is about to hit, y = the ball's initial direction, z = the reflection. The game has a top-down perspective, and the instructions are based on the wall's perspective. I'm also new to C#, so the code is potentially eye burning.
Instructions: Tll, Trl; Bll, Brl; Rls or after hitting another wall Rlr, Rrl; Lls or after hitting another wall Llr, Lrl
Generally, when the ball stops, it jumps in the air. I wonder if this is because the angle reflects along the wrong axis, but why would this only sometimes happen? Also, when only one key is held, the ball bounces back and forth until it leaves the arena. I know about discrete and continuous hit detection, and the setting is on discrete, but the walls generally contain the ball well enough, with this case being the exception.
What I tried:
Figuring out how to use Vector3.Reflect. I do not understand what variables this function should contain and how to apply this to my code. I did look at the Unity Documentation page for help, but it did not answer my question.
Changing the negative signs, as the angle has to be a reflection on the y-axis, and this does change how the reflections work, but does not solve the problem. The current way the negatives are ordered are the most ideal I found.
Giving the ball a physics material for bounciness.
Adding a small number to the denominator in the arctan equation to help prevent a division by zero. This did not help at all.
Creating different equations (basically changing the negatives) for varying combinations of positive and negative accelerations. Since there is a certain positive or negative acceleration associated with each button press (see Movement script), and there seems to be an issue with said signs, I wondered if associating each acceleration with its own set of equations could solve the problem. It did not work.
Checked if the walls were at different angles.
Deleting the variables xA and yA, or placing them in different spots.
Experimented with finding the speed of the object, but had no idea how to implement it.
The code for the Movement script for the player named Controller:
public class Movement : MonoBehaviour
{
public static float xAcceleration = 0.0f;
public static float yAcceleration = 0.0f;
// Update is called once per frame
void Update()
{
if (Input.GetKey(KeyCode.W)) //If the key W is pressed:
{
Vector3 position = this.transform.position; //Variable position is set to transform the players placement in the game.
if (yAcceleration >= -5 && yAcceleration <= 5) //If the y vector of the acceleration is >= -5 and <= 5:
{
yAcceleration = yAcceleration + 0.01f; //The y vector of the acceleration increases by 0.01 as long as the key W is pressed.
}
position.z = position.z + (0.1f * yAcceleration); //The position of the object on the z-axis (pretend it is the y-axis in the game world) is transformed by its original position plus its speed times its yAcceleration.
this.transform.position = position; //The gameObject is now transformed to a position equal to the variable position by the z-axis.
}
else //If the key W is let go of:
{
Vector3 position = this.transform.position;
position.z = position.z + (0.1f * yAcceleration);
this.transform.position = position; //The position of the gameObject continues to update, but its acceleration does not change. Basically, it continues to move forward.
}
//The rest of the code is very similar to the above, but I included it just in case there was something wrong.
if (Input.GetKey(KeyCode.S))
{
Vector3 position = this.transform.position;
if (yAcceleration >= -5 && yAcceleration <= 5)
{
yAcceleration = (yAcceleration) - 0.01f;
}
position.z = position.z + (0.1f * yAcceleration);
this.transform.position = position;
}
else
{
Vector3 position = this.transform.position;
position.z = position.z + (0.1f * yAcceleration);
this.transform.position = position;
}
if (Input.GetKey(KeyCode.A))
{
Vector3 position = this.transform.position;
if (xAcceleration >= -5 && xAcceleration <= 5)
{
xAcceleration = (xAcceleration) - 0.01f;
}
position.x = position.x + (0.1f * xAcceleration);
this.transform.position = position;
}
else
{
Vector3 position = this.transform.position;
position.x = position.x + (0.1f * xAcceleration);
this.transform.position = position;
}
if (Input.GetKey(KeyCode.D))
{
Vector3 position = this.transform.position;
if (xAcceleration >= -5 && xAcceleration <= 5)
{
xAcceleration = (xAcceleration) + 0.01f;
}
position.x = position.x + (0.1f * xAcceleration);
this.transform.position = position;
}
else
{
Vector3 position = this.transform.position;
position.x = position.x + (0.1f * xAcceleration);
this.transform.position = position;
}
}
}
This is the code for the collider and reflection:
public class Collider : MonoBehaviour
{
public float xA;
public float yA;
void OnCollisionEnter(Collision collision) //If a gameObject enters the collision of another object, this immediately happens once.
{
if (gameObject.tag == "Boundary") //If the gameObject has a tag named Boundary:
{
yA = -Movement.yAcceleration; //yA stores the value of yAcceleration after being called from script Movement as a negative. Its a reflection.
Movement.xAcceleration = (Movement.xAcceleration * -Mathf.Atan(yA / Movement.xAcceleration)); //xAcceleration is changed based on this equation: A * artan(A_y / A_x). The 0.000001 was here, adding to A_x to help prevent a 0 as the denominator.
xA = Movement.xAcceleration; //This is declared now...
Movement.yAcceleration = (Movement.yAcceleration * Mathf.Atan(Movement.yAcceleration / xA)); //This uses xA because Movement.xAcceleration is changed, and the yAcceleration calculation is based on the xAcceleration prior the collision.
}
}
void OnCollisionStay(Collision collision)
{
if (gameObject.tag == "Boundary")
{
yA = Movement.yAcceleration; //The same thing happens as before.
Movement.xAcceleration = (Movement.xAcceleration * -Mathf.Atan(yA / Movement.xAcceleration));
xA = Movement.xAcceleration;
Movement.yAcceleration = (Movement.yAcceleration * Mathf.Atan(Movement.yAcceleration / xA));
Movement.xAcceleration = -Movement.xAcceleration / 2; //On collision, the ball is reflected across the x-axis at half its speed.
Movement.yAcceleration = Movement.yAcceleration / 2; //yAcceleration is half its original value.
}
}
}
The picture below is the game setup. I apologize that it is a link; I do not have enough Reputation to merit a loaded image on this page. Also, if anything is unclear, please message me.
https://i.stack.imgur.com/VREV4.png
I would really appreciate the help. Thanks!
One very important note here: As soon as there is any Rigidbody involved you do not want to set any values through the .transform - This breaks the physics and collision detection!
Your Movement should rather alter the behavior of the Rigidbody e.g. by simply changing its Rigibody.velocity
I would then also place the collision check directly into the balls's component and check whether you hit a wall ("Boundary")
Then another note: Your code is currently frame-rate dependent. It means that if your target device runs with only 30 frames per second you will add 0.3 per second to the acceleration. If you run however on a more powerful device that manages to run with 200 frames per second then you add 2 per second.
You should rather define the de/increase per second and multiply it by Time.deltaTime
All together maybe something like this
public class Movement : MonoBehaviour
{
// Adjust these settings via the Inspector
[SerializeField] private float _maxMoveSpeed = 5f;
[SerializeField] private float _speedIncreasePerSecond = 1f;
// Already reference this via the Inspector
[SerializeField] private Rigidbody _rigidbody;
private void Awake()
{
if(!_rigidbody) _rigidbody = GetComponent<Rigidbody>();
}
// Get User Input in Update
private void Update()
{
var velocity = _rigidbody.velocity;
velocity.y = 0;
if (Input.GetKey(KeyCode.W) && velocity.z < _maxMoveSpeed)
{
velocity.z += _speedIncreasePerSecond * Time.deltaTime;
}
if (Input.GetKey(KeyCode.S) && velocity.z > -_maxMoveSpeed)
{
velocity.z -= _speedIncreasePerSecond * Time.deltaTime;
}
if (Input.GetKey(KeyCode.A) && velocity.x > -_maxMoveSpeed)
{
velocity.x -= _speedIncreasePerSecond * Time.deltaTime;
}
if (Input.GetKey(KeyCode.D) && velocity.x < _maxMoveSpeed)
{
velocity.x += _speedIncreasePerSecond * Time.deltaTime;
}
// clamp to the max speed in case you move diagonal
if(velocity.magnitude > _maxMoveSpeed)
{
velocity = velocity.normalized * _maxMoveSpeed;
}
_rigidbody.velocity = velocity;
}
}
And then finally simply add a PhysicsMaterial with desired settings to the walls and ball.
I used Friction = 0f and Bounciness = 0.7f for ball and walls. For slow movements you also might want/have to adjust the Bounce Threshold in the Project's Physics Settings otherwise there will be no bouncing if the velocity is smaller then 2 by default.
This depends a bit on your definition of "realistic". I disabled gravity so the ball also has no rotation and angular friction:

How do i add a line which tells the code that i want the platforms to speed up once a loop of the level has been completed/

public class PlatformMoving : MonoBehaviour
{
public float speed = 1.5f; //How fast the platforms are moving
// use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
// Every frame we look at the position of the ground and it is moved to the left
transform.position = transform.position - (Vector3.right * speed * Time.deltaTime);
// If the position of the ground is off the left of the screen
if (transform.position.x <= -13.05f)
{
// Move it to the far right of the screen
transform.position = transform.position + (Vector3.right * 53.3f);
}
}
}
Assuming the loop you're talking about is the lines following the comment // If the position of the ground is off the left of the screen. The increase of the speed would be specified within the if statement that follows it, since that's where the cycle is occuring.
I'd refrain from calling it a loop btw, simply because it makes one search for a for, while or foreach loop when skimming the code, leading to confusion when one isn't there.
I've commented in your code where it would be located.
public class PlatformMoving : MonoBehaviour
{
public float speed = 1.5f; //How fast the platforms are moving
// use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
// Every frame we look at the position of the ground and it is moved to the left
transform.position = transform.position - (Vector3.right * speed * Time.deltaTime);
// If the position of the ground is off the left of the screen
if (transform.position.x <= -13.05f)
{
// Move it to the far right of the screen
transform.position = transform.position + (Vector3.right * 53.3f);
// Increase speed here
// speed += x;
}
}
}

Why the camera is not moving at all if the rotation speed value is for example set to 0.01?

If I set the rotation speed to 5 for example it will rotate facing the next target waypoint and then will move to it. But the camera rotation will be too fast.
Changing the speed to 0.01 make it rotating in a good slowly smooth speed. But then at 0.01 the camera rotate facing the next waypoint but never move to it. It stay on place.
This is the waypoints script:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Waypoints : MonoBehaviour
{
private GameObject[] waypoints;
private Transform currentWaypoint;
private enum CameraState
{
StartRotating,
Rotating,
Moving,
Waiting
}
private CameraState cameraState;
public GameObject player;
public float speed = 5;
public float WPradius = 1;
public LookAtCamera lookAtCam;
void Start()
{
cameraState = CameraState.StartRotating;
}
void Update()
{
switch (cameraState)
{
// This state is used as a trigger to set the camera target and start rotation
case CameraState.StartRotating:
{
// Sanity check in case the waypoint array was set to length == 0 between states
if (waypoints.Length == 0)
break;
// Tell the camera to start rotating
currentWaypoint = waypoints[UnityEngine.Random.Range(0, waypoints.Length)].transform;
lookAtCam.target = currentWaypoint;
lookAtCam.setTime(0.0f);
cameraState = CameraState.Rotating;
break;
}
// This state only needs to detect when the camera has completed rotation to start movement
case CameraState.Rotating:
{
if (lookAtCam.IsRotationFinished)
cameraState = CameraState.Moving;
break;
}
case CameraState.Moving:
{
// Move
transform.position = Vector3.MoveTowards(transform.position, currentWaypoint.position, Time.deltaTime * speed);
// Check for the Waiting state
if (Vector3.Distance(currentWaypoint.position, transform.position) < WPradius)
{
// Set to waiting state
cameraState = CameraState.Waiting;
// Call the coroutine to wait once and not in CameraState.Waiting
// Coroutine will set the next state
StartCoroutine(WaitForTimer(3));
}
break;
}
case CameraState.Waiting:
// Do nothing. Timer has already started
break;
}
}
IEnumerator WaitForTimer(float timer)
{
yield return new WaitForSeconds(timer);
cameraState = CameraState.StartRotating;
}
public void RefreshWaypoints()
{
waypoints = GameObject.FindGameObjectsWithTag("Target");
}
}
And the look at camera script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LookAtCamera : MonoBehaviour
{
// Values that will be set in the Inspector
public Transform target;
public float RotationSpeed;
private float timer = 0.0f;
public bool IsRotationFinished
{
get { return timer > 0.99f; }
}
// Update is called once per frame
void Update()
{
if (target != null && timer < 0.99f)
{
// Rotate us over time according to speed until we are in the required rotation
transform.rotation = Quaternion.Slerp(transform.rotation,
Quaternion.LookRotation((target.position - transform.position).normalized),
timer);
timer += Time.deltaTime * RotationSpeed;
}
}
public void setTime(float time)
{
timer = time;
}
}
Problem
Your script basically works! The problem is in
private void Update()
{
if (target != null && timer < 0.99f)
{
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation((target.position - transform.position).normalized), timer);
timer += Time.deltaTime * RotationSpeed;
}
}
there are two issues with that:
You add Time.deltaTime * RotationSpeed so the time it takes to reach the 1 or in your case 0.99 simply takes 1/RotationSpeed = 100 times longer than usual. So your camera will stay in the Rotating state for about 100 seconds - after that it moves just fine!
(This one might be intentional but see below for a Better Solution) Quaternion.Slerp interpolates between the first and second rotation. But you always use the current rotation as startpoint so since the timer never reaches 1 you get a very fast rotation at the beginning but a very slow (in fact never ending) rotation in the end since the distance between the current rotation and the target rotation gets smaller over time.
Quick-Fixes
Those fixes repair your current solution but you should checkout the section Better Solution below ;)
In general for comparing both float values you should rather use Mathf.Approximately and than use the actual target value 1.
if (target != null && !Mathf.Approximately(timer, 1.0f))
{
//...
timer += Time.deltaTime * RotationSpeed;
// clamps the value between 0 and 1
timer = Mathf.Clamp01(timer);
}
and
public bool IsRotationFinished
{
get { return Mathf.Approximately(timer, 1.0f); }
}
You should either use Quaternion.Slerp storing the original rotation and use it as first parameter (than you will see that you need a way bigger RotationSpeed)
private Quaternion lastRotation;
private void Update()
{
if (target != null && !Mathf.Approximately(timer, 1.0f))
{
transform.rotation = Quaternion.Slerp(lastRotation, Quaternion.LookRotation((target.position - transform.position).normalized), timer);
timer += Time.deltaTime * RotationSpeed;
}
else
{
lastRotation = transform.rotation;
}
}
Or instead of Quaternion.Slerp use Quaternion.RotateTowards like
transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.LookRotation((target.position - transform.position).normalized), RotationSpeed * Time.deltaTime);
Better Solution
I would strongly suggest to use the Coroutines for everything instead of handling this kind of stuff in Update. They are way easier to control and makes your code very clean.
Look how your scripts would shrink and you wouldn't need all the properties, fields and comparing floats anymore. You could do most things you are currently getting and setting to wait for a certain thing to happen in only a few single lines.
In case you didn't know: You can actually simply yield return another IEnumerator on order to wait for it to finish:
Waypoints
public class Waypoints : MonoBehaviour
{
private GameObject[] waypoints;
public GameObject player;
public float speed = 5;
public float WPradius = 1;
public LookAtCamera lookAtCam;
private Transform currentWaypoint;
private void Start()
{
// maybe refresh here?
//RefreshWaypoints();
StartCoroutine(RunWaypoints());
}
private IEnumerator RunWaypoints()
{
// Sanity check in case the waypoint array has length == 0
if (waypoints.Length == 0)
{
Debug.Log("No Waypoints!", this);
yield break;
}
// this looks dnagerous but as long as you yield somewhere it's fine ;)
while (true)
{
// maybe refresh here?
//RefreshWaypoints();
// Sanity check in case the waypoint array was set to length == 0 between states
if (waypoints.Length == 0)
{
Debug.Log("No Waypoints!", this);
yield break;
}
// first select the next waypoint
// Note that you might get the exact same waypoint again you currently had
// this will throw two errors in Unity:
// - Look rotation viewing vector is zero
// - and transform.position assign attempt for 'Main Camera' is not valid. Input position is { NaN, NaN, NaN }.
//
// so to avoid that rather use this (not optimal) while loop
// ofcourse while is never good but the odds that you will
// always get the same value over a longer time are quite low
//
// in case of doubt you could still add a yield return null
// than your camera just waits some frames longer until it gets a new waypoint
Transform newWaypoint = waypoints[Random.Range(0, waypoints.Length)].transform;
while(newWaypoint == currentWaypoint)
{
newWaypoint = waypoints[Random.Range(0, waypoints.Length)].transform;
}
currentWaypoint = newWaypoint;
// tell camera to rotate and wait until it is finished in one line!
yield return lookAtCam.RotateToTarget(currentWaypoint);
// move and wait until in correct position in one line!
yield return MoveToTarget(currentWaypoint);
//once waypoint reached wait 3 seconds than start over
yield return new WaitForSeconds(3);
}
}
private IEnumerator MoveToTarget(Transform currentWaypoint)
{
var currentPosition = transform.position;
var duration = Vector3.Distance(currentWaypoint.position, transform.position) / speed;
var passedTime = 0.0f;
do
{
// for easing see last section below
var lerpFactor = passedTime / duration;
transform.position = Vector3.Lerp(currentPosition, currentWaypoint.position, lerpFactor);
passedTime += Time.deltaTime;
yield return null;
} while (passedTime <= duration);
// to be sure to have the exact position in the end set it fixed
transform.position = currentWaypoint.position;
}
public void RefreshWaypoints()
{
waypoints = GameObject.FindGameObjectsWithTag("Target");
}
}
LookAtCamera
public class LookAtCamera : MonoBehaviour
{
// Values that will be set in the Inspector
public float RotationSpeed;
public IEnumerator RotateToTarget(Transform target)
{
var timePassed = 0f;
var targetDirection = (target.position - transform.position).normalized;
var targetRotation = Quaternion.LookRotation(targetDirection);
var currentRotation = transform.rotation;
var duration = Vector3.Angle(targetDirection, transform.forward) / RotationSpeed;
do
{
// for easing see last section below
var lerpFactor = timePassed / duration;
transform.rotation = Quaternion.Slerp(currentRotation, targetRotation, lerpFactor);
timePassed += Time.deltaTime;
yield return null;
} while (timePassed <= duration);
// to be sure you have the corrcet rotation in the end set it fixed
transform.rotation = targetRotation;
}
}
Note
Again instead of Quaternion.Slerp and currentRotation you could also simply use Quaternion.RotateTowards like
transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, RotationSpeed * Time.deltaTime);
And for the movement you can also still use Vector3.MoveTowards if you want
while (Vector3.Distance(currentWaypoint.position, transform.position) < WPradius)
{
transform.position = Vector3.MoveTowards(transform.position, currentWaypoint.position, Time.deltaTime * speed);
yield return null;
}
but I would prefer to use the Lerp solutions. Why I suggest to rather use Lerp?
You can very easy controll now whether you want to move/rotate by a certain speed or rather give it fixed duration in which the move/rotation shall be finished regardless how big the differenc is - or even have some additional checks in order to decide for one of those options!
You can ease-in and -out the movement/rotation! See below ;)
Hint for easing Lerp movements
For still maintaining an eased-in and/or eased-out movement and rotation I found this block How to Lerp like a pro very helpfull! (adopted to my examples)
For example, we could “ease out” with sinerp:
var lerpFactor = Mathf.Sin(passedTime / duration * Mathf.PI * 0.5f);
Or we could “ease in” with coserp:
var lerpFactor = 1f - Mathf.Cos(passedTime / duration * Mathf.PI * 0.5f);
We could even create exponential movement:
var lerpFactor = Mathf.Pow(passedTime / duration, 2);
The multiplication property mentioned above is the core concept behind some interpolation methods which ease in and ease out, such as the famous “smoothstep” formula:
var lerpFactor = Mathf.Pow(passedTime / duration, 2) * (3f - 2f * passedTime / duration);
Or my personal favorite, “smootherstep”:
var lerpFactor = Mathf.Pow(passedTime / duration, 3) * (6f * (passedTime / duration) - 15f) + 10f);

Categories