├── .gitignore ├── LICENSE ├── Migrate_v2.md ├── Migrate_v3.md ├── Migrate_v4.md ├── README.md ├── defgraph └── defgraph.lua ├── examples ├── assets │ └── atlas.atlas ├── example_dynamic_nodes │ ├── example_dynamic_nodes_dot.go │ ├── example_dynamic_nodes_dot.script │ ├── example_dynamic_nodes_main.collection │ └── example_dynamic_nodes_main.script ├── example_static_nodes │ ├── example_static_nodes_dot.go │ ├── example_static_nodes_dot.script │ ├── example_static_nodes_main.collection │ └── example_static_nodes_main.script ├── main.input_binding └── raw │ ├── dot.png │ └── pointy_dot.png └── game.project /.gitignore: -------------------------------------------------------------------------------- 1 | /.internal 2 | /build 3 | .externalToolBuilders 4 | .DS_Store 5 | Thumbs.db 6 | .lock-wscript 7 | *.pyc 8 | .project 9 | .cproject 10 | builtins -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 dev-masih 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Migrate_v2.md: -------------------------------------------------------------------------------- 1 | # Changelog and migration guild from version 1 to 2 2 | 3 | * Internal structure for storing routes changes to reduce process time in other functions for calculations and there are several improvements to local functions that calculate paths, now move_data table contains the path to the destination and not need to rethink every time to save processing power. 4 | * `move_initialize` function no longer need `threshold` as argument. 5 | * `move_player` function now gets `threshold` as an argument so you don't need to call `move_initialize` if you want to change it. 6 | * `move_player` function now returns two things, first is new movement data as a table that you should overwrite the old one and second is move result table with the structure like { `position`: next position of game object as vector3, `is_reached`: is game object reached the destination as boolean } 7 | * `debug_draw_player_move` function now draw game object route through the destination. 8 | 9 | player move 10 | 11 | * version 2 is tagged as `v2` in GitHub repository. -------------------------------------------------------------------------------- /Migrate_v3.md: -------------------------------------------------------------------------------- 1 | # Changelog and migration guild from version 2 to 3.x 2 | 3 | ## 3.1 4 | * Fixed issue with rotation calculation that may cause the game object to scale to flicker. [#4](https://github.com/dev-masih/defgraph/issues/4) 5 | 6 | ## 3.0 7 | * Added ability for game objects to have curved corner paths. 8 | * Added ability to track game object rotation as move result. 9 | * Added only one ways routes, and added separate arguments `two_way_route_color` and `one_way_route_color` in `debug_draw_map_nodes` function. 10 | 11 | one way route 12 | 13 | In the above image green line is a two-way path and the light blue line is a one-way path, a little square is placed on a one-way route near the destination, for example in this image the route is one way from node 5 to 6. 14 | 15 | * Added separate examples of static and dynamic map nodes. 16 | * Added documentation for module settings. 17 | * Added `is_one_way` argument to `map_add_route` function, to able to add just one-way route. 18 | * Added `is_remove_one_way` argument to `map_remove_route` function, to able to remove just one-way route or both between two nodes. 19 | * Added `map_remove_node` function to remove a node and it's connected routes to it. 20 | * Added `map_update_node_position` function to update node positions. now the entire map can move dynamically. 21 | * Added `map_set_properties` function to replace module default settings. 22 | * `move_initialize` function gets `initial_face_vector` as a vector3 to calculate game object face direction based on this value. setting this value to `nil` will disable rotation tracking system and `rotation` field in move result table will always be `nil`. 23 | * `move_player` function no longer needs `threshold` as an argument. 24 | * `move_initialize` function now gets `settings_go_threshold` as a number and you do need to call `move_initialize` if you want to change it. This allows us to prevent situations that a game object always moves with a moving destination node without reaching it, forever. setting this value to `nil` will fall back to the module default value. 25 | * Adding `settings_path_curve_tightness`, `settings_path_curve_roundness`, `settings_path_curve_max_distance_from_corner` and `settings_allow_enter_on_route` to `move_initialize` arguments. you can overwrite these values for a single game object by them. setting these values to `nil` will fall back to the module default value. 26 | * Added `rotation` to move result table that returned from function `move_player` and you can set game object rotation to it. 27 | * Fixed bug that caused a game object to get stuck in a complex intersection. 28 | * Added `is_show_intersection` argument to `debug_draw_player_move` function to allow debugger mark/not mark intersections of game object path. 29 | * version 3 is tagged as `v3` in GitHub repository. -------------------------------------------------------------------------------- /Migrate_v4.md: -------------------------------------------------------------------------------- 1 | # Changelog and migration guild from version 3 to 4.x 2 | 3 | ## 4.1 4 | * Fixed issue that when a game object will reach the last destination it has a minor flicker. 5 | * `ROUTETYPE` members `onetime`, `shuffle` and `cycle` are changed to `ONETIME`, `SHUFFLE` and `CYCLE`. 6 | * Lots of code quality and style improvements and optimized memory and speed consumption. 7 | 8 | ## 4.0 9 | * Added ability to specify multiple destination node id as `destination_list` and type of route that the game object has to walk as `route_type` in `move_initialize` function. 10 | * Support 3 routing method when there is more than one destination: onetime, shuffle and cycle. 11 | * Added `destination_id` to move result table that returned from function `move_player` and you can get node id of current destination from it. 12 | * Fixed bug when game object failed to react when destination get inaccessible in middle of the way. 13 | * version 4 is tagged as `v4` in GitHub repository. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DefGraph v4.1 2 | 3 | defgraph banner 4 | 5 | * **Changelog and migration guild from version 3.1 to 4.x** 6 | * **Changelog and migration guild from version 2 to 3.x** 7 | * **Changelog and migration guild from version 1 to 2** 8 | 9 | This module contains functions to create a world map as a shape of a graph and the ability to manipulate it at any time, easily see debug drawing of this graph and move the game objects inside of this graph with utilizing auto pathfinder with different patterns. 10 | 11 | You can define a graph with several nodes and routes between them and the extension takes care of finding and moving your game object inside this graph with just one call inside player update function. 12 | The gif below shows you this exactly when the destination for all red circles will be selected shuffled between node numbers 6, 18, 14, 2, 4 and 10. 13 | 14 | static routing gif version 4 15 | 16 | As you can see staying on the routes is the number one rule for red circles and they are going to the destination with minimum distance. all you have seen in this gif except for red circles, drawn by defGraph module debug functions and all of them are customizable. 17 | defGraph is adaptable to map change so even if you add or remove routes in the middle of the game, extension tries to find the better road for you. 18 | also, you can update nodes positions, in another word you can have dynamically moving routes ;) 19 | The gif below shows you this exactly when the destination for all red points is cycled between two ends of a dynamic route. 20 | 21 | dynamic routing gif version 4 22 | 23 | This is a community project you are welcome to contribute to it, sending PR, suggest a feature or report a bug. 24 | 25 | ## Installation 26 | You can use DefGraph in your project by adding this project as a [Defold library dependency](http://www.defold.com/manuals/libraries/). Open your game.project file and in the dependencies field under project add: 27 | 28 | https://github.com/dev-masih/defgraph/archive/master.zip 29 | 30 | Once added, you must require the main Lua module via 31 | 32 | ``` 33 | local defgraph = require("defgraph.defgraph") 34 | ``` 35 | Then you can use the DefGraph functions using this module. 36 | 37 | [Official Defold game asset page for DefGraph](https://defold.com/assets/defgraph/) 38 | 39 | ## Module Settings 40 | There are several parameters for the module to works with, you can change these parameters one time for the entire module with `map_set_properties` and let each game object inherit those or set these parameters or each game object with `move_initialize` function. If you choose to not change any of them, the module uses it's own default values. 41 | #### **`Threshold`** 42 | This `number` value used as detection that an object is on a route or not. It's better to use a bigger value as object speed is getting higher to have better movement experience. The module default value is `1` and minimum for this value should be `1`. 43 | #### **`Path Curve Tightness`** 44 | This `number` value determines how tight a turn on the path should be. The module default value is `4` and minimum for this value should be `2`. 45 | 46 | Tightness 2 | Tightness 3 | Tightness 8 47 | :-------------: | :-------------: | :-------------: 48 | Tightness: 2 | Tightness: 3 | Tightness: 8 49 | 50 | #### **`Path Curve Roundness`** 51 | This `number` value determines how round a turn on a path should be. The module default value is `3`. If this value equals `0` the path will not have any curve and the value of `settings_path_curve_tightness` and `settings_path_curve_max_distance_from_corner` will get ignored. The higher value for roundness will need more processing power especially when your map nodes are dynamically moving. 52 | 53 | Roundness 0 | Roundness 1 | Roundness 5 54 | :-------------: | :-------------: | :-------------: 55 | Roundness: 0 | Roundness: 1 | Roundness: 5 56 | 57 | #### **`Path Curve Max Distance From Corner`** 58 | This `number` value determines the maximum value of a turn distance to a corner. The module default value is `10`. If this value equals `0` the path will not have any curve but you should set `settings_path_curve_roundness` to `0` if this is what you want. 59 | 60 | Max 10 | Max 30 | Max 50 61 | :-------------: | :-------------: | :-------------: 62 | Max: 10 | Max: 30 | Max: 50 63 | 64 | #### **`Allow Enter on Route`** 65 | This `boolean` value determines is a game object can enter a map in the middle of a route or is should enter it from corners only. The module default value is `true`. 66 | 67 | False | True 68 | :-------------: | :-------------: 69 | False | True 70 | 71 | ## ROUTETYPE 72 | This extension uses an enum named `ROUTETYPE` to specify how game objects are going to move inside the graph with multiple destinations. 73 | #### **`ROUTETYPE.ONETIME`** 74 | This option allows the game object to go through destinations one by one and when it arrived at the last destination it will stop. 75 | #### **`ROUTETYPE.SHUFFLE`** 76 | This option allows the game object to go through destinations in the shuffled order none stop. 77 | #### **`ROUTETYPE.CYCLE`** 78 | This option allows the game object to go through destinations one by one and when it arrived at the last destination it will go back to the first one and cycle through all destinations none stop. 79 | > **Note:** These enums only affect when the game object has more than one destination. 80 | 81 | ## Functions 82 | These are the list of available functions to use, for better understanding of how this module works, please take a look at project example. 83 | 84 | ### `defgraph.map_set_properties([settings_gameobject_threshold], [settings_path_curve_tightness], [settings_path_curve_roundness], [settings_path_curve_max_distance_from_corner], [settings_allow_enter_on_route])` 85 | Set the main path and move calculation properties, nil inputs will fall back to module default values. These values will overwrite default module values. 86 | #### **arguments:** 87 | * **settings_gameobject_threshold** `(optional number)` - Optional threshold `[1]` 88 | * **settings_path_curve_tightness** `(optional number)` - Optional curve tightness `[4]` 89 | * **settings_path_curve_roundness** `(optional number)` - Optional curve roundness `[3]` 90 | * **settings_path_curve_max_distance_from_corner** `(optional number)` - Optional maximum distance from corner `[10]` 91 | * **settings_allow_enter_on_route** `(optional boolean)` - Optional Is game object allow entring on route `[true]` 92 | 93 | ### `defgraph.map_add_node(position)` 94 | Adding a node at the given position (position.z will get ignored). 95 | #### **arguments:** 96 | * **position** `(vector3)` - New node position 97 | #### **return:** 98 | * `(number)` - Newly added node id 99 | 100 | > **Note:** Single nodes with no route attached to them are not participating in any routing calculations and it's better to remove them if you are not using them. 101 | 102 | ### `defgraph.map_add_route(source_id, destination_id, [is_one_way])` 103 | Adding a two-way route between two nodes, you can set it as one way or two way. 104 | #### **arguments:** 105 | * **source_id** `(number)` - Source node id 106 | * **destination_id** `(number)` - Destination node id 107 | * **is_one_way** `(optional boolean)` - Optional Is adding just one-way route `[false]` 108 | 109 | > **Note:** If you never need to get pathfinding result in two way it's better to use a one-way path because it will be a bit computationally lighter. 110 | 111 | ### `defgraph.map_remove_route(source_id, destination_id, [is_remove_one_way])` 112 | Removing an existing route between two nodes, you can set it to remove just one way or both ways. 113 | #### **arguments:** 114 | * **source_id** `(number)` - Source node id 115 | * **destination_id** `(number)` - Destination node id 116 | * **is_remove_one_way** `(optional boolean)` - Optional Is removing just one-way route `[false]` 117 | 118 | ### `defgraph.map_remove_node(node_id)` 119 | Removing an existing node, attached routes to this node will remove. 120 | #### **arguments:** 121 | * **node_id** `(number)` - Node id 122 | 123 | ### `defgraph.map_update_node_position(node_id, position)` 124 | Update an existing node position. 125 | #### **arguments:** 126 | * **node_id** `(number)` - Node id 127 | * **position** `(vector3)` - New node position 128 | 129 | ### `defgraph.move_initialize(source_position, destination_list, [route_type], [initial_face_vector], [settings_gameobject_threshold], [settings_path_curve_tightness], [settings_path_curve_roundness], [settings_path_curve_max_distance_from_corner], [settings_allow_enter_on_route])` 130 | Initialize moves from a source position to destination node list inside the created map and using given threshold and initial face vector as game object initial face direction and path calculate settings considering the route type, **the optional value will fall back to module default values.** 131 | #### **arguments:** 132 | * **source_position** `(vector3)` - Node start position 133 | * **destination_list** `(table)` - Table of destinations id 134 | * **route_type** `(optional ROUTETYPE)` - Optional Type of route `[ROUTETYPE.ONETIME]` 135 | * **initial_face_vector** `(optional vecotr3)` - Optional Initial game object face vector `[nil]` 136 | * **settings_gameobject_threshold** `(optional number)` - Optional threshold `[settings_main_gameobject_threshold]` 137 | * **settings_path_curve_tightness** `(optional number)` - Optional curve tightness `[settings_main_path_curve_tightness]` 138 | * **settings_path_curve_roundness** `(optional number)` - Optional curve roundness `[settings_main_path_curve_roundness]` 139 | * **settings_path_curve_max_distance_from_corner** `(optional number)` - Optional maximum distance from corner `[settings_main_path_curve_max_distance_from_corner]` 140 | * **settings_allow_enter_on_route** `(optional boolean)` - Optional Is game object allow entring on route `[settings_main_allow_enter_on_route]` 141 | #### **return:** 142 | * `(table)` - Special movement data table 143 | > **Note:** The returned special table consists of combined data to use later in `move_player` and `debug_draw_player_move` functions. If at any time you decided to change the destination of game object you have to call this function and overwrite old movement data with returned one. 144 | 145 | ### `defgraph.move_player(current_position, speed, move_data)` 146 | Calculate movements from current position of the game object inside the created map considering given speed, using last calculated movement data. 147 | #### **arguments:** 148 | * **current_position** `(vector3)` - Game object current position 149 | * **speed** `(number)` - Game object speed 150 | * **move_data** `(table)` - Special movement data table 151 | #### **return:** 152 | * `(table)` - New movement data 153 | * `(table)` - Move result table 154 | * **position** `(vector3)` - Next position of game object 155 | * **rotation** `(quat)` - Next rotation of game object 156 | * **is_reached** `(boolean)` - Is game object reached the destination 157 | * **destination_id** `(number)` - Current node id of the game object's destination 158 | 159 | > **Note:** The returned new movement data should overwrite old movement data. normally this function is placed inside game object update function and you can set the game object position to `position` and rotation to `rotation` that is inside move result table. also, you should multiply `dt` with speed yourself before passing it to function. 160 | 161 | > **Note:** In case of a multidestination scenario, `is_reached` is going to be `true` when each time the game object reached destination with an id of `destination_id` after that `is_reached` is back to `false` and `destination_id` will set to next destination node id. 162 | 163 | ### `defgraph.debug_set_properties([node_color], [two_way_route_color], [one_way_route_color], [draw_scale])` 164 | set debug drawing properties 165 | #### **arguments:** 166 | * **node_color** `(optional vector4)` - Optional debug color of nodes `[vector4(1, 0, 1, 1)]` 167 | * **two_way_route_color** `(optional vector4)` - Optional debug color of two-way routes `[vector4(0, 1, 0, 1)]` 168 | * **one_way_route_color** `(optional vector4)` - Optional debug color of one-way routes `[vector4(0, 1, 1, 1)]` 169 | * **draw_scale** `(optional number)` - Optional drawing scale `[5]` 170 | 171 | ### `defgraph.debug_draw_map_nodes([is_show_ids])` 172 | Debug draw all map nodes and choose to show node ids or not. 173 | #### **arguments:** 174 | * **is_show_ids** `(optional boolean)` - Optional Is draw nodes id `[false]` 175 | 176 | ### `defgraph.debug_draw_map_routes()` 177 | Debug draw all map routes. 178 | 179 | ### `defgraph.debug_draw_player_move(movement_data, color, [is_show_intersection])` 180 | Debug draw player specific path with given color. 181 | #### **arguments:** 182 | * **movement_data** `(table)` - Special movement data table 183 | * **color** `(vector4)` - Debug color of paths 184 | * **is_show_intersection** `(optional boolean)` - Optional Is draw intersections `[false]` 185 | 186 | ## Donations 187 | If you really like my work and want to support me, consider donating to me with LTC, BTC or ETH. All donations are optional and are greatly appreciated. 🙏 188 | 189 | | LTC | BTC | ETH | 190 | | ------------- | ------------- | ------------- | 191 | | ![LTC](https://user-images.githubusercontent.com/8469800/160749469-811a4395-4b71-4e03-890d-6f260c4ff36a.jpeg) | ![BTC](https://user-images.githubusercontent.com/8469800/160749502-c5b380d3-455f-483f-9f7f-d6948949259d.jpeg) | ![ETH](https://user-images.githubusercontent.com/8469800/160749569-ad99965c-ddd5-43da-8728-4b79c37fc3f5.jpeg) | 192 | | ltc1qm6r32vjahm8wwd688enxnutks0jffc3kqg7ps5 | bc1qcuuc5r4jw38vf2eztsxag68papuwzd25chrepx | 0x02c22832bc115933Ac11388D5A91e0990eE84667 | 193 | 194 | ## License 195 | DefGraph is released under the MIT License. See the [bundled LICENSE](https://github.com/dev-masih/defgraph/blob/master/LICENSE) file for details. 196 | -------------------------------------------------------------------------------- /defgraph/defgraph.lua: -------------------------------------------------------------------------------- 1 | -- DefGraph 2 | -- This module contains functions to create a world map as a shape of a graph and the ability 3 | -- to manipulate it at any time, easily see debug drawing of this graph and move and rotate 4 | -- game objects inside of this graph with utilizing auto pathfinder. 5 | 6 | local M = {} 7 | 8 | math.randomseed(os.time() - os.clock() * 1000) 9 | 10 | --- Store node data and it's neighbors. 11 | --- Structure: map_node_list[node_id] = { position, type, neighbor_id[]:number } 12 | local map_node_list = {} 13 | 14 | --- Store routes data and line equation info. 15 | --- Structure: map_route_list[from_id][to_id] = { a, b, c, distance } 16 | local map_route_list = {} 17 | 18 | --- Store cached data from pathfinder algorithm. 19 | --- Structure: pathfinder_cache[from_id][to_id] = { change_number, distance, path[]:number } 20 | local pathfinder_cache = {} 21 | 22 | local map_node_id_iterator = 0 23 | local map_change_iterator = 0 24 | 25 | local NODETYPE = {} 26 | NODETYPE.SINGLE = hash("nodetype_single") 27 | NODETYPE.DEADEND = hash("nodetype_deadend") 28 | NODETYPE.INTERSECTION = hash("nodetype_intersection") 29 | 30 | -- color vectors and scale of debug drawing 31 | local debug_node_color = vmath.vector4(1, 0, 1, 1) 32 | local debug_two_way_route_color = vmath.vector4(0, 1, 0, 1) 33 | local debug_one_way_route_color = vmath.vector4(0, 1, 1, 1) 34 | local debug_draw_scale = 5 35 | 36 | -- main settings 37 | local settings_main_gameobject_threshold = 1 38 | local settings_main_path_curve_tightness = 4 39 | local settings_main_path_curve_roundness = 3 40 | local settings_main_path_curve_max_distance_from_corner = 10 41 | local settings_main_allow_enter_on_route = true 42 | 43 | -- math functions 44 | local sqrt = math.sqrt 45 | local pow = math.pow 46 | local abs = math.abs 47 | local huge = math.huge 48 | local pi = math.pi 49 | local atan2 = math.atan2 50 | 51 | --- routing types 52 | M.ROUTETYPE = {} 53 | M.ROUTETYPE.ONETIME = hash("routetype_onetime") 54 | M.ROUTETYPE.SHUFFLE = hash("routetype_shuffle") 55 | M.ROUTETYPE.CYCLE = hash("routetype_cycle") 56 | 57 | --- Set the main path and move calculation properties, nil inputs will fall back to default values. 58 | --- @param settings_gameobject_threshold (number|nil) optional game object threshold [1] 59 | --- @param settings_path_curve_tightness (number|nil) optional path curvature tightness [4] 60 | --- @param settings_path_curve_roundness (number|nil) optional path curvature roundness [3] 61 | --- @param settings_path_curve_max_distance_from_corner (number|nil) optional path curvature maximum distance from corner [10] 62 | --- @param settings_allow_enter_on_route (boolean|nil) optional is game object allow enter on route [true] 63 | function M.map_set_properties(settings_gameobject_threshold, settings_path_curve_tightness, settings_path_curve_roundness, 64 | settings_path_curve_max_distance_from_corner, settings_allow_enter_on_route) 65 | settings_main_gameobject_threshold = settings_gameobject_threshold or settings_main_gameobject_threshold 66 | settings_main_path_curve_tightness = settings_path_curve_tightness or settings_main_path_curve_tightness 67 | settings_main_path_curve_roundness = settings_path_curve_roundness or settings_main_path_curve_roundness 68 | settings_main_path_curve_max_distance_from_corner = settings_path_curve_max_distance_from_corner or settings_main_path_curve_max_distance_from_corner 69 | if settings_allow_enter_on_route ~= nil then 70 | settings_main_allow_enter_on_route = settings_allow_enter_on_route 71 | end 72 | end 73 | 74 | --- Update an existing node position. 75 | --- @param node_id (number) node id 76 | --- @param position (vecotr3) node position 77 | function M.map_update_node_position(node_id, position) 78 | assert(node_id, "You must provide a node id") 79 | assert(position, "You must provide a position") 80 | 81 | assert(map_node_list[node_id], ("Unknown node id %s"):format(tostring(node_id))) 82 | 83 | map_node_list[node_id].position = position 84 | 85 | for from_id, routes in pairs(map_route_list) do 86 | for to_id, route in pairs(routes) do 87 | if from_id == node_id or to_id == node_id then 88 | -- line equation: ax + by + c = 0 89 | local a, b, c 90 | local from_pos = map_node_list[from_id].position 91 | local to_pos = map_node_list[to_id].position 92 | if from_pos.x ~= to_pos.x then 93 | --non vertical 94 | a = (from_pos.y - to_pos.y)/(to_pos.x - from_pos.x) 95 | b = 1 96 | c = ((from_pos.x * to_pos.y) - (to_pos.x * from_pos.y))/(to_pos.x - from_pos.x) 97 | else 98 | --vertical 99 | a = 1 100 | b = 0 101 | c = -from_pos.x 102 | end 103 | map_route_list[from_id][to_id] = { 104 | a = a, 105 | b = b, 106 | c = c, 107 | distance = sqrt(pow(from_pos.x - to_pos.x, 2) + pow(from_pos.y - to_pos.y, 2)) 108 | } 109 | end 110 | end 111 | end 112 | -- map shape is changed 113 | map_change_iterator = map_change_iterator + 1 114 | end 115 | 116 | --- Set the debug drawing properties, nil inputs will fall back to default values. 117 | --- @param node_color (vector4|nil) optional nodes color [vector4(1, 0, 1, 1)] 118 | --- @param two_way_route_color (vector4|nil) optional two-way routes color [vector4(0, 1, 0, 1)] 119 | --- @param one_way_route_color (vector4|nil) optional one-way routes color [vector4(0, 1, 1, 1)] 120 | --- @param draw_scale (number|nil) optional drawing scale [5] 121 | function M.debug_set_properties(node_color, two_way_route_color, one_way_route_color, draw_scale) 122 | debug_node_color = node_color or debug_node_color 123 | debug_two_way_route_color = two_way_route_color or debug_two_way_route_color 124 | debug_one_way_route_color = one_way_route_color or debug_one_way_route_color 125 | debug_draw_scale = draw_scale or debug_draw_scale 126 | end 127 | 128 | --- Count size of non-sequential table. 129 | local function table_size(table) 130 | local count = 0 131 | for _ in pairs(table) do count = count + 1 end 132 | return count 133 | end 134 | 135 | --- Add one way route from one node to another. 136 | local function map_add_oneway_route(source_id, destination_id, route_info) 137 | if not map_route_list[source_id] then map_route_list[source_id] = {} end 138 | 139 | if not map_route_list[source_id][destination_id] then 140 | if not route_info then 141 | -- line equation: ax + by + c = 0 142 | local a, b, c 143 | local from_pos = map_node_list[source_id].position 144 | local to_pos = map_node_list[destination_id].position 145 | if from_pos.x ~= to_pos.x then 146 | --non vertical 147 | a = (from_pos.y - to_pos.y)/(to_pos.x - from_pos.x) 148 | b = 1 149 | c = ((from_pos.x * to_pos.y) - (to_pos.x * from_pos.y))/(to_pos.x - from_pos.x) 150 | else 151 | --vertical 152 | a = 1 153 | b = 0 154 | c = -from_pos.x 155 | end 156 | map_route_list[source_id][destination_id] = { 157 | a = a, 158 | b = b, 159 | c = c, 160 | distance = sqrt(pow(from_pos.x - to_pos.x, 2) + pow(from_pos.y - to_pos.y, 2)) 161 | } 162 | else 163 | map_route_list[source_id][destination_id] = route_info 164 | end 165 | 166 | if not route_info then 167 | local is_found = false 168 | for i = 1, #map_node_list[source_id].neighbor_id do 169 | if map_node_list[source_id].neighbor_id[i] == destination_id then 170 | is_found = true 171 | break 172 | end 173 | end 174 | if not is_found then 175 | table.insert(map_node_list[source_id].neighbor_id, destination_id) 176 | end 177 | 178 | is_found = false 179 | for i = 1, #map_node_list[destination_id].neighbor_id do 180 | if map_node_list[destination_id].neighbor_id[i] == source_id then 181 | is_found = true 182 | break 183 | end 184 | end 185 | if not is_found then 186 | table.insert(map_node_list[destination_id].neighbor_id, source_id) 187 | end 188 | end 189 | end 190 | 191 | return map_route_list[source_id][destination_id] 192 | end 193 | 194 | --- Update node type parameter. 195 | local function map_update_node_type(node_id) 196 | if #map_node_list[node_id].neighbor_id == 0 then 197 | map_node_list[node_id].type = NODETYPE.SINGLE 198 | elseif #map_node_list[node_id].neighbor_id == 1 then 199 | map_node_list[node_id].type = NODETYPE.DEADEND 200 | elseif #map_node_list[node_id].neighbor_id > 1 then 201 | map_node_list[node_id].type = NODETYPE.INTERSECTION 202 | end 203 | end 204 | 205 | --- Remove an existing route between two nodes. 206 | local function map_remove_oneway_route(source_id, destination_id) 207 | map_route_list[source_id][destination_id] = nil 208 | if table_size(map_route_list[source_id]) == 0 then 209 | map_route_list[source_id] = nil 210 | end 211 | if not (map_route_list[destination_id] and map_route_list[destination_id][source_id]) then 212 | for i = 1, #map_node_list[destination_id].neighbor_id do 213 | if map_node_list[destination_id].neighbor_id[i] == source_id then 214 | table.remove(map_node_list[destination_id].neighbor_id, i) 215 | break 216 | end 217 | end 218 | for i = 1, #map_node_list[source_id].neighbor_id do 219 | if map_node_list[source_id].neighbor_id[i] == destination_id then 220 | table.remove(map_node_list[source_id].neighbor_id, i) 221 | break 222 | end 223 | end 224 | end 225 | end 226 | 227 | --- Adding a node at the given position (position.z will get ignored). 228 | --- @param position (vector3) node position 229 | --- @return (number) Newly added node id 230 | function M.map_add_node(position) 231 | assert(position, "You must provide a position") 232 | 233 | map_node_id_iterator = map_node_id_iterator + 1 234 | local node_id = map_node_id_iterator 235 | map_node_list[node_id] = { position = vmath.vector3(position.x, position.y, 0), type = NODETYPE.SINGLE, neighbor_id = {} } 236 | map_change_iterator = map_change_iterator + 1 237 | return node_id 238 | end 239 | 240 | --- Adding a two-way route between two nodes, you can set it as one way or two way. 241 | --- @param source_id (number) source node id 242 | --- @param destination_id (number) destination node id 243 | --- @param is_one_way (boolean|nil) optional is one-way route [false] 244 | function M.map_add_route(source_id, destination_id, is_one_way) 245 | assert(source_id, "You must provide a source id") 246 | assert(destination_id, "You must provide a destination id") 247 | 248 | assert(map_node_list[source_id], ("Unknown source id %s"):format(tostring(source_id))) 249 | assert(map_node_list[destination_id], ("Unknown destination id %s"):format(tostring(destination_id))) 250 | 251 | if source_id == destination_id then return end 252 | 253 | local route_info = map_add_oneway_route(source_id, destination_id, nil) 254 | if not is_one_way then 255 | map_add_oneway_route(destination_id, source_id, route_info) 256 | end 257 | map_update_node_type(source_id) 258 | map_update_node_type(destination_id) 259 | map_change_iterator = map_change_iterator + 1 260 | end 261 | 262 | --- Removing an existing route between two nodes, you can set it to remove just one way or both ways. 263 | --- @param source_id (number) source node id 264 | --- @param destination_id (number) destination node id 265 | --- @param is_remove_one_way (boolean|nil) optional is remove only one-way route [false] 266 | function M.map_remove_route(source_id, destination_id, is_remove_one_way) 267 | assert(source_id, "You must provide a source id") 268 | assert(destination_id, "You must provide a destination id") 269 | 270 | assert(map_node_list[source_id], ("Unknown source id %s"):format(tostring(source_id))) 271 | assert(map_node_list[destination_id], ("Unknown destination id %s"):format(tostring(destination_id))) 272 | 273 | if source_id == destination_id then return end 274 | 275 | map_remove_oneway_route(source_id, destination_id) 276 | if not is_remove_one_way then 277 | map_remove_oneway_route(destination_id, source_id) 278 | end 279 | map_update_node_type(source_id) 280 | map_update_node_type(destination_id) 281 | map_change_iterator = map_change_iterator + 1 282 | end 283 | 284 | --- Removing an existing node, attached routes to this node will remove. 285 | --- @param node_id (number) node id 286 | function M.map_remove_node(node_id) 287 | assert(node_id, "You must provide a node id") 288 | 289 | assert(map_node_list[node_id], ("Unknown node id %s"):format(tostring(node_id))) 290 | 291 | for from_id, routes in pairs(map_route_list) do 292 | for to_id, route in pairs(routes) do 293 | if from_id == node_id or to_id == node_id then 294 | map_remove_oneway_route(from_id, to_id) 295 | map_update_node_type(from_id) 296 | map_update_node_type(to_id) 297 | end 298 | end 299 | end 300 | map_node_list[node_id] = nil 301 | map_change_iterator = map_change_iterator + 1 302 | end 303 | 304 | --- Debug draw all map nodes and choose to show node ids or not. 305 | --- @param is_show_ids (boolean|nil) optional is show nodes id [false] 306 | function M.debug_draw_map_nodes(is_show_ids) 307 | for node_id, node in pairs(map_node_list) do 308 | if is_show_ids then 309 | msg.post("@render:", "draw_text", { text = node_id, position = node.position + vmath.vector3(debug_draw_scale, -debug_draw_scale, 0) } ) 310 | end 311 | 312 | if node.type == NODETYPE.SINGLE then 313 | msg.post("@render:", "draw_line", { start_point = node.position + vmath.vector3(debug_draw_scale, -debug_draw_scale, 0), end_point = node.position + vmath.vector3(-debug_draw_scale, -debug_draw_scale, 0), color = debug_node_color } ) 314 | msg.post("@render:", "draw_line", { start_point = node.position + vmath.vector3(debug_draw_scale, -debug_draw_scale, 0), end_point = node.position + vmath.vector3(0, debug_draw_scale, 0), color = debug_node_color } ) 315 | msg.post("@render:", "draw_line", { start_point = node.position + vmath.vector3(-debug_draw_scale, -debug_draw_scale, 0), end_point = node.position + vmath.vector3(0, debug_draw_scale, 0), color = debug_node_color } ) 316 | end 317 | 318 | if node.type == NODETYPE.DEADEND then 319 | msg.post("@render:", "draw_line", { start_point = node.position + vmath.vector3(debug_draw_scale, debug_draw_scale, 0), end_point = node.position + vmath.vector3(-debug_draw_scale, -debug_draw_scale, 0), color = debug_node_color } ) 320 | msg.post("@render:", "draw_line", { start_point = node.position + vmath.vector3(-debug_draw_scale, debug_draw_scale, 0), end_point = node.position + vmath.vector3(debug_draw_scale, -debug_draw_scale, 0), color = debug_node_color } ) 321 | end 322 | 323 | if node.type == NODETYPE.INTERSECTION then 324 | msg.post("@render:", "draw_line", { start_point = node.position + vmath.vector3(debug_draw_scale, debug_draw_scale, 0), end_point = node.position + vmath.vector3(debug_draw_scale, -debug_draw_scale, 0), color = debug_node_color } ) 325 | msg.post("@render:", "draw_line", { start_point = node.position + vmath.vector3(-debug_draw_scale, debug_draw_scale, 0), end_point = node.position + vmath.vector3(-debug_draw_scale, -debug_draw_scale, 0), color = debug_node_color } ) 326 | msg.post("@render:", "draw_line", { start_point = node.position + vmath.vector3(-debug_draw_scale, debug_draw_scale, 0), end_point = node.position + vmath.vector3(debug_draw_scale, debug_draw_scale, 0), color = debug_node_color } ) 327 | msg.post("@render:", "draw_line", { start_point = node.position + vmath.vector3(-debug_draw_scale, -debug_draw_scale, 0), end_point = node.position + vmath.vector3(debug_draw_scale, -debug_draw_scale, 0), color = debug_node_color } ) 328 | end 329 | 330 | end 331 | end 332 | 333 | --- Debug draw all map routes. 334 | function M.debug_draw_map_routes() 335 | for from_id, routes in pairs(map_route_list) do 336 | for to_id, route in pairs(routes) do 337 | if map_route_list[to_id] and map_route_list[to_id][from_id] then 338 | msg.post("@render:", "draw_line", { start_point = map_node_list[from_id].position, end_point = map_node_list[to_id].position, color = debug_two_way_route_color } ) 339 | else 340 | msg.post("@render:", "draw_line", { start_point = map_node_list[from_id].position, end_point = map_node_list[to_id].position, color = debug_one_way_route_color } ) 341 | 342 | local arrow_postion = 4 / 5 * map_node_list[to_id].position + map_node_list[from_id].position / 5 343 | msg.post("@render:", "draw_line", { start_point = arrow_postion + vmath.vector3(3, 3, 0), end_point = arrow_postion + vmath.vector3(3, -3, 0), color = debug_one_way_route_color } ) 344 | msg.post("@render:", "draw_line", { start_point = arrow_postion + vmath.vector3(-3, 3, 0), end_point = arrow_postion + vmath.vector3(-3, -3, 0), color = debug_one_way_route_color } ) 345 | msg.post("@render:", "draw_line", { start_point = arrow_postion + vmath.vector3(-3, 3, 0), end_point = arrow_postion + vmath.vector3(3, 3, 0), color = debug_one_way_route_color } ) 346 | msg.post("@render:", "draw_line", { start_point = arrow_postion + vmath.vector3(-3, -3, 0), end_point = arrow_postion + vmath.vector3(3, -3, 0), color = debug_one_way_route_color } ) 347 | end 348 | end 349 | end 350 | end 351 | 352 | --- Debug draw player specific path with given color. 353 | --- @param movement_data (table) special movement data table 354 | --- @param color (vector4) path color 355 | --- @param is_show_intersection (boolean|nil) optional is show intersection [false] 356 | function M.debug_draw_player_move(movement_data, color, is_show_intersection) 357 | assert(movement_data, "You must provide a movement data") 358 | assert(color, "You must provide a color") 359 | 360 | if movement_data.path_index ~= 0 then 361 | for index = movement_data.path_index, #movement_data.path do 362 | if index ~= #movement_data.path then 363 | msg.post("@render:", "draw_line", { start_point = movement_data.path[index], end_point = movement_data.path[index + 1], color = color } ) 364 | end 365 | if is_show_intersection then 366 | msg.post("@render:", "draw_line", { start_point = movement_data.path[index] + vmath.vector3(debug_draw_scale + 2, debug_draw_scale + 2, 0), end_point = movement_data.path[index] + vmath.vector3(-debug_draw_scale - 2, -debug_draw_scale - 2, 0), color = color } ) 367 | msg.post("@render:", "draw_line", { start_point = movement_data.path[index] + vmath.vector3(-debug_draw_scale - 2, debug_draw_scale + 2, 0), end_point = movement_data.path[index] + vmath.vector3(debug_draw_scale + 2, -debug_draw_scale - 2, 0), color = color } ) 368 | end 369 | end 370 | end 371 | end 372 | 373 | --- Calculate distance between two vector3. 374 | local function distance(source, destination) 375 | return sqrt(pow(source.x - destination.x, 2) + pow(source.y - destination.y, 2)) 376 | end 377 | 378 | --- Shallow copy a table. 379 | local function shallow_copy(table) 380 | local new_table = {} 381 | for key, value in pairs(table) do 382 | new_table[key] = value 383 | end 384 | return new_table 385 | end 386 | 387 | --- Calculate the nearest position on the nearest route on the map from the given position. 388 | local function calculate_to_nearest_route(position) 389 | local min_from_id, min_to_id 390 | local min_near_pos_x, min_near_pos_y 391 | local min_dist = huge 392 | local already_calculated = {} 393 | 394 | for from_id, routes in pairs(map_route_list) do 395 | for to_id, route in pairs(routes) do 396 | if not (already_calculated[from_id] and already_calculated[from_id][to_id]) then 397 | 398 | local is_between, near_pos_x, near_pos_y, dist, dist_from_id, dist_to_id 399 | local from_pos = map_node_list[from_id].position 400 | local to_pos = map_node_list[to_id].position 401 | 402 | -- calculate nearest position for every route to it's line equation 403 | if from_pos.x ~= to_pos.x then 404 | --non vertical 405 | near_pos_x = (route.b * ((route.b * position.x) - (route.a * position.y)) - (route.a * route.c))/((route.a * route.a) + (route.b * route.b)) 406 | near_pos_y = (route.a * ((-route.b * position.x) + (route.a * position.y)) - (route.b * route.c))/((route.a * route.a) + (route.b * route.b)) 407 | else 408 | --vertical 409 | near_pos_x = from_pos.x 410 | near_pos_y = position.y 411 | end 412 | 413 | -- check if nearest postion is between route nodes 414 | if (abs(to_pos.x - from_pos.x) >= abs(to_pos.y - from_pos.y)) then 415 | if(to_pos.x - from_pos.x) > 0 then 416 | is_between = from_pos.x <= near_pos_x and near_pos_x <= to_pos.x 417 | else 418 | is_between = to_pos.x <= near_pos_x and near_pos_x <= from_pos.x 419 | end 420 | else 421 | if (to_pos.y - from_pos.y) > 0 then 422 | is_between = from_pos.y <= near_pos_y and near_pos_y <= to_pos.y 423 | else 424 | is_between = to_pos.y <= near_pos_y and near_pos_y <= from_pos.y 425 | end 426 | end 427 | 428 | -- calculate minimum distance to every routes 429 | if is_between then 430 | dist = abs((route.a * position.x) + (route.b * position.y) + route.c)/sqrt((route.a * route.a) + (route.b * route.b)) 431 | else 432 | dist_from_id = distance(position, from_pos) 433 | dist_to_id = distance(position, to_pos) 434 | if dist_from_id < dist_to_id then 435 | dist = dist_from_id 436 | else 437 | dist = dist_to_id 438 | end 439 | end 440 | 441 | -- update min values if calculated distance is lower 442 | if dist < min_dist then 443 | if is_between then 444 | min_dist = dist 445 | min_near_pos_x = near_pos_x 446 | min_near_pos_y = near_pos_y 447 | else 448 | if dist_from_id < dist_to_id then 449 | min_dist = dist_from_id 450 | min_near_pos_x = from_pos.x 451 | min_near_pos_y = from_pos.y 452 | else 453 | min_dist = dist_to_id 454 | min_near_pos_x = to_pos.x 455 | min_near_pos_y = to_pos.y 456 | end 457 | end 458 | min_from_id = from_id 459 | min_to_id = to_id 460 | end 461 | 462 | if not already_calculated[to_id] then already_calculated[to_id] = {} end 463 | already_calculated[to_id][from_id] = 1 464 | end 465 | end 466 | end 467 | 468 | if min_dist == huge then 469 | -- if no route exists 470 | return nil 471 | else 472 | return { 473 | position_on_route = vmath.vector3(min_near_pos_x, min_near_pos_y, 0), 474 | distance = min_dist, 475 | route_from_id = min_from_id, 476 | route_to_id = min_to_id 477 | } 478 | end 479 | end 480 | 481 | --- Calculate graph path inside map from a node to another node. 482 | local function calculate_path(start_id, finish_id) 483 | local previous = {} 484 | local distances = {} 485 | local nodes = {} 486 | local path = nil 487 | local path_distance = 0 488 | 489 | for node_id in pairs(map_node_list) do 490 | if node_id == start_id then 491 | distances[node_id] = 0 492 | else 493 | distances[node_id] = huge 494 | end 495 | 496 | table.insert(nodes, node_id) 497 | end 498 | 499 | while #nodes ~= 0 do 500 | table.sort(nodes, function(x, y) return distances[x] < distances[y] end) 501 | 502 | local smallest = nodes[1] 503 | table.remove(nodes, 1) 504 | 505 | if smallest == finish_id then 506 | path = {} 507 | path_distance = 0 508 | while previous[smallest] do 509 | 510 | table.insert(path, 1, { id = smallest, distance = path_distance }) 511 | 512 | if not map_route_list[previous[smallest]] then return nil end 513 | if not map_route_list[previous[smallest]][smallest] then return nil end 514 | 515 | path_distance = path_distance + map_route_list[previous[smallest]][smallest].distance 516 | smallest = previous[smallest]; 517 | end 518 | if path_distance ~= 0 then 519 | table.insert(path, 1, { id = smallest, distance = path_distance }) 520 | end 521 | break 522 | end 523 | 524 | if distances[smallest] == huge then 525 | break; 526 | end 527 | 528 | if map_route_list[smallest] then 529 | for to_id, neighbor in pairs(map_route_list[smallest]) do 530 | local alt = distances[smallest] + neighbor.distance 531 | if alt < distances[to_id] then 532 | distances[to_id] = alt; 533 | previous[to_id] = smallest; 534 | end 535 | end 536 | end 537 | 538 | end 539 | 540 | return path 541 | end 542 | 543 | --- Retrive path results from cache or update cache. 544 | local function fetch_path(change_number, from_id, to_id) 545 | -- check for same from and to id 546 | if from_id == to_id then 547 | return { 548 | change_number = change_number, 549 | distance = 0, 550 | path = {} 551 | } 552 | end 553 | 554 | -- check for existing cache 555 | if pathfinder_cache[from_id] then 556 | local cache = pathfinder_cache[from_id][to_id] 557 | if cache and cache.change_number == change_number then 558 | return cache 559 | end 560 | end 561 | 562 | -- calculate path 563 | local path = calculate_path(from_id, to_id) 564 | if not path or #path == 0 then return nil end 565 | 566 | -- update cache 567 | local route = {} 568 | for index = #path, 1, -1 do 569 | if path[index].distance ~= 0 then 570 | if not pathfinder_cache[path[index].id] then 571 | pathfinder_cache[path[index].id] = {} 572 | end 573 | pathfinder_cache[path[index].id][to_id] = { 574 | change_number = change_number, 575 | distance = path[index].distance, 576 | path = shallow_copy(route) 577 | } 578 | end 579 | table.insert(route, 1, path[index].id) 580 | end 581 | 582 | return pathfinder_cache[from_id][to_id] 583 | end 584 | 585 | --- Calculate path curvature. 586 | local function process_path_curvature(before, current, after, roundness, settings_path_curve_tightness, 587 | settings_path_curve_max_distance_from_corner) 588 | local Q_before = (settings_path_curve_tightness - 1) / settings_path_curve_tightness * before + current / settings_path_curve_tightness 589 | local R_before = before / settings_path_curve_tightness + (settings_path_curve_tightness - 1) / settings_path_curve_tightness * current 590 | local Q_after = (settings_path_curve_tightness - 1) / settings_path_curve_tightness * current + after / settings_path_curve_tightness 591 | local R_after = current / settings_path_curve_tightness + (settings_path_curve_tightness - 1) / settings_path_curve_tightness * after 592 | 593 | if distance(Q_before, before) > settings_path_curve_max_distance_from_corner then 594 | Q_before = vmath.lerp(settings_path_curve_max_distance_from_corner / distance(before, current), before, current) 595 | end 596 | if distance(R_before, current) > settings_path_curve_max_distance_from_corner then 597 | R_before = vmath.lerp(settings_path_curve_max_distance_from_corner / distance(before, current), current, before) 598 | end 599 | if distance(Q_after, current) > settings_path_curve_max_distance_from_corner then 600 | Q_after = vmath.lerp(settings_path_curve_max_distance_from_corner / distance(current, after), current, after) 601 | end 602 | if distance(R_after, after) > settings_path_curve_max_distance_from_corner then 603 | R_after = vmath.lerp(settings_path_curve_max_distance_from_corner / distance(current, after), after, current) 604 | end 605 | 606 | if roundness ~= 1 then 607 | local list_before = process_path_curvature(Q_before, R_before, Q_after, roundness - 1, settings_path_curve_tightness, 608 | settings_path_curve_max_distance_from_corner) 609 | local list_after = process_path_curvature(R_before, Q_after, R_after, roundness - 1, settings_path_curve_tightness, 610 | settings_path_curve_max_distance_from_corner) 611 | 612 | for key, value in pairs(list_after) do 613 | table.insert(list_before, value) 614 | end 615 | 616 | return list_before, Q_before, R_after 617 | else 618 | return {R_before, Q_after}, Q_before, R_after 619 | end 620 | end 621 | 622 | --- Initialize moves from source position to a node with an destination node inside the created map. 623 | local function move_internal_initialize(source_position, move_data) 624 | local near_result = calculate_to_nearest_route(source_position) 625 | if not near_result or #move_data.destination_list == 0 then 626 | -- stay until something changes 627 | move_data.change_number = map_change_iterator 628 | move_data.path_index = 0 629 | move_data.path = {} 630 | return move_data 631 | else 632 | local from_path = nil 633 | local to_path = nil 634 | 635 | if map_route_list[near_result.route_to_id] and map_route_list[near_result.route_to_id][near_result.route_from_id] then 636 | from_path = fetch_path(map_change_iterator, near_result.route_from_id, move_data.destination_list[move_data.destination_index]) 637 | end 638 | if map_route_list[near_result.route_from_id] and map_route_list[near_result.route_from_id][near_result.route_to_id] then 639 | to_path = fetch_path(map_change_iterator, near_result.route_to_id, move_data.destination_list[move_data.destination_index]) 640 | end 641 | 642 | local position_list = {} 643 | table.insert(position_list, source_position) 644 | 645 | if (near_result.distance > move_data.settings_gameobject_threshold + 1) and move_data.settings_allow_enter_on_route then 646 | table.insert(position_list, near_result.position_on_route) 647 | end 648 | 649 | if from_path or to_path then 650 | local from_distance, to_distance 651 | 652 | if not from_path then 653 | from_distance = huge 654 | else 655 | from_distance = from_path.distance + distance(source_position, map_node_list[near_result.route_from_id].position) 656 | end 657 | 658 | if not to_path then 659 | to_distance = huge 660 | else 661 | to_distance = to_path.distance + distance(source_position, map_node_list[near_result.route_to_id].position) 662 | end 663 | 664 | if from_distance <= to_distance then 665 | table.insert(position_list, map_node_list[near_result.route_from_id].position) 666 | for index = 1, #from_path.path do 667 | table.insert(position_list, map_node_list[from_path.path[index]].position) 668 | end 669 | else 670 | table.insert(position_list, map_node_list[near_result.route_to_id].position) 671 | for index = 1, #to_path.path do 672 | table.insert(position_list, map_node_list[to_path.path[index]].position) 673 | end 674 | end 675 | end 676 | 677 | if move_data.settings_path_curve_roundness ~= 0 then 678 | move_data.path = {} 679 | 680 | table.insert(move_data.path, position_list[1]) 681 | 682 | for i = 2, #position_list - 1 do 683 | local partial_position_list, Q_before, R_after = process_path_curvature(position_list[i - 1], position_list[i], position_list[i + 1], 684 | move_data.settings_path_curve_roundness, move_data.settings_path_curve_tightness, 685 | move_data.settings_path_curve_max_distance_from_corner) 686 | 687 | if i == 2 then 688 | table.insert(move_data.path, Q_before) 689 | end 690 | 691 | for key, value in pairs(partial_position_list) do 692 | table.insert(move_data.path, value) 693 | end 694 | 695 | if i == #position_list - 1 then 696 | table.insert(move_data.path, R_after) 697 | end 698 | end 699 | 700 | table.insert(move_data.path, position_list[#position_list]) 701 | else 702 | move_data.path = position_list 703 | end 704 | 705 | move_data.change_number = map_change_iterator 706 | move_data.path_index = 1 707 | return move_data 708 | end 709 | end 710 | 711 | --- Initialize moves from a source position to destination node list inside the created 712 | --- map and using given threshold and initial face vector as game object initial face direction 713 | --- and path calculate settings considering the route type, the optional value will fall back 714 | --- to their default values. 715 | --- @param source_position (vector3) position of game object 716 | --- @param destination_list (table) list of destinations id 717 | --- @param route_type (ROUTETYPE|nil) optional route type [ROUTETYPE.ONETIME] 718 | --- @param initial_face_vector (vecotr3|nil) optional initial game object face vector [nil] 719 | --- @param settings_gameobject_threshold (number|nil) optional game object threshold [settings_main_gameobject_threshold] 720 | --- @param settings_path_curve_tightness (number|nil) optional path curvature tightness [settings_main_path_curve_tightness] 721 | --- @param settings_path_curve_roundness (number|nil) optional path curvature roundness [settings_main_path_curve_roundness] 722 | --- @param settings_path_curve_max_distance_from_corner (number|nil) optional path curvature maximum distance from corner [settings_main_path_curve_max_distance_from_corner] 723 | --- @param settings_allow_enter_on_route (boolean|nil) optional is game object allow to enter on route [settings_main_allow_enter_on_route] 724 | --- @return (table) special movement data 725 | function M.move_initialize(source_position, destination_list, route_type, initial_face_vector, settings_gameobject_threshold, 726 | settings_path_curve_tightness, settings_path_curve_roundness, settings_path_curve_max_distance_from_corner, 727 | settings_allow_enter_on_route) 728 | assert(source_position, "You must provide a source position") 729 | assert(destination_list, "You must provide a destination list") 730 | 731 | route_type = route_type or M.ROUTETYPE.ONETIME 732 | settings_gameobject_threshold = settings_gameobject_threshold or settings_main_gameobject_threshold 733 | settings_path_curve_roundness = settings_path_curve_roundness or settings_main_path_curve_roundness 734 | settings_path_curve_tightness = settings_path_curve_tightness or settings_main_path_curve_tightness 735 | settings_path_curve_max_distance_from_corner = settings_path_curve_max_distance_from_corner or settings_main_path_curve_max_distance_from_corner 736 | if settings_allow_enter_on_route == nil then 737 | settings_allow_enter_on_route = settings_main_allow_enter_on_route 738 | end 739 | 740 | local destination_id = 1 741 | if route_type == M.ROUTETYPE.SHUFFLE and #destination_list > 1 then 742 | math.random(#destination_list) 743 | math.random(#destination_list) 744 | math.random(#destination_list) 745 | 746 | destination_id = math.random(#destination_list) 747 | end 748 | 749 | local move_data = { 750 | change_number = map_change_iterator, 751 | destination_list = destination_list, 752 | destination_index = destination_id, 753 | route_type = route_type, 754 | path_index = 0, 755 | path = {}, 756 | initial_face_vector = initial_face_vector, 757 | current_face_vector = initial_face_vector, 758 | settings_gameobject_threshold = settings_gameobject_threshold, 759 | settings_path_curve_tightness = settings_path_curve_tightness, 760 | settings_path_curve_roundness = settings_path_curve_roundness, 761 | settings_allow_enter_on_route = settings_allow_enter_on_route, 762 | settings_path_curve_max_distance_from_corner = settings_path_curve_max_distance_from_corner 763 | } 764 | 765 | return move_internal_initialize(source_position, move_data) 766 | end 767 | 768 | --- Calculate movements from current position of the game object inside the created map 769 | --- considering given speed, using last calculated movement data. 770 | --- @param current_position (vector3) current position of game object 771 | --- @param speed (number) game object speed 772 | --- @param move_data (table) special movement data table 773 | --- @return (table) new movement data 774 | --- @return (table) move result this table includes: 775 | --- * position (vector3) game object next postion. 776 | --- * rotation (vector3|nil) game object next rotation if rotation calculation was on. 777 | --- * is_reached (boolean) is game object reached a destination. 778 | --- * destination_id (number) node id of destination. 779 | function M.move_player(current_position, speed, move_data) 780 | assert(current_position, "You must provide a current position") 781 | assert(speed, "You must provide a speed") 782 | assert(move_data, "You must provide a move data") 783 | 784 | -- check for map updates 785 | if move_data.change_number ~= map_change_iterator then 786 | move_data = move_internal_initialize(current_position, move_data) 787 | end 788 | 789 | local rotation = nil 790 | -- stand still if no route found 791 | if move_data.path_index == 0 then 792 | if move_data.initial_face_vector then 793 | rotation = vmath.quat_rotation_z(atan2(move_data.current_face_vector.y, move_data.current_face_vector.x) - atan2(move_data.initial_face_vector.y, move_data.initial_face_vector.x)) 794 | end 795 | return move_data, { 796 | position = current_position, 797 | rotation = rotation, 798 | is_reached = false, 799 | destination_id = move_data.destination_list[move_data.destination_index] 800 | } 801 | end 802 | 803 | -- check for reaching path section 804 | while distance(current_position, move_data.path[move_data.path_index]) <= move_data.settings_gameobject_threshold + 1 do 805 | if move_data.path_index == #move_data.path then 806 | -- reached next path node 807 | if move_data.initial_face_vector then 808 | rotation = vmath.quat_rotation_z(atan2(move_data.current_face_vector.y, move_data.current_face_vector.x) - atan2(move_data.initial_face_vector.y, move_data.initial_face_vector.x)) 809 | end 810 | 811 | -- reached destination 812 | local is_reached = true 813 | local destination_id = move_data.destination_list[move_data.destination_index] 814 | if distance(current_position, map_node_list[destination_id].position) > move_data.settings_gameobject_threshold + 1 then 815 | is_reached = false 816 | else 817 | if move_data.route_type == M.ROUTETYPE.ONETIME then 818 | if move_data.destination_index < #move_data.destination_list then 819 | move_data.destination_index = move_data.destination_index + 1 820 | move_data = move_internal_initialize(current_position, move_data) 821 | end 822 | elseif move_data.route_type == M.ROUTETYPE.SHUFFLE then 823 | if #move_data.destination_list > 1 then 824 | local new_destination_id = move_data.destination_index 825 | repeat 826 | new_destination_id = math.random(#move_data.destination_list) 827 | until new_destination_id ~= move_data.destination_index 828 | move_data.destination_index = new_destination_id 829 | move_data = move_internal_initialize(current_position, move_data) 830 | end 831 | elseif move_data.route_type == M.ROUTETYPE.CYCLE then 832 | if move_data.destination_index < #move_data.destination_list then 833 | move_data.destination_index = move_data.destination_index + 1 834 | else 835 | move_data.destination_index = 1 836 | end 837 | move_data = move_internal_initialize(current_position, move_data) 838 | end 839 | end 840 | 841 | return move_data, { 842 | position = current_position, 843 | rotation = rotation, 844 | is_reached = is_reached, 845 | destination_id = destination_id 846 | } 847 | else 848 | -- go for next section 849 | move_data.path_index = move_data.path_index + 1 850 | end 851 | end 852 | 853 | -- movement calculation 854 | local direction_vector = move_data.path[move_data.path_index] - current_position 855 | direction_vector.z = 0 856 | direction_vector = vmath.normalize(direction_vector) 857 | if move_data.initial_face_vector then 858 | local rotation_vector = vmath.lerp(0.2 * speed, move_data.current_face_vector, direction_vector) 859 | rotation = vmath.quat_rotation_z(atan2(rotation_vector.y, rotation_vector.x) - atan2(move_data.initial_face_vector.y, move_data.initial_face_vector.x)) 860 | move_data.current_face_vector = rotation_vector 861 | end 862 | return move_data, { 863 | position = current_position + direction_vector * speed, 864 | rotation = rotation, 865 | is_reached = false, 866 | destination_id = move_data.destination_list[move_data.destination_index] 867 | } 868 | end 869 | 870 | return M -------------------------------------------------------------------------------- /examples/assets/atlas.atlas: -------------------------------------------------------------------------------- 1 | images { 2 | image: "/examples/raw/dot.png" 3 | } 4 | images { 5 | image: "/examples/raw/pointy_dot.png" 6 | } 7 | margin: 0 8 | extrude_borders: 2 9 | inner_padding: 0 10 | -------------------------------------------------------------------------------- /examples/example_dynamic_nodes/example_dynamic_nodes_dot.go: -------------------------------------------------------------------------------- 1 | components { 2 | id: "dot" 3 | component: "/examples/example_dynamic_nodes/example_dynamic_nodes_dot.script" 4 | position { 5 | x: 0.0 6 | y: 0.0 7 | z: 0.0 8 | } 9 | rotation { 10 | x: 0.0 11 | y: 0.0 12 | z: 0.0 13 | w: 1.0 14 | } 15 | } 16 | embedded_components { 17 | id: "sprite" 18 | type: "sprite" 19 | data: "tile_set: \"/examples/assets/atlas.atlas\"\n" 20 | "default_animation: \"pointy_dot\"\n" 21 | "material: \"/builtins/materials/sprite.material\"\n" 22 | "blend_mode: BLEND_MODE_ALPHA\n" 23 | "" 24 | position { 25 | x: 0.0 26 | y: 0.0 27 | z: 0.0 28 | } 29 | rotation { 30 | x: 0.0 31 | y: 0.0 32 | z: 0.0 33 | w: 1.0 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/example_dynamic_nodes/example_dynamic_nodes_dot.script: -------------------------------------------------------------------------------- 1 | -- require defgraph 2 | local defgraph = require "defgraph.defgraph" 3 | 4 | function init(self) 5 | -- initilize variables 6 | local my_position = go.get_position() 7 | self.speed = 200.0 8 | 9 | -- initialize movement for go inside given map 10 | self.movement_data = defgraph.move_initialize(my_position, { 36, 1 }, defgraph.ROUTETYPE.CYCLE, vmath.vector3(0, 1, 0)) 11 | end 12 | 13 | function update(self, dt) 14 | -- move go inside given map and update move_data 15 | local my_position = go.get_position() 16 | self.movement_data, self.move_result = defgraph.move_player(my_position, self.speed * dt, self.movement_data) 17 | 18 | -- update go postion based of returned result 19 | go.set_position(self.move_result.position) 20 | 21 | -- update go rotation based of returned result 22 | go.set_rotation(self.move_result.rotation) 23 | 24 | -- you can check if go reached to the destination 25 | if self.move_result.is_reached then 26 | print("I'm at destination id: " .. self.move_result.destination_id) 27 | end 28 | end -------------------------------------------------------------------------------- /examples/example_dynamic_nodes/example_dynamic_nodes_main.collection: -------------------------------------------------------------------------------- 1 | name: "default" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "go" 5 | data: "components {\n" 6 | " id: \"script\"\n" 7 | " component: \"/examples/example_dynamic_nodes/example_dynamic_nodes_main.script\"\n" 8 | " position {\n" 9 | " x: 0.0\n" 10 | " y: 0.0\n" 11 | " z: 0.0\n" 12 | " }\n" 13 | " rotation {\n" 14 | " x: 0.0\n" 15 | " y: 0.0\n" 16 | " z: 0.0\n" 17 | " w: 1.0\n" 18 | " }\n" 19 | "}\n" 20 | "embedded_components {\n" 21 | " id: \"factory\"\n" 22 | " type: \"factory\"\n" 23 | " data: \"prototype: \\\"/examples/example_dynamic_nodes/example_dynamic_nodes_dot.go\\\"\\n" 24 | "load_dynamically: false\\n" 25 | "\"\n" 26 | " position {\n" 27 | " x: 0.0\n" 28 | " y: 0.0\n" 29 | " z: 0.0\n" 30 | " }\n" 31 | " rotation {\n" 32 | " x: 0.0\n" 33 | " y: 0.0\n" 34 | " z: 0.0\n" 35 | " w: 1.0\n" 36 | " }\n" 37 | "}\n" 38 | "" 39 | position { 40 | x: 0.0 41 | y: 0.0 42 | z: 0.0 43 | } 44 | rotation { 45 | x: 0.0 46 | y: 0.0 47 | z: 0.0 48 | w: 1.0 49 | } 50 | scale3 { 51 | x: 1.0 52 | y: 1.0 53 | z: 1.0 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /examples/example_dynamic_nodes/example_dynamic_nodes_main.script: -------------------------------------------------------------------------------- 1 | -- require defgraph 2 | local defgraph = require "defgraph.defgraph" 3 | 4 | function init(self) 5 | msg.post(".", "acquire_input_focus") 6 | 7 | -- change map properties 8 | defgraph.map_set_properties(15, 3, 1, 100, true) 9 | 10 | self.node_ids = {} 11 | self.node_positions = {} 12 | self.node_goas_up = {} 13 | 14 | -- defining graph nodes of the map 15 | local is_up = true 16 | local start_position = vmath.vector3(100, 200, 0) 17 | for i = 1, 36 do 18 | if is_up then 19 | start_position = start_position + vmath.vector3(20, 20, 0) 20 | else 21 | start_position = start_position + vmath.vector3(20, -20, 0) 22 | end 23 | 24 | if start_position.y >= 400 then 25 | is_up = false 26 | elseif start_position.y <= 200 then 27 | is_up = true 28 | end 29 | 30 | table.insert(self.node_ids, defgraph.map_add_node(start_position)) 31 | table.insert(self.node_positions, start_position) 32 | table.insert(self.node_goas_up, is_up) 33 | end 34 | 35 | -- defining routes between nodes 36 | for i = 1, #self.node_ids - 1 do 37 | defgraph.map_add_route(self.node_ids[i], self.node_ids[i + 1]) 38 | end 39 | end 40 | 41 | function on_input(self, action_id, action) 42 | if action_id == hash("left_click") and action.pressed then 43 | factory.create("#factory", vmath.vector3(action.x, action.y, 0), nil, nil, 0.3) 44 | end 45 | end 46 | 47 | function update(self, dt) 48 | for i = 1, 36 do 49 | if self.node_goas_up[i] then 50 | self.node_positions[i] = self.node_positions[i] + vmath.vector3(0, 1, 0) 51 | if self.node_positions[i].y >= 400 then 52 | self.node_positions[i].y = 400 53 | self.node_goas_up[i] = false 54 | end 55 | else 56 | self.node_positions[i] = self.node_positions[i] - vmath.vector3(0, 1, 0) 57 | if self.node_positions[i].y <= 200 then 58 | self.node_positions[i].y = 200 59 | self.node_goas_up[i] = true 60 | end 61 | end 62 | defgraph.map_update_node_position(self.node_ids[i], self.node_positions[i]) 63 | end 64 | 65 | -- draw debug info of nodes and routes 66 | defgraph.debug_draw_map_routes() 67 | 68 | msg.post("@render:", "draw_text", { text = "example dynamic node", position = vmath.vector3(20, 630, 0) } ) 69 | msg.post("@render:", "draw_text", { text = "destinations : cycle between { 36, 1 }", position = vmath.vector3(20, 610, 0) } ) 70 | msg.post("@render:", "draw_text", { text = "left click: deploy dot", position = vmath.vector3(20, 590, 0) } ) 71 | end 72 | -------------------------------------------------------------------------------- /examples/example_static_nodes/example_static_nodes_dot.go: -------------------------------------------------------------------------------- 1 | components { 2 | id: "dot" 3 | component: "/examples/example_static_nodes/example_static_nodes_dot.script" 4 | position { 5 | x: 0.0 6 | y: 0.0 7 | z: 0.0 8 | } 9 | rotation { 10 | x: 0.0 11 | y: 0.0 12 | z: 0.0 13 | w: 1.0 14 | } 15 | } 16 | embedded_components { 17 | id: "sprite" 18 | type: "sprite" 19 | data: "tile_set: \"/examples/assets/atlas.atlas\"\n" 20 | "default_animation: \"dot\"\n" 21 | "material: \"/builtins/materials/sprite.material\"\n" 22 | "blend_mode: BLEND_MODE_ALPHA\n" 23 | "" 24 | position { 25 | x: 0.0 26 | y: 0.0 27 | z: 0.0 28 | } 29 | rotation { 30 | x: 0.0 31 | y: 0.0 32 | z: 0.0 33 | w: 1.0 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/example_static_nodes/example_static_nodes_dot.script: -------------------------------------------------------------------------------- 1 | -- require defgraph 2 | local defgraph = require "defgraph.defgraph" 3 | 4 | function init(self) 5 | -- initilize variables 6 | local my_position = go.get_position() 7 | self.speed = 100.0 8 | 9 | -- initialize movement for go inside given map 10 | self.movement_data = defgraph.move_initialize(my_position, { 6, 18, 14, 2, 4, 10 }, defgraph.ROUTETYPE.SHUFFLE) 11 | end 12 | 13 | function update(self, dt) 14 | -- move go inside given map and update move_data 15 | local my_position = go.get_position() 16 | self.movement_data, self.move_result = defgraph.move_player(my_position, self.speed * dt, self.movement_data) 17 | 18 | -- update go postion based of returned result 19 | go.set_position(self.move_result.position) 20 | 21 | -- you can check if go reached to the destination 22 | if self.move_result.is_reached then 23 | print("I'm at destination id : " .. self.move_result.destination_id) 24 | end 25 | 26 | -- debug draw movement_data with specific color 27 | defgraph.debug_draw_player_move(self.movement_data, vmath.vector4(1, 1, 0, 1)) 28 | end -------------------------------------------------------------------------------- /examples/example_static_nodes/example_static_nodes_main.collection: -------------------------------------------------------------------------------- 1 | name: "default" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "go" 5 | data: "components {\n" 6 | " id: \"script\"\n" 7 | " component: \"/examples/example_static_nodes/example_static_nodes_main.script\"\n" 8 | " position {\n" 9 | " x: 0.0\n" 10 | " y: 0.0\n" 11 | " z: 0.0\n" 12 | " }\n" 13 | " rotation {\n" 14 | " x: 0.0\n" 15 | " y: 0.0\n" 16 | " z: 0.0\n" 17 | " w: 1.0\n" 18 | " }\n" 19 | "}\n" 20 | "embedded_components {\n" 21 | " id: \"factory\"\n" 22 | " type: \"factory\"\n" 23 | " data: \"prototype: \\\"/examples/example_static_nodes/example_static_nodes_dot.go\\\"\\n" 24 | "load_dynamically: false\\n" 25 | "\"\n" 26 | " position {\n" 27 | " x: 0.0\n" 28 | " y: 0.0\n" 29 | " z: 0.0\n" 30 | " }\n" 31 | " rotation {\n" 32 | " x: 0.0\n" 33 | " y: 0.0\n" 34 | " z: 0.0\n" 35 | " w: 1.0\n" 36 | " }\n" 37 | "}\n" 38 | "" 39 | position { 40 | x: 0.0 41 | y: 0.0 42 | z: 0.0 43 | } 44 | rotation { 45 | x: 0.0 46 | y: 0.0 47 | z: 0.0 48 | w: 1.0 49 | } 50 | scale3 { 51 | x: 1.0 52 | y: 1.0 53 | z: 1.0 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /examples/example_static_nodes/example_static_nodes_main.script: -------------------------------------------------------------------------------- 1 | -- require defgraph 2 | local defgraph = require "defgraph.defgraph" 3 | 4 | function init(self) 5 | msg.post(".", "acquire_input_focus") 6 | 7 | -- change map properties 8 | defgraph.map_set_properties(1, 3, 3, 15, true) 9 | 10 | -- change debug properties 11 | defgraph.debug_set_properties(vmath.vector4(1, 0, 1, 1), vmath.vector4(0, 1, 0, 1), vmath.vector4(0, 1, 1, 1), 5) 12 | 13 | -- defining graph nodes of the map 14 | node01 = defgraph.map_add_node(vmath.vector3(100, 550, 0)) 15 | node02 = defgraph.map_add_node(vmath.vector3(710, 550, 0)) 16 | node03 = defgraph.map_add_node(vmath.vector3(100, 270, 0)) 17 | node04 = defgraph.map_add_node(vmath.vector3(100, 100, 0)) 18 | node05 = defgraph.map_add_node(vmath.vector3(275, 270, 0)) 19 | node06 = defgraph.map_add_node(vmath.vector3(275, 455, 0)) 20 | node07 = defgraph.map_add_node(vmath.vector3(405, 360, 0)) 21 | node08 = defgraph.map_add_node(vmath.vector3(538, 360, 0)) 22 | node09 = defgraph.map_add_node(vmath.vector3(580, 455, 0)) 23 | node10 = defgraph.map_add_node(vmath.vector3(800, 550, 0)) 24 | node11 = defgraph.map_add_node(vmath.vector3(450, 100, 0)) 25 | node12 = defgraph.map_add_node(vmath.vector3(450, 160, 0)) 26 | node13 = defgraph.map_add_node(vmath.vector3(625, 100, 0)) 27 | node14 = defgraph.map_add_node(vmath.vector3(800, 100, 0)) 28 | node15 = defgraph.map_add_node(vmath.vector3(800, 160, 0)) 29 | node16 = defgraph.map_add_node(vmath.vector3(625, 160, 0)) 30 | node17 = defgraph.map_add_node(vmath.vector3(538, 160, 0)) 31 | node18 = defgraph.map_add_node(vmath.vector3(538, 215, 0)) 32 | node19 = defgraph.map_add_node(vmath.vector3(625, 215, 0)) 33 | node20 = defgraph.map_add_node(vmath.vector3(625, 270, 0)) 34 | node21 = defgraph.map_add_node(vmath.vector3(800, 270, 0)) 35 | 36 | -- defining routes between nodes 37 | defgraph.map_add_route(node01, node02) 38 | defgraph.map_add_route(node01, node03) 39 | defgraph.map_add_route(node03, node04) 40 | defgraph.map_add_route(node03, node05) 41 | defgraph.map_add_route(node05, node06) 42 | defgraph.map_add_route(node06, node09) 43 | defgraph.map_add_route(node08, node07, true) 44 | defgraph.map_add_route(node09, node20) 45 | defgraph.map_add_route(node05, node12) 46 | defgraph.map_add_route(node04, node10) 47 | defgraph.map_add_route(node04, node11) 48 | defgraph.map_add_route(node11, node12) 49 | defgraph.map_add_route(node12, node17) 50 | defgraph.map_add_route(node11, node13) 51 | defgraph.map_add_route(node13, node16) 52 | defgraph.map_add_route(node13, node14) 53 | defgraph.map_add_route(node14, node15) 54 | defgraph.map_add_route(node17, node16) 55 | defgraph.map_add_route(node16, node15) 56 | defgraph.map_add_route(node17, node18) 57 | defgraph.map_add_route(node16, node19) 58 | defgraph.map_add_route(node18, node19) 59 | defgraph.map_add_route(node15, node21) 60 | defgraph.map_add_route(node19, node20) 61 | defgraph.map_add_route(node20, node21) 62 | defgraph.map_add_route(node21, node10) 63 | 64 | self.map_trigger = true 65 | end 66 | 67 | function on_input(self, action_id, action) 68 | if action_id == hash("left_click") and action.pressed then 69 | factory.create("#factory", vmath.vector3(action.x, action.y, 0), nil, nil, 0.3) 70 | end 71 | if action_id == hash("right_click") and action.pressed then 72 | if self.map_trigger then 73 | defgraph.map_add_route(node08, node18) 74 | defgraph.map_add_route(node02, node10) 75 | defgraph.map_add_route(node05, node01, true) 76 | defgraph.map_add_route(node07, node05, true) 77 | defgraph.map_remove_route(node05, node06) 78 | defgraph.map_remove_route(node17, node16) 79 | defgraph.map_remove_route(node19, node16) 80 | defgraph.map_remove_route(node05, node03) 81 | defgraph.map_remove_route(node04, node10) 82 | self.map_trigger = false 83 | else 84 | defgraph.map_remove_route(node08, node18) 85 | defgraph.map_remove_route(node02, node10) 86 | defgraph.map_remove_route(node05, node01, true) 87 | defgraph.map_remove_route(node07, node05, true) 88 | defgraph.map_add_route(node05, node06) 89 | defgraph.map_add_route(node17, node16) 90 | defgraph.map_add_route(node19, node16) 91 | defgraph.map_add_route(node05, node03) 92 | defgraph.map_add_route(node04, node10) 93 | self.map_trigger = true 94 | end 95 | end 96 | end 97 | 98 | function update(self, dt) 99 | -- draw debug info of nodes and routes 100 | defgraph.debug_draw_map_nodes(true) 101 | defgraph.debug_draw_map_routes() 102 | 103 | msg.post("@render:", "draw_text", { text = "example static node", position = vmath.vector3(20, 630, 0) } ) 104 | msg.post("@render:", "draw_text", { text = "destinations : shuffled between { 6, 18, 14, 2, 4, 10 }", position = vmath.vector3(20, 610, 0) } ) 105 | msg.post("@render:", "draw_text", { text = "left click: deploy dot - right click: change routes", position = vmath.vector3(20, 590, 0) } ) 106 | end 107 | -------------------------------------------------------------------------------- /examples/main.input_binding: -------------------------------------------------------------------------------- 1 | mouse_trigger { 2 | input: MOUSE_BUTTON_1 3 | action: "left_click" 4 | } 5 | mouse_trigger { 6 | input: MOUSE_BUTTON_2 7 | action: "right_click" 8 | } 9 | -------------------------------------------------------------------------------- /examples/raw/dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev-masih/defgraph/6e209164d78a85907664fa333adca2f51bdc1d79/examples/raw/dot.png -------------------------------------------------------------------------------- /examples/raw/pointy_dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev-masih/defgraph/6e209164d78a85907664fa333adca2f51bdc1d79/examples/raw/pointy_dot.png -------------------------------------------------------------------------------- /game.project: -------------------------------------------------------------------------------- 1 | [project] 2 | title = DefGraph 3 | version = 4.1 4 | 5 | [library] 6 | include_dirs = defgraph 7 | 8 | [input] 9 | game_binding = /examples/main.input_bindingc 10 | 11 | [bootstrap] 12 | main_collection = /examples/example_static_nodes/example_static_nodes_main.collectionc 13 | 14 | [script] 15 | shared_state = 1 16 | 17 | --------------------------------------------------------------------------------