2

I'm coding my games boss behaviour and in the final stage of the battle the boss is supposed to charge towards the player and then move back to its original position. Wait for 5 seconds and then do the same.

I tried to achieve this using coroutines and Vector2.MoveTowards() but am not getting the desired effect, first off the boss does not "Move Towards" the player but instantly appears at the targetPosition and then just stays there, does not move back.

Below is my code:

private Vector2 chargeTarget;
private Vector2 tankStartPosition;

void Start()
{
    chargeTarget = new Vector2(-5.0f, transform.position.y);
    tankStartPosition = transform.position;
}

void Update()
{
    if (Time.time > nextCharge)
    {
        StartCoroutine(TankCharge());
        nextCharge = Time.time + chargeRate;
    }
}

IEnumerator TankCharge()
{
    transform.position = Vector2.MoveTowards(tankStartPosition, chargeTarget, Time.deltaTime * chargeSpeed);

    transform.position = Vector2.MoveTowards(chargeTarget, tankStartPosition, Time.deltaTime * returnSpeed);
}

Any idea what I am doing wrong here? And how to get my desired action?

Thank you

Ben Rubin
  • 6,909
  • 7
  • 35
  • 82
StuckInPhDNoMore
  • 2,507
  • 4
  • 41
  • 73

1 Answers1

1

Calling MoveTowards once only moves the game object once during that iteration of the game loop. Calling MoveTowards once doesn't move the game object all the way to its target (unless the maxDistanceDelta parameter is big enough to move the game object to its target in one iteration).

If the boss is instantly appearing at the target, I'm guessing your chargeSpeed is too big.

What you want to do is call MoveTowards once per Update cycle. However, the way you're doing your coroutine, the coroutine will only move the game object once and then exit. Normally coroutines will have a loop within them (otherwise the coroutine will exit after running once). Something like this:

IEnumerator TankCharge()
{
    while (Vector3.Distance(transform.position, chargeTarget.position) > Mathf.Epsilon)
    {
        // Adjust this so this game object doesn't move the entire
        // distance in one iteration
        float distanceToMove = Time.deltaTime * chargeSpeed;

        transform.position = Vector3.MoveTowards(transform.position, chargeTarget.position, distanceToMove)

        yield return null;
    }
}

However, for your situation, you don't really need a coroutine. You can just do this directly in Update()

    private bool returnToStart = false;
    private float timer;

    void Update
    {
        float distanceToMove = Time.deltaTime * chargeSpeed;

        if (timer <= 0)
        {
            if (!returnToStart)
            {
                transform.position = Vector3.MoveTowards(transform.position, chargeTarget.position, distanceToMove)

                // Target reached?  If so, start moving back to the original position
                if (Vector3.Distance(transform.position, chargeTarget.position) <= Mathf.Epsilon)
                {
                    returnToStart = true;
                    this.timer = this.chargeRate;
                }
            }
            else
            {
                transform.position = Vector3.MoveTowards(transform.position, tankStartPosition.position, distanceToMove)

                // Original position reached?  If so, start moving to the target
                if (Vector3.Distance(transform.position, tankStartPosition.position) <= Mathf.Epsilon)
                {
                    returnToStart = false;
                    this.timer = this.chargeRate;
                }
            }
        }
        else
        {
            this.timer -= Time.time;
        }
    }    
Ben Rubin
  • 6,909
  • 7
  • 35
  • 82
  • it should rather be `if(transform.position == tankStartPosition.position)` with a double `==`. It is also worth mentioning that the [`==`](https://docs.unity3d.com/ScriptReference/Vector3-operator_eq.html) operator for `Vector3` actually uses a precision of `0.00001` for equality so the object might not reach exactly the correct positions. – derHugo May 24 '19 at 08:35
  • @BenRubin Thanks for the solution. I tried your suggested method of within ``Update`` without coroutine but it is not working properly. It is moving incrementally towards the player at every time ``if (Time.time > nextCharge)`` is true. at lower ``chargeSpeed`` it moves incrementally and at higher values it covers the gap in two or three instant moves. when it reaches target the ``bool`` is set ``true`` and then it starts moving incrementally backwards. Its not at the desired effect of ``MoveTowards`` :( – StuckInPhDNoMore May 25 '19 at 00:41
  • 1
    @StuckInPhD You shouldn't have the `if (Time.time > nextCharge)` part in there if you're using my method of doing it all with `Update` with no coroutine. If it's still not working after you remove that, update your question with your new code and I'll see if I can give you an answer. – Ben Rubin May 25 '19 at 01:35
  • @BenRubin Yes, you're right, after removing the ``if (Time.time > nextCharge)`` condition it is working perfectly. But then, with this ``if`` condition how will I have control over time between charges? Right now it just keeps going back and forth. Which is good, but I want the player to breathe a little after a charge attack. Thanks – StuckInPhDNoMore May 25 '19 at 02:27
  • 1
    @StuckInPhD I edited my answer to include a delay between charges. – Ben Rubin May 25 '19 at 14:41