Enemy not rotating properly towards the player

woopdeedoowoopdeedoo Posts: 109Member
edited January 2017 in Programming

I made this guard rotate towards the player when he enters his field of view, but I'm having a problem: he follows the player only until the angle of the player gets to 180 or -180, because when I go over that it turns negative or positive, respectively, and no longer falls within the view range from the left_boundary to the right_boundary (image below).

(EDIT: I tried inverting angles in several ways and stuff like that. Either it doesn't work, or I missed the right way to do it.)

Here's how I'm doing it:

var idle_view_left_boundary     = 45    # defaults for idle state
var idle_view_right_boundary    = -45

# (...)

func detect_player():
    # if player within range of sight   
    if distance_to_player < radius_3 

        # I calculate the boudaries based on rotation
        left_boundary = get_rot() + deg2rad(idle_view_left_boundary)
        right_boundary = get_rot() + deg2rad(idle_view_right_boundary)

        # then the angle (according to "AB = A - B" from the vector math page of the docs)
        var player_angle = Vector2( player.get_pos() - get_pos() ).angle()

        # then I check if the angle is within the boudaries
        player_in_sight = player_angle <= left_boundary and player_angle >= right_boundary

        # and if it is... do a murderous stare
        if player_in_sight:

And a visual representation of the result:


  • x1212x1212 Posts: 9Member

    Maybe it would help to use a Vector for the direction the enemy looks at and to use the angle_to method to get the angle between the direction vector and the Playerpos-Enemypos vector.
    something like:
    direction = Vector2(1,0).rotated(get_rot()) # or Vector2(0,1).rotated(get_rot()) depending on the default direction
    player_angle = Vector2( player.get_pos() - get_pos() ).angle_to(direction)

  • AvencherusAvencherus Posts: 49Member
    edited January 2017

    There are a lot ways to conditionally evaluate the unit circle, but you may want to try something different. For example you can use the dot product of two normalized vectors to get information about the angle between them. It's used frequently for positional tests. The dot product on normalized vectors is a cosine value of the angle between vectors, so it will range from -1 to 1.

    Here is an example to solve this kind of problem with the dot product, the threshold here is 22.5 degrees on either side, so a total sweep of 45 degrees in front.

    const THRESHOLD = cos(deg2rad(22.5))
    func is_in_view():
        var direction_to_target = (target_pos - player_pos).normalized()
        var facing_direction = player_facing_direction.normalized()
        return direction_to_target.dot(facing_direction) > THRESHOLD
  • woopdeedoowoopdeedoo Posts: 109Member
    edited January 2017

    @x1212 I tried using the Vector2.angle_to() before and it wasn't giving me the correct angle, but I was doing it differently. It may work the way you're saying. Meanwhile I managed to make it "work" by inverting the guard's angles before they reach 180/-180, so I decided to prioritize some other stuff. I'll tackle the issue again later.

    @Avencherus I will try that too. One thing, though: you're suggesting using velocity's direction. What if the player is standing still (velocity (0,0)) while the guard is still turning? I don't think there's much chance of that happening, there's no delay in turning or anything, and I don't plan to have any, so probably it's not an issue. I'm just wondering.

  • AvencherusAvencherus Posts: 49Member

    @woopdeedoo That would be a typo on my part, I intended to express it as player_direction. An empty velocity vector would give a bad result.

  • AvencherusAvencherus Posts: 49Member
    edited January 2017

    As an practice exercise (no optimization) I wrote a quick demo for this. You can copy and paste this in a new project on a Node2D's script.

    extends Node2D
    var tex = preload("res://icon.png")
    var tex_ofs
    const FOV_HALF_ANGLE = cos(deg2rad(22.5))
    class Entity:
        var pos
        var facing_direction # Should be a unit length
        var fov_edge = FOV_HALF_ANGLE
        func _init(x, y, angle = 0):
            pos = Vector2(x, y)
        func set_direction(angle):
            facing_direction = Vector2(sin(angle), cos(angle)) 
        func can_see(target):
            var direction_to_target = (target.pos - self.pos).normalized()
            return direction_to_target.dot(facing_direction) >= fov_edge
    var center_canvas
    var player
    var enemy
    func _ready():
        center_canvas = get_viewport().get_rect().size / 2
        tex_ofs = -tex.get_size()/2
        player = Entity.new(0, 0)
        enemy  = Entity.new(0, 200)
    var player_rotation = 0
    var enemy_orbit_angle = PI
    var oscillation = 0
    var turn_speed = -.01
    var move_speed = .02
    func _fixed_process(delta):
        enemy.pos = player.pos + Vector2(sin(enemy_orbit_angle), cos(enemy_orbit_angle)) * (150 + 60 * sin(oscillation))
        player_rotation   += turn_speed
        enemy_orbit_angle += move_speed
        oscillation       += move_speed
    func _draw():
        var player_pos = center_canvas + player.pos
        draw_texture(tex, player_pos + tex_ofs)
        var left =  player.facing_direction.angle() - player.fov_edge/2 + PI/2
        var right = player.facing_direction.angle() + player.fov_edge/2 + PI/2
        var fov_poly = [player_pos]
        fov_poly.append(player_pos + Vector2(-cos(left),  sin(left))  * 2000)
        fov_poly.append(player_pos + Vector2(-cos(right), sin(right)) * 2000)
        draw_colored_polygon(fov_poly, Color(0,.8,.6, .8))
            draw_texture(tex, center_canvas + enemy.pos + tex_ofs, Color(1,0,0,.8))
            draw_texture(tex, center_canvas + enemy.pos + tex_ofs)
Sign In or Register to comment.