├── .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 |
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 |
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 |
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 |
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 |
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
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 | |  |  |  |
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 |
--------------------------------------------------------------------------------