In mini game what I'm working on in Unity I want to loop movetowards. I want to move object and back to 1st position but not only 1 time. Now I have this code what give me working "move to postition and back system". How can I change this code to make loop in moving?
public class TestMovement : MonoBehaviour
{
public float speed = 3;
public Vector3 target = Vector3.zero;
private Vector3 origin;
void Start(){
origin = transform.position;
}
void Update(){
TestTransform ();
}
void TestTransform (){
transform.position = Vector3.MoveTowards (transform.position, target, speed * Time.deltaTime);
if (transform.position == target) target = origin;
}
}
TestTransform is getting called from Update, meaning it gets called once for every frame. So, you are essentially already in a loop (an endless loop as long as your game is running). Let's look at your existing code and your desired outcome:
Your existing code starts immediately moving toward the target. Upon reaching the target, it switches the target back to the origin, and starts moving back toward this new target.
Your code is changing the "target" to origin on the first pass. After that, once it has returned to the new "target" (which is now "origin"), it's just sitting there, testing that transform.position == target, and setting target = origin over and over on every frame.
If you want the object to bounce back and forth between origin and target, there are a couple of changes you need to make:
You need a third Vector3 (let's call it "currentTarget").
On start, set currentTarget = target.
In TestTransform, change where you're testing against and changing "target" to operate on "currentTarget" instead. When you change it, you'll need to consider whether currentTarget is set to target or origin, and pick the other as your next target.
End result should be "currentTarget" changes back and forth between "target" and "origin".
Side note: as someone else mentioned in comment, testing for exact equality of vectors won't always work -- you should be checking the distance between vectors and waiting for it to be less than some very small value.
this alow you to have as many positions to move as you want, just create empty GameObjects for each target and add them to list in inspector. Your transform will move in loop, of course if you want only 2 positions add 2 elements to list. (ps. i added everything in your script just to make it easy to understand, i would recomend to make another method CheckNextPosition() with the logic that manages targets)
public class TestMovement : MonoBehaviour
{
public float speed = 3;
public List<Transform> targets= new List<Transform();
public Transform target;
protected int currentTargetIndex=0;
void Start(){
origin = transform.position;
target= targets[currentTargetIndex];
}
void Update(){
TestTransform ();
}
void TestTransform (){
transform.position = Vector3.MoveTowards (transform.position, target.position, speed * Time.deltaTime);
if (transform.position == target.position)//check if you reached the target
{
if(currentTargetIndex >= targets.Count)//check if you reached the last position in your targets list
{
currentTargetIndex=0;//go to first target in your target list
}
else
{
currentTargetIndex++;// go to next target in your target list
}
target=targets[currentTargetIndex];// set the next target
}
}
}
Use Mathf.PingPong to easily do oscillation.
float distanceBetweenPosts = Vector3.Distance(origin, target);
float distanceTowardTarget = Mathf.PingPong(Time.time * speed, distanceBetweenPosts);
float percentageDistance = distanceTowardTarget / distanceBetweenPosts;
Vector3 currentOffset = (target-origin ) * percentageDistance;
Vector3 currentPosition = origin + currentOffset;
transform.position = currentPosition;
If you want to set a specific time startTime for the target starting at origin, then you can use :
float distanceTowardTarget = Mathf.PingPong((Time.time-startTime) * speed,
distanceBetweenPosts);
Related
When I try to use Vector2.Lerp in unity, I run into a problem. The object flies downwards at a very high speed.
I am trying to make a moving platform in a 2D game. It moves from minimum x value to maximum x value. I want to use Vector2.Lerp to make the speed in both directions the same, but when I apply transform.Translate, and pass Vector2.Lerp as argument, the object flies down with very high speed.
That's the problem, because when I pass in Vector 3 with coordinates divided by 100, everything works fine. But different speeds appear in different directions.
The object has a box collider 2D and a script that moves it. It has no rigidbody 2D.
What am I doing wrong?
Here's my function that moves the object in one direction (it's called in FixedUpdate):
Vector2 target = new Vector3(xMin, 0);
Vector2 moving = Vector2.Lerp(transform.position, target, speed * Time.fixedDeltaTime);
transform.Translate(moving);
Lerp(a, b, t) interpolates between a and b.
t must be in the range [0,1], so that Lerp(a, b, 0) = a and Lerp(a, b, 1) = b. Using speed * Time.fixedDeltaTime in the place of t doesn't make sense.
If you want to move your object from a to b with a constant speed of speed (units per second), you can do:
IEnumerator Move(Vector2 a, Vector2 b, float speed){
float movementDuration = Vector2.Distance(a, b) / speed;
for(float t = 0; t < 1; t += Time.deltaTime / movementDuration){
transform.position = Vector2.Lerp(a, b, t);
yield return null;
}
transform.position = b;
}
And you can call this method using StartCoroutine(Move(a, b, speed));
Be careful not to call this every frame. Call only once and see what happens.
As explained here Lerp interpolates linear from the given start value to he target value using a relative factor from 0 to 1.
You can go with a Coroutine but would need to start it and make sure it is running only once at a time.
Or if you simply want to continuously move towards the target you would want to rather use
private void Update()
{
transform.position = Vector2.MoveTowards(transform.position, new Vector2(xMin, 0), speed * Time.deltaTime);
}
Another note for your usecase: If you use Rigibody/Rigidbody2D or any other component form the physics engines you do not want to move anything via Transform as this breaks with the physics, usually looks weird and breaks collision detection etc.
You rather want to go though the according component - in your case e.g.
[SerializeField] private Rigidbody2D _rigidbody;
pivate void Awake()
{
if(!_rigidbody) _rigidbody = GetComponent<Rigidbody2D>();
}
private void FixedUpdate()
{
var position = Vector2.MoveTowards(_rigidbody.position, new Vector2(xMin, 0), speed * Time.deltaTime);
_rigidbody.MovePosition(position);
}
I am moving a rigidbody using rb.AddForce(force,ForceMode.Impulse) where force is the target position the rigidbody have to reach.
Now the speed it goes directly depends on the distance it has to cover.
Let's say the time taken to reach the target position is 3sec. I need the rigidbody to cover the same target pos in 5sec.
I dont want to change the timescale as it affects my gameflow
On Changing the velocity of rigidbody it fails to reach the target position
Some basic physics/math:
velocity = change-in-position / travel-time
force = mass * change-in-velocity / acceleration-time
For ease, we're going to call change-in-position as distance, and change-in-velocity/acceleration-time as acceleration
Now, since the acceleration-time component is effectively zero because you're using Impulse, we're going to remove it from the equation (in math terms, we set it at '1')
force = mass * change-in-velocity
Assuming your object starts at zero velocity, we can simplify change-in-velocity to just velocity
force = mass * velocity
force = mass * distance / travel-time
To bring that back into Unity code:
var mass = rb.mass;
var distance = destination.position - transform.position;
var travelTime = 5f; // seconds
var force = mass * distance / travelTime;
rb.AddForce(force, ForceMode.Impulse);
Note that this assumes a frictionless transfer and constant velocity.
If you ignore gravity, this code solves the problem, here I changed the drag according to weight and distance, it may be a little bit away from the destination at the end, the reason should be higher drag friction.
public void ForceToTarget(Transform target, float time = 1f)
{
var rb = GetComponent<Rigidbody>();
var vector = target.position - transform.position;
var distance = vector.magnitude;
rb.drag = distance/time;
rb.AddForce(vector*rb.mass*distance/time, ForceMode.Impulse);
}
If you want precise control over your speed, then stop using ForceMode.Impulse because other physics effects like drag will make your answers wrong. Instead, just set the speed yourself. You can do this with a Coroutine to control timing and ForceMode.VelocityChange to control the speed. Basically, just look at where you are, where the target is, how much time is left, and apply the speed directly.
private bool canMove = true;
public void MoveTo(Vector3 targetPosition, float targetTime)
{
if(canMove)
{
StartCoroutine(MoveToCoroutine(targetPosition,targetTime));
}
}
private IEnumerator MoveToCoroutine(Vector3 targetPosition, float time)
{
canMove = false;
while(time > 0)
{
var positionDelta = transform.position - targetPosition;
var targetSpeed = positionDelta / time;
var speedDelta = targetSpeed - rb.velocity;
rb.AddForce(speedDelta , ForceMode.VelocityChange);
yield return null;
time -= Time.deltaTime;
}
// Bring the object to a stop before fully releasing the coroutine
rb.AddForce(-rb.velocity, ForceMode.VelocityChange);
canMove = true;
}
I wrote this here into the text editor, no IDE and haven't tested it, but I'm pretty sure this'll do what you want.
Assuming you're using the target position as-is then larger vectors will cause larger force to be applied than smaller vectors. Similarly, if using a direction vector as-is then as the rb gets closer to the target the magnitute of the vector gets smaller and thus less force is applied.
To get a constant speed use the direction to the target and Normalise it instead. Regardless of the distance the direction vector will always have a magnitude of 1 so you can multiply it by any value to accurately control the speed of the object:
Rigidbody rb;
public Transform target;
public float dist;
public float speed = 2f; // or whatever
public float targetDistance = 40f; // or whatever
private void Start()
{
rb = GetComponent<Rigidbody>();
StartCoroutine("IMove");
}
IEnumerator IMove()
{
dist = Vector3.Distance(transform.position, target.position);
while (dist > targetDistance)
{
dist = Vector3.Distance(transform.position, target.position);
rb.AddForce(Vector3.Normalize(target.position - transform.position) * speed, ForceMode.Impulse);
yield return new WaitForFixedUpdate();
}
}
Without getting too much into the physics and maths, if you want it to travel slower but the same distance you need to reduce the gravity on it and the initial force.
Note in this example I am assuming the weight is 1 to make the calculation a bit easier for force.
public class TrevelSpeedAdjusted
{
public float speedFactor = 1;
void FixedUpdate()
{
// Reduce the gravity on the object
rb.AddForce(-Physics.gravity * rigidbody.mass * (1 - speedFactor));
}
public float AddAdjustedForce(Vector3 force, ForceMode forceMode)
{
rb.AddForce(force * speedFactor, forceMode);
}
}
So you can try DoTween package to do this pretty easily and its very convenient to use a package instead of using Unity's inbuilt system.
With doTween use this:
DOMove(Vector3 to, float duration, bool snapping) condition to tween your physics Gameobject to a given target position in the duration you require.
Here's documentation you can refer to if you want: http://dotween.demigiant.com/documentation.php
Let me give you an example:
Install the doTween Package. http://dotween.demigiant.com/download
Import it to unity.
Go to your script where you want to achieve the functionality you mentioned on your question and add this header "using DG.Tweening".
Now get access of your RigidBody.
For Example Lets say: I have a cube gameobject with rigidbidy and this script attached.
The Cube Initial Position is at 0,0,0.
And I want it to move to 5,5,5 in 3 seconds or 5 seconds as per your questions request. And lets say I want this to happen when I click SpaceBar on keyboard.
So I would simply do.
Rigidbody rb;
void Start()
{
rb= GetComponent<Rigibody>();
}
void Update()
{
if(Input.GetButtonDown(Keycode.Space))
{
MoveCube(new Vector3(5,5,5),5);
}
}
void MoveCube(Vector3 inTargetPosition , float durationToReachTheTarget)
{
//What this line does is first take in the target position you want your physics object to reach as it first parameter, Then takes in the duration in which you want it to reach there.
rb.DoMove(inTargetPosition,durationToReachTheTarget);
}
This should help you. But remember this is only if you okay with adding an extra package. Personally this package is very good and I would recommend you this.
I just start making a game with Unity and Visual Studio, i have a bit confuse that why my Speed value always at zero even i set it equal to another variable.
public class Ballista : MonoBehaviour
{
public Transform firePoint;
public GameObject arrowPrefab;
public float speed = 10f;
public float startTimeHold;
public float holdDownTime;
public float arrowSpeed = 100f;
public float finalSpeed;
private void Update()
{
if (Input.GetKeyDown(KeyCode.M))
{
startTimeHold = Time.time;
}
if (Input.GetKeyUp(KeyCode.M))
{
holdDownTime = Time.time - startTimeHold;
Shoot();
finalSpeed = holdDownTime * arrowSpeed;
}
Debug.Log("holddowntimeis: " + holdDownTime);
Debug.Log("final speed is: " + finalSpeed);
}
public void Shoot()
{
GameObject arrowGO = (GameObject)Instantiate(arrowPrefab, firePoint.position, firePoint.rotation);
}
}
and this is the script that my speed value always zero
{
Rigidbody2D rgBody2D;
public GameObject ballista;
private float speed;
private void Awake()
{
rgBody2D = GetComponent<Rigidbody2D>();
}
private void Start()
{
\\ i though the code below will set my speed equal to my finalSpeed but it still 0
speed = ballista.GetComponent<Ballista>().finalSpeed;
Vector2 mousePos = Input.mousePosition;
Debug.Log("speed: " + speed);
rgBody2D.AddForce(mousePos * speed * Time.deltaTime);
}
void Update()
{
}
}
Different to what others said here you actually do NOT want to do it in Update.
Your goal here is to once give your newly spawned arrow a start velocity, not a continuous force.
The issue here is of other nature I think:
You are always spawning a new instance of your second script from a given prefab. This prefab seems to hold a reference to a Ballista prefab instance. At least you are never assigning a new value to the ballista! It might simply be a wrong reference where the finalSpeed is never updated.
Your are first doing the Shoot and after it set the finalSpeed -> even if it would be the correct reference you always get the wrong finalSpeed value!
I would actually change your two scripts in order toake your arrow instance being controlled by the Ballista instead of letting each spawned arrow poll the speed itself:
public class Ballista : MonoBehaviour
{
public Transform firePoint;
// By giving it the correct type here you don't need GetComponent later
public Rigidbody2D arrowPrefab;
public float startTimeHold;
public float arrowSpeed = 100f;
// I personally would add aax value and clamp since to fast RigidBodies can break collision detection eventually
public float maxArrowSpeed = 300;
private void Update()
{
if (Input.GetKeyDown(KeyCode.M))
{
startTimeHold = Time.time;
}
if (Input.GetKeyUp(KeyCode.M))
{
var holdDownTime = Time.time - startTimeHold;
// get the speed before you shoot
// Here personally I would clamp to be sure the arrow is never faster than maxArrowSpeed
// regardless of how long the user held the button pressed
var finalSpeed = Mathf.Min(holdDownTime * arrowSpeed, maxArrowSpeed);
Debug.Log("holddowntimeis: " + holdDownTime);
Debug.Log("final speed is: " + finalSpeed);
// pass in your speed
Shoot(finalSpeed);
}
}
private void Shoot(float speed)
{
// Instantiate anyway returns the type of the given prefab
// which now is a Rigidbody2D
var arrow = Instantiate(arrowPrefab, firePoint.position, firePoint.rotation);
// Directly set the speed from here
// -> your arrow doesn't even need an extra component
// Since you already spawned it with the correct rotation you maybe don't even need the mouse position thing
// AddForceRelative adds a force in the local space of the arrow so if the rotation is correctly
// this simply adds the force in its forward direction
// Note that also using Time.deltaTime actually only makes sense if you set something continuously
// For a one-time force you wouldn't need it, rather adjust your arrowSpeed field
arrow.AddForceRelative(Vector2.forward * speed);
}
}
Instead of using AddForce or AddForceRelative you could actually also simply set the target velocity:
arrow.velocity = Vector2.forward * speed;
Since you are not updateding it continuously this is totally fine and a lot easier to foresee the actual target speed since when adding a force you have to take the mass and frictions into account. You would than ofcourse have to adjust the arrowSpeed (and evtl maxArrowSpeed) accordingly to not anymore represent a force but an actual velocity in Units/second.
I hope I made my points clear enough, don't hesitate to ask if something stayed unclear ;)
speed = ballista.GetComponent().finalSpeed;
should Come in
void Update(){
}
block and not in
void Start() {
}
as void start is only run once and at that point in time the speed is zero
Hope it helped :)
Start happens only once.
Start is called on the frame when a script is enabled just before any
of the Update methods are called the first time.
Disregarding everything else, i am guessing you want it to update the speed every frame in Update
Update is called every frame, if the MonoBehaviour is enabled.
private void Update()
{
\\ i though the code below will set my speed equal to my finalSpeed but it still 0
speed = ballista.GetComponent<Ballista>().finalSpeed;
Vector2 mousePos = Input.mousePosition;
Debug.Log("speed: " + speed);
rgBody2D.AddForce(mousePos * speed * Time.deltaTime);
}
I'm making a small game in Unity and part of it it's that glass balls spawn every few seconds and they follow a path composed of targets, and the prefab has these targets specified:
Prefab picture
And here is the code:
public class move_to_target : MonoBehaviour
{
public GameObject[] target;
public float speed;
int current = 0;
float radius_target = 1;
// Update is called once per frame
void Update()
{
if (Vector3.Distance(target[current].transform.position, transform.position) < radius_target)
{
current = Random.Range(0, target.Length);
if (current >= target.Length)
{
current = 0;
}
}
transform.position = Vector3.MoveTowards(transform.position, target[current].transform.position, Time.deltaTime * speed);
}
}
So, whenever the ball spawns, it should go for the 1st target, then 2nd and finally the 3rd, but when I load the game, all the balls go to the horizon without stopping.
The problem seems to solve itself when I put the ball/fairy prefab in the scene and load the targets in the scene instead of the prefabs but that isn't the ideal solution.
How can I make it so the balls go to the targets in the scene?
Don't have enough rep to comment but this looks like it should work, what do you see if you Debug.Log(target[current].transform.position)?
If you don't need the target game object references, you could store Vector3[]'s instead.
A couple of things: You're randomizing your target every single update frame, so there is actually no progression from one target to the next in any kind of order. Is that intentional?
Also, you're moving the ball transform only when its distance is less than (<) a certain value. It seems more likely that you should be moving the ball as long as its distance is greater than (>) that value. Are you trying to go from a distance of greater than 1, to a distance of less than / equal to one?
If that's the case, maybe something like (untested):
public List<Transform> target = new List<Transform>();
public float speed;
int current = 0;
float radius_target = 1;
void Update()
{
if (Vector3.Distance(target[current].transform.position, transform.position) > radius_target)
{
transform.position = Vector3.MoveTowards(transform.position, target[current].transform.position, Time.deltaTime * speed);
} else {
current++;
if (current == target.Count)
{
current = 0;
}
}
}
But by this method the ball will seek to return to target[0] after target[2] (or whatever is the last one) and keep going forever in a loop. If you want to stop after reaching the last target you'd want to only execute this whole block if the current target < target.Count without returning to zero as it currently does.
And finally, your radius threshold of 1 could either work or not work, depending on how these targets are set up in the scene. This whole approach only works if they are all more than one unit (or radius_target units) away from each other.
I'm trying to move my enemy mob back to it's starting location but it's going somewhere no even remotely close to it's starting position. Here is what I have
private Vector3 startingPosition;
void Start ()
{
startingPosition = transform.TransformPoint(Vector3.zero);
}
In the update position I do some checks to make sure when we lose chase range for the mob to run back to it's starting location with this. Well I'm trying to get it to run back to it's starting location
void Return2Location()
{
Debug.Log("Start" + startingPosition);
Debug.Log("Current " + transform.position);
if (transform.position != startingPosition)
{
Debug.Log("returning");
controller.Move(startingPosition);
animation.CrossFade(runClip.name);
}
}
If i set transform.position to startingPosition it's placed back to it's original location.
Update
Tried this also
Transform newLocation = new GameObject().transform;
newLocation.position = startingPosition;
newLocation.rotation = startingRotationPosition;
controller.SimpleMove(newLocation.forward * speed);
The problem there seems to be your use of Move(). Move takes a (scaled) direction as an argument. Not a position it needs to go to. So if you want to move it towards your starting position, you'll have to do something like the following instead:
Vector3 direction = startingPosition - Transform.position;
direction.Normalize(); //Make sure to have a pure "unit" direction
controller.Move(direction * speed * Time.deltaTime);
That should take you in the right direction at your desired speed. Of course you'll have to make sure you don't overshoot your target, but you get the idea.