Search code examples
c#unity-game-enginecollision-detectiongame-physics

EnemyAI rotating on Y-axis in opposite direction


After a battle flag has gone off and the player has entered an enemy's battle zone I want to keep track of the player when it moves and make sure the enemy's Z-axis is always pointing towards the player.

Upon the player entering the battle zone, the enemy automatically marks the player as its target However there is a chance that the Z-axis may not be pointing towards the enemy's target when the battle is first initiated. I decided to shoot a ray cast from the Z-axis of the enemy when the battle flag goes off and rotate the enemy until the ray and the player intersect thus telling me that enemy's Z-Axis is lined up correctly.

the enemy always has a reference to its current target. My idea was that as once the enemy spots its target initially, it can save the position of the most recent spotting and then take the delta of the CurrentTargetPosition.x and the LastSpottingPosition.x If the delta is positive rotate right and if the delta is negative rotate left.

THE ISSUE: my math or logic must be flawed somewhere because although it works sometimes, other times the enemy will randomly rotate itself in the opposite direction and the ray cast will essentially take the long route to hit the player instead of the shortest route as intended. Code Below.

//create ray from enemy position orgin and point it on the Z-Axis
    Ray ray = new Ray(CurrentEnemyTransform.position, CurrentEnemyTransform.forward);
    RaycastHit hit;

    if (Physics.Raycast(ray, out hit, 50f))
    {
        Debug.DrawLine(ray.origin, hit.point);

        //ray hits current enemy target
        if (hit.transform == CurrentTargetTransform)
        {
            //check if enemy has seen its target before while in this battle
            //or is it the first time
            if (enemy.hasTargetLastPosition)
                //replace old last seen position with most recent spotting
                enemy.ReplaceTargetLastPosition(hit.transform.position);
            //or add the first spotting of target while in this battle
            else enemy.AddTargetLastPosition(hit.transform.position);
        }
        //ray is not intersectiing with the enemy's current target so rotate to look for it
        if (hit.transform != CurrentTargetTransform)
        {
            //check if enemy has seen its target once before in battle
            if (enemy.hasTargetLastPosition)
            {
                //since last spotting has my target moved more to the left or right?
                if (enemy.targetLastPosition.value.x > CurrentTargetTransform.position.x)
                {
                    //rotate right
                    CurrentEnemyTransform.Rotate(-Vector3.up, 5f, Space.Self);
                }
                        //rotate left
                else CurrentEnemyTransform.Rotate(Vector3.up, 5f, Space.Self);
            }
            //never seen target yet during this battle so default rotation to right till you found it
            else CurrentEnemyTransform.Rotate(-Vector3.up, 5f, Space.Self);
        }

Solution

  • An obvious problem is this line:

    if (enemy.targetLastPosition.value.x > CurrentTargetTransform.position.x)
    

    This supposedly checks for whether the target is on the right of the enemy's aim position. But it does not work for all possible positions. (Picture flipping the coordinates in the Z-axis - does it still work then?)

    A correct way to do this is to use the 2D cross-product:

    (Ax, Az) x (Bx, Bz) = Ax * Bz - Az * Bx

    This is positive if the vector A is at a clockwise rotation with respect to B, and vice versa.

    (Also, we need to examine the relative positions with respect to the enemy rather than their world positions; to do this simply subtract CurrentEnemyTransform.position)

    private static bool isOnRight(Vector3 a, Vector3 b)
    {
        return a.x * b.z - a.z * b.x > 0.0f;
    }
    
    //...
    
    // is last known position on the right of the actual position?
    if (isOnRight(enemy.targetLastPosition - CurrentEnemyTransform.position,
                  CurrentTargetTransform.position - CurrentEnemyTransform.position))
    {
        // rotate *left*, not right
        CurrentEnemyTransform.Rotate(Vector3.up, -5f, Space.Self);
    }
    else CurrentEnemyTransform.Rotate(Vector3.up, 5f, Space.Self);
    

    There are other improvements which can be made to this, e.g. making the enemy aim exactly at the target instead of doing so to within a 5-degree margin. But this should do for a crude approach.