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.
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.
@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.
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
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
@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)”
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.