Figured how to use 3D Navigation! (Defold Detour)

I was trying to find how to use 3D navigation and finally found a way to use it. Since I remember others also having problems with it, here is how you can use it simply:

Add the Defold Detour to your game as dependency.

The main problem with using the asset was installing recast nav to get the mesh that can be used for navigation using models. The solution is not installing it at all! While searching for a way, I found this site where you can upload your model and get the mesh as bin file easily. The site is for another engine/framework (might be babylon js) but works with Defold too. Here is the site: NavMesh Generator - recast-navigation

After that, you can use a script and make your NPCs follow a target. Here is an example script (I stripped down my enemy script so only the navigation logic will remain but did not test this version of the code):

go.property("move_speed", 2)
go.property("turn_speed", 15)
go.property("repath_interval", 0.5)

local function move_towards(self, target, dt)
	local pos = go.get_position()
	local dir = target - pos
	dir.y = 0

	local dist = vmath.length(dir)
	if dist < 0.05 then return true end

	dir = vmath.normalize(dir)
	local next_pos = pos + dir * self.move_speed * dt
	go.set_position(next_pos)

	local current_rot = go.get_rotation()
	local target_angle = math.atan2(dir.x, dir.z)
	local target_rot = vmath.quat_rotation_y(target_angle)
	go.set_rotation(vmath.slerp(dt * self.turn_speed, current_rot, target_rot))

	return false
end

local function draw_path_lines(self)
	for _, seg in ipairs(self.debug_lines) do
		msg.post("@render:", "draw_line", {
			start_point = seg.a,
			end_point = seg.b,
			color = vmath.vector4(0, 1, 0, 1)
		})
	end
end

function init(self)
	self.target = hash("/Player/Player")

	self.repath_timer = 0
	self.path = {}
	self.path_index = 1
	self.debug_lines = {}

	-- Load navmesh
	local data = sys.load_resource("/_Resources/Navigation/level_navmesh.bin")
	self.query = detour.init(data)
	assert(self.query, "Navmesh initialization failed")
end

function update(self, dt)
	if not self.query then return end

	self.repath_timer = self.repath_timer + dt

	-- Repathing on interval
	if self.repath_timer >= self.repath_interval then
		self.repath_timer = 0

		local start = go.get_position()
		local goal = go.get_position(self.target)

		self.path = self.query:find_path(start, goal)
		self.path_index = 1
		self.debug_lines = {}

		-- Create debug line segments
		if self.path and #self.path > 1 then
			for i = 1, #self.path - 1 do
				table.insert(self.debug_lines, {
					a = self.path[i].position,
					b = self.path[i + 1].position
				})
			end
		end
	end

	-- Follow the path
	if self.path and #self.path > 0 then
		local waypoint = self.path[self.path_index]
		if waypoint then
			local reached = move_towards(self, waypoint.position, dt)
			if reached then
				self.path_index = self.path_index + 1
			end
		end
	end

	draw_path_lines(self)
end

function final(self)
	if self.query then
		self.query:delete()
	end
end

6 Likes