Best way to move my Gameobject smoothly? - c#

I've got an issue when I try to move smoothly a Gameobject.
I receive every second through a TCP Protocol a position, where my Gameobject have to move. So I have my start position, my end position, I can calculate the distance between the two position , and I know my Gameobject have to move with a constant speed to my end point in 1 second.
I try 3 solutions that are : Learp, MoveToward and SmoothDamp , but none of them work, my Gameobject just teleport from point A to point B every time.
Here's what I try in my code (my Gameobject is referenced in a dictionnary, my Gameobject are planes) :
// Distance between my 2 points
float step = Vector3.Distance(planeId_Object_Dictionnary[newPlane.Flight].transform.position, new Vector3((float)newPlane.X, (float)newPlane.Afl / (3.2808f * 1852f), (float)newPlane.Y));
//Use of Lerp
//planeId_Object_Dictionnary[newPlane.Flight].transform.position = Vector3.Lerp(planeId_Object_Dictionnary[newPlane.Flight].transform.position, new Vector3((float)newPlane.X, (float)newPlane.Afl / (3.2808f * 1852f), (float)newPlane.Y), step);
//Use of MoveTowards
planeId_Object_Dictionnary[newPlane.Flight].transform.position = Vector3.MoveTowards(planeId_Object_Dictionnary[newPlane.Flight].transform.position, new Vector3((float)newPlane.X, (float)newPlane.Afl / (3.2808f * 1852f), (float)newPlane.Y), step);
//Use of SmoothDamp
//planeId_Object_Dictionnary[newPlane.Flight].transform.position = Vector3.SmoothDamp(planeId_Object_Dictionnary[newPlane.Flight].transform.position, new Vector3((float)newPlane.X, (float)newPlane.Afl / (3.2808f * 1852f), (float)newPlane.Y), ref newPlane.GroundSpeed, 1);
The code is a function that is called in my Update this way :
void Update () {
lock (threadLock)
{
// Message receive from my TCP Protocol
while (q_orders.Count > 0)
{
switch (q_orders.Dequeue())
{
case OrderType.trackmovedevent:
aircraftMajInfos(q_args.Dequeue()); // My function to move my Object
break;
}
}
}
}

Its better to use a tween engine , like http://dotween.demigiant.com/.
If you install Dotween then you can simply use
transform.DOMove(new vector3(1 ,0 , 1) , duration);
You can also set Ease for tweens. or use Oncomplete fucntions;
transform.DOMove(new vector3(1 ,0 , 1) , duration)
.SetEase(Ease.OutCubic)
.OnCompelete(() => { shouldClose = true; });

I must say that you have understand all three functions totally wrong. They should be called in multiple updates, not just once.
In this situation I recommend MoveTowards. Because SmoothDamp doesn't move the object at a constant speed, and to use Lerp, you need to caltulate the ratio between the two points (note that you CAN move the object constantly if you add the amount to the t parameter every fixed update).
Here's a code snippet I wrote for the MonoBehaviour of your gameobject
const float moveTime = 1;
Vector3 target, step;
void Update () {
transform.position = Vector3.MoveTowards(transform.position, target,
step / moveTime * Time.deltaTime);
}
// Call this method to set the new target (the currently received position from network)
public void SetTarget (Vector3 positon) {
target = positon;
step = Vector3.Distance(transform.position, target);
}
Time.deltaTime is the interval between updates (where the Update () function called)

I would suggest the following:
Create a new script "ObjectMover" (maybe not the best name) and apply it on the Game Objects that you want to be able to move.
In the script create a public method: MoveToNewPos(Vector3 newPos) which stores the new position that you are aiming for with this object.
Inside the Update method of "ObjectMover" use lerp or calculate the stepping yourself using step / moveTime * Time.deltaTime to move towards the wanted new position and only adjust the speed to you desired look and feel.
From your "main" script do theObjectIWantToMove.GetComponent().MoveToNewPos(newPos); and it will smoothly move there.

Related

Multi target orthographic camera in unity not working

I'm trying to make a camera track two targets in a 2D game on unity but I can't quite get it to work.
This is the code I currently have, but it's changing the rotation in transform instead of position. the ortho size is changing but it is not properly tracking the center point between the characters. is there anyway I can fix this?
using System.Collections.Generic;
using UnityEngine;
public class CameraZoom : MonoBehaviour
{
private Camera cameraRef;
private GameObject[] playerPos;
void Start()
{
cameraRef = GetComponent<Camera>();
playerPos = GameObject.FindGameObjectsWithTag("Player");
Debug.Log(playerPos[0].transform.position);
Debug.Log(playerPos[1].transform.position);
Debug.Log(cameraRef.tag);
StartCoroutine(ZoomInOut());
}
// Update is called once per frame
void Update()
{
}
IEnumerator ZoomInOut()
{
while (true)
{
if (playerPos[0] != null && playerPos[1] != null)
{
Vector3 lookPoint = Vector3.Lerp(playerPos[0].transform.position, playerPos[1].transform.position, 0.5f);
cameraRef.transform.LookAt(lookPoint);
float distance = Vector3.Distance(playerPos[0].transform.position, playerPos[1].transform.position);
if (distance > (cameraRef.orthographicSize * 2))
{
cameraRef.orthographicSize += 0.05f;
// if (distance < (cameraRef.orthographicSize * 2))
//{
// cameraRef.orthographicSize -= 0.1f;
//}
}
else
if (distance < (cameraRef.orthographicSize))
{
cameraRef.orthographicSize -= 0.05f;
}
yield return new WaitForSeconds(0.02f);
}
}
}
}
Well cameraRef.transform.LookAt does exactly what you described
Rotates the transform so the forward vector points at worldPosition.
Since your game is 2D anyway what you rather want to do is set the camera to exactly the same XY position
var lookPoint = (playerPos[0].transform.position, playerPos[1].transform.position) * 0.5f;
//Maintain the Z depth position
lookPoint.z = cameraRef.transform.position.z;
cameraRef.transform.position = lookPoint;
Also instead of
yield return new WaitForSeconds(0.02f);
rather use
yield return null;
The latter makes it run every frame. But anyway assuming 60FPS a frame takes about 0.017seconds so waiting0.02` will most probably mean you wait two frames since this value is not the exact time but rather the minimum time to wait/skip to the next frame.
And move this OUT of the if block!!
If one of your objects is missing => endless loop -> Freeze/Crash of Unity and your app! You definitely want to wait one frame for every iteration of your while loop!
Actually you could as well do the thing in Update without the loop at all :)
And then instead of
cameraRef.orthographicSize -= 0.05f;
I would rather either use
// Take differences in the frame rate into account (jitter)
cameraRef.orthographicSize -= 3 * Time.deltaTime;
or directly interpolate
// Smooth interpolate -> if the target and current value are far apart
// this zooms faster and then becomes slower when already very close
// Adjust that "5" according to your needs
cameraRef.orthographicSize = Mathf.Lerp(cameraRef.orthographicSize, distance, 5 * Time.deltaTime);
without using your conditions at all.
Typed on the phone but I hope the idea gets clear

How to transition smoothly from AddForce() to MovePosition() in Unity?

I have a character that dashes using AddForce(), after dashing, i want him to auto walk in set speed using MoovePosition().
This project was made using the 3D engine and it work flawlessly. But I've changed it to 2D and after the AddForce, the character abruptly changes to the speed that MovePosition() set, it looks bad.
I thought about checking the velocity after the dash and when it reaches the same velocity as when auto-walk is happening, it calls for MovePosition. But it doesn't seem a good approach.. I'd love to receive some tips on this issue.
if(moveFactor!= 0)
{
Vector2 appliedForce = transform.position + transform.up * moveFactor;
Debug.Log("appliedForce: " + appliedForce.magnitude);
playerRigidbody.AddForce(appliedForce);
autoWalk = true;
moveFactor = 0;
}
if (autoWalk) playerRigidbody.MovePosition(walkForce);
moveFactor is a float value that a swipe detector script returns. The code is in FixedUpdate as well.
It would be helpful to see the problem instead of trying to reconstruct the picture from your description, but I'll try my best.
The Problem
The same frame you start the dash, you set autoWalk to true. As soon as the if statement is exited, the if (autoWalk) is evaluated and you start manually moving the RigidBody using MovePosition() — before the force has even been added.
I'm not sure why that ever worked in 3D, though.
Solution #1
Instead of using MovePosition(), move with AddForce(), with the default ForceMode2D; or with playerRigidbody.velocity, where-in you set it once, not every frame.
Solution #2
Instead of using AddForce(), for the dash, use the same old MovePosition() you use inside if (autoWalk).
Just increase the magnitude of walkForce several times for the duration of the dash.
Solution #3
The problem with using AddForce() for it is that you don't know when it's "finished", so you don't know when to transition to MovePosition().
If the above solutions don't work for you, implement the dash manually. You can do this with Vector2.Lerp() (or Vector3) inside a coroutine.
if (moveFactor != 0)
{
Vector2 endPosition = transform.position + transform.up * moveFactor;
StartCoroutine(Dash(endPosition));
moveFactor = 0;
}
Where Dash() is:
public float dashDuration = 1f;
private IEnumerator Dash(Vector2 endPosition)
{
autoWalk = false;
// Record the original position before you begin moving the object.
float startingPosition = transform.position;
float elapsed = 0f;
while (elapsed < dashDuration )
{
elapsed += Time.deltaTime;
Vector2 nextPosition = Vector2.Lerp(startingPosition, endPosition, elapsed / dashDuration );
playerRigidbody.MovePosition(nextPosition);
// Note: if the RigidBody is kinematic (isKinematic == true), this will simply teleport it to the given position.
// Otherwise, it will comply with its interpolation settings.
// You could directly modify playerRigidbody.position or transform.position (though the former is faster) for the same teleportation.
yield return null;
}
autoWalk = true;
}
appliedForce might not immediately work as endPosition — modify it so it does, in that case. dashDuration is in seconds.
Make sure Dash() is started only once, until it is finished. You can do this by saving the Coroutine returned by StartCoroutine() in a private field member and comparing it to null before you start, but you shouldn't have to. If the code inside the if (moveFactor != 0) statement is executed something like every frame, change your code accordingly.
I've not tested this, so I'm not sure it will check for collisions. If it doesn't but you need it to, you can add that funcionality by raycasting (or box-casting, sphere-casting, or whatever kind of Collider you use):
private IEnumerator Dash(Vector2 endPosition)
{
float startingPosition = transform.position;
float elapsed = 0f;
while (elapsed < dashDuration )
{
elapsed += Time.deltaTime;
Vector2 nextPosition = Vector2.Lerp(startingPosition, endPosition, elapsed / dashDuration );
// Add the raycast here and only execute the MovePosition() if the returned RaycastHit2D's collider property is null.
// (The raycast's should extend to nextPosition.)
playerRigidbody.MovePosition(nextPosition);
yield return null;
}
}

Is there a way to create a curve when launching a gameobject to the player?

This is what I have tried so far:
I create a raycast and if it hits an object on layer 8 (the layer in which objects need to be launched to the player), I call the SlerpToHand() function.
private void Update()
{
if(Physics.Raycast(transform.position, transform.forward * raycastLength, out hit))
{
if(hit.collider.gameObject.layer == 8)
{
// Launch object to player
SlerpToHand(hit.collider.transform);
}
}
}
Inside of SlerpToHand(), I set the object's position to Vector3.Slerp(), that vector being created from values in the hit object.
private void SlerpToHand(Transform hitObj)
{
Vector3 hitObjVector = new Vector3(hitObj.transform.position.x, hitObj.transform.position.y, hitObj.transform.position.z);
hitObj.position = Vector3.Slerp(hitObjVector, transform.position, speed);
}
But the result of this is all wrong, the object just gets teleported to the player's hands. Is Vector3.Slerp() not a good way to curve an object to the player? For context I am trying to recreate Half-Life: Alyx's grabbity gloves. There is still some work to do with the hand gestures but I am just trying to get the object curve down. Help is much appreciated, let me know if more info is needed.
See unity docs:
public static Vector3 Slerp(Vector3 a, Vector3 b, float t);
Here, t is a normalized position between two input values. It means, if t = 0, result will be exactly first value. If t = 1, result will be exactly second value. If t = 0.5, result will be the middle between two values.
So, usually, you need to call Slerp every Update, step by step increasing t from 0 to 1. For this, usually Time.deltaTime used (which equals the time between updates). For speed control, multiply your speed by Time.deltaTime.
Update()
{
if (t < 1)
{
t += Time.deltaTime * speed;
hitObj.position = Vector3.Slerp(startPosition, endPosition, t);
}
}
...and in this case, for start moving, you just need to set t = 0. Probably, you have to implement your own logic here, but this should show the idea.
In addition:
Slerp used to interpolate between vector directions, for positions use Lerp.
Consider use DOTween plugin - its free and powerful for such cases.

Moving Prefabs, Unity Spawner, Move Position

I have one problem. I want my prefabs to spawn every time my player picks them up. I did research on Google and YouTube and I tried to use the random function and instantiate. I don't know how to use them. I wrote this code I saw on YouTube and my prefab Sphere moves like 1cm to z position. I want to every time when I pick up object or my player go to spawn more of this on the z position. How do I do this?
My smaller script:
public GameObject Sphere;
public float zrange;
// Use this for initialization
void Start () {
RandomPosition();
}
void RandomPosition()
{
zrange = Random.Range(0f, 2f);
this.transform.position = new Vector3(0, 0, zrange);
}
You achieve that by not messing with the x and y values (your code sets them both to 0).
Vector3 p = transform.position;
p.z = zrange;
transform.position = p;
This assumes that your code to instantiate the object is already correctly placing the object. If not, more information is needed.

Change movement speed in Vector.Lerp Unity 5.6

I am using Vector3.Lerp in unity game to simply move a gameobject from one position to other smoothly. Below is my code:
public class playermovement : MonoBehaviour {
public float timeTakenDuringLerp = 2f;
private bool _isLerping;
private Vector3 _startPosition;
private Vector3 _endPosition;
private float _timeStartedLerping;
void StartLerping(int i)
{
_isLerping = true;
_timeStartedLerping = Time.time ; // adding 1 to time.time here makes it wait for 1 sec before starting
//We set the start position to the current position, and the finish to 10 spaces in the 'forward' direction
_startPosition = transform.position;
_endPosition = new Vector3(transform.position.x + i,transform.position.y,transform.position.z);
}
void Update()
{
//When the user hits the spacebar, we start lerping
if(Input.GetKey(KeyCode.Space))
{
int i = 65;
StartLerping(i);
}
}
//We do the actual interpolation in FixedUpdate(), since we're dealing with a rigidbody
void FixedUpdate()
{
if(_isLerping)
{
//We want percentage = 0.0 when Time.time = _timeStartedLerping
//and percentage = 1.0 when Time.time = _timeStartedLerping + timeTakenDuringLerp
//In other words, we want to know what percentage of "timeTakenDuringLerp" the value
//"Time.time - _timeStartedLerping" is.
float timeSinceStarted = Time.time - _timeStartedLerping;
float percentageComplete = timeSinceStarted / timeTakenDuringLerp;
//Perform the actual lerping. Notice that the first two parameters will always be the same
//throughout a single lerp-processs (ie. they won't change until we hit the space-bar again
//to start another lerp)
transform.position = Vector3.Lerp (_startPosition, _endPosition, percentageComplete);
//When we've completed the lerp, we set _isLerping to false
if(percentageComplete >= 1.0f)
{
_isLerping = false;
}
}
}
}
The code works fine and the gameobject moves smoothly between two points. But it takes about 1 sec to reach destination. I want to make it move faster. I have tried decreasing the value of float timeTakenDuringLerp but the speed isn't affected. I have followed this tutorial and the explanation there also says to change timeTakenDuringLerp variable in order to change speed but its not working here.
Any Suggestions please?
H℮y, thanks for linking to my blog!
Decreasing timeTakenDuringLerp is the correct solution. That reduces the time it takes for the object to move from start to finish, which is another way of saying "it increases the speed".
If there is a specific speed you want the object to move at, you'll need to make timeTakenDuringLerp a variable rather than a constant, and set it to distance/speed. Or better yet, don't use Lerp at all, and instead set the object's velocity and let Unity's physics engine take care of it.
Multiplying percentageComplete by a constant, as suggested by #Thalthanas, is incorrect. That causes the lerping updates to continue occurring after the lerping has completed. It also makes the code hard to understand because timeTakenDuringLerp is no longer the time taken during the lerp.
I've double-checked with my code and it does indeed work, so the problem you are experiencing must be elsewhere. Or maybe you accidentally increased the time, which would decrease the speed?
The solution is to
multiply percentageComplete value with a speed value like,
transform.position = Vector3.Lerp (_startPosition, _endPosition, percentageComplete*speed);
So when you increase speed, it will go faster.

Categories