halfheartd
halfheartd

Reputation: 69

Unity Character Moving Too Quickly

I'm attempting to write an AI for an enemy so that every second he moves to a new available square. I have him placed at a point on a tile map with a script attached to him. Since I don't want him to be trying to pick a new direction every frame I did some research and it appears the best option for this is to do a coroutine so that it's an action that doesn't execute every single frame. I've been trying for hours writing this different ways in the update function, in IEnumerator, having all sorts of checks turned on, but so far my character always seems to either be bouncing around like a pingpong ball or standing still. This is the code I have in the coroutine right now.

IEnumerator iMove()
{
    List<string> available = new List<string>();

    available.Add("North");
    available.Add("South");
    available.Add("East");
    available.Add("West");

    if (tilemap.GetTile(new Vector3Int(currentX, currentY + 1, 0)) == null || tilemap.GetTile(new Vector3Int(currentX, currentY + 1, 0)).name != floor || myStack.Contains(new int[] { currentX, currentY + 1 }))
    {
        available.Remove("North");
    }
    if (tilemap.GetTile(new Vector3Int(currentX, currentY - 1, 0)) == null || tilemap.GetTile(new Vector3Int(currentX, currentY - 1, 0)).name != floor || myStack.Contains(new int[] { currentX, currentY - 1 }))
    {
        available.Remove("South");
    }
    if (tilemap.GetTile(new Vector3Int(currentX - 1, currentY, 0)) == null || tilemap.GetTile(new Vector3Int(currentX - 1, currentY, 0)).name != floor || myStack.Contains(new int[] { currentX - 1, currentY }))
    {
        available.Remove("West");
    }
    if (tilemap.GetTile(new Vector3Int(currentX + 1, currentY, 0)) == null || tilemap.GetTile(new Vector3Int(currentX + 1, currentY, 0)).name != floor || myStack.Contains(new int[] { currentX + 1, currentY }))
    {
        available.Remove("East");
    }

    if (available.Count > 0)
    {
        int random = Random.Range(0, (available.Count));
        string direction = available[random];
        Debug.Log("attempting to move..." + direction);
        switch (direction)
        {
            case "North":
                moveVector.y = 1f;
                moveVector.x = 0;
                currentY += 1;
                break;
            case "South":
                moveVector.y = -1f;
                moveVector.x = 0;
                currentY -= 1;
                break;
            case "East":
                moveVector.y = 0;
                moveVector.x = -1f;
                currentX += 1;
                break;
            case "West":
                moveVector.y = 0;
                moveVector.x = 1f;
                currentX -= 1;
                break;
        }
        Debug.Log("current goal:" + currentX + "," + currentY);
        myStack.Push(new int[] { currentX, currentY });
    }
    else
    {
        int[] test = myStack.Pop();

        currentX = test[0];
        currentY = test[1];
    }

    yield return new WaitForSeconds(1);
    transform.position = new Vector3Int(currentX, currentY, 0);
    moveState = MoveState.Walk;
    anim.SetFloat("moveX", moveVector.x);
    anim.SetFloat("moveY", moveVector.y);
    anim.SetBool("isMoving", true);
    yield return new WaitForSeconds(1);
}

Upvotes: 0

Views: 80

Answers (1)

Basile Perrenoud
Basile Perrenoud

Reputation: 4110

It depends how you start the coroutine. If you start is in Update, of course it will be started every frame an you will have many coroutines running at the same time.

Try starting it only once:

public void Start()
{
    StartCoroutine(iMove());
}

Now, it will execute once, so you will see your player moving once. If you want to repeat it, you can:

1) Add a while loop in the coroutine

IEnumerator iMove()
{
    while(true) // or a boolean condition that you can set yourself
    {
        // Your code
        // ...
        yield return new WaitForSeconds(1f);
    }
}

That way it will wait a second, the go back in the beggining of the loop and execute again

The other possibility is to start the coroutine again when it is done:

IEnumerator iMove()
{
    // Your code
    // ...
    yield return new WaitForSeconds(1f);

    StartCoroutine(iMove());
}

Finally, note that this coroutine will be destroyed when you destroy the game object. You can also stop it by calling StopAllCoroutines()

Upvotes: 1

Related Questions