0
\$\begingroup\$

I am working on a "quick project" to get my feet wet and to put something finished in my projects. I want to make an enemy that constantly tries to be aligned with the player object, but doesn't always move directly toward it, and which only moves in 8 cardinal directions. The idea is that if the enemy is not aligned with the player on the X axis, then the enemy will attempt to rectify by moving on the X axis until the player and enemy object are on the same x axis. At that point, the enemy should not move at all on the X axis unless the player changes its own X-position.

I coded this as the following:

    void OnEnable()
    {
        playerRef = FindObjectOfType<Player>();
    }

 void Update()
    {
        if (playerRef != null)
        {
            UpdatePlayerLocation();

            TryMove(playerPosition);
        }
        else
        {
            playerRef = FindObjectOfType<Player>();
        }
    }

    void UpdatePlayerLocation()
    {
        playerPosition = playerRef.transform.position;
    }

    Vector3 GetUpdatedFacing(Vector3 playerPosition)
    {
        Vector3 newFacing = Vector3.zero;

        if (transform.position.x != playerPosition.x)
        {
            newFacing.x = transform.position.x < playerPosition.x ? 1 : -1;
        }
        else { newFacing.x = 0; }

        if (transform.position.z != playerPosition.z)
        {
            newFacing.z = (transform.position.z < playerPosition.z ? 1 : -1);
        }
        else { newFacing.z = 0; }

        return newFacing;
    }

    void TryMove(Vector3 playerPosition)
    {
        //update facing
        transform.forward = GetUpdatedFacing(playerPosition);

        MoveTowardPlayer();
    }

    void MoveTowardPlayer()
    {
        transform.position += transform.forward * movementSpeed * Time.deltaTime; //movementSpeed = 5f for now
    }

This works to a point, except that as soon as the enemy becomes aligned with the player object (on either axis), rather than stop moving in the now-aligned axis, the enemy proceeds to rapidly move and rotate back and forth between the positive and negative facings of that aligned axis. For practical needs of the enemy, it is aligned and shouldn't be moving on that axis anymore. Despite this, it seems to rapidly go back and forth between being misaligned in very small increments.

I tried implementing a solution to this, namely, I expanded the check for the correction in movement to add a "buffer" of being off by 0.1f. Meaning that, if the enemy is within 0.1f in either direction of the player, then the enemy will ignore that input direction. This seems to produce the intended effect.

Vector3 GetUpdatedFacing(Vector3 playerPosition)
{
    Vector3 newFacing = Vector3.zero;

    if (transform.position.x != playerPosition.x)
    {
        newFacing.x += transform.position.x < playerPosition.x - 0.1f ? 1 : 0;
        newFacing.x += transform.position.x > playerPosition.x + 0.1f ? -1 : 0;
    }
    else { newFacing.x = 0; }

    if (transform.position.z != playerPosition.z)
    {
        newFacing.z += transform.position.z < playerPosition.z - 0.1f ? 1 : 0;
        newFacing.z += transform.position.z > playerPosition.z + 0.1f ? -1 : 0;
    }
    else { newFacing.z = 0; }


    return newFacing;
}

My only concerns are that:

  1. I would prefer based on how I'm seeing it, that the enemy weights movements in cardinal directions above those in diagonal directions. At a future implementation, I intend to do that by having a "wall check" which will prevent movement in a given direction if a wall is blocking the path ahead. That said, I was looking to encourage cardinal movement more than others. Call it a peculiar desire if you want, I would like to see some ideas for how that could be done.

  2. I am concerned about the 0.1 buffer being needed. Maybe less that the buffer is 0.1f, the real issue to me is that the buffer is needed at all. I am looking to see if any different implementation of the concept could improve the behavior so that this buffer wasn't necessary.

Maybe this is a bit more open-ended than some questions, if this is inappropriate, then I apologize for the format.

\$\endgroup\$
1
\$\begingroup\$

The reason you see the rapid back and forth movement as it tried to align itself to the player's axis is because of overshoot, as you probably guessed. I reconstructed what I believe you are trying to do using Vector3.MoveTowards instead. This function is useful because it does not overshoot. Unfortunately this code is untested so let me know if it works.

To make it easier on myself, any instance where I refer to the "Target" you can think of it as the player, and I have chosen to define your 8 directional movement as "Ordinal directions". I'm also not sure why the formatting is so weird on this, I apologize.


Transform Target;
float speed = 5f;
Vector3 direction;

void OnEnable(){ Target = GameObject.Find("Player").GetComponent(); }

void Update() { if (Target != null{ direction = GetOrdinalDirection(Target.position);

    MoveTowardsTargetAxis();
}
else{
    Target = GameObject.Find("Player").GetComponent<Transform>();
}

}

Vector3 GetOrdinalDirection(Vector3 targetPosition){ Vector3 directionToTarget = (targetPosition - transform.position).normalized; Vector3 ordinalDirection = Vector3.zero; if (directionToTarget.x != 0f){ ordinalDirection.x = directionToTarget.x / Mathf.Abs(directionToTarget.x); //If not 0, divide by the absolute of itself to make it 1 or -1 } if (directionToTarget.z != 0f){ ordinalDirection.z = directionToTarget.z / Mathf.Abs(directionToTarget.z); //If not 0, divide by the absolute of itself to make it 1 or -1 }

return ordinalDirection.normalized; //normalize the ordinal direction to make sure diagonal speed isn't faster

}

void MoveTowardsTargetAxis(){ Vector3 targetXaxis = new Vector3(Target.position.x, transform.position.y, transform.position.z); Vector3 targetZaxis = new Vector3(transform.position.x, transform.position.y, Target.position.z);

//Here you use Vector3.MoveTowards to make your x position move directly towards the Player's x axis, and your z position directly
// towards their z. Vector3.MoveTowards does not overshoot so you will not see your vibrating effect from before.
transform.position = new Vector3((Vector3.MoveTowards(transform.position, targetXaxis, speed * direction.x * Time.deltaTime)).x,
                                  transform.position.y,
                                 (Vector3.MoveTowards(transform.position, targetZaxis, speed * direction.z * Time.deltaTime)).z);

}

Edit: This does not include a weighted result towards a path that goes in cardinal directions. To be honest that's a bit of a strange concept because preferring cardinal directions would just mean you are always moving cardinally, since any point can be reached that way.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.