@@ -12,7 +12,7 @@ Defold Graph Pathfinder Extension - Navigation mesh pathfinding with polygon-bas
1212- [ Spatial Index] ( #spatial-index )
1313- [ Statistics] ( #statistics )
1414- [ Enumerations] ( #enumerations )
15- - [ Complete Example ] ( #complete-example )
15+
1616
1717---
1818
288288- ` agent_radius = 0 ` : No portal offsetting, path hugs walls
289289- ` agent_radius > 0 ` : Portals offset inward by radius, provides collision clearance
290290- Narrow portals (< ` agent_radius * collapse_threshold ` ) collapse to midpoint
291- - Recommended: Set ` agent_radius ` to character's collision radius
292291
293292### pathfinder.navmesh_cell_at_position()
294293
@@ -523,214 +522,6 @@ end
523522
524523---
525524
526- ## Complete Example
527-
528- Here's a complete example demonstrating navmesh pathfinding in a 3D game:
529-
530- ``` lua
531- -- navmesh_controller.script
532-
533- go .property (" navmesh_buffer" , resource .buffer (" /assets/level1.navmesh" ))
534-
535- local COLORS = {
536- RED = vmath .vector4 (1 , 0 , 0 , 1 ),
537- GREEN = vmath .vector4 (0 , 1 , 0 , 1 ),
538- BLUE = vmath .vector4 (0 , 0 , 1 , 1 )
539- }
540-
541- -- State
542- local player_position = vmath .vector3 ()
543- local target_position = vmath .vector3 ()
544- local current_path = {}
545- local path_length = 0
546- local path_index = 1
547- local grid = {}
548-
549- -- Constants
550- local MOVE_SPEED = 5.0
551- local WAYPOINT_RADIUS = 0.5
552- local AGENT_RADIUS = 0.5
553-
554- -- Utility: Convert screen coordinates to world plane
555- local function screen_to_world (camera_url , screen_x , screen_y )
556- local PLANE_POINT = vmath .vector3 (0 , 0 , 0 )
557- local PLANE_NORMAL = vmath .vector3 (0 , 1 , 0 )
558-
559- local p0 = camera .screen_to_world (vmath .vector3 (screen_x , screen_y , 0 ), camera_url )
560- local p1 = camera .screen_to_world (vmath .vector3 (screen_x , screen_y , 1 ), camera_url )
561-
562- local dir = p1 - p0
563- local denom = vmath .dot (PLANE_NORMAL , dir )
564- if math.abs (denom ) < 0.000001 then
565- return nil
566- end
567-
568- local t = vmath .dot (PLANE_POINT - p0 , PLANE_NORMAL ) / denom
569- if t < 0 then
570- return nil
571- end
572-
573- return p0 + dir * t
574- end
575-
576- function init (self )
577- msg .post (" ." , " acquire_input_focus" )
578-
579- -- Initialize navmesh system
580- pathfinder .navmesh_init (
581- 600 , -- max_cells
582- 6 , -- max_edges_per_cell
583- 32 , -- pool_block_size
584- 16 , -- cache_size
585- 256 , -- max_cache_path_length
586- 5 , -- min_cell_size
587- 10 , -- max_cell_size
588- 1000 , -- max_grid_dim
589- false -- debug
590- )
591-
592- -- Optional: Customize funnel tolerances
593- pathfinder .navmesh_set_funnel (0.002 , 0.1 , 0.001 )
594-
595- -- Load navmesh from buffer
596- local buffer = resource .get_buffer (self .navmesh_buffer )
597- pathfinder .navmesh_set_buffer (buffer )
598-
599- -- Get spatial index for visualization
600- grid = pathfinder .navmesh_get_spatial_index ()
601-
602- -- Initialize player position
603- player_position = go .get_position ()
604-
605- print (" Navmesh initialized successfully" )
606- end
607-
608- function final (self )
609- pathfinder .navmesh_shutdown ()
610- end
611-
612- function update (self , dt )
613- -- Follow current path
614- if path_index <= path_length then
615- local waypoint = current_path [path_index ]
616- local target = vmath .vector3 (waypoint .x , 0 , waypoint .y )
617-
618- -- Move towards waypoint
619- local to_target = target - player_position
620- local distance = vmath .length (to_target )
621-
622- if distance < WAYPOINT_RADIUS then
623- -- Reached waypoint, move to next
624- path_index = path_index + 1
625- else
626- -- Move towards waypoint
627- local direction = vmath .normalize (to_target )
628- player_position = player_position + direction * MOVE_SPEED * dt
629- go .set_position (player_position )
630- end
631- end
632-
633- -- Draw current path
634- draw_path (path_length , current_path )
635-
636- -- Draw spatial index grid (for debugging)
637- draw_spatial_grid (grid )
638-
639- -- Display stats periodically
640- self .stats_timer = (self .stats_timer or 0 ) + dt
641- if self .stats_timer >= 1.0 then
642- self .stats_timer = 0
643- local stats = pathfinder .navmesh_get_stats ()
644- print (string.format (" Cache: %d/%d, Hit Rate: %d%%" ,
645- stats .path_cache .cache_entries ,
646- stats .path_cache .cache_capacity ,
647- stats .path_cache .cache_hit_rate ))
648- end
649- end
650-
651- function on_input (self , action_id , action )
652- if action_id == hash (" mouse_click" ) and action .pressed then
653- -- Get world position from click
654- local world_pos = screen_to_world (msg .url (" /camera#camera" ), action .x , action .y )
655- if not world_pos then
656- return
657- end
658-
659- target_position = world_pos
660-
661- -- Find path from player to target
662- local length , status , status_text , path = pathfinder .navmesh_find_path (
663- player_position .x , -- start_x
664- player_position .z , -- start_y (using Z for 3D)
665- target_position .x , -- goal_x
666- target_position .z , -- goal_y
667- 128 , -- max_path_length
668- AGENT_RADIUS , -- agent_radius
669- true -- enable_fallback
670- )
671-
672- if status == pathfinder .PathStatus .SUCCESS then
673- print (" Path found with" , length , " waypoints" )
674- current_path = path
675- path_length = length
676- path_index = 1
677- elseif status == pathfinder .PathStatus .SUCCESS_START_FALLBACK then
678- print (" Path found (start position corrected)" )
679- current_path = path
680- path_length = length
681- path_index = 1
682- elseif status == pathfinder .PathStatus .SUCCESS_GOAL_FALLBACK then
683- print (" Path found (goal position corrected)" )
684- current_path = path
685- path_length = length
686- path_index = 1
687- elseif status == pathfinder .PathStatus .ERROR_NO_PATH then
688- print (" No path exists to target" )
689- else
690- print (" Pathfinding failed:" , status_text )
691- end
692- end
693- end
694-
695- -- Draw path as connected lines
696- function draw_path (length , path )
697- for i = 1 , length - 1 do
698- local from_node = path [i ]
699- local to_node = path [i + 1 ]
700- msg .post (" @render:" , " draw_line" , {
701- start_point = vmath .vector3 (from_node .x , 0 , from_node .y ),
702- end_point = vmath .vector3 (to_node .x , 0 , to_node .y ),
703- color = COLORS .GREEN
704- })
705- end
706- end
707-
708- -- Draw spatial index grid
709- function draw_spatial_grid (grid )
710- if grid .vertical then
711- for _ , line in ipairs (grid .vertical ) do
712- msg .post (" @render:" , " draw_line" , {
713- start_point = line .start_position ,
714- end_point = line .end_position ,
715- color = vmath .vector4 (1 , 0 , 0 , 0.2 ) -- Semi-transparent red
716- })
717- end
718- end
719-
720- if grid .horizontal then
721- for _ , line in ipairs (grid .horizontal ) do
722- msg .post (" @render:" , " draw_line" , {
723- start_point = line .start_position ,
724- end_point = line .end_position ,
725- color = vmath .vector4 (1 , 0 , 0 , 0.2 ) -- Semi-transparent red
726- })
727- end
728- end
729- end
730- ```
731-
732- ---
733-
734525## Performance Tips
735526
736527### Cache Configuration
@@ -763,11 +554,7 @@ pathfinder.navmesh_init(
763554- ** Medium meshes** (200-500 cells): ` pool_block_size = 64 `
764555- ** Large meshes** (>500 cells): ` pool_block_size = 128 `
765556
766- ### Agent Radius
767557
768- - Set to character's collision radius for realistic clearance
769- - Increase for safety margin (e.g., 1.2× collision radius)
770- - Use 0 only if characters can move exactly along walls
771558
772559### Fallback Strategy
773560
0 commit comments