[Solved]How do you do top-down field of vision for enemies?

I’m making a top-down stealth game and I want to code in “field of vision” for an enemy. I would like the enemy to ignore the player whilst not in the enemy’s field of vision and charge at the player if in the field of vision. I’d also like to be able to conveniently edit how big the field of vision is. Here is my code for the enemy so far; it makes the enemy look at the player:

Enemy1 = class()

function Enemy1:init(E1X,E1Y,E1D)
    -- you can accept and set parameters here
    self.x = E1X
    self.y = E1Y
    self.d = E1D
end
function Enemy1:draw()
    if math.sqrt(((ninjaX-E1X)^2)+((ninjaY-E1Y)^2)) < 100 then
    E1D = math.atan(ninjaY-E1Y,ninjaX-E1X)
    
    end
    pushMatrix()
    translate(E1X,E1Y)
    rotate(math.deg(E1D))
    sprite("Project:Enemy")
    popMatrix()
end

I’ve tried counless times to code field of vision into my game, but I have had no luck with my knowledge of codea.
Can I have some help please? Thank you in advance.
Btw, ninjaX = X position of the player. ninjaY = Y postion of the player.

@PlatinumFrog -

First, it’s probably faster to code the relative positions as vec2, eg self.pos=vec2(E1X,E1Y), similarly for (say) ninjaPos, then you can calculate distance between them as self.pos:dist(ninjaPos)

Second, by field of vision, do you simply mean distance, or do you mean allowing for things that may block the enemy’s vision? You appear to be just using distance.

In that case, I think you might want this for E1D (the angle needs to be negative because rotations are anti clockwise in Codea)

E1D =  math.atan(-(ninjaX-E1X1),(ninjaY-E1Y))
--or if you're using vec2 as I suggested
local v = ninjaPos - self.pos
E1D = math.atan(-v.x,v.y)

If you want to allow for buildings blocking the view, the best way is to divide your map into little squares or tiles, ie a grid, and have a table that stores what is in each tile, eg a wall or whatever. Then when you want to check if you can see the enemy (or vice versa) you calculate the direction the enemy, then “walk” along it testing the point every X pixels, calculate which tile that is, see what is in it and whether it blocks the view. Choose X to be as large as possible to minimise the work, but small enough so it doesn’t accidentally skip over a tile, eg if your tiles are 10 pixels, maybe x=3.

deleted

yep, that looks good

@PlatinumFrog Are you after something like this. Since I don’t know everything you’re trying to do, I’m just making the one object look in a counterclockwise rotation. If the other object is in his field of view ( between the 2 lines ) I display the word ATTACK on the screen.

EDIT: Modified the code so the object looks randomly and tries to lock on the enemy.

EDIT: Modified the code again so the object moves toward the enemy as long as it’s in the field of vision.


displayMode(FULLSCREEN)

function setup()
    o1=obj(200,200)
    o2=obj(500,700)
end

function draw()
    background(40, 40, 50)
    o1:dir()
    o1:view()
    o1:draw()
    o2:dir()
    o2:draw()
    o1:attack()
end

obj=class()

function obj:init(x,y)
    self.x=x    
    self.y=y
    self.t=0
    self.dx=0
    self.dy=0
    self.ang=90
    self.rot=.5
    self.dur=math.random(10,30)
end

function obj:draw()
    fill(255)
    self.x=self.x+self.dx
    self.y=self.y+self.dy
    if self.x<0 then
        self.x=WIDTH
    end
    if self.x>WIDTH then
        self.x=0
    end
    if self.y<0 then
        self.y=HEIGHT
    end
    if self.y>HEIGHT then
        self.y=0
    end
    ellipse(self.x,self.y,10)
end

function obj:view()
    stroke(255,255,255,50)
    strokeWidth(2)
    self.ang=(self.ang+self.rot)%360
    local x1=math.cos(math.rad(self.ang+4))*2000+self.x
    local y1=math.sin(math.rad(self.ang+4))*2000+self.y
    local x2=math.cos(math.rad(self.ang-4))*2000+self.x
    local y2=math.sin(math.rad(self.ang-4))*2000+self.y
    line(self.x,self.y,x1,y1)
    line(self.x,self.y,x2,y2)
end

function obj:attack()
    ab=math.deg(math.atan((o2.y-self.y),(o2.x-self.x)))
    if ab<0 then
        ab=ab+360
    end
    if ab<self.ang+4 and ab>self.ang-4 then
        fill(255)
        text("ATTACK",WIDTH/2,HEIGHT/2)
        self.rot=0
        v1=vec2(o2.x-self.x,o2.y-self.y)
        v1=v1:normalize()
        self.dx=v1.x
        self.dy=v1.y
    else
        self.rot=math.random(-10,10)
    end   
end

function obj:dir()
    self.t=self.t+1
    if self.t>self.dur then
        self.dx=math.random(-1,1)
        self.dy=math.random(-1,1) 
        self.t=0
        self.dur=math.random(10,40)
    end   
end

actually, I should have remembered, there is an `angleBetween’ function for vec2 in Codea. You can get rid of all that atan stuff and just use this one line. It works for all angles without the need for any adjustments.

angleToEnemy = math.deg(vec2(0,1):angleBetween(enemyPos-self.pos))

The reason for using vec(0,1) is that Codea’s starting angle is upright, ie facing up the screen, which is a direction of vec2(0,1). We rotate from that direction to face the enemy.

Here is a simple example to show it works. The red “enemy” moves across the top of the screen, and we are the spaceship (change it by touching the screen anywhere). The code calculates the angle between them, and rotates the spaceship to face the enemy.

function setup()
    --make the enemy move left and right at top of screen
    enemy=vec2(100,HEIGHT-100)
    tween(10,enemy,{x=WIDTH-100,HEIGHT-50},
         { easing = tween.easing.linear,loop = tween.loop.pingpong })
    pos=vec2(300,200) --our starting position
end

function draw()
    background(50)
    --draw enemy
    fill(255,0,0)
    ellipse(enemy.x,enemy.y,50)
    --calculate angle to enemy
    local a=math.deg(vec2(0,1):angleBetween(enemy-pos))
    --draw ourselves as a spaceship
    translate(pos.x,pos.y)
    rotate(a)
    sprite("Space Art:Red Ship",0,0)
end

--touch anywhere on screen to change our own position
function touched(t)
    pos=vec2(t.x,t.y)
end

Sorry, I had fallen asleep because it was late. Thanks for all the help I’ve been getting. I will try both of your codes.

It seems like I hadn’t thoroughly explained what I ment by field of vision. I wasn’t thinking distance, I was thinking more like if the player (which is the ninja) were to be behind the enemy, the enemy wouldn’t be able to see the player and the enemy would ignore the player, but if the player were to be in a range of angles in front of the enemy, the enemy would be able to see the player and that would trigger the enemy to attack or something. One thing I don’t get, is what enemyPos is, as it gives me errors. Also the player does not need to face the enemy, and is already coded to follow my finger.

@PlatinumFrog I think I get what your trying to do. Your trying to make it so thats the enemy’s ‘field of view’ is a cone shape with the point starting from their eyes and the rest of the cone facing the direction that they are facing.

You would do this by checking wether or not the player pos is inside each of the enemy’s cones every frame. Since a cone is a sector of a circle, you’d do this by first treating the cone as a full circle and seeing if the distance in between the player pos is <= the radius of the circle. Then if thats true, find out what the angles of the two cone lines are, treating north as 0 degrees and the cone point, the center. Then you’d find the characters angle relative to the cone point and if its angle is between the angles of the two sides of the cone, the players is inside the cone.

@Ignatz Were you intending that 2D block detention system to work for only a world made by having 2D squares define it or where you thinking that a square grid is overlaid across the world and, as a part of setup, it finds out whats in each square and stores that info away.

@PlatinumFrog Did you try my post? its pretty much exactly what you described. Here it is again, should fit right into your code.

Enemy1 = class()

function Enemy1:init(E1X,E1Y,E1D)
    self.x = E1X
    self.y = E1Y
    self.ang = E1D -- angle of enemy
    self.fov = 45 -- angle range of detection
    self.range = 100 -- distance range of detection
end

function Enemy1:draw()
     -- check if player is in range of enemy
    if vec2(self.x,self.y):dist( vec2(ninjaX,ninjaY) ) < self.range then
        -- get the current direction enemy is facing
        self.dir = vec2( math.cos( math.rad( self.ang ) ), math.sin( math.rad( self.ang ) ) )
        -- get the angle between player and direction the enemy is facing
        local angle = math.deg( self.dir:angleBetween( vec2(ninjaX,ninjaY) - vec2(self.x,self.y) ) )
        -- if angle < fov/2 then it must be within cone of vision
        if math.abs( angle ) < self.fov / 2 then
            -- update enemy angle
            self.ang = self.ang + angle
        end
    end

    pushMatrix() 
    translate(self.x,self.y) rotate(self.ang)
    rotate(-self.fov/2) line(0,0,self.range,0) rotate(self.fov) line(0,0,self.range,0)
    popMatrix()
    
    pushMatrix()
    translate(self.x,self.y)
    rotate(self.ang)
    sprite("Planet Cute:Character Boy")
    --sprite("Project:Enemy")
    popMatrix()
    
end

@PlatinumFrog I made changes to my code above. See the EDIT comments.

Thanks, I’ll try these out.

@Jaybob your code is just what I need, however when I run it, it gives me an error:
“error: [string “Enemy = class()…”]:26: bad argument #1 to ‘translate’ (number expected, got nil)”

@PlatinumFrog - that’s not so mysterious

The translate function has two arguments, x and y. It’s telling you that the first one is nil (undefined), ie there is a problem with self.x.

I can’t see any error in Jaybobs code, so you need to check you passed a value through when you set up Enemy, and look at any other code that changes the value of self.x.

Everything looks defined, as this is my main file:

function setup()
    ninjaD = 90
    ninjaX = WIDTH/2
    ninjaY = HEIGHT/2
    fdistance = 0
    speedX = 0
    speedY = 0
    E1X = WIDTH/2
    E1Y = HEIGHT/2
    E1D = 90
    displayMode(OVERLAY)
    parameter.number("GameSpeed",5,10)
end
function draw()
    background(65, 91, 99, 255)
    Ninja:draw()
    Enemy1:init()
    Enemy1:draw()
    EnemySight:draw()


    
    
end

I don’t think you understand classes yet. See if this helps.

https://coolcodea.wordpress.com/2013/03/22/7-classes-in-codea/

I read the article, and fixed what was wrong.
Thank you for helping me!