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