- Home /
Unity 2D: Visualise a field of view cone
Good Afternoon, I'm trying to implement a field of view cone for my enemy where if the player enters this cone the enemy will chase them. I have worked out the logic for the enemy regarding the player detection. Now I need to visualise the cone in the gameview to show the user the area they will be spotted. An important feature of this cone is that it wont pass through objects with the layer tag "obstacle" this way the player has hiding spots.
The Image below demonstates how I have used raycasts to show the side boundaries of the view cone and also to the vertices of the object from the enemy (this probably isn't ideal since my object might have many vertices and at the moment I need to input the vertex positions manually in the inspector which is tedious). I believe all I need to do is "fill in" the gaps between the raycasts but im unsure how easy this is to do.
Here is the mentioned image (The "G" shape is an obstacle):
And here is the code I have so far:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyFOV : MonoBehaviour
{
[SerializeField] private List<Vector3> obstacleVertices; // a list of all the obstabcle vertices in the level
[SerializeField] private List<Vector3> obstacleVerticesInRange = new List<Vector3>(); // a list of the obstacle vertices that will be within the enemy's FOV
[SerializeField] private float enemyViewDistance; // the max distance the enemy can see
[SerializeField] [Range(0, Mathf.PI)] private float enemyViewAngle; // the angle above and below the horizontal axis the enemy can see
[SerializeField] private LayerMask hitLayers; // the layers the enemy can't see through
[SerializeField] private RaycastHit2D boundLineTop; // the top bounding line of the cone
[SerializeField] private RaycastHit2D boundLineBottom; // the bottom bounding line of the cone
// Update is called once per frame
void Update()
{
bool movingRight = GetComponent<EnemyMovement>().movingRight; // get which way the enemy is facing so we know where to search for obstacle vertices
int sign = 0;
// get a list off all the obstical vertices in the enemy's FOV
obstacleVerticesInRange = new List<Vector3>(); // clear the list of vertices in range
for (int vertexIndex = 0; vertexIndex < obstacleVertices.Count; vertexIndex++) // for each element of the list
{
// find the angle between the two positions
Vector2 direction = new Vector2(obstacleVertices[vertexIndex].x - transform.position.x, obstacleVertices[vertexIndex].y - transform.position.y);
if (obstacleVertices[vertexIndex].y > transform.position.y)
{
sign = 1;
}
else
{
sign = -1;
}
float angle = Vector2.Angle(Vector2.right, direction) * sign * (Mathf.PI / 180);
if (Vector3.Distance(obstacleVertices[vertexIndex], transform.position) <= enemyViewDistance && ((movingRight && Mathf.Abs(angle) <= enemyViewAngle) || (!movingRight && Mathf.Abs(Mathf.PI - angle) <= enemyViewAngle)))
{
obstacleVerticesInRange.Add(obstacleVertices[vertexIndex]); // add the object to the List
// Send a raycast from the enemy to the vertex
Vector3 origin = transform.position;
RaycastHit2D sightLine = Physics2D.Raycast(origin, direction, enemyViewDistance, hitLayers); // creates a raycast from the enemy in the direction of the obstacle vertex colliding only with the listed layers
//TODO chuck out a ray at the angles of view of enemy
// simulate the raycast (for demonstration purposes)
if (sightLine.collider) // if the ray has hit an obsticle
{
UnityEngine.Debug.DrawLine(new Vector2(transform.position.x, transform.position.y), sightLine.point, Color.red); // if a collider is hit draw from enemy to collision point
}
else
{
UnityEngine.Debug.DrawLine(new Vector2(transform.position.x, transform.position.y), new Vector2(transform.position.x, transform.position.y) + direction * enemyViewDistance, Color.white);
}
}
// also send the rays of the sides of the FOV cone
Vector2 pointOnTopLine;
Vector2 pointOnBottomLine;
if (movingRight)
{
pointOnTopLine.x = enemyViewDistance * Mathf.Cos(enemyViewAngle) + transform.position.x;
pointOnTopLine.y = enemyViewDistance * Mathf.Sin(enemyViewAngle) + transform.position.y;
pointOnBottomLine.x = enemyViewDistance * Mathf.Cos(enemyViewAngle) + transform.position.x;
pointOnBottomLine.y = enemyViewDistance * -Mathf.Sin(enemyViewAngle) + transform.position.y;
}
else
{
pointOnTopLine.x = enemyViewDistance * Mathf.Cos(Mathf.PI - enemyViewAngle) + transform.position.x;
pointOnTopLine.y = enemyViewDistance * Mathf.Sin(Mathf.PI - enemyViewAngle) + transform.position.y;
pointOnBottomLine.x = enemyViewDistance * Mathf.Cos(Mathf.PI - enemyViewAngle) + transform.position.x;
pointOnBottomLine.y = enemyViewDistance * -Mathf.Sin(Mathf.PI - enemyViewAngle) + transform.position.y;
}
// visualise the boundaries
UnityEngine.Debug.DrawLine(new Vector2(transform.position.x, transform.position.y), pointOnTopLine, Color.yellow);
UnityEngine.Debug.DrawLine(new Vector2(transform.position.x, transform.position.y), pointOnBottomLine, Color.yellow);
}
}
}
I should mention I'm new to Unity and coding so thank you for your patience!
You are nearly there with the logic for creating the field of view visualisation and occlusion, but to make things simpler rather than raycasting specific vertices, you can cast rays at an even rate throughout the cone to get an approximation of scene occlusion (the more rays, the closer it gets). From there you can use those rays to generate and fill in triangles on a mesh. This tutorial series by Sebastian Lague does exactly what you are looking for and explains it thoroughly along the way.
Thank you for the video link, its very informative. This will need to be applied to all enemies in the level however, would this option hurt the performance of the game? Im estimating ~=400 raycasts (20 casts for 20 enemies) in total using this method.
Chances are it isn't going to be cheap regardless of option. There are a few ways to optimise it however - using binary search to use fewer samples for greater precision, restricting possible raycast checks using a sphere-test, etc. You could also only run the effect when an enemy's view cone is on screen (I would imagine you would only ever have a few enemies on screen at any one time).
Answer by FeedMyKids1 · Jun 18, 2020 at 12:22 AM
Sebastian Lague did it in 3d - but really it could be considered 2d since it was top down...
He creates a dynamic mesh on the fly.
https://www.youtube.com/watch?v=rQG9aUWarwE&list=PLFt_AvWsXl0dohbtVgHDNmgZV_UY7xZv7
Hopefully that's what you're talking about.
Thank you for the link, it pretty much does exactly what I need. This coupled with the advice Namey5 gave me solves my problem!
Answer by N-8-D-e-v · Jun 17, 2020 at 04:37 PM
There is a better (and much more performant way to do this, using Vector calculus. https://gamedev.net/tutorials/programming/math-and-physics/vector-maths-for-game-dev-beginners-r5442/ check out this article and read about dot product, I am making a stealth game (in 3d) right now, and I've used this method and it's worked great
I assumed that the Vector2.Angle used the dot product to find the angle. In any case, which angles would I be finding using the dot product? Or are you suggesting I do what im doing but use the dot product ins$$anonymous$$d of Vector2.Angle?
I would just do this
Quaternion look = Quaternion.LookRotation(enemy.position - player.position).normalized; //finds difference in rotation, normalizing makes the value independent of the magnitude (distance apart)
if (Vector2.Dot(transform.right, look) > 0.1f) //transform.right is the direction it's looking, if dot product is > 0 the player is in front of the enemy
{
//do whatever
}
It's much simpler
I think maybe I wasn't very clear with what i wanted. I'm not stuck with getting the enemy to detect the player. Rather, I'm stuck with just displaying the field of view cone.
Answer by JonPQ · Jun 18, 2020 at 06:07 PM
Why not use a 2D lighting solution from the asset store? this one is free... https://assetstore.unity.com/packages/tools/particles-effects/hard-light-2d-152208
OR to build it yourself... I think you'd need to iterate through all of your scene objects/blocks finding near and far corners, then projecting them in 2D onto a virtual plane or a circle represented in a list of run-length data.... the list would be a list of pretty much 2 states.... I can see clearly to the max distance ( to the plane), or it is blocked by an object... then you enter in sorted z-co-ordinate / ranges into the list, sorted by view angle from top to bottom of the view arc. This would also work for objects 'shadowed'/hidden by other objects... you wouldn't enter those values in the list as they are hidden but other items already in the list that are closer. You need to do some vector 2d math here.... especially for partially hidden line segments. Or you could actually draw into it like a z-buffer.
Then to re-render it.... you'd walk the list, from tip to bottom.... building and rendering triangles like a fan. You could either render shadows, or light.... view cones, or blocked view areas....
Your answer
Follow this Question
Related Questions
2D click on object with a raycast not working 2 Answers
Physics.Raycast doesn't hit anything even though Debug.DrawRay works 1 Answer
Raycast to ignore caster (Not by layer) 1 Answer
Need help to convert this raycasting 3d Script into a 2D x&y version 0 Answers
Cast multiple rays from an object between two angles? 0 Answers