Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

My goal is to draw a dynamic indicator for a cube, which represents the field of view. The ideal indicator would look the same as in Metal Gear Solid(the blue ones):

enter image description here

From what I have gathered, I need an angle and a length for the indicator as such for a 50° FoV: enter image description here

Now my issue is what method should I use to draw the indicator? A procedural mesh or is there a less demanding way of doing this?

share|improve this question

2 Answers 2

up vote 11 down vote accepted

I can't give you a Unity-specific answer (sorry!), but I can tell how I would solve this. I would generate a bunch of points on a circle using the blue vector.

First, what is a circle? Well, a circle looks like this:

Circle

However, graphics hardware can't really draw perfect circles. You're always going to end up with a polygon approximation:

Approximation

We do this approximation by using sine and cosine functions. If you remember from trigonometry, a circle is 360 degrees or 2 pi. Sine and cosine functions take a value out of 2 pi and give you a distance either in the x (cosine) or y (sine) axis. A very useful property indeed.

In code:

x = (cos(degrees_to_radians(angle));
y = (sin(degrees_to_radians(angle));

Now, what you want is not a circle, but a half-circle. Specifically, you want this:

Bit of circle

This can be achieved by using an angle_start and an angle_end. We then loop over this using an angle_delta that is calculated by dividing the difference between angle_start and angle_end over the quality we want.

Another useful property of approximating circles with sine and cosine (also known as the "unit circle") is that it can be shrunk or grown using a radius value. So let's add a radius to our pseudo-code:

x = (cos(degrees_to_radians(angle)) * radius;
y = (sin(degrees_to_radians(angle)) * radius;

How can we use this for our field-of-view cone? Well, we can use the same circle but draw it at different distances:

Almost a cone

And when I add a dotted line to connect the two half-circles, it's starting to look like a cone!

Cone!

Here, we have two radii: dist_min and dist_max. Our code becomes:

x_min = (cos(degrees_to_radians(angle)) * dist_min;
y_min = (sin(degrees_to_radians(angle)) * dist_min;

x_max = (cos(degrees_to_radians(angle)) * dist_max;
y_max = (sin(degrees_to_radians(angle)) * dist_max;

However, we also need two angles: one for the current step and one for the next step. We'll call these angle_curr and angle_next. Now our code becomes:

x_curr_min = (cos(degrees_to_radians(angle_curr)) * dist_min;
y_curr_min = (sin(degrees_to_radians(angle_curr)) * dist_min;

x_curr_max = (cos(degrees_to_radians(angle_curr)) * dist_max;
y_curr_max = (sin(degrees_to_radians(angle_curr)) * dist_max;

x_next_min = (cos(degrees_to_radians(angle_next)) * dist_min;
y_next_min = (sin(degrees_to_radians(angle_next)) * dist_min;

x_next_max = (cos(degrees_to_radians(angle_next)) * dist_max;
y_next_max = (sin(degrees_to_radians(angle_next)) * dist_max;

Now we have four points on our half-circles. Let's store these points as triangles. Here's a diagram that should help clear up how can make triangles out of these four points:

Triangles yo

So, what do we need to do draw a cone?

  • Determine the start angle and the end angle. This is done using the enemy's direction and field of view variable.
  • Determine the quality of our approximation. 15 steps should be good enough for most purposes.
  • Loop over the difference between start and end using the delta ((end - start) / quality).
  • Convert our angles to four points.
  • Convert the four points to two triangles.

When we combine these steps, here's what we will end up with:

Finished product

In full pseudo-code:

int quality = 15;

float dist_min = 0.5f;
float dist_max = 15.f;

vec2 pos = GetEnemyPosition();
float angle_lookat = GetEnemyAngle();
float angle_fov = GetEnemyFieldOfView();

float angle_start = angle_lookat - angle_fov;
float angle_end = angle_lookat + angle_fov;
float angle_delta = (angle_end - angle_start) / quality;

float angle_curr = 0.f;
float angle_next = angle_delta;

for (int i = 0; i < quality - 1; i++)
{
    vec2 sphere_curr;
    sphere_curr.x = cos(degrees_to_radians(angle_curr));
    sphere_curr.y = sin(degrees_to_radians(angle_curr));

    vec2 sphere_next;
    sphere_next.x = cos(degrees_to_radians(angle_next));
    sphere_next.y = sin(degrees_to_radians(angle_next));

    vec2 pos_curr_min = pos + sphere_curr * dist_min;
    vec2 pos_curr_max = pos + sphere_curr * dist_max;

    vec2 pos_next_min = pos + sphere_next * dist_min;
    vec2 pos_next_max = pos + sphere_next * dist_max;

    WriteTriangle(pos_curr_min, pos_curr_max, pos_next_max);
    WriteTriangle(pos_next_max, pos_curr_min, pos_next_min);

    angle_curr += angle_delta;
    angle_next += angle_delta;
}

This should create the cone you want. Try playing with the parameters (dist_min, dist_max and quality) to get the effect you want. It should only be a few triangles per enemy, I don't think it's much of a problem to generate this every frame. Only consider optimizing it when it becomes a problem.

share|improve this answer
    
I tried to figure out what happens in there, hard without any comments, but apparently your WriteTriangle method draws a procedural mesh according to the angles? –  Esa Jun 27 '12 at 7:21
1  
I always try to make code readable without any comments (because comments lie). However, in this case I will have to make an exception. WriteTriangle is a method that takes three vec2's and saves it as a triangle in some kind of mesh structure. Internally, the mesh would store a (dynamic) list of vertices and use that to render a model. –  knight666 Jun 27 '12 at 7:47
    
I have updated my answer (with pictures!) to hopefully clear things up for you. –  knight666 Jun 27 '12 at 8:28
    
+1. Very detailed answer. Just lacks the texturing to get the same look as in the reference image :) –  bummzack Jun 27 '12 at 8:50
    
+1 for the comment about lying comments :) –  Boris B. Oct 25 '13 at 17:02

Good day y'all!

I've implemented knight666's answer from pseudo-code to Unity-code (C#). Some slight changes were needed but it works like charm, just attach the script to an enemy. I don't know if there are more efficient ways to do some of the things.

One thing worth mentioning is that the number of vertices can be reduced from 4*quality to 2*quality+2, since for example triangle 2 and 3 share two vertices. This is true if the circle segment is continuous, but in my application it isn't, which is my I didn't optimize that part :)

Also note that you cannot set mesh.vertices[i] or mesh.triangles[i], which is why I used the auxiliary arrays vertices and triangles (which probably should be declared in global space for speed).

The code is sparsely commented since knight666's awesome answer and pics say it all. The biggest difference is that his answer is in right hand coordinates, whereas Unity works in left hand coordinates, so some sin & cos needed switching places as well as the order of the vertices in the triangles to get the correct normal (y axis in this case)

using UnityEngine;
using System.Collections;

public class drawFieldOfView : MonoBehaviour {

    int quality = 15;
    Mesh mesh;
    public Material material;

    float angle_fov = 40;

    float dist_min = 5.0f;
    float dist_max = 15.0f;

    void Start()
    {
        mesh = new Mesh();
        mesh.vertices = new Vector3[4 * quality];   // Could be of size [2 * quality + 2] if circle segment is continuous
        mesh.triangles = new int[3 * 2 * quality];

        Vector3[] normals = new Vector3[4 * quality];
        Vector2[] uv = new Vector2[4 * quality];

        for (int i = 0; i < uv.Length; i++)
            uv[i] = new Vector2(0, 0);
        for (int i = 0; i < normals.Length; i++)
            normals[i] = new Vector3(0, 1, 0);

        mesh.uv = uv;
        mesh.normals = normals;

    }

    void Update()
    {
        float angle_lookat = GetEnemyAngle();

        float angle_start = angle_lookat - angle_fov;
        float angle_end = angle_lookat + angle_fov;
        float angle_delta = (angle_end - angle_start) / quality;

        float angle_curr = angle_start;
        float angle_next = angle_start + angle_delta;

        Vector3 pos_curr_min = Vector3.zero;
        Vector3 pos_curr_max = Vector3.zero;

        Vector3 pos_next_min = Vector3.zero;
        Vector3 pos_next_max = Vector3.zero;

        Vector3[] vertices = new Vector3[4 * quality];   // Could be of size [2 * quality + 2] if circle segment is continuous
        int[] triangles = new int[3 * 2 * quality];

        for (int i = 0; i < quality; i++)
        {
            Vector3 sphere_curr = new Vector3(
            Mathf.Sin(Mathf.Deg2Rad * (angle_curr)), 0,   // Left handed CW
            Mathf.Cos(Mathf.Deg2Rad * (angle_curr)));

            Vector3 sphere_next = new Vector3(
            Mathf.Sin(Mathf.Deg2Rad * (angle_next)), 0,
            Mathf.Cos(Mathf.Deg2Rad * (angle_next)));

            pos_curr_min = transform.position + sphere_curr * dist_min;
            pos_curr_max = transform.position + sphere_curr * dist_max;

            pos_next_min = transform.position + sphere_next * dist_min;
            pos_next_max = transform.position + sphere_next * dist_max;

            int a = 4 * i;
            int b = 4 * i + 1;
            int c = 4 * i + 2;
            int d = 4 * i + 3;

            vertices[a] = pos_curr_min; 
            vertices[b] = pos_curr_max;
            vertices[c] = pos_next_max;
            vertices[d] = pos_next_min;

            triangles[6 * i] = a;       // Triangle1: abc
            triangles[6 * i + 1] = b;  
            triangles[6 * i + 2] = c;
            triangles[6 * i + 3] = c;   // Triangle2: cda
            triangles[6 * i + 4] = d;
            triangles[6 * i + 5] = a;

            angle_curr += angle_delta;
            angle_next += angle_delta;

        }

        mesh.vertices = vertices;
        mesh.triangles = triangles;

        Graphics.DrawMesh(mesh, Vector3.zero, Quaternion.identity, material, 0);
    }

    float GetEnemyAngle()
    {
        return 90 - Mathf.Rad2Deg * Mathf.Atan2(transform.forward.z, transform.forward.x); // Left handed CW. z = angle 0, x = angle 90
    }

}
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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