Use Lerp Position and Slerp Rotation together (Unity) - c#

This is what I try to do, When I click on a UI Element, the camera smoothly rotate (to look at the target) and simultaneously move on top of the target.
To perform that I use a two Coroutine one for the Lerp Position and the other one for the Slerp Rotation.
The issue is that the rotation doesn't work correctly, normally the camera should look down to see the top of the target but instead of doing this it look like the Camera first look at the target and after that move to his position.
I hope this is understandable ;)
Here is the code c#
Vector3 LocationProjectForPos = new Vector3(Loc_X, 100, Loc_Z);
Vector3 LocationProjectForRot = new Vector3(Loc_X, Loc_Y, Loc_Z);
Vector3 MainCameraPos = MainCamera.transform.position;
if(!IsCameraMoving & LocationProjectForPos != MainCameraPos)
{
StartCoroutine (CoroutineMovePositionCamera(LocationProjectForPos));
StartCoroutine (CoroutineMoveRotationCamera(LocationProjectForRot));
}
}
Moving the position of the Camera with Lerp
public IEnumerator CoroutineMovePositionCamera(Vector3 LocationProject)
{
float lerpTime = 5f;
float currentLerpTime = 0f;
IsCameraMoving = true;
Vector3 startPos = MainCamera.transform.localPosition;
Vector3 endPos = LocationProject;
while (lerpTime > 0)
{
lerpTime -= Time.deltaTime;
currentLerpTime += Time.deltaTime;
if (currentLerpTime > lerpTime)
{
currentLerpTime = lerpTime;
}
float t = currentLerpTime / lerpTime;
t = t*t*t * (t * (6f*t - 15f) + 10f);
//t = t*t * (3f - 2f*t);
//t = 1f - Mathf.Cos(t * Mathf.PI * 0.5f);
MainCamera.transform.localPosition = Vector3.Lerp(startPos, endPos, t);
yield return null;
}
IsCameraMoving = false;
}
Rotate the Camera with Slerp
public IEnumerator CoroutineMoveRotationCamera(Vector3 LocationProject)
{
float lerpTime = 5f;
float currentLerpTime = 0f;
IsCameraMoving = true;
Vector3 relativePos = LocationProject - MainCamera.transform.localPosition;
Quaternion rotation = Quaternion.LookRotation(relativePos);
Quaternion current = MainCamera.transform.localRotation;
while (lerpTime > 0)
{
lerpTime -= Time.deltaTime;
currentLerpTime += Time.deltaTime;
if (currentLerpTime > lerpTime)
{
currentLerpTime = lerpTime;
}
float t = currentLerpTime / lerpTime;
t = t*t*t * (t * (6f*t - 15f) + 10f);
//t = t*t * (3f - 2f*t);
//t = 1f - Mathf.Cos(t * Mathf.PI * 0.5f);
MainCamera.transform.localRotation = Quaternion.Slerp(current, rotation, t);
yield return null;
}
IsCameraMoving = false;
}
Thanks for your help.

In CoroutineMoveRotationCamera, you need to update the relativePostion and rotation inside the while loop.
The relative position is changing while the camera is moving. Right now, your camera rotates based on the vector snapshot you took before the while loop started.
Add the following code after yield statement.
relativePos = LocationProject - Camera.main.transform.position;
rotation = Quaternion.LookRotation(relativePos);

before start coroutine, you should save the initial vars with member vars instead of function local vars

Related

In-game Dash not consistent, I think effected by velocity

I'm working on a 3D fps, and want a dash. The player uses a character controller, and no rigidbody. My original implementation:
x = Input.GetAxis("Horizontal"); //just gets ur wasd inputs by default
z = Input.GetAxis("Vertical");
Vector3 move = transform.right * x + transform.forward * z; //creates a movement vector
controller.Move(move * speed * Time.deltaTime); //moves the player
if (currentDashLength < dashLength ) //dashes for a set time period
{
currentDashLength += Time.deltaTime;
velocity.y = 0; //keeps you up in the air
controller.Move(move * Time.deltaTime * dashSpeed);
}
else
{
currentDashLength = dashLength;
isDashing = false;
}
if (Input.GetKeyDown(KeyCode.LeftShift) && dashTimer >= 1 && !isCrouching)
{
isDashing = true;
dashTimer -= 1;
currentDashLength = 0f;
health.GiveIFrames(dashLength);
}
This dash works fine, but I realized that if you had pressed shift right after starting to move, the dash would be significantly weaker. I assumed this was due to the fact that my current velocity was low, so the acceleration from the Move function didn't make me reach the speed I would when already moving at terminal velocity. I tried to fix this by multiplying the Move function inputs by 1/(the horizontal velocity of the player) but this didn't fix the issue. My full dashing code:
float dispX = transform.position.x - posX; //gets chnage in position since last frame, this is important to caluclate velocity, since the player isnt using a rigidbody
float dispY = transform.position.y - posY;
float dispZ = transform.position.z - posZ;
posX = transform.position.x;
posY = transform.position.y;
posZ = transform.position.z;
float horizontalVelocity = Mathf.Sqrt(dispX*dispX + dispZ*dispZ) / Time.deltaTime;
x = Input.GetAxis("Horizontal"); //just gets ur wasd inputs by default
z = Input.GetAxis("Vertical");
Vector3 move = transform.right * x + transform.forward * z; //creates a movement vector
controller.Move(move * speed * Time.deltaTime); //moves the player
if (currentDashLength < dashLength )
{
currentDashLength += Time.deltaTime;
velocity.y = 0;
if(horizontalVelocity == 0)
{
Debug.Log("Cannot dash while standing still");
}
else
{
controller.Move(move * Time.deltaTime * dashSpeed * (1 / horizontalVelocity));
}
}
else
{
currentDashLength = dashLength;
isDashing = false;
}
if (Input.GetKeyDown(KeyCode.LeftShift) && dashTimer >= 1 && !isCrouching)
{
isDashing = true;
dashTimer -= 1;
currentDashLength = 0f;
health.GiveIFrames(dashLength);
}
How would I go about ensuring that the dash speed is constant?
(I tried to post this on unity answers, but the website isn't responding to me)
To get the desired behaviour I have re-written your original script by quite a lot, hope that is okay! This code only give you movement and dash control, and nothing else. You'll have to add your crouch check and health stuff back in.
Essentially, we check if the player is dashing before applying any movement, this way we don't add the dash to the current movement. If the player is dashing, we ignore their forward input and apply forward movement based on the predetermined dash. If we aren't dashing then we apply the calculated movement as usual.
private CharacterController controller;
public float speed = 10.0f;
public float dashSpeed = 20.0f;
public float dashLength = 0.5f;
private float currentDashLength = 0;
private bool isDashing = false;
private void Start()
{
controller = GetComponent<CharacterController>();
}
private void Update()
{
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
// Same as before
Vector3 move = new Vector3(x, 0, z);
// gets the move direction
// before we move, check if dashing
if (isDashing)
{
currentDashLength += Time.deltaTime;
move = new Vector3(move.x * dashSpeed, move.y, 1 * dashSpeed);
// this gets the current player movement, and replaces the forward velocity rather than adding to it. We add to the sideways velocity to allow for slight directional control.
// do this instead if you want the dash to only move the player in the forward direction, and prevent strafing: move = new Vector3(move.x, move.y, 1 * dashSpeed);
if (currentDashLength >= dashLength) // when we run out of time, set dash to false
{
isDashing = false;
}
}
else // if we are not dashing then move as normal
{
move *= speed;
if (Input.GetKeyDown(KeyCode.LeftShift) && move.magnitude > 0) // stops the dash ability being used when stationary
{
isDashing = true;
currentDashLength = 0;
}
}
controller.Move(move * Time.deltaTime);
}
float dispX = transform.position.x - posX;
float dispY = transform.position.y - posY;
float dispZ = transform.position.z - posZ;
posX = transform.position.x;
posY = transform.position.y;
posZ = transform.position.z;
float horizontalVelocity = Mathf.Sqrt(dispX*dispX + dispZ*dispZ) / Time.deltaTime;
x = Input.GetAxis("Horizontal");
z = Input.GetAxis("Vertical");
Vector3 move = transform.right * x + transform.forward * z;
controller.Move(move * speed * Time.deltaTime);
if (currentDashLength < dashLength )
{
currentDashLength += Time.deltaTime;
velocity.y = 0;
Vector3 dashMove = move * Time.deltaTime * dashSpeed; // Calculates the dash movement vector
// Move the player based on the dash movement vector
controller.Move(dashMove);
}
else
{
currentDashLength = dashLength;
isDashing = false;
}
if (Input.GetKeyDown(KeyCode.LeftShift) && dashTimer >= 1 && !isCrouching)
{
isDashing = true;
dashTimer -= 1;
currentDashLength = 0f;
health.GiveIFrames(dashLength);
}
By calculating the dash movement vector independently from the player's horizontal velocity, you can ensure that the dash speed remains constant regardless of whether the player is moving before dashing or not.
I think this is what you were trying to achieve? Hope it helps!

How to make a curved flight path?

How to make a bullet fly in an arc when shooting and hit the player.
As in this picture . I tried to use formulas from physics, the body is thrown at an angle to the horizon, that's what came of it . But the bullet flies away into the void
velocity = Mathf.Round(Vector3.Distance(lastpos, transform.position) / Time.deltaTime);
lastpos = transform.position;
Vector3 direction = PlayeCar.position - Vector3.zero;
float angle = Mathf.Atan2(direction.y, direction.x); // радианы
float x = velocity * Mathf.Cos(angle) + gravity * (time* time) / 2;
float y = velocity * Mathf.Sin(angle);
transform.position = new Vector2(x, y)
;
Sample orientative method could be (The script would be attached to the gun that shots, so when the code referes to transform, refers to the gun's transfom):
public void LaunchTarget()
{
Vector3 initialSpeed = transform.forward; //shot direction, gun's forward
GameObject go = GameObject.Instantiate(m_bullet);//reference to bullet prefab
//in case you need to randomize the shot speed
initialSpeed = transform.forward * Random.Range(m_minLaunchSpeed, m_maxLaunchSpeed);
//set the initial position of the bullet in the gun
go.transform.position = transform.position;
// shoot (give speed to the bullets rigidbody)
go.GetComponent<Rigidbody>().velocity = initialSpeed;
//initially disabled soas to see the bullet when shot
go.SetActive(true);
}
Hope that helps
I did it !!!! at the time of the bullet’s flight, I turn it toward the player in an intermittent manner. And after a while I delete it
void Start()
{
rigidbodyBullet = GetComponent<Rigidbody2D>();
Player = GameObject.Find("carPlayer").GetComponent<Transform>();
_distance = Vector3.Distance(Player.position, transform.position);
Invoke("DestroyArcBullet", 1.5f);
limitDistance = _distance / 1.3f;
}
void Update()
{
transform.position += transform.up * Time.deltaTime * 5f;
_distance = Vector3.Distance(Player.position, transform.position);
if (_distance < limitDistance)
{
var turn = Quaternion.Lerp(transform.rotation,
Quaternion.LookRotation(Vector3.forward, Player.position - transform.position), Time.deltaTime * 2);
rigidbodyBullet.MoveRotation(turn.eulerAngles.z);
}
if (this.transform.position.y + this.transform.position.y <= -10 || this.transform.position.y + this.transform.position.y >= 10
|| this.transform.position.x + this.transform.position.x <= -10 || this.transform.position.x + this.transform.position.x >= 10)
{
Destroy(gameObject);
}
}

Camera Behaviour

This camera script is intended to rotate and look at the player while it is moving and snapping to the player slowly while it isn't. (Player passes over a Vector3 beforeMoving before it is starting movement). My issue is I want to "feed" the deltaPosition slowly so it isn't a sudden snap but rather a slow and smooth transition, also stop adding if arrived.
private void LateUpdate()
{
if (player.isMoving)
{
desiredPosition = player.beforeMoving + offset;
}
else
{
Vector3 deltaPosition = player.transform.position - player.beforeMoving;
desiredPosition += deltaPosition * Time.deltaTime;
}
Quaternion camTurnAngle =
Quaternion.AngleAxis(input * rotationSpeed, Vector3.up);
desiredPosition = camTurnAngle * desiredPosition;
transform.position = Vector3.Slerp(transform.position, desiredPosition, smoothFactor);
transform.LookAt(player.transform);
}
Edit: I thought I would share the final code.
private void LateUpdate()
{
Quaternion rotation = Quaternion.Euler(GetDegree(), input.x * rotationSpeed, 0f);
if (player.isMoving)
{
desiredPosition = player.beforeMoving + offset;
CalculatePanTime();
}
else if (!player.isMoving)
{
desiredPosition = player.transform.position + offset;
}
transform.position = Vector3.Slerp(transform.position, rotation * desiredPosition, GetSpeed());
transform.LookAt(player.transform);
}
private void CalculatePanTime()
{
stoppedTime = Time.time;
playerDelta = Vector3.Distance(player.transform.position, player.beforeMoving);
timeToPan = (playerDelta / snappingSpeed) * Time.deltaTime;
}
private float GetSpeed()
{
if (Time.time < stoppedTime + timeToPan)
{
controlsDisabled = true; return snappingSpeed;
}
else
{
controlsDisabled = false; return smoothSpeed;
}
}
You are telling us what you want the code to do, that is good. You are also posting the code you implemented to achieve your goal, that is also good. Can you also tell us what is not working as you want it to work as a result of that code?
From what I understand it is the "Vector3 deltaPosition = player.transform.position - player.beforeMoving;
desiredPosition += deltaPosition * Time.deltaTime; " that is not behaving as you expect it to
maybe try something like this:
private void LateUpdate()
{
// snap the rotation center slowly to the player's position if not moving, or player's position before he started moving
desiredPosition = Vector3.Lerp(desiredPosition, player.beforeMoving, 0.1f);
// rotate around rotation center
Quaternion camTurnAngle = Quaternion.AngleAxis(rotationSpeed * Time.time, Vector3.up);
desiredPosition += camTurnAngle * offset;
// set the position to rotate around rotation center, and look towards player
transform.position = Vector3.Lerp(transform.position, desiredPosition, smoothFactor);
transform.LookAt(player.transform);
}
the problem with your code is that you are overshooting. If you want to implement something that decides how fast the camera snaps, or how much time it should take to snap, try introducing a float player.timeStopMoving = Time.time that you can use to correctly compute the position correction while he is not moving.
if(player.isMoving)
{
desiredPosition = player.beforeMoving;
}
else
{
const float timeNeededToSnap = 2f;
// or float timeNeededToSnap = (player.transform.position - player.beforeMoving).magnitude; // (which you could compute only once in player script, when he stops moving, and then reuse the value instead of performing a ".magnitude" every frame)
if(Time.time < player.timeStopMoving + timeNeededToSnap)
{
desiredPosition = Vector3.Lerp(desiredPosition, player.transform.position, (Time.time - player.timeStopMoving) / timeNeededToSnap);
}
else
{
// an other problem here is: if the player starts moving AGAIN before your desiredPosition got the value, do you want desired position to glich to the new player.beforeMoving?...
desiredPosition = player.transform.position;
}
}
EDIT:
To make he lerp less linear, you can use:
if(Time.time < player.timeStopMoving + timeNeededToSnap)
{
var t = Mathf.Cos(Maths.Pi * 0.5f * (Time.time - player.timeStopMoving) / timeNeededToSnap); // as timeDelta/totalTime goes from 0->1, multiply it by Pi/2 and the Cos will also go from 0->1 but with a smoothing speed
desiredPosition = Vector3.Lerp(desiredPosition, player.transform.position, t);
}

How can i keep the transform height while it's moving?

bool flying = false; // shows when FlyTo is running
// coroutine that moves to the specified point:
IEnumerator FlyTo(Vector3 targetPos)
{
flying = true; // flying is true while moving to the target
Vector3 startPos = transform.position;
Vector3 dir = targetPos - startPos;
float distTotal = dir.magnitude;
dir /= distTotal; // normalize vector dir
// calculate accDist even for short distances
float accDist = Mathf.Min(accDistance, distTotal / 2);
do
{
float dist1 = Vector3.Distance(transform.position, startPos);
float dist2 = distTotal - dist1;
float speed = maxVel; // assume cruise speed
if (dist1 < accDist)
{ // but if in acceleration range...
// accelerate from startVel to maxVel
speed = Mathf.Lerp(startVel, maxVel, dist1 / accDist);
}
else
if (dist2 < accDist)
{ // or in deceleration range...
// fall from maxVel to stopVel
speed = Mathf.Lerp(stopVel, maxVel, dist2 / accDist);
}
// move according to current speed:
transform.position = Vector3.MoveTowards(transform.position, targetPos, speed * Time.deltaTime);
yield return 0; // let Unity breathe till next frame
} while (transform.position != targetPos); // finish when target reached
flying = false; // shows that flight has finished
}
The problem is if for example the transform is at height 200 in the start when it's getting to the targetPos it's in height 0.
But i want it to get to the targetPos above it in the same height not on height 0. It does not matter what it will do when it's getting to the targetPos but what i want is that it will get there at the same height level.
This is the full script:
using UnityEngine;
using System.Collections;
public class ControlShip : MonoBehaviour
{
public float maxVel = 30; // cruise speed
public float startVel = 5; // speed at the starting point
public float stopVel = 0.5f; // speed at the destination
public float accDistance = 20; // acceleration/deceleration distance
public float factor = 0.25f; // max inclination
public float turnSpeed = 0.8f; // speed to turn/bank in the target direction
Vector3 lastPos; // used to calculate current velocity
Transform baseTarget;
private Rigidbody _rigidbody;
void Start()
{
_rigidbody = GetComponent<Rigidbody>();
baseTarget = GameObject.Find("Base").transform;
lastPos = transform.position;
StartCoroutine(Fly()); // demo routine
}
// calculate bank/turn rotation at Update
void Update()
{
// calculate the displacement since last frame:
Vector3 dir = transform.position - lastPos;
lastPos = transform.position; // update lastPos
float dist = dir.magnitude;
if (dist > 0.001f)
{ // if moved at least 0.001...
dir /= dist; // normalize dir...
float vel = dist / Time.deltaTime; // and calculate current velocity
// bank in the direction of movement according to velocity
Quaternion bankRot = Quaternion.LookRotation(dir + factor * Vector3.down * vel / maxVel);
Vector3 rotation = Quaternion.Lerp(transform.rotation, bankRot, turnSpeed * Time.deltaTime).eulerAngles;
rotation.x = 0;
transform.rotation = Quaternion.Euler(rotation);
}
}
bool flying = false; // shows when FlyTo is running
// coroutine that moves to the specified point:
IEnumerator FlyTo(Vector3 targetPos)
{
flying = true; // flying is true while moving to the target
Vector3 startPos = transform.position;
Vector3 dir = targetPos - startPos;
float distTotal = dir.magnitude;
dir /= distTotal; // normalize vector dir
// calculate accDist even for short distances
float accDist = Mathf.Min(accDistance, distTotal / 2);
do
{
float dist1 = Vector3.Distance(transform.position, startPos);
float dist2 = distTotal - dist1;
float speed = maxVel; // assume cruise speed
if (dist1 < accDist)
{ // but if in acceleration range...
// accelerate from startVel to maxVel
speed = Mathf.Lerp(startVel, maxVel, dist1 / accDist);
}
else
if (dist2 < accDist)
{ // or in deceleration range...
// fall from maxVel to stopVel
speed = Mathf.Lerp(stopVel, maxVel, dist2 / accDist);
}
// move according to current speed:
Vector3 pos = Vector3.MoveTowards(transform.position, targetPos, speed * Time.deltaTime);
pos.y = lastPos.y;
transform.position = pos;
yield return 0; // let Unity breathe till next frame
} while (transform.position != targetPos); // finish when target reached
flying = false; // shows that flight has finished
}
// example routine: fly to 3 different points in sequence
IEnumerator Fly()
{
Vector3 p0 = baseTarget.position;
while (true)
{
StartCoroutine(FlyTo(p0));
while (flying)
{
yield return 0;
}
yield return new WaitForSeconds(5);
}
}
void OnTriggerEnter(Collider other)
{
if (other.gameObject.name == "Base")
{
}
}
}
As stated in my comments, don't update the position twice, this will cause an overhead, easily avoidable by changing the Target's Y coordinate BEFORE the loop.
IEnumerator FlyTo(Vector3 targetPos)
{
flying = true; // flying is true while moving to the target
Vector3 startPos = transform.position;
//Insert this line right here.
targetPos.y = startPos.y;
Vector3 dir = targetPos - startPos;
//The rest of your code remains the same...
Find this line, inside your DO, near the end and remove it
Vector3 pos = Vector3.MoveTowards(transform.position, targetPos, speed * Time.deltaTime);
//pos.y = lastPos.y; ~ REMOVE THIS LINE~
assuming that Y is the height axis, maybe its actually Z axis,try it out.

Unity2D Asteroids style game

I am trying to build an asteroids style game in Unity and could really use some help. I believe all my math is correct as far as the ship movement but I am having trouble getting it to work inside Unity. I am having a couple different problems.
The ship does not update with velocity ( if you start moving and then let go, it will stand still)
I am unsure in Unity how to set the ships rotation to my specific angle.
Any help would be greatly appreciated.
public class playerController : MonoBehaviour {
public static float timer;
public static bool timeStarted = false;
Vector2 accel;
Vector2 velocity;
float direction;
float angle;
float shotCooldown;
float speed;
const float pi = 3.141592f;
const float maxSpeed = 300;
const float maxAccel = 500;
void Start () {
timeStarted = true;
}
void Update () {
if (timeStarted == true) {
timer += Time.deltaTime;
}
shotCooldown -= (timer%60);
angle = direction * pi / 180;
if (Input.GetKey(KeyCode.W)) {
accel.y = -Mathf.Cos(angle) * maxAccel;
accel.x = Mathf.Sin(angle) * maxAccel;
velocity += accel * Time.deltaTime;
}
if (Input.GetKey(KeyCode.S)) {
accel.y = -Mathf.Cos(angle) * maxAccel;
accel.x = Mathf.Sin(angle) * maxAccel;
velocity -= accel * Time.deltaTime;
}
if (Input.GetKey(KeyCode.Space)) {
if (shotCooldown <= 0)
{
// Create new shot by instantiating a bullet with current position and angle
shotCooldown = .25f;
}
}
if (Input.GetKey(KeyCode.D)) {
direction += 360 * Time.deltaTime;
}
if (Input.GetKey(KeyCode.A)) {
direction -= 360 * Time.deltaTime;
}
/*
if (this.transform.position.x >= Screen.width && velocity.x > 0) {
this.transform.position.x = 0;
}*/
while (direction < 0) {
direction += 360;
}
while (direction > 360) {
direction -= 360;
}
speed = Mathf.Sqrt( (velocity.x * velocity.x) + (velocity.y * velocity.y));
if (speed > maxSpeed) {
Vector2 normalizedVector = velocity;
normalizedVector.x = normalizedVector.x / speed;
normalizedVector.y = normalizedVector.y / speed;
velocity = normalizedVector * maxSpeed;
}
this.transform.position = velocity * Time.deltaTime;
transform.rotation = Quaternion.AngleAxis(0, new Vector3(0,0,angle));
}
}
It's usually a bad idea to set the position the way you are, because you're not actually using any physics. The way you're doing it, velocity is a new position for the ship, not a speed. Once you let go of the keys, it stops calculating new positions, and thus stops moving.
There are a couple of alternatives which would make for a better result:
1) One way this can be done is by calling: transform.Translate(Vector3.forward * speed * Time.deltaTime) Vector3.forward should correspond to the direction you consider as "forward", but if not, you can change it to whichever works (eg Vector3.up). This means you only really need to calculate a speed and let unity hangle the rest.
2) If you're using a rigidbody on your ship, you could simply do:
rigidbody.AddForce(Vector3.forward * speed * Time.deltaTime) which will automatically accelerate the ship in the given direction by whatever speed you give it.
As for rotation, perhaps try something like this:
if (Input.GetKey(KeyCode.D))
{
Vector3 newRotation = transform.rotation.eulerAngles;
newRotation.z += 10;
transform.rotation = Quaternion.Euler (newRotation);
}
else if (Input.GetKey(KeyCode.A))
{
Vector3 newRotation = transform.rotation.eulerAngles;
newRotation.z -= 10;
transform.rotation = Quaternion.Euler (newRotation);
}

Categories