2D Meshes Tips and Tricks

Create A View Cone Obscured By Colliders

Here is the code with comments that explain it:

using UnityEngine;

public class FOV_v2 : MonoBehaviour
{
    private int ray_count = 50; // The more rays we have the more defined our fov but it becomes an intensive process
                               // This does not include the origin point
    private Mesh mesh;
    [SerializeField] private LayerMask layer_mask;

    public float field_of_view = 90f; // FOV range in degrees
    public float view_distance = 5f;

    // Start is called before the first frame update
    void Start()
    {
        // Assign mesh to MeshFilter
        mesh = new Mesh();
        GetComponent<MeshFilter>().mesh = mesh;

        UpdateMesh();
    }

    public Vector3 GetVectorFromAngle(float angle)
    {
        // Returns a vector from the origin of the unit circle to a point on the circle edge at the angle specified.
        // This also means the magnitude of the vector is always 1.
        // We expect an angle from 0 to 360 degrees
        float angle_radians = angle * (Mathf.PI / 180f);
        return new Vector3(Mathf.Cos(angle_radians), Mathf.Sin(angle_radians));
    }

    // Creates the mesh for our FOV
    void UpdateMesh()
    {
        float angle = field_of_view / 2; // the forward vector of the game object is pointed towards the center of the fov
        float angle_increase = field_of_view / ray_count;
        Vector3 origin = Vector3.zero;  // Because vertex coordinates are in local space, this is the coordinates of the game object.

        Vector3[] vertices = new Vector3[ray_count + 2]; // Need one vertex for the origin and the top vertex of our first ray triangle
        Vector2[] uv = new Vector2[vertices.Length];
        int[] triangles = new int[ray_count * 3]; // Eeach ray is a triangle and each triangle has 3 vertices.

        vertices[0] = origin;

        int vertex_index = 1; // start at 1 since origin is already set
        int triangle_index = 0;
        for (int i = 0; i <= ray_count; i++)
        {
            Vector3 vertex;

            //// The raycast is dealing with global coordinates but our mesh vertices are in local coordinates so we need to translate
            Vector3 global_origin = transform.position;
            Vector3 local_angle_vector = GetVectorFromAngle(angle);
            Vector3 scaled_angle_vector = Vector3.Scale(local_angle_vector, transform.lossyScale); // lossy scale ~~ world scale
            Vector3 global_angle_vector = Quaternion.Euler(0, 0, transform.eulerAngles.z) * scaled_angle_vector; // eulerAngles ~~ world space
            float global_view_distance = Vector3.Magnitude(scaled_angle_vector) * view_distance;

            //RaycastHit2D raycast_hit2d = Physics2D.Raycast(global_origin, global_angle_vector, global_view_distance, layer_mask);


            RaycastHit2D raycast_hit2d = Physics2D.Raycast(global_origin, global_angle_vector, global_view_distance, layer_mask);

            if (raycast_hit2d.collider == null)
            {
                // We did not hit anything so place the vertex at the edge of the view distance
                // Recall we are drawing the vertices in the local coordinates
                vertex = origin + GetVectorFromAngle(angle) * view_distance;
            }
            else
            {
                // We hit something so we place it on the point where it was hit
                // We need to convert this world point to a local point however
                vertex = transform.InverseTransformPoint(raycast_hit2d.point);
            }
            vertices[vertex_index] = vertex;

            if (i > 0)
            {
                triangles[triangle_index + 0] = 0; // root of triangle always at origin
                triangles[triangle_index + 1] = vertex_index - 1; // Recall vertex_index starts at 1 so when i == 1 vertex_index == 2
                triangles[triangle_index + 2] = vertex_index;
                triangle_index += 3; // 3 Vertices per triangle
            }

            vertex_index++;
            angle -= angle_increase; // this makes the angle go clockwise in unity this is important because our triangle indices
                                     // should be clockwise (creates front facing mesh) for the shader to work properly
                                     // (since shaders works on front facing meshes by default)
        }

        mesh.vertices = vertices;
        mesh.uv = uv;
        mesh.triangles = triangles;
    }

    // Update is called once per frame
    void Update()
    {
        UpdateMesh();
    }
}

Next in our scene:

  1. We create an empty game object

  2. We add a mesh filter component and mesh renderer component to the game object

  3. We apply whatever material our fov is to the mesh renderer material.

  4. We add our script as a component to the game object.

Layering Meshes with Sprites

Somtimes you may encounter the issue that a sprite renders on top of a mesh and vice versa and you wish to change the order. You can achieve this by raising a mesh above a sprite physically in 3d space. Set the z offset of the mesh so that it is closer to the camera and it will render above the default layer containing the sprite.