Move GameObject to Position, Waiting, and then Moving to Another Position
Greetings, I have been playing around with WaitForSeconds and I can't seem to get this right. The script I'm trying to create is for a simple simulation game. This particular script is for a customer. The outcome I am trying to get is for an object to go to a certain object, stop and wait for 30 seconds and then go to another position. Right now the object does not move at all. Could I get some pointers about where I'm going wrong. Perhaps there is a better solution for what I am trying to achieve?
{ public Transform target; public Transform target2; public float speed;
void Start()
{
float step = speed * Time.deltaTime;
transform.position = Vector3.MoveTowards(transform.position, target.position, step);
StartCoroutine(WaitAndGo());
}
IEnumerator WaitAndGo()
{
yield return new WaitForSeconds(30);
float step = speed * Time.deltaTime;
transform.position = Vector3.MoveTowards(transform.position, target2.position, step);
}
}
Hi, about the [SerializeField]
tag. Basically you use it when you are going to declare a private variable at the class level (can't do it inside functions). What it does is 1. exposes a private variable in the inspector, and 2. Ensures that the information of the variable will be remembered by the inspector (there's some times where you'll need to Serialize variables just for that if you are making custom editors). You basically use it like this:
[SerializeField]
private bool mySwitch;
You can also safely put that in one line if you feel like it:
[SerializeField]private bool mySwitch;
If for some reason you are having trouble you can just use public variables, they have the same effect (they are serialized by default), but as I mentioned they are visible by other scripts. It's usually bad practice to make every variable public because 1. when you then try to access the script from the outside and you use intellisense you'll get a huge list full of variables and methods, many of which you don't want others to run from the outside, and 2. To make sure there are no duplicated names which may clash later on, specially if you have many third party assets and you have not created your own namespace.
Edit: you can read more about it here http://forum.unity3d.com/threads/serialization-best-practices-megapost.155352/
Thank you! Between your explanation and the link you provided, I am pretty sure I understand. I'm going to have to play with it a bit more before I'm able to get it to work as intended, but I am better off and more on track now. Thank you for your time!
Answer by 3tai · Sep 12, 2016 at 02:36 AM
You have nothing indicating when the transform has arrived and so the movement functions are only moving for single frames. It will move for a single frame when Start()
is called, starts the coroutine, wait for 30 seconds, them take another step towards target2.position
. The Vector3.MoveTowards
needs to be in Update()
or a looped Coroutine for it to be run every frame, and you'll need to add in a way to detect whether the transform has arrived so that it knows when to stop Vector3.MoveTowards
.
the following code is probably full of errors but it'll give the general idea of what you need to do:
public IEnumerator $$anonymous$$ovementCoroutine() {
bool arrived = false;
while(!arrived)
{
Vector3.$$anonymous$$ovetowards(thing1, thing2, step)
if(Vector3.Distance(thing1, thing2) == 0) arrived = true;
yield return null;
}
if(arrived)
{
//do something when it arrives
}
}
Answer by ComicGirl · Sep 12, 2016 at 03:14 AM
Okay, this is great! But I am running into an error that says "Argument 1: Cannot Convert from 'unityEngine.Transform to "UnityEngine Vector 3"
You'll have to bear with me for I am very new and am still looking over the Scripting API. This is like taking a foreign language haha. Thank you for your time and patience thus far.
Okay, so I got rid of the errors by adding in ".position" to the targets. The script no longer has errors, but the object is not moving.
I kinda tinkered with the script but I don't think this is right.
{ public float speed; public Transform target1; public Transform target2; void Start() {
}
public IEnumerator $$anonymous$$ovementCoroutine()
{
float step = speed * Time.deltaTime;
bool arrived = false;
while (!arrived)
{
Vector3.$$anonymous$$oveTowards(transform.position, target1.position, step);
if (Vector3.Distance(transform.position, target1.position) == 0) arrived = true;
yield return null;
}
if (arrived)
{
yield return new WaitForSeconds(5);
Vector3.$$anonymous$$oveTowards(transform.position, target2.position, step);
}
}
}
Have you verified that the $$anonymous$$ovementCoroutine() started? the logic looks good to me. Double check all the other variables involved too (e.g. is speed set to 0?) add some debug logs like Debug.Logs("$$anonymous$$ovementCoroutine() started main while loop!");
here and there and that'll help you figure out how far your scripts have run to. (the messages show up in the console)
The second part of the movement coroutine will do the thing as your first snippet of code where it only goes for one frame, because it's outside of a while() loop. You'll have to reset arrived
to false
and have another while(!arrived)
loop. Later on as you develop your code you'll probably set up something like public bool IEnumerator $$anonymous$$ovementCoroutine(GameObject targetObject)
that returns true only when the object has arrived, so you can use a second Coroutine to easily set up multiple destinations.
oh, I incorreclty used Vector3.$$anonymous$$oveTowards()
in my sample code.
It should be transform.position = Vector3.$$anonymous$$oveTowards(param1, param2, param3)
Brilliant! The object is moving again. Okay so I have updated the script. This is what I have thus far-
void Start () { StartCoroutine($$anonymous$$ovementCoroutine()); } public IEnumerator $$anonymous$$ovementCoroutine() {
float step = speed * Time.deltaTime;
bool arrived = false;
Debug.Log("$$anonymous$$ovementCoroutine() started main while loop!");
while (!arrived)
{
transform.position = Vector3.$$anonymous$$oveTowards(transform.position, target1.position, step);
if (Vector3.Distance(transform.position, target1.position) == 0) arrived = true;
yield return null;
}
if (arrived)
{
yield return new WaitForSeconds(5);
transform.position = Vector3.$$anonymous$$oveTowards(transform.position, target2.position, step);
}
So we have the object going to the target1 position, but it is still not going to the next. I believe this is because as you said it is still in "void Start()" so it is only calling it one time. I can see the object move for a split second and then it stops. How would I incorporate a "void Update()" to make it happen every frame? I attempted some tinkering but had no success.
Again thank you for your time, this has already been a tremendous help!
Answer by Alverik · Sep 12, 2016 at 10:08 AM
Looking at your latest attempt, I notice you are trying to verify if a float distance equals to 0. That has a lot of chances for failing. It may be that the number never gets to a perfect 0 (could end up 0.011111, etc), so the loop never ends. This is the kind of place where you would put a debug.log inside the loop to verify if it's still running, and if needed find a better condition for exiting the loop (you could compare if both transform.positions are the same or you could check if the current value is less than <0.1f instead). Now the second movement is just wrong anyways. If you are planning to have the same kind of operation it stands to reason that you'd need another while loop for moving the second time (just like the first one), just an if conditional won't do. As it is now, the system will wait 5f seconds then move the object a tiny, tiny bit and the coroutine will finalize, stopping forever.
Also, seeing how you'd repeat the code for the other segment I would just ins$$anonymous$$d create a single method which takes a public array of gameobjects. And use a foreach to get the current object to move to the targets (the foreach would have the same while loop inside, and when it's finished it would just repeat for the next game object). That way the user would just need to change the number of objects to use as markers and place them in the world. Something along the lines of:
/// <summary>
/// $$anonymous$$oves an object to an specified set of targets.
/// </summary>
/// <param name="markers">the objects to be used as markers.</param>
/// <param name="durations">the movement duration for each marker.</param>
/// <param name="waitTime">the wait time before moving to the next marker.</param>
/// <returns></returns>
public IEnumerator<float> $$anonymous$$oveTo$$anonymous$$arker(GameObject[] markers, float[] durations, float[] waitTime)
{
//for each target
foreach (var target in markers)
{
//ready the vars for current target's loop
bool shouldContinue = true;
int i = 0;
//start moving the object
do
{
transform.position = Vector3.$$anonymous$$oveTowards(transform.position, target.transform.position, durations[i]);
if (transform.position == target.transform.position || Vector3.Distance(transform.position, target.transform.position) <= 0.1f) shouldContinue = false;
//release control for a frame
yield return 0f;
} while (shouldContinue);
//release control for a frame
yield return Ti$$anonymous$$g.WaitForSeconds(waitTime[i]);
}
}
You'll have to change the yields and the coroutine declaration a bit to test it, because I use $$anonymous$$EC coroutines (Or just get $$anonymous$$ore Efficient Coroutines in the asset store. It's free).
Thank you for your reply. So if I understand correctly use a public array of objects rather than what I was using before? I'm attempting to read the code above but I'm having some difficulty in understanding a few things. In attempting to understand I duplicated your work in a script. The only error the script is reporting is in the last line
yield return Ti$$anonymous$$g.WaitforSeconds(waitTime[I]);
the error is saying that Ti$$anonymous$$g does not exist in the current context.
I think I understand how this will be more effective in what I'm trying to do. That this will enable me to have an object move to multiple positions and better organize where those positions are correct? Thank you again for your time :)
Hi, sorry for taking a while to respond. Yes, as I mentioned in my previous post I had $$anonymous$$EC coroutines in $$anonymous$$d when I wrote that up (sorry, I just use it so much). The Ti$$anonymous$$g class is part of $$anonymous$$EC coroutines. If I remember correctly, you need to use yield return new WaitForSeconds();
for unity coroutines ins$$anonymous$$d. Also, $$anonymous$$EC coroutines need the IEnumerator to return a float, so you may not need to have IEnumerator <float>
but just IEnumerator
. For examples regarding Unity coroutines and the yields you can use on them you can check this page: https://docs.unity3d.com/$$anonymous$$anual/Coroutines.html
Edit: For the same reason (about unity coroutines having no need to return floats) the yield return statements can simple be turned into yield return null;
Edit 2: And yes, having arrays will allow you to have as many targets as you need. Just remember to expose the arrays in the inspector either using the [SerializeField]
tag above or right before a private variable or by making the variable public (Just try to bear in $$anonymous$$d, that making a variable public is best left for times when you are planing to access or modify said variable from other scripts, if you are not going to do that, then just make it private and tag it to expose it on the inspector)
Answer by guinoalaskapp · Jun 08, 2021 at 11:49 PM
Hello, Similar Isue, i have a code to change GameObject.transform.position to another position after counting. The isue is when I run the code, it changes the position first even when the WaitForSeconds() is before it.
here is the code of my IEnumerator():
IEnumerator MyCoroutine(){
yield return new WaitForSeconds(5);
transform.position = Other.position;
}