wade-drummond

VHS AI Enemy Pathfinding

Jul 14th, 2024
27
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Game Maker 15.66 KB | Gaming | 0 0
  1. #macro PATHFINDING_GRID_CELL_DIVISOR 2
  2. #macro PATHFINDING_GRID_CELL_DIAGONAL_COST (1.4)
  3. #macro PATHFINDING_GRID_CELL_DIAGONAL_ENABLED false
  4.  
  5. enum E_ENEMY_PATHFINDING_TILE_TYPES {
  6.     EMPTY,
  7.     DOOR_OPEN,
  8.     DOOR_CLOSED,
  9.     WINDOW_OPEN,
  10.     WINDOW_CLOSED,
  11.     WALL
  12. }
  13.  
  14. function enemy_pathfinding_node(_x, _y, _type) constructor  {
  15.     x = _x;
  16.     y = _y;
  17.     type = _type;
  18.     traversable = true;
  19.     neighbors = [];
  20.  
  21.     cost_base = 1.0;
  22.     cost_from_start = infinity;
  23.     cost_to_goal = infinity;
  24.     cost_total = infinity;
  25.     parent = undefined;
  26.     priority_queue_added = false;
  27.  
  28.     function add_neighbor(_neighbor)    {
  29.         var _neighbor_is_diagonal = (abs(x - _neighbor.x) == 1 and abs(y - _neighbor.y) == 1);
  30.         if ((not PATHFINDING_GRID_CELL_DIAGONAL_ENABLED) and _neighbor_is_diagonal) return;
  31.         var _diagonal_cost = _neighbor_is_diagonal ? (PATHFINDING_GRID_CELL_DIAGONAL_COST) : (0.0);
  32.         var _neighbor_cost = cost_base + _neighbor.cost_base + _diagonal_cost;
  33.         var _neighbor_object = {
  34.             node: _neighbor,
  35.             cost: _neighbor_cost
  36.         }
  37.         array_push(neighbors, _neighbor_object);
  38.     }
  39.     function costs_reset()  {
  40.         cost_from_start = infinity;
  41.         cost_to_goal = infinity;
  42.         cost_total = infinity;
  43.         parent = undefined;
  44.         priority_queue_added = false;
  45.     }
  46.     function init() {
  47.         switch (type)  {
  48.             case E_ENEMY_PATHFINDING_TILE_TYPES.EMPTY:
  49.                 traversable = true;
  50.                 cost_base = 1.0;
  51.                 break;
  52.  
  53.             case E_ENEMY_PATHFINDING_TILE_TYPES.DOOR_OPEN:
  54.                 traversable = true;
  55.                 cost_base = 1.0;
  56.                 break;
  57.  
  58.             case E_ENEMY_PATHFINDING_TILE_TYPES.DOOR_CLOSED:
  59.                 traversable = true;
  60.                 cost_base = 10.0;
  61.                 break;
  62.  
  63.             case E_ENEMY_PATHFINDING_TILE_TYPES.WINDOW_OPEN:
  64.                 traversable = true;
  65.                 cost_base = 2.0;
  66.                 break;
  67.  
  68.             case E_ENEMY_PATHFINDING_TILE_TYPES.WINDOW_CLOSED:
  69.                 traversable = true;
  70.                 cost_base = 4.0;
  71.                 break;
  72.  
  73.             case E_ENEMY_PATHFINDING_TILE_TYPES.WALL:
  74.                 traversable = false;
  75.                 cost_base = infinity;
  76.                 break;
  77.         }
  78.     }
  79. }
  80.  
  81. function init_pathfinding() {
  82.     pathfinding_priority_queue = ds_priority_create();
  83.     pathfinding_grid_cell_size = (GRID_CELL_SIZE / PATHFINDING_GRID_CELL_DIVISOR);
  84.  
  85.     var _level_current = global.master_instance.level_selected_current;
  86.     enemy_pathfinding_grid_width = ceil(global.level_border_width / pathfinding_grid_cell_size);
  87.     enemy_pathfinding_grid_height = ceil(global.level_border_height / pathfinding_grid_cell_size);
  88.     enemy_pathfinding_cells_affected = {};
  89.  
  90.     execute_grid_bake();
  91.  
  92.     pathfinding_debug = false;
  93. }
  94.  
  95. function execute_grid_reset_costs() {
  96.     for (var _grid_x = 0; _grid_x < enemy_pathfinding_grid_width; _grid_x ++) {
  97.         for (var _grid_y = 0; _grid_y < enemy_pathfinding_grid_height; _grid_y ++) {
  98.             enemy_pathfinding_grid[_grid_x][_grid_y].costs_reset();
  99.         }
  100.     }
  101. }
  102. function execute_grid_add_object(_instance_id, _type)  {
  103.     var _instance_uuid = string(_instance_id.uuid);
  104.     var _cell_left = floor(_instance_id.bbox_left / pathfinding_grid_cell_size);
  105.     var _cell_top = floor(_instance_id.bbox_top / pathfinding_grid_cell_size);
  106.     var _cell_right = floor((_instance_id.bbox_right - 1) / pathfinding_grid_cell_size);
  107.     var _cell_bottom = floor((_instance_id.bbox_bottom - 1) / pathfinding_grid_cell_size);
  108.  
  109.     for (var _grid_x = _cell_left; _grid_x <= _cell_right; _grid_x ++) {
  110.         for (var _grid_y = _cell_top; _grid_y <= _cell_bottom; _grid_y ++) {
  111.             var _affected_cells_array = enemy_pathfinding_cells_affected[$ _instance_uuid];
  112.             if is_undefined(_affected_cells_array) {
  113.                 _affected_cells_array = [];
  114.                 enemy_pathfinding_cells_affected[$ _instance_uuid] = _affected_cells_array;
  115.             }
  116.             if not return_pathfinding_coordinate_valid(_grid_x, _grid_y) {
  117.                 continue;
  118.             }
  119.             var _node = enemy_pathfinding_grid[_grid_x][_grid_y];
  120.             _node.type = _type;
  121.             _node.init();
  122.  
  123.             array_push(_affected_cells_array, _node);
  124.         }
  125.     }
  126. }
  127. function execute_grid_update_object(_instance_id, _type)   {
  128.     var _instance_uuid = _instance_id.uuid;
  129.     var _affected_cells = enemy_pathfinding_cells_affected[$ _instance_uuid];
  130.     if is_undefined(_affected_cells) {
  131.         return;
  132.     }
  133.     for (var _cell_index = 0; _cell_index < array_length(_affected_cells); _cell_index++) {
  134.         var _cell = _affected_cells[_cell_index];
  135.         _cell.type = _type;
  136.         _cell.init();
  137.         _cell.neighbors = [];
  138.         for (var _neighbor_offset_x = -1; _neighbor_offset_x <= 1; _neighbor_offset_x++) {
  139.             for (var _neighbor_offset_y = -1; _neighbor_offset_y <= 1; _neighbor_offset_y++) {
  140.                 var _neighbor_x = _cell.x + _neighbor_offset_x;
  141.                 var _neighbor_y = _cell.y + _neighbor_offset_y;
  142.                 if return_pathfinding_coordinate_valid(_neighbor_x, _neighbor_y) {
  143.                     var _neighbor = enemy_pathfinding_grid[_neighbor_x][_neighbor_y];
  144.                     if _neighbor.traversable {
  145.                         _cell.add_neighbor(_neighbor);
  146.                     }
  147.                 }
  148.             }
  149.         }
  150.     }
  151. }
  152. function execute_grid_bake()    {
  153.     enemy_pathfinding_grid = [];
  154.     for (var _grid_x = 0; _grid_x < enemy_pathfinding_grid_width; _grid_x ++) {
  155.         enemy_pathfinding_grid[_grid_x] = [];
  156.         for (var _grid_y = 0; _grid_y < enemy_pathfinding_grid_height; _grid_y ++) {
  157.             var _node_object = new enemy_pathfinding_node(_grid_x, _grid_y, E_ENEMY_PATHFINDING_TILE_TYPES.EMPTY);
  158.             _node_object.init();
  159.             enemy_pathfinding_grid[_grid_x][_grid_y] = _node_object;
  160.         }
  161.     }
  162.  
  163.     var _level_cell_coordinates = return_pathfinding_cell_coordinate;
  164.     with (Wall) {
  165.         other.execute_grid_add_object(id, E_ENEMY_PATHFINDING_TILE_TYPES.WALL);
  166.     }
  167.  
  168.     with (Door) {
  169.         var _mask_index = mask_index;
  170.         mask_index = mskWall;
  171.         if not opened   {
  172.             other.execute_grid_add_object(id, E_ENEMY_PATHFINDING_TILE_TYPES.DOOR_CLOSED);
  173.         }
  174.         mask_index = _mask_index;
  175.     }
  176.  
  177.     with (Corner)   {
  178.         other.execute_grid_add_object(id, E_ENEMY_PATHFINDING_TILE_TYPES.WALL);
  179.     }
  180.  
  181.     with (Window)   {
  182.         var _type = window_barricaded ? E_ENEMY_PATHFINDING_TILE_TYPES.WINDOW_CLOSED : E_ENEMY_PATHFINDING_TILE_TYPES.WINDOW_OPEN;
  183.         other.execute_grid_add_object(id, _type);
  184.     }
  185.  
  186.     for (var _grid_x = 0; _grid_x < enemy_pathfinding_grid_width; _grid_x ++) {
  187.         for (var _grid_y = 0; _grid_y < enemy_pathfinding_grid_height; _grid_y ++) {
  188.             for (var _neighbor_x = -1; _neighbor_x <= 1; _neighbor_x ++) {
  189.                 for (var _neighbor_y = -1; _neighbor_y <= 1; _neighbor_y ++) {
  190.                     var _neighbor_x_index = _grid_x + _neighbor_x;
  191.                     var _neighbor_y_index = _grid_y + _neighbor_y;
  192.                     var _neighbor_coordinate_valid = return_pathfinding_coordinate_valid(_neighbor_x_index, _neighbor_y_index);
  193.                     if (_neighbor_coordinate_valid) {
  194.                         var _neighbor_node = enemy_pathfinding_grid[_neighbor_x_index][_neighbor_y_index];
  195.                         if (_neighbor_node.traversable) {
  196.                             enemy_pathfinding_grid[_grid_x][_grid_y].add_neighbor(_neighbor_node);
  197.                         }
  198.                     }
  199.                 }
  200.             }
  201.         }
  202.     }
  203. }
  204.  
  205. function return_pathfinding_coordinate_valid(_cell_x, _cell_y)  {
  206.     if (_cell_x < 0 || _cell_x >= enemy_pathfinding_grid_width) {
  207.         return false;
  208.     }
  209.     if (_cell_y < 0 || _cell_y >= enemy_pathfinding_grid_height) {
  210.         return false;
  211.     }
  212.     return true;
  213. }
  214. function return_pathfinding_result(_node_begin, _node_end) {
  215.     execute_grid_reset_costs();
  216.     ds_priority_clear(pathfinding_priority_queue);
  217.     var _enemy_pathfinding_open = [];
  218.     _node_begin.cost_from_start = 0;
  219.     _node_begin.cost_to_goal = return_pathfinding_heuristic(_node_begin, _node_end);
  220.     _node_begin.cost_total = _node_begin.cost_to_goal;
  221.     ds_priority_add(pathfinding_priority_queue, _node_begin, _node_begin.cost_total);
  222.  
  223.     var _nearest_traversable_node = undefined;
  224.     var _min_distance = infinity;
  225.  
  226.     while (not ds_priority_empty(pathfinding_priority_queue)) {
  227.         var _node_current = ds_priority_delete_min(pathfinding_priority_queue);
  228.  
  229.         if (return_pathfinding_coordinate_valid(_node_current.x, _node_current.y)) {
  230.             var _distance_to_end = return_pathfinding_heuristic(_node_current, _node_end);
  231.             if (_distance_to_end < _min_distance) {
  232.                 _nearest_traversable_node = _node_current;
  233.                 _min_distance = _distance_to_end;
  234.             }
  235.         }
  236.  
  237.         if (_node_current == _node_end) {
  238.             if return_pathfinding_coordinate_valid(_node_end.x, _node_end.y) {
  239.                 return return_pathfinding_path_array_reconstructed(_node_begin, _node_end);
  240.             }
  241.         }
  242.  
  243.         for (var _neighbor_index = 0; _neighbor_index < array_length(_node_current.neighbors); _neighbor_index++) {
  244.             var _neighbor_object = _node_current.neighbors[_neighbor_index];
  245.             var _neighbor_node = _neighbor_object.node;
  246.             var _neighbor_cost = _neighbor_object.cost;
  247.             var _cost_from_start = _node_current.cost_from_start + _neighbor_cost;
  248.             var _cost_to_goal = return_pathfinding_heuristic(_neighbor_node, _node_end);
  249.             var _cost_total = _cost_from_start + _cost_to_goal;
  250.  
  251.             if (_cost_total < _neighbor_node.cost_total) {
  252.                 _neighbor_node.cost_from_start = _cost_from_start;
  253.                 _neighbor_node.cost_to_goal = _cost_to_goal;
  254.                 _neighbor_node.cost_total = _cost_total;
  255.                 _neighbor_node.parent = _node_current;
  256.  
  257.                 if not _neighbor_node.priority_queue_added {
  258.                     ds_priority_add(pathfinding_priority_queue, _neighbor_node, _neighbor_node.cost_total);
  259.                     _neighbor_node.priority_queue_added = true;
  260.                 }
  261.             }
  262.         }
  263.     }
  264.  
  265.     if (_nearest_traversable_node != undefined) {
  266.         return return_pathfinding_path_array_reconstructed(_node_begin, _nearest_traversable_node);
  267.     }
  268.  
  269.     return undefined;
  270. }
  271. function return_pathfinding_path_array_reconstructed(_node_begin, _node_end)  {
  272.     var _node_current = _node_end;
  273.     var _path_array = [];
  274.     while (_node_current != _node_begin) {
  275.         array_insert(_path_array, 0, {
  276.             x: (_node_current.x * pathfinding_grid_cell_size) + (pathfinding_grid_cell_size / 2),
  277.             y: (_node_current.y * pathfinding_grid_cell_size) + (pathfinding_grid_cell_size / 2),
  278.         });
  279.        
  280.         _node_current = _node_current.parent;
  281.     }
  282.     return _path_array;
  283. }
  284. function return_pathfinding_path(_begin_x, _begin_y, _end_x, _end_y)    {
  285.     var _begin_coordinates = return_pathfinding_cell_coordinate(_begin_x, _begin_y);
  286.     var _end_coordinates = return_pathfinding_cell_coordinate(_end_x, _end_y);
  287.  
  288.     if not return_pathfinding_coordinate_valid(_begin_coordinates.x, _begin_coordinates.y) {
  289.         return undefined;
  290.     }
  291.     if not return_pathfinding_coordinate_valid(_end_coordinates.x, _end_coordinates.y) {
  292.         return undefined;
  293.     }
  294.  
  295.     var _node_begin = return_pathfinding_node(_begin_coordinates.x, _begin_coordinates.y);
  296.     var _node_end = return_pathfinding_node(_end_coordinates.x, _end_coordinates.y);
  297.     var _node_path = return_pathfinding_result(_node_begin, _node_end);
  298.  
  299.     if is_undefined(_node_path) or array_length(_node_path) == 0 {
  300.         return undefined;
  301.     }
  302.  
  303.     array_push(_node_path, {
  304.         x: _end_x,
  305.         y: _end_y
  306.     });
  307.  
  308.     return _node_path;
  309. }
  310. function return_pathfinding_cell_coordinate(_room_pos_x, _room_pos_y) {
  311.     var _cell_x = floor(_room_pos_x / pathfinding_grid_cell_size);
  312.     var _cell_y = floor(_room_pos_y / pathfinding_grid_cell_size);
  313.     return { x: _cell_x, y: _cell_y };
  314. }
  315. function return_pathfinding_node(_cell_x, _cell_y)  {
  316.     if not return_pathfinding_coordinate_valid(_cell_x, _cell_y) {
  317.         return undefined;
  318.     }
  319.     return enemy_pathfinding_grid[_cell_x][_cell_y];
  320. }
  321. function return_pathfinding_node_nearest_traversable(_start_x, _start_y)    {
  322.     var _node_array = [{
  323.         x: _start_x,
  324.         y: _start_y
  325.     }];
  326.     var _nodes_visited = [];
  327.  
  328.     array_push(_nodes_visited, {
  329.         x: _start_x,
  330.         y: _start_y
  331.     });
  332. }
  333. function return_actor_position_valid(_pos_x, _pos_y)    {
  334.     var _cell_coordinates = return_pathfinding_cell_coordinate(_pos_x, _pos_y);
  335.     if not return_pathfinding_coordinate_valid(_cell_coordinates.x, _cell_coordinates.y) {
  336.         return false;
  337.     }
  338.     var _node = return_pathfinding_node(_cell_coordinates.x, _cell_coordinates.y);
  339.     switch (_node.type) {
  340.         case E_ENEMY_PATHFINDING_TILE_TYPES.WALL:
  341.             return false;
  342.     }
  343.     return true;
  344. }
  345. function return_pathfinding_heuristic(_node_current, _node_target) {
  346.     if PATHFINDING_GRID_CELL_DIAGONAL_ENABLED {
  347.         var _direction_x = abs(_node_current.x - _node_target.x);
  348.         var _direction_y = abs(_node_current.y - _node_target.y);
  349.         return sqrt((_direction_x * _direction_x) + (_direction_y * _direction_y));
  350.     }
  351.     var _distance_x = abs(_node_current.x - _node_target.x);
  352.     var _distance_y = abs(_node_current.y - _node_target.y);
  353.     return _distance_x + _distance_y;
  354. }
  355.  
  356. function draw_debug_grid()  {
  357.     if not pathfinding_debug return;
  358.     draw_set_halign(fa_center);
  359.     draw_set_valign(fa_middle);
  360.     draw_set_font(global.font_main);
  361.     for (var _grid_x = 0; _grid_x < enemy_pathfinding_grid_width; _grid_x ++) {
  362.         for (var _grid_y = 0; _grid_y < enemy_pathfinding_grid_height; _grid_y ++) {
  363.             var _node = enemy_pathfinding_grid[_grid_x][_grid_y];
  364.             var _node_x = _node.x * pathfinding_grid_cell_size;
  365.             var _node_y = _node.y * pathfinding_grid_cell_size;
  366.            
  367.             switch (_node.type) {
  368.                 case E_ENEMY_PATHFINDING_TILE_TYPES.EMPTY:
  369.                     draw_set_color(c_white);
  370.                     break;
  371.  
  372.                 case E_ENEMY_PATHFINDING_TILE_TYPES.DOOR_OPEN:
  373.                     draw_set_color(c_aqua);
  374.                     break;
  375.                    
  376.                 case E_ENEMY_PATHFINDING_TILE_TYPES.DOOR_CLOSED:
  377.                     draw_set_color(c_navy);
  378.                     break;
  379.  
  380.                 case E_ENEMY_PATHFINDING_TILE_TYPES.WINDOW_OPEN:
  381.                     draw_set_color(c_lime);
  382.                     break;
  383.  
  384.                 case E_ENEMY_PATHFINDING_TILE_TYPES.WINDOW_CLOSED:
  385.                     draw_set_color(c_yellow);
  386.                     break;
  387.  
  388.                 case E_ENEMY_PATHFINDING_TILE_TYPES.WALL:
  389.                     draw_set_color(c_red);
  390.                     break;
  391.             }
  392.  
  393.             draw_set_alpha(0.1);
  394.             draw_rectangle(_node_x, _node_y, _node_x + pathfinding_grid_cell_size, _node_y + pathfinding_grid_cell_size, false);
  395.             draw_set_colour(c_white);
  396.             draw_set_alpha(0.6);
  397.             var _text = string(floor(_node.cost_total));
  398.             draw_text_transformed(_node_x + (pathfinding_grid_cell_size / 2), _node_y + (pathfinding_grid_cell_size / 2), _text, 0.4, 0.4, 0);
  399.         }
  400.     }
  401.     draw_set_alpha(1.0);
  402. }
  403.  
  404. init_pathfinding();
Advertisement
Add Comment
Please, Sign In to add comment