NPC chasing the player

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
User avatar
Taehl
Dreaming in associative arrays
Posts: 1025
Joined: Mon Jan 11, 2010 5:07 am
Location: CA, USA
Contact:

Re: NPC chasing the player

Post by Taehl »

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

Post by giniu »

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).
User avatar
vrld
Party member
Posts: 917
Joined: Sun Apr 04, 2010 9:14 pm
Location: Germany
Contact:

Re: NPC chasing the player

Post by vrld »

Off topic post ahead:
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
vec_norm.png (21.95 KiB) Viewed 6764 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
User avatar
Robin
The Omniscient
Posts: 6506
Joined: Fri Feb 20, 2009 4:29 pm
Location: The Netherlands
Contact:

Re: NPC chasing the player

Post by Robin »

Good post, vrld.
vrld wrote:Off topic post ahead:
Actually, I think this post is more on-topic than most of this thread.
Help us help you: attach a .love.
User avatar
Taehl
Dreaming in associative arrays
Posts: 1025
Joined: Mon Jan 11, 2010 5:07 am
Location: CA, USA
Contact:

Re: NPC chasing the player

Post by Taehl »

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+.
User avatar
slime
Solid Snayke
Posts: 3131
Joined: Mon Aug 23, 2010 6:45 am
Location: Nova Scotia, Canada
Contact:

Re: NPC chasing the player

Post by slime »

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
Head Over Heels
Prole
Posts: 9
Joined: Wed Feb 09, 2011 4:40 pm

Re: NPC chasing the player

Post by Head Over Heels »

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.
User avatar
Taehl
Dreaming in associative arrays
Posts: 1025
Joined: Mon Jan 11, 2010 5:07 am
Location: CA, USA
Contact:

Re: NPC chasing the player

Post by Taehl »

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+.
Head Over Heels
Prole
Posts: 9
Joined: Wed Feb 09, 2011 4:40 pm

Re: NPC chasing the player

Post by Head Over Heels »

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.
User avatar
vrld
Party member
Posts: 917
Joined: Sun Apr 04, 2010 9:14 pm
Location: Germany
Contact:

Re: NPC chasing the player

Post by vrld »

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:
Head Over Heels wrote:

Code: Select all

Syntax Error: main.lua:19: '}' expected (to close '{' at line 17) near 'x'
You need to add a , after the love.graphics.newImage(...).
I have come here to chew bubblegum and kick ass... and I'm all out of bubblegum.

hump | HC | SUIT | moonshine
Post Reply

Who is online

Users browsing this forum: Google [Bot] and 45 guests