2
\$\begingroup\$

I would like to create a game in which PC and NPCs would have a field of view, shown as a cone in the front of the entity (see picture for an example).

a character seen from above, in front of which there is a pink cone

As it would be the first game I develop using Godot Engine, I am wondering what would be the best way to implement this feature. Also if possible, in later versions of the game, I would like to be able to make the decor stop the field of view, like what is done in Monaco: What's yours is mine.

Would you have any recommendation?

\$\endgroup\$
1
\$\begingroup\$

I don't have much experience with Godot, so this answer is only about the theory.


Think of things in polar coordinates.

You can calculate an absolute angle and radius between any object that "sees" and an object that can be "seen". Using the "seer" as the origin of the polar coordinate system will be ideal.

The "seer" has a facing angle and can see some range of degrees to the left and right. If the angle between the two objects isn't within that range spread about the facing angle, then it is not "seen".
bool angle_visible = (facing_angle - 30° <= angle_between <= facing_angle + 30°)

If an object is too far away it can't be "seen" either.
bool distance_visible = (r <= sight_range)

If the angle and distance between an object and the entity is within the limits of the cone, then it might be "seen" by the entity.

bool seen = angle_visible && distance_visible && not_obstructed

To simulate obstruction of vision you can use ray-casting to see if the line between the object and the "seeing" entity is blocked. You'll need to check the distance and angles first to avoid performing ray-casts on objects that aren't in the cone at all.

bool CanSee(Entity other)
{
    float x_dist_between = other.X - this.X;
    float y_dist_between = other.Y - this.Y;
    float distance_between = sqrt(x_dist_between^2 + y_dist_between^2);

    if (distance_between <= max_sight_range)
    {
        float angle_between = radtodeg(atan2(y_dist_between, x_dist_between));
        // Be careful of angles near 0/360°
        // You may want to normalize angles around the facing angle
        // so that the angle_between's zero is at the facing_angle
        if (angle_between <= facing_angle + 30 && angle_between >= facing_angle - 30)
        {
            return !RayCastHitsSomething(this, other);
        }
    }

    return false;
}

There are more efficient ways than this to implement cone of vision though, this pseudocode was provided just as a quick example.

  • Comparing squares of distances is faster than finding the square root first
  • The dot product of vectors is a cheap operation that can tell you if two vectors are pointing in the same general direction or not (is object behind or in front)
  • The built-in physics may have routines for raising collision events with between a circle or cone positioned at the "seer" and the objects that can be "seen". That could replace the need to implement your own distance check (and maybe angle check) if the collision detection in the game engine is fast too. Then you would only be implementing ray-casting inside the handler for the collision event instead of these checks on a frame-by-frame update.
| improve this answer | |
\$\endgroup\$
0
\$\begingroup\$

This can be achieved in Godot using a simple Area2D.

First create an area2d node as a new scene and add a sprite and a collisionshape2D as children of the Area2D, rename the Area2D, something like Vision_Cone for neatness.

Now add the sprite and fill the collisionshape2D with an appropriate shape.

Now with the Vision_Cone selected, choose Collisions from the inspector and make sure under Mask that both the cone and the player (or what ever object interacts with the cone) are able to interact.

Next create a script for the Vision_Cone, then close the script interface, so you are looking back at the 2D view and, with the Vision_Cone selected, open the Node tab on the inspector. Find body_entered(body, node) and double click it, then choose Vision_Cone (connecting from) from the list.

Your Vision_Cone.gd script will look like this

extends Area2D

func _on_Vision_Cone_body_entered(body: PhysicsBody2D) -> void:
    pass # Replace with function body.

Change it to interact with physics objects and not just Nodes, like this

extends Area2D

func _on_Vision_Cone_body_entered(body: PhysicsBody2D) -> void:
    pass # Replace with function body.

That is it for setup, now what ever you write in that function will happen when a physics object enters the collisionshape2D of the Vision_Cone.

For a small example, this code will make the cone change colour as a physics body enters and exits the cone

func _on_Vision_Cone_body_entered(body: PhysicsBody2D) -> void:
        modulate = Color(0,0,0)

func _on_Vision_Cone_body_exited(body: PhysicsBody2D) -> void:
        modulate = Color(255,255,255)

A good tip is that if you want to know exactly which body the Vision_Cone is interacting with, you can use

body.name = ""

So for example

func _on_Vision_Cone_body_entered(body: PhysicsBody2D) -> void:
    if body.name == "player":
        modulate = Color(0,0,0)

will only change the colour when a body with the name "Player" enters it and not when others do, this is very useful.

That is it, you can now attach the Vision_Cone scene to any object in your game as a child and it will inherit any rotation data from the parent and just work, no need to remake it for every object.

Player is out of the cone:

enter image description here

Player is in the cone:

enter image description here

You can download and review the project for this example from my Google Drive.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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