NPC chasing the player

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Taehl
Dreaming in associative arrays
Posts: 1024
Joined: Mon Jan 11, 2010 5:07 am
Location: CA, USA
Contact:

Re: NPC chasing the player

Aha, I didn't know about unpack. Very handy. So here's what I have now:

Code: Select all

-- Normalizes a list of numbers
function math.normalize(...)
local t,m,n = {}, 0, select('#',...)
for i=1, n do local x=select(i,...) t[i],m=x,m+x end
m=1/m
for i=1, n do t[i] = t[i]*m end
return unpack(t)
end

-- Normalizes a TABLE of numbers (faster, but more annoying to use...)
function math.normalize(t) local n,m = #t,0 for i=1,n do m=m+t[i] end m=1/m for i=1,n do t[i]=t[i]*m end return t end

Anyway, I tested that against trig. Trig was about 3 times as fast as the list-using normalizer, and about 1.5 times as fast as the table-using normalizer.
Earliest Love2D supporter who can't Love anymore. Let me disable pixel shaders if I don't use them, dammit!
Lenovo Thinkpad X60 Tablet, built like a tank. But not fancy enough for Love2D 0.10.0+.

giniu
Party member
Posts: 221
Joined: Mon Nov 30, 2009 4:44 pm

Re: NPC chasing the player

Head Over Heels wrote:I don't want to spam the forum with topics but I am in need of assistance. I'm trying to find examples of a script/function that would allow me to have a number of 'enemies' chase the player around a fixed area/room, basically flocking to wherever the player has moved to. I suppose a bit like they do in Geometry Wars. Can anyone point me in the right direction here?
even that it's not love/lua related and discussion went that way already, there is very good resource for this topic in general: http://www.red3d.com/cwr/steer/. There are lots of behavious, from simple to very (like seek or pursue) to complicated ones (with multiple entities and behaviours at once) - there are also pointers to articles about it, and OpenSteer library (C++, but still can be used to look up how they did it).

vrld
Party member
Posts: 917
Joined: Sun Apr 04, 2010 9:14 pm
Location: Germany
Contact:

Re: NPC chasing the player

Taehl wrote:That vector stuff sounds neat. Would it be faster than using trig?
For normalizing, yes (see below). Also, your method of computing the angle and then use sin/cos on it is essentially the same as normalizing the vector directly:
vec_norm.png (21.95 KiB) Viewed 3103 times
Taehl wrote:Also to test my understanding, this would correctly normalize a list of numbers, yes?
Yes, and no: It normalizes the list by the sum of it's components. It would be the "correct" way to normalize, if you define the distance of two points to be the sum of absoulte differences of it's components (=L1/Manhatten distance). If you use the euclidian distance, was you'd normally do, then no.
Taehl wrote:Anyway, I tested that against trig. Trig was about 3 times as fast as the list-using normalizer, and about 1.5 times as fast as the table-using normalizer.
That's comparing apples with screwdrivers: they are completely different things. The table-normalizer as well as the vararg normalizer operates on more than two numbers (mathematically, you normalize an n-dimensional vector vs. normalizing a two-dimensional vector). To have a more fair comparison, try this:

Code: Select all

function normalize(x,y)
local len = math.sqrt(x*x + y*y)
return x/len, y/len
end

function normalize_angle(x,y)
local alpha = math.atan2(x,y)
return math.cos(alpha), math.sin(alpha)
end

function profile(func, iterations)
math.randomseed(1) -- get same numbers
local start = os.clock()
for i=1,iterations do
-- normalize a random vector
func((math.random()*2 - 1) * 100, (math.random()*2 - 1) * 100)
end
print("time: ", os.clock() - start)
end

profile(normalize, 10000000)        --> time: 	5.51
profile(normalize_angle, 10000000)  --> time: 	10.59
The angle method appears to be nearly twice as slow and is at least in my opinion less readable than the vector notation (but that may be personal taste).
Last edited by vrld on Sun Feb 13, 2011 6:44 pm, edited 1 time in total.
I have come here to chew bubblegum and kick ass... and I'm all out of bubblegum.

hump | HC | SUIT | moonshine

Robin
The Omniscient
Posts: 6506
Joined: Fri Feb 20, 2009 4:29 pm
Location: The Netherlands
Contact:

Re: NPC chasing the player

Good post, vrld.
Actually, I think this post is more on-topic than most of this thread.

Taehl
Dreaming in associative arrays
Posts: 1024
Joined: Mon Jan 11, 2010 5:07 am
Location: CA, USA
Contact:

Re: NPC chasing the player

vrld wrote:

Code: Select all

    local len = math.sqrt(x*x, y*y)

That doesn't make any sense. math.sqrt only takes one parameter...

Anyway, when trying to compare them, it's not working. Despite being given the same numbers, your normalize function doesn't return the same answers as the trig... The x-coordinate seems to be coming out very wrong (the y-coordinate is different too, however, it's not an inexplicable integer like the x-coordinate is).
Earliest Love2D supporter who can't Love anymore. Let me disable pixel shaders if I don't use them, dammit!
Lenovo Thinkpad X60 Tablet, built like a tank. But not fancy enough for Love2D 0.10.0+.

slime
Solid Snayke
Posts: 2896
Joined: Mon Aug 23, 2010 6:45 am
Contact:

Re: NPC chasing the player

It should be

Code: Select all

local len = math.sqrt(x*x + y*y)
Making the change didn't affect the speed of the tests at all for me.

EDIT: On a side note, localizing all the math functions (not the table itself) brought my times down from

Code: Select all

time: 	4.746087
time: 	8.156244
to

Code: Select all

time: 	3.405954
time: 	6.183561

Prole
Posts: 9
Joined: Wed Feb 09, 2011 4:40 pm

Re: NPC chasing the player

Heya guys,

I appreciate the help but in all honesty, it's all a bit too complex for me at this stage and I'm not sure how to apply your suggestions to my actual script. I have tried many times but with no luck. I really am at the most very basic of levels.

I have this script where I am trying to make the 2 enemies (the chomps) attack the player (mocky)

Code: Select all

function love.load()
love.graphics.setBackgroundColor(211,217,255)
mocky = love.graphics.newImage("mocky.gif")
x = 50
y = 50
speed = 100
enemy = love.graphics.newImage("Chomp1.png")
enemy2 = love.graphics.newImage("Chomp2.png")
end

function love.update(dt)
if love.keyboard.isDown("right") then
x = x + (speed * dt)
elseif love.keyboard.isDown("left") then
x = x - (speed * dt)
end

if love.keyboard.isDown("down") then
y = y + (speed * dt)
elseif love.keyboard.isDown("up") then
y = y - (speed * dt)
end
end

function love.draw()
love.graphics.draw(mocky, x, y)
love.graphics.draw(enemy, 400, 400)
love.graphics.draw(enemy2, 200, 200)
end

I tried inserting vrld's vector function but I keep getting errors.

One assumes I also need to put some kind of collision script in so that when mocky touches the chomps something happens.

Taehl
Dreaming in associative arrays
Posts: 1024
Joined: Mon Jan 11, 2010 5:07 am
Location: CA, USA
Contact:

Re: NPC chasing the player

I had to use trig since I can't get vrld's code to work, but:

Code: Select all

function love.load()
-- Returns the angle between two points
function math.getAngle(x1,y1, x2,y2) return math.atan2(x2-x1, y2-y1) end
-- Returns the distance between two points
function math.dist(x1,y1, x2,y2) return ((x2-x1)^2+(y2-y1)^2)^0.5 end

love.graphics.setBackgroundColor(211,217,255)

-- mocky is a table, containing an image, x and y coordinates, and a speed
mocky = {
image = love.graphics.newImage("mocky.gif"),
x = 50, y = 50,	speed = 100
}

-- enemies is a table of tables, where each table is a seperate enemy with an image, coordinates, and speed
enemies = {
{
image = love.graphics.newImage("Chomp1.png"),
x = 200, y = 300, speed = 80
},
{
image = love.graphics.newImage("Chomp2.png"),
x = 300, y = 300, speed = 60
}
}
end

function love.update(dt)
-- mocky movement code
if love.keyboard.isDown("right") then mocky.x = mocky.x + mocky.speed * dt
elseif love.keyboard.isDown("left") then mocky.x = mocky.x - mocky.speed * dt
end
if love.keyboard.isDown("down") then mocky.y = mocky.y + mocky.speed * dt
elseif love.keyboard.isDown("up") then mocky.y = mocky.y - mocky.speed * dt
end

-- enemy movement code
for k,enemy in pairs(enemies) do
local angle = math.getAngle(enemy.x, enemy.y, player.x, player.y)
-- angle = angle + 1	-- uncomment this line to make enemies spiral towards mocky! XD
enemy.x = enemy.x + math.cos(angle) * enemy.speed * dt
enemy.y = enemy.y + math.sin(angle) * enemy.speed * dt

if math.dist(mocky.x, mocky.y, enemy.x, enemy.y) < 30 then		-- you may need to adjust this number
-- code in here will execute if an enemy is too close to mocky ("touching")
-- make mocky lose a life or whatever
end
end
end

function love.draw()
love.graphics.draw(mocky.image, mocky.x, mocky.y)
for k,enemy in pairs(enemies) do love.graphics.draw(enemy.image, enemy.x, enemy.y) end
end

You'll notice I threw in a few new things aside from the enemy movement: Tables and "for" statements. You should get familiar with these, as tables are the best part of Lua. Hopefully my changes will make sense, but if not, just ask and I'll try to explain it.

EDIT) Whoops, forgot two commas. Fixed.
Last edited by Taehl on Mon Feb 14, 2011 6:20 pm, edited 1 time in total.
Earliest Love2D supporter who can't Love anymore. Let me disable pixel shaders if I don't use them, dammit!
Lenovo Thinkpad X60 Tablet, built like a tank. But not fancy enough for Love2D 0.10.0+.

Prole
Posts: 9
Joined: Wed Feb 09, 2011 4:40 pm

Re: NPC chasing the player

Wow, I really appreciate the help Taehl. Thanks alot, there's alot of useful new stuff in there that I had not thought of.

I tried your script but keep getting the following error:

Code: Select all

Syntax Error: main.lua:19: '}' expected (to close '{' at line 17) near 'x'
Which is weird because it looks to me like all brackets have been closed ok... I'll keep trying to find out what is going wrong there.

Yeah, I'm trying to familiarise myself with tables as I realise they can be a useful tool.

vrld
Party member
Posts: 917
Joined: Sun Apr 04, 2010 9:14 pm
Location: Germany
Contact:

Re: NPC chasing the player

Head Over Heels wrote:I tried inserting vrld's vector function but I keep getting errors.
Can you tell what errors those are? Maybe we can help to fix them.
Head Over Heels wrote:One assumes I also need to put some kind of collision script in so that when mocky touches the chomps something happens.
A simple way of collision detection would be to test if the images - to be more precise: the rectangles around the images - overlap. It's easier (for me) to wrap the head around the condition that two rectangles don't overlap, so we will test if the two rectangles are not not overlapping.
Two rectangles do not overlap, if one rectangle lies completely left, i.e. the rightmost edge of r1 is left of the leftmost edge of r1, or if the rectangle lies above the other, i.e. the bottom edge of r1 is above the top edge of r2:

Code: Select all

-- assuming the rectangles are given as position + width and height
function rectangles_overlap(r1, r2)
return not ((r1.x + r1.width) < r2.x or (r1.y + r1.height) < r2.y or -- test if rectangle r1 is left/above r2
(r2.x + r2.width) < r1.x or (r2.y + r2.height) < r1.y) -- test if rectangle r2 is left or above r1
end
You can eliminate the not with De Morgan's laws of boolean logic:

Code: Select all

-- assuming the rectangles are given as position + width and height
function rectangles_overlap(r1, r2)
return (r1.x + r1.width) >= r2.x and (r1.y + r1.height) >= r2.y and
(r2.x + r2.width) >= r1.x and (r2.y + r2.height) >= r1.y
end
You can use this code by supplying width/height-filds in the player and enemy tables and replacing the line if math.dist(...) then in Taehl's code by if rectangles_overlap(mocky, enemy) then
Taehl wrote:I had to use trig since I can't get vrld's code to work
Really? What doesn't work? This seems to be working just fine for me:

Code: Select all

    -- enemy movement code
for k,enemy in pairs(enemies) do
local dx,dy = mocky.x - enemy.x, mocky.y - enemy.y
local len = math.sqrt(dx*dx + dy*dy)
if len > 0 then
enemy.x = enemy.x + dx/len * enemy.speed * dt
enemy.y = enemy.y + dy/len * enemy.speed * dt
end
end
Edit:
Syntax Error: main.lua:19: '}' expected (to close '{' at line 17) near 'x'