Easy Pathfinding with NavGO

I also made an update so that directional nodes can now be out of sequential order. Meaning that you can traverse from node ID 1 to 4, 4 to 2, then 2 to 1. This will also allow for non-integer numbers such as 1.1, 1.2, etc.

@Jb_Skaggs for your specific use case, I recommend having a set start Node ID and set end Node ID for each map / level in your tower defense game, such as 1 to start and 100 to end. That way you don’t have to recode the pathfinding or force units to be map dependent. Instead you can just leave that as the defined path. Due to the non-sequential functionality I added, you can go over the max value if needed for a complex path then simply point back to 100 to end it.

1 Like

I shall try now.

That is good news. Congratulations!

1 Like

Ok I got that to work.
changed singleton script start 1 end 0
cut and pasted one into collection changed id to 2 and next to 100
changed starting to start 1 next 2
changed end node to start 100 and next to 100 thinking it would stop- but it goes back to 1.

But yeah its working!

Last thing - all the sprites of the copied nodes still show at run but not the first ones. Which script controls that and Ill see if I can figure it out, how to duplicate: I cut and pasted the directional control renamed them and re-id’d them. Also the end one showed up as well. But I did rename it to to goend.

Also does the libraries dependencies auto update or do I have to manually reset them?

here is the image of nodes at runtime

here the image with the nodes in editor

To stop it from going back to 1, you need to modify the code within the directional movement character, currently it has the following code block:

local function distance(vec1, vec2)
	return math.ceil( math.sqrt( math.pow(vec1.x - vec2.x, 2) + math.pow(vec1.y - vec2.y, 2) ) )
end
local function moveCharacter(self)
	local path, found = NAVGO.GET_PATH_FROM_DIRECTIONAL_ID(self.Start_path_ID, self.End_path_ID)
	if not found then
		print("Directional - NO PATH FOUND")
	else
		print("Directional - PATH FOUND")
		local delay = 0
		local lastPos = go.get_position(go.get_id())
		
		for i=1, #path do
			local newPosition = vmath.vector3(path[i].x, path[i].y, 1)
			local time = distance(newPosition, lastPos) / self.speed -- move at a consistent speed
			go.animate(go.get_id(), "position", go.PLAYBACK_ONCE_FORWARD, newPosition, go.EASING_LINEAR, time, delay)
			lastPos = newPosition
			delay = delay + time
		end
		timer.delay(delay, false, moveCharacter) --comment this out to prevent looping
	end
end

To stop the character from looping, you just need to remove the “timer.delay” call from the end of the function and the character will no longer loop.

The code that controls that is within the NavGo_directionalScript.script, the code will wait until a message from the NavGo Handler to determine if it should show or not. The NavGO handler should automatically hide everything during runtime unless you specificy otherwise. The names shouldn’t matter, I rename them in the same way all the time and don’t have the same issue. The sprites within the game objects do have to be called sprite still, that’s the only think I can think of at this point that would cause some to be hidden while others remain. Are you getting an error message still? If you are still having issues, can you post a screenshot of the contents of the directional node you cut and pasted.

That depends on how you have it set up within your project. If you are pointing to the master branch on git, you just need to click project->fetch libraries and it should update them. If you are pointing to a specific release, you would need to change the release to get a newer one. As for the downloaded version you are testing with, you would have to download the newest version from git, which can be found at: https://github.com/DrCampbell2017/NavGO/archive/Alpha.13.zip

Does that answer your questions?

Yes. I will see whats happening with the sprites. No I didnt change anything about them except copied the node.

One thing I may do a little different is change the sprite for each node with a number displayed. That way its easy to tell which node is which in editor. I know it may take a little longer to make path but saves me time in long being able to see at a glance which is which.

I applaud your dedication to this project.

I can make that as a quick change to implement that if you want. I wouldn’t make a separate sprite per number, I would just add a label to it and set it to the ID of the node during runtime. That way there wouldn’t be tons of extra sprites and the same effect would still be accomplished.

You could also change the text of the label while in the editor and not worry about anything changing or breaking during runtime.

1 Like

Sounds good!

I havent worked in the sprite not hiding at runtime. But

So to simplify this whole thread:

A. install library for the project as a dependency
B. Then in the collection:

  1. in collection add go handler
  2. edit handler script for start =1 and end equals 100 or whatever node qty you might need
  3. add directional nodes (you can cut and paste as well)
  4. in direction node script update this node to current node number, and goto node as next node number. Start node: this=1, next= 2, second node: this=2, next=3 … end this=100, next=1. End and start nodes must match script in handler script
  5. sprite must stay named sprite in the nodes

Do I have this correct?

Almost, from within the collection you have to initialize the NavGo handler by sending an init message. I recommend having the following code in your main script to handle it.

function init(self)
        local message = {}
        message.collisions = { hash("wall") }
    	message.debug = false
    	message.deleteNodeAfterGotten = true
    	message.nodeNeighborRange = 400
    	message.nodeNeighborRange = 400
    	msg.post("/NavGO_HandlerGO#NavGO_HandlerScript", hash("init"), message)
end

function on_message(self, message_id, message, sender)
       if message_id == hash("NavGO_Ready") then
                -- NavGo is ready to be used
                -- Add game start code
       end
end

After sending this message, you can also wait about 1 second using a timer.delay function to access various functions provided in the singleton. If you try to access a function right away, the NavGo will not have had time to calculate everything and will print or send a message saying it’s not ready yet.

So to simplify this whole thread: REDO 2

A. install library for the project as a dependency
B. Then in the collection:

  1. in collection add go handler

  2. create a folder in main called: directionalCharacter
    3.Add a game obejct called directionalCharacterGo

  3. add a script called directionalCharacterSingleton

  4. Add this text in directionalCharacterSingleton script:

  5. Add this text in directionalCharacterSingleton script:


require("navGo_pathfinding.NavGO_Global")

go.property("Start_path_ID", -1)
go.property("End_path_ID", -1)
go.property("speed", 400)

local function distance(vec1, vec2)
	return math.ceil( math.sqrt( math.pow(vec1.x - vec2.x, 2) + math.pow(vec1.y - vec2.y, 2) ) )
end

local function moveCharacter(self)
	local path, found = NAVGO.GET_PATH_FROM_DIRECTIONAL_ID(self.Start_path_ID, self.End_path_ID)
	if not found then
		print("Directional - NO PATH FOUND")
	else
		print("Directional - PATH FOUND")
		local delay = 0
		local lastPos = go.get_position(go.get_id())

		for i=1, #path do
			local newPosition = vmath.vector3(path[i].x, path[i].y, 1)
			local time = distance(newPosition, lastPos) / self.speed -- move at a consistend speed
			go.animate(go.get_id(), "position", go.PLAYBACK_ONCE_FORWARD, newPosition, go.EASING_LINEAR, time, delay)
			lastPos = newPosition
			delay = delay + time
		end
		--timer.delay(delay, false, moveCharacter)
	end
end


------------------
--Core functions--
------------------

function init(self)
	timer.delay(1, false, moveCharacter)
end

function on_message(self, message_id, message, sender)
	if message_id == hash("contact_point_response") then
		if message.group == hash("wall") then
			local newpos = go.get_position() + message.normal * message.distance
			go.set_position(newpos)
		end
	end
end
  1. in outline click on the singleton script and set the start =1 and end = 100 or whatever node qty you might need
  2. add directional nodes (you can cut and paste as well)
  3. in direction node script update this node to current node number, and goto node as next node number. Start node: this=1, next= 2, second node: this=2, next=3 … end this=100, next=1. End and start nodes must match script in handler script
  4. sprite must stay named sprite in the nodes
  5. From within the collection you have to initialize the NavGo handler by sending an init message. I recommend having the following code in your main script to handle it.
function init(self)
        local message = {}
        message.collisions = { hash("wall") }
    	message.debug = false
    	message.deleteNodeAfterGotten = true
    	message.nodeNeighborRange = 400
    	message.nodeNeighborRange = 400
    	msg.post("/NavGO_HandlerGO#NavGO_HandlerScript", hash("init"), message)
end

function on_message(self, message_id, message, sender)
       if message_id == hash("NavGO_Ready") then
                -- NavGo is ready to be used
                -- Add game start code
       end
end

Thanks I am now going to start building a map with this without using the example as base file. Hopefully I have it figured out. :slight_smile:

Once I have this down making tower defense games becomes much easier.

Best of luck! Let me know if you run into any other issues and I can help troubleshoot or make changes to the NavGo.

1 Like

Just the label thing for now I think.
Thanks I hope all this boosts your libraries popularity

On your new library there isnt a singleton as there was in the previous version. Is this purposeful?

I found it in sample demo- but if its required why wouldnt it be in the library load?

Ok so created the directional character and copied the singleton script over and edited it.

No node sprites show, no looping, no errors thrown.

And heres the successful result:

Now I need to build a factory to spawn marbles based on an array.

1 Like

So to simplify this whole thread and keep at end for easy find: REDO 3

A. install library for the project as a dependency
B. Then in the collection:

  1. in collection add go handler
  2. create a folder in main called: directionalCharacter
    3.Add a game obejct called directionalCharacterGo
  3. add a script called directionalCharacterSingleton
  4. Add this text in directionalCharacterSingleton script:
  5. Add this text in directionalCharacterSingleton script:
require("navGo_pathfinding.NavGO_Global")

go.property("Start_path_ID", -1)
go.property("End_path_ID", -1)
go.property("speed", 400)

local function distance(vec1, vec2)
	return math.ceil( math.sqrt( math.pow(vec1.x - vec2.x, 2) + math.pow(vec1.y - vec2.y, 2) ) )
end

local function moveCharacter(self)
	local path, found = NAVGO.GET_PATH_FROM_DIRECTIONAL_ID(self.Start_path_ID, self.End_path_ID)
	if not found then
		print("Directional - NO PATH FOUND")
	else
		print("Directional - PATH FOUND")
		local delay = 0
		local lastPos = go.get_position(go.get_id())

		for i=1, #path do
			local newPosition = vmath.vector3(path[i].x, path[i].y, 1)
			local time = distance(newPosition, lastPos) / self.speed -- move at a consistend speed
			go.animate(go.get_id(), "position", go.PLAYBACK_ONCE_FORWARD, newPosition, go.EASING_LINEAR, time, delay)
			lastPos = newPosition
			delay = delay + time
		end
		--timer.delay(delay, false, moveCharacter)
	end
end


------------------
--Core functions--
------------------

function init(self)
	timer.delay(1, false, moveCharacter)
end

function on_message(self, message_id, message, sender)
	if message_id == hash("contact_point_response") then
		if message.group == hash("wall") then
			local newpos = go.get_position() + message.normal * message.distance
			go.set_position(newpos)
		end
	end
end
  1. in outline click on the singleton script and set the start =1 and end = 100 or whatever node qty you might need
  2. add directional nodes (you can cut and paste as well)
  3. in direction node script update this node to current node number, and goto node as next node number. Start node: this=1, next= 2 , second node: this=2, next=3 … end this=100 , next=1 . End and start nodes must match script in handler script
  4. sprite must stay named sprite in the nodes
  5. From within the collection you have to initialize the NavGo handler by sending an init message. I recommend having the following code in your main script to handle it.
function init(self)
        local message = {}
        message.collisions = { hash("wall") }
    	message.debug = false
    	message.deleteNodeAfterGotten = true
    	message.nodeNeighborRange = 400
    	message.nodeNeighborRange = 400
    	msg.post("/NavGO_HandlerGO#NavGO_HandlerScript", hash("init"), message)
end

function on_message(self, message_id, message, sender)
       if message_id == hash("NavGO_Ready") then
                -- NavGo is ready to be used
                -- Add game start code
       end
end

Sorry one last thing:

I have a timer in init in main script:

timer.delay(1, true, function() 
	factory.create("#factory")
end)

When I instance the directionalCharacterGO with factory I get:

ERROR:SCRIPT: /navGo_pathfinding/NavGO_Global.lua:134: attempt to index a nil value
stack traceback:
	/navGo_pathfinding/NavGO_Global.lua:134: in function 'GET_PATH_FROM_DIRECTIONAL_ID'
	/directional_character/directionalCharacterSingleton.script:12: in function </directional_character/directionalCharacterSingleton.script:11>

How do you instance multiple directionalCharacterGO to the path?

To answer all of your questions at once:

The singleton in the project I very much added as a way for me to test out the system. I left it in intended it to be an example of how to query the NavGo or to have it be a reference to people programming their own NPC’s or other objects with the NavGo. While you can definitely use it like that, it does have some unintended effects. Due to the fact you are trying to spawn it through a factory, all of the information currently held in the property (go.property variables) isn’t being stored. To fix this you would need to either change the default values of self.Start_path_ID and self.End_path_ID in the script by doing something like this:

go.property(Start_path_ID , 1) -- ID to start at
go.property(End_path_ID, 100) -- ID to end at

This solution would require you to have a different script per enemy type. So, thankfully there is an easier way to do this by sending values with the factory. The factory function can take many variables changing the spawn position, initial rotation, properties to send and scale of the object. You can send initial properties that will fill out the go.property variables that exist within a character. You do this by creating a table and assigning values to all of the properties, see the code bellow as an example.

local function spawnEnemy(self)
	local url = "/go#characterFactory"
	local position = vmath.vector3(330, 360, 0) -- position where character spawns
	local rotation = vmath.quat_rotation_z(0) --quat angle the character spawns at
	local properties = { Start_path_ID = 1, End_path_ID = 2, speed = 400 } -- table of values to send to object
	local scale = vmath.vector3(1, 1, 1) -- scale to spawn at
	factory.create(url, position, rotation, properties, scale)
end

Does that answer your questions?

I hope so. I dont like imposing on you this much. As you can tell I am very much just learning the ropes on Defold which is very different from the other engines I use.

I dont know if ill get to it tonight or morrow But Ill let you know if I have success.

Thank you so much for your patience with me.

1 Like