top of page

Dynamic Field Of View

Updated: Jul 12, 2021


In most of the 2D and Top Down shooter games, you would find that the players/enemies have their own “field of view(FOV)” which determines and visually represents their detection radius. Although the FOV can be thought of as a simple 2D cone with a certain view angle and radius, the concept and its implementation is often not that straight forward. The following video shows a turret and its FOV which is used to detect enemies and obstacles.




The turret uses its FOV to detect objects in its view range, and to shoot enemies. The cone in the above video visually represents the turret’s FOV. The cone can be observed changing its shape dynamically based on its hit(enemy/obstacle). You can also observe the blockage of the turret’s FOV in case of the presence of an obstacle in its view.


The cone generated in the above demonstration is not just a 2D image, but is a dynamically generated Mesh which changes its shape based on the turret view and the obstacle area. This can also be used anywhere along the 3D space.


How can you use this in your project?

To use the dynamic FOV in your project:

  1. Create an empty game object.

  2. Attach the script DynamicFieldOfView.cs to it.

  3. Add a material to that game object (You can add your own shader effect to the cone).

Inside the script attached in Step 2, look out for the following variables -

Radius - Field Of View Radius

Field Of View Angle - ranges between 0 and 360

Ignore Object Layer Mask - Layers of objects which are see through and ignored by

the field of view cone.

Detailing - Keeps in check which triangle counts of the cone generated.

Optimize - checking this option makes sure the Mesh Generation code runs only when the cone is moved. It also limits the triangle count in the detailing field to 40.


This game object is to be attached to any game object to see the dynamic FOV in action. In my example I have attached this to the turret.




The working logic for this dynamic FOV is pretty simple. I used Dynamic Mesh Generation to generate the FOV cone. One vertex of the cone is fixed, which is at the position (0,0,0) - local to the mesh. To generate other vertices of the cone, Ray casting is used to cast a ray with a length equal to the distance between the initial position(0,0,0) to the radius of the cone. These rays are generated within the view angle specified as a script parameter. The number of rays which in turn would generate triangles for the mesh would be dependent on the script parameter “Detailing”. If any of these generated rays hits an obstacle, a vertex is spawned at that particular hit position. In case of the absence of obstacles, the vertices are spawned at a distance equal to the radius of the cone. Once the vertices are generated, we start to fill up the triangles and recalculate the normals.


This is the script.

using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
public class DynamicFieldOfView : MonoBehaviour
{
    [Header("FOV Properties")]
    [Space(10)]
    [SerializeField] private float radius;
    [Range(0,360)]
    [SerializeField] private float fieldOfViewAngle;
    [SerializeField] LayerMask ignoreObjectsLayerMask;
    [SerializeField] public bool optimize;
        
    public int Detailing;
   
    private const float DEGREE_TO_RADIAN_CONVERSION_FACTOR = 0.0174533f;
    private List<Vector3> vertices;
    private List<int> triangles;
 
    private void Update()
    {
        if (optimize)
        {
            if (transform.hasChanged)
            {
                GenerateFieldOfView();
                transform.hasChanged = false;
            }
        }
        else
        {
            GenerateFieldOfView();
        }
    }

    public void GenerateFieldOfView()
    {
        Mesh mesh = new Mesh();

        vertices = new List<Vector3>();
        triangles = new List<int>();

        float angle = 90 - (fieldOfViewAngle/2);
        float angleIncrement = ((fieldOfViewAngle / 2)) / (Detailing);

        //Generate first vertex. Vertices for mesh are always passed local.
        vertices.Add(new Vector3(0,0,0));

        for (int i = 0; i <= Detailing * 2; i++)
        {
            RaycastHit hit;
            //Debug.DrawRay(transform.position, Quaternion.AngleAxis(90 - angle,transform.up) * transform.forward * radius,Color.red);
            if (Physics.Raycast(transform.position, Quaternion.AngleAxis(90 - angle, transform.up) * transform.forward * radius, out hit, radius,~ignoreObjectsLayerMask))
            {
                vertices.Add((transform.InverseTransformPoint(hit.point)));
            }
            else
            {
                vertices.Add(new Vector3(radius * Mathf.Cos(angle * DEGREE_TO_RADIAN_CONVERSION_FACTOR), 0, radius * Mathf.Sin(angle * DEGREE_TO_RADIAN_CONVERSION_FACTOR)));
            }

            if (i != 0)
            {
                triangles.Add(i + 1);
                triangles.Add(i);
                triangles.Add(0);
            }

            angle = angle + angleIncrement;
        }

        mesh.SetVertices(vertices);
        mesh.SetTriangles(triangles, 0);
        mesh.RecalculateNormals();
        transform.GetComponent<MeshFilter>().sharedMesh = mesh;
    }
}


 
 
 

Recent Posts

See All

Comments


bottom of page