Implementing a FOV (Field Of View)

Hi! I made a FOV that, basically it’s a cone of view (in the Enemy GO) if anyone needs it.

You will need these variables

    [Header("General Parameters for FOV")]
    public float heightSight = 1.65f; //Look from the eyes
    public float heightPlayer = -0.15f; //Height distance from the enemy eyes to the player's neck

    [Header("FOV Range")]
    public float sightDistance = 8.0f; //Aggro distance
    public float patrolFovAngle = 160f; //FOV Angle
    float FOVStart; //Auxiliary varible to call the FOV method
    float FOVEnd; //Auxiliary varible to call the FOV method
    RaycastHit hit; //Check if player is in FOV.
    RaycastHit[] hits; //Detect if there are more objects in the way (player is hiding, or something)

    [Header("Layer Parameters and TAG")]
    public int ignoreLayer = 13; //Ignore Enemy Layer: Enemies can block enemie's FOV
    public string playerTag = "Player"; //Auxiliary variable

    //Patrol location variables
    private Vector3 lastKnownPosition; //Player's last known Position

Start method

    private void Start()
    {
        lastKnownPosition = transform.position; //The enemy will look into his eyes
        FOVEnd = (patrolFovAngle/2f); //if FOV Angle is 160°, this is -80°
        FOVStart = -FOVEnd; //if FOV Angle is 160°, this is 80°
    }

Then, you need to call the following methods: They will return a “true” or “false” if the Player is in the FOV

    private bool isInFOV(float FOVangleStart, float FOVangleEnd, GameObject player)
    {
        Vector3 targetDir = player.transform.position - transform.position; //Get the Vector of Direction of player relative to this gameObject
        float angleToPlayer = (Vector3.Angle(targetDir, transform.forward)); //Get the relative vector of player and this gameObject
        float inRange = Vector3.Distance(player.transform.position, transform.position); //Get distance between this gameObject and the Player

        Vector3 heightSelf = new Vector3(0f, heightSight, 0f); //aux variable to detect height of this gameObject relative to the player
        Vector3 heightTarget = new Vector3(0f, heightPlayer, 0f); //aux variable to detect height of this gameObject relative to the player

        Ray FOVRay = new Ray(transform.position + heightSelf, targetDir + heightTarget); //Rascating from this gameObject (adjusted) to Player gameObject (adjusted)

        Debug.DrawRay(transform.position + heightSelf, (lastKnownPosition - transform.position) + heightTarget, Color.yellow); //Check Last Knwon Position

        if (angleToPlayer >= FOVangleStart && angleToPlayer <= FOVangleEnd && (inRange <= sightDistance)) //Check is the Player is angle and distance of the FOV
        {
            return IgnoreEnemyFOV(targetDir, heightSelf, heightTarget, FOVRay, player); //Ignore enemies MASK
        }
        else
        {
            return false;
        }
    }

    private bool IgnoreEnemyFOV(Vector3 targetDir, Vector3 heightSelf, Vector3 heightTarget, Ray FOVRay, GameObject player)
    {
        int mask = 1 << ignoreLayer; // Ignore all Layers but ignoreLayer
        mask = ~mask; //Ignore only ignoreLayer

        if (Physics.Raycast(FOVRay, out hit, sightDistance, mask)) //Check if the Player is Blocked | 11: Ignore Enemy Layer
        {
            if (hit.collider.tag == playerTag) //Check if the impact is actually the player
            {
                lastKnownPosition = player.transform.position; //LastKnownPosition
                Debug.DrawRay(transform.position + heightSelf, targetDir + heightTarget, Color.green); //Where is the player
                return true;
            }
            else
            {
                Debug.DrawRay(transform.position + heightSelf, targetDir + heightTarget, Color.red); //The player is beyond an obstacle
                return false;
            }
        }
        else
        {
            return false;
        }
    }

At the end you will see a Yellow Ray to the last know position, a Green to the actual position, and a Red if the player is hiding.

Just remember to change your Enemies to the Enemy Layer (in my case is 13), name your player tag to “Player”, and I think that is all.

PS: Remember to remove the Debug.DrawRay.

3 Likes

Thanks for the script! That’s an amazing feature.

I think your code needs a little retouch, it’s kinda hard to read, if you want some feedback I’ll gladly give it.

Just one thing, don’t use Vector3.Distance, that’s an awful method, try using Vector3.sqrMagnitude, you’ll need to square your sightDistance but that’s far better than calculating the real distance.

1 Like

Thank you so much! I’ll check how the method works and give it a try!

it works perfectly, thanks!

This is excellent work! It’s exactly what I need for my game. There are a few bits I’ve tidied up (for example instead of having the player tag as a string I’ve made it a gameobject called player which I define in start).

Really excellent stuff!

EDIT: Couldn’t get it to work for me. Will retouch and come back to it later.

Confused about this bit - surely patrolangle/2 wouldn’t return a negative?

1 Like

I forgot to mention, but the tag is required for the following reason: At some point, the player will be blocked by a wall, prop, or another thing that could block the enemy’s sight, that is why I check if the ray target is only the player with the tag, if something else is in the way, the player is considered out of FOV.

On the other hand, the start and end angle are reversed because I used reverse logic: is the player between this value and this value, for that the starting value is negative, instead of the ending value.

if (angleToPlayer >= FOVangleStart && angleToPlayer <= FOVangleEnd && (inRange <= sightDistance))

Could you please tell me what went wrong?

PS: Also, remember to adjust the heighSight and heightPlayer to your own models, heighSight start from the eyes, and heightSight aims to the neck (which is the difference of distance between the eyes and the neck or chest).

This might be something to consider not checking every frame (once you have 20 or 30 enemies, you’ll quickly see why).

To do this, we need to make a slight change to the logic in AIController:

List<AIController> activeControllers = new List<AIController>();

Then add to Awake():

controllers.Add(this);

Finally, in Update, move UpdateTimers to the top of the method, and add this bit of logic:

UpdateTimers(); //this was at the end of the method, but we want it to always run
if(controllers[0]!=this) return; //It's not our frame, so we'll let the current IAction ride
controllers.Remove(this); //it's our turn, remove ourselves from the start of the list
controllers.Add(this); //And add ourselves to the end of the list
... //the rest of Update, including your Field Of View code.

So here’s what’s happening here:
At the start of the game, all AIControllers are added to a list of controllers to be be processed. The list is static, meaning that it is a share list amongst all AIControllers.
Each frame, only the first controller in the list is allowed to run. If Update determines that this AIController is not the winning AIController, then it just exits. That’s why I move UpdateTimers to the beginning of Update, because we want that code to run no matter what.
If it is this AIController’s turn, then it moves itself to the end of the list and does it’s update slice.
This is an effective tool even if you’re not using this FOV method because if we’ve done our job right, the framerate should still be high enough that enemies are still quick and responsive. We don’t actually need our enemies to be making decisions every single frame of the game.

Thanks for the strategy Brian. I’m trying to understand the logic a bit. If we have 10 enemies in a scene, it means that an enemy will take a decision every 10 frames?

Yes, that’s correct. If your game is running at say 100fps, that means 10 decisions a second for each
enemy.

1 Like

Privacy & Terms