9

I am learning Unity from a Swift SpriteKit background where moving a sprite's x Position is as straight forward as an running an action as below:

let moveLeft = SKAction.moveToX(self.frame.width/5, duration: 1.0)
let delayAction = SKAction.waitForDuration(1.0)
let handSequence = SKAction.sequence([delayAction, moveLeft])
sprite.runAction(handSequence)

I would like to know an equivalent or similar way of moving a sprite to a specific position for a specific duration (say, a second) with a delay that doesn't have to be called in the update function.

Programmer
  • 121,791
  • 22
  • 236
  • 328
Nullititiousness
  • 361
  • 1
  • 4
  • 14
  • I highly recommend using an extension like iTween for this kind of controlled animations – caulitomaz Apr 25 '16 at 20:21
  • 1
    @caulitomaz Does not need iTween or plugin for that.He simply need to learn Coroutune. – Programmer Apr 25 '16 at 21:36
  • 1
    do not use iTween here, forget that. – Fattie Apr 26 '16 at 12:50
  • Hi @Nullititiousness. What you fundamentally have to get with is that Unity is a game engine, hence: **is a frame based system**. Everything is about frames. (Note indeed that in iOS, since it's not fundamentally frame based, they screw about and do things "frame-like" to compensate for the fact that it's just not a frame-based system, but sometimes you need things "like that".) In any event, if you say "I want to 'move' something". What are you **actually saying**? You're saying you want to do something every frame - right? If you start thinking that way it's easy. – Fattie Apr 26 '16 at 12:54

3 Answers3

21

gjttt1's answer is close but is missing important functions and the use of WaitForSeconds() for moving GameObject is unacceptable. You should use combination of Lerp, Coroutine and Time.deltaTime. You must understand these stuff to be able to do animation from Script in Unity.

public GameObject objectectA;
public GameObject objectectB;

void Start()
{
    StartCoroutine(moveToX(objectectA.transform, objectectB.transform.position, 1.0f));
}


bool isMoving = false;

IEnumerator moveToX(Transform fromPosition, Vector3 toPosition, float duration)
{
    //Make sure there is only one instance of this function running
    if (isMoving)
    {
        yield break; ///exit if this is still running
    }
    isMoving = true;

    float counter = 0;

    //Get the current position of the object to be moved
    Vector3 startPos = fromPosition.position;

    while (counter < duration)
    {
        counter += Time.deltaTime;
        fromPosition.position = Vector3.Lerp(startPos, toPosition, counter / duration);
        yield return null;
    }

    isMoving = false;
}

Similar Question: SKAction.scaleXTo

Community
  • 1
  • 1
Programmer
  • 121,791
  • 22
  • 236
  • 328
  • Thanks! Can `Vector3.Lerp` also be used on a `rotation`, imagine if you want to also have the object rotate from `startRotation` to `endRotation` (x,y,z) – vikingsteve Jun 13 '17 at 12:06
  • @vikingsteve Yes. You can use `Vector3.Lerp` to change the `transform.eulerAngles` if it is an incremental rotation. If you just want to go from rotationA to rotationB then `Quaternion.Lerp` should be used to change `transform.rotation`. The rest of the code remains the-same. See my [other](https://stackoverflow.com/a/37588536/3785314) answer for that solution. – Programmer Jun 13 '17 at 12:12
  • It's not so much that Unity threads are sheit as that Unity has to support 20 different platforms with some reasonable guarantees about behavior. In that light, they've done a great job IMO. –  May 24 '19 at 02:46
3

The answer of git1 is good but there is another solution if you do not want to use couritines.

You can use InvokeRepeating to repeatedly trigger a function.

float duration; //duration of movement
float durationTime; //this will be the value used to check if Time.time passed the current duration set

void Start()
{
    StartMovement();
}

void StartMovement()
{
    InvokeRepeating("MovementFunction", Time.deltaTime, Time.deltaTime); //Time.deltaTime is the time passed between two frames
    durationTime = Time.time + duration; //This is how long the invoke will repeat
}

void MovementFunction()
{
    if(durationTime > Time.time)
    {
        //Movement
    } 
    else 
    {
        CancelInvoke("MovementFunction"); //Stop the invoking of this function
        return;
    }
}
Fattie
  • 27,874
  • 70
  • 431
  • 719
Mennolp
  • 424
  • 4
  • 17
  • 1
    It's true that this works perfectly well, and everyone should learn to use InvokeRepeating. Normally, it's idiomatic to use a coroutine here. – Fattie Apr 26 '16 at 12:51
  • Note that `Time.deltaTime` is the time passed since the last frame was rendered => This value might be inaccurate during the first frame (when `Start` is called) – derHugo Jul 18 '22 at 07:29
1

You can use co-routines to do this. To do this, create a function that returns type IEnumerator and include a loop to do what you want:

private IEnumerator foo()
{
    while(yourCondition) //for example check if two seconds has passed
    {
        //move the player on a per frame basis.
        yeild return null;
    }
}

Then you can call it by using StartCoroutine(foo())

This calls the function every frame but it picks up where it left off last time. So in this example it stops at yield return null on one frame and then starts again on the next: thus it repeats the code in the while loop every frame.

If you want to pause for a certain amount of time then you can use yield return WaitForSeconds(3) to wait for 3 seconds. You can also yield return other co-routines! This means the current routine will pause and run a second coroutine and then pick up again once the second co-routine has finished.

I recommend checking the docs as they do a far superior job of explaining this than I could here

Danny Herbert
  • 2,002
  • 1
  • 18
  • 26