diff --git a/Migrate_v2.md b/Migrate_v2.md index 6f74f51..75cbdc1 100644 --- a/Migrate_v2.md +++ b/Migrate_v2.md @@ -6,6 +6,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 } * `debug_draw_player_move` function now draw game object route through the destination. -routing gif +player move * version 2 is tagged as `v2` in GitHub repository. \ No newline at end of file diff --git a/Migrate_v3.md b/Migrate_v3.md new file mode 100644 index 0000000..9029bcf --- /dev/null +++ b/Migrate_v3.md @@ -0,0 +1,25 @@ +# Changelog and migration guild from version 2 to 3 + +* Added ability for game objects to have curved corner paths. +* Added ability to track game object rotation as move result. +* Added only one ways routes, and added separate arguments `two_way_route_color` and `one_way_route_color` in `debug_draw_map_nodes` function. + +one way route + +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. + +* Added separate examples of static and dynamic map nodes. +* Added documentation for module settings. +* Added `is_one_way` argument to `map_add_route` function, to able to add just one-way route. +* Added `is_remove_one_way` argument to `map_remove_route` function, to able to remove just one-way route or both between two nodes. +* Added `map_remove_node` function to remove a node and it's connected routes to it. +* Added `map_update_node_position` function to update node positions. now the entire map can move dynamically. +* Added `map_set_properties` function to replace module default settings. +* `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`. +* `move_player` function no longer needs `threshold` as an argument. +* `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. +* 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. +* Added `rotation` to move result table that returned from function `move_player` and you can set game object rotation to it. +* Fixed bug that caused a game object to get stuck in a complex intersection. +* Added `is_show_intersection` argument to `debug_draw_player_move` function to allow debugger mark/not mark intersections of game object path. +* version 3 is tagged as `v3` in GitHub repository. \ No newline at end of file diff --git a/README.md b/README.md index d4476c6..efb0de2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ -# DefGraph v2 +# DefGraph v3 -routing gif +routing gif +* **Changelog and migration guild from version 2 to 3** * **Changelog and migration guild from version 1 to 2** 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 go's inside of this graph with utilizing auto pathfinder. @@ -9,12 +10,13 @@ This module contains functions to create a world map as a shape of a graph and t You can define a graph with several nodes and routes between them and the extension takes care of finding and moving your go inside this graph with just one call inside player update function. the gif bellow shows you this exactly when the destination for all red circles is node number 6. -routing gif +static routing gif 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 and all of them are customizable. 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. +also, you can update nodes positions, another word you can have dynamically moving routes ;) -routing gif +dynamic routing gif This is a community project you are welcome to contribute to it, sending PR, suggest a feature or report a bug. @@ -30,45 +32,139 @@ local defgraph = require("defgraph.defgraph") ``` Then you can use the DefGraph functions using this module. +[Official Defold game asset page for DefGraph](https://defold.com/assets/defgraph/) + +## Module Settings +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. +#### **Threshold:** +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`. +#### **Path Curve Tightness:** +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`. + +Tightness 2 | Tightness 3 | Tightness 8 +:-------------: | :-------------: | :-------------: +Tightness: 2 | Tightness: 3 | Tightness: 8 + +#### **Path Curve Roundness:** +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. + +Roundness 0 | Roundness 1 | Roundness 5 +:-------------: | :-------------: | :-------------: +Roundness: 0 | Roundness: 1 | Roundness: 5 + +#### **Path Curve Max Distance From Corner:** +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. + +Max 10 | Max 30 | Max 50 +:-------------: | :-------------: | :-------------: +Max: 10 | Max: 30 | Max: 50 + +#### **Allow Enter on Route:** +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`. + +False | True +:-------------: | :-------------: +False | True + ## Functions These are the list of available functions to use, for better understanding of how this module works, please take a look at project example. +--- +### map_set_properties([settings_go_threshold], [settings_path_curve_tightness], [settings_path_curve_roundness], [settings_path_curve_max_distance_from_corner], [settings_allow_enter_on_route]) +Set the main path and move calculation properties, nil inputs will fall back to module default values. These values will overwrite default module values. +#### **arguments:** +* `optional number` settings_go_threshold `[default = 1]` +* `optional number` settings_path_curve_tightness `[default = 4]` +* `optional number` settings_path_curve_roundness `[default = 3]` +* `optional number` settings_path_curve_max_distance_from_corner `[default = 10]` +* `optional boolean` settings_allow_enter_on_route `[default = true]` +--- ### map_add_node(position) -Adding a node at the given position (position.z will get ignored) -**arguments:** position as vector3 -**return:** Newly added node id as number - -### map_add_route(source_id, destination_id) -Adding a two-way route between nodes with ids of source_id and destination_id -**arguments:** source_id as number, destination_id as number - -### map_remove_route(source_id, destination_id) -Removing an existing route between nodes with ids of source_id and destination_id -**arguments:** source_id as number, destination_id as number - -### move_initialize(source_position, destination_id) -initialize moves from source_position to a node with an id of destination_id inside the created map -**arguments:** source_position as vector3, destination_id as number -**return:** special movement data as table +Adding a node at the given position (position.z will get ignored). +#### **arguments:** +* `vector3` position +#### **return:** +* `number` Newly added node id + +> **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. +--- +### map_add_route(source_id, destination_id, is_one_way) +Adding a two-way route between two nodes, you can set it as one way or two way. +#### **arguments:** +* `number` source_id +* `number` destination_id +* `optional boolean` is_one_way `[false]` + +> **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. +--- +### map_remove_route(source_id, destination_id, is_remove_one_way) +Removing an existing route between two nodes, you can set it to remove just one way or both ways. +#### **arguments:** +* `number` source_id +* `number` destination_id +* `optional boolean` is_remove_one_way `[false]` +--- +### map_remove_node(node_id) +Removing an existing node, attached routes to this node will remove. +#### **arguments:** +* `number` node_id +--- +### map_update_node_position(node_id, position) +Update an existing node position. +#### **arguments:** +* `number` node_id +* `vector3` position +--- +### move_initialize(source_position, destination_id, initial_face_vector, settings_go_threshold, settings_path_curve_tightness, settings_path_curve_roundness, settings_path_curve_max_distance_from_corner, settings_allow_enter_on_route) +Initialize moves from a source position to destination node inside the created map and using given threshold and initial face vector as game object initial face direction and path calculate settings, **the optional value will fall back to module default values.** +#### **arguments:** +* `vector3` source_position +* `number` destination_id +* `optional vecotr3` initial_face_vector +* `optional number` settings_go_threshold +* `optional number` settings_path_curve_tightness +* `optional number` settings_path_curve_roundness +* `optional number` settings_path_curve_max_distance_from_corner +* `optional boolean` settings_allow_enter_on_route +#### **return:** +* `table` special movement data > **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. - -### move_player(current_position, speed, threshold, move_data) -calculate movements from current_position of the game object inside the created map considering given speed and threshold, using last calculated movement data -**arguments:** current_position as vector3, speed as number, threshold as number, move_data as table -**return:** new movement data as table, move result table like { `position`: next position of game object as vector3, `is_reached`: is game object reached the destination as boolean } -> **Note:** The returned new movement data should overwrite old movement data. normally this function is placed inside go update function and you can set go position to `position` that is inside move result table. also, you should multiply `dt` with speed yourself before passing it to function. - -### debug_set_properties(node_color, route_color, draw_scale) +--- +### move_player(current_position, speed, move_data) +Calculate movements from current position of the game object inside the created map considering given speed, using last calculated movement data. +#### **arguments:** +* `vector3` current_position +* `number` speed +* `table` move_data +#### **return:** +* `table` new movement data +* `table` move result + * `position`: `vector3` next position of game object + * `rotation`: `quat` next rotation of game object + * `is_reached`: `boolean` is game object reached the destination +> **Note:** The returned new movement data should overwrite old movement data. normally this function is placed inside go update function and you can set go 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. +--- +### debug_set_properties(node_color, two_way_route_color, one_way_route_color, draw_scale) set debug drawing properties -**arguments:** node_color as vector4, route_color as vector4, draw_scale as number - +#### **arguments:** +* `optional vector4` node_color `[vector4(1, 0, 1, 1)]` +* `optional vector4` two_way_route_color `[vector4(0, 1, 0, 1)]` +* `optional vector4` one_way_route_color `[vector4(0, 1, 1, 1)]` +* `optional number` draw_scale `[5]` +--- ### debug_draw_map_nodes(is_show_ids) -debug draw all map nodes and choose to show node ids or not -**arguments:** is_show_ids as boolean - +Debug draw all map nodes and choose to show node ids or not. +#### **arguments:** +* `optional boolean` is_show_ids `[false]` +--- ### debug_draw_map_routes() -debug draw all map routes - -### debug_draw_player_move(movement_data, color) -debug draw player route to destination with given color -**arguments:** movement_data as table, color as vector4 +Debug draw all map routes. + +--- +### debug_draw_player_move(movement_data, color, is_show_intersection) +Debug draw player specific path with given color. +#### **arguments:** +* `table` movement_data +* `vector4` color +* `optional boolean` is_show_intersection `[false]` +--- \ No newline at end of file diff --git a/defgraph/defgraph.lua b/defgraph/defgraph.lua index 2f3fe94..c8b1c67 100644 --- a/defgraph/defgraph.lua +++ b/defgraph/defgraph.lua @@ -1,12 +1,13 @@ -- DefGraph -- 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 go's inside --- of this graph with utilizing auto pathfinder. +-- to manipulate it at any time, easily see debug drawing of this graph and move and rotate +-- game objects inside of this graph with utilizing auto pathfinder. local M = {} local map_node_list = {} --- map_node_list[node_id] = { position, type } +-- map_node_list[node_id] = { position, type, neighbor_id:{} } + local map_route_list = {} -- map_route_list[from_id][to_id] = { a, b, c, distance } @@ -21,28 +22,112 @@ local NODETYPE = { intersection = 2 } --- local: color vectors for debug drawing +-- local: color vectors and scale of debug drawing local debug_node_color = vmath.vector4(1, 0, 1, 1) -local debug_route_color = vmath.vector4(0, 1, 0, 1) - --- local: scale of node symboles for debug drawing +local debug_two_way_route_color = vmath.vector4(0, 1, 0, 1) +local debug_one_way_route_color = vmath.vector4(0, 1, 1, 1) local debug_draw_scale = 5 +-- local: main settings +local settings_main_go_threshold = 1 +local settings_main_path_curve_tightness = 4 +local settings_main_path_curve_roundness = 3 +local settings_main_path_curve_max_distance_from_corner = 10 +local settings_main_allow_enter_on_route = true + -- local: math functions local sqrt = math.sqrt local pow = math.pow local abs = math.abs local huge = math.huge +local pi = math.pi + +-- global: Set the main path and move calculation properties, nil inputs will fall back to default values. +-- arguments: settings_go_threshold as optional number [1] +-- settings_path_curve_tightness as optional number [4] +-- settings_path_curve_roundness as optional number [3] +-- settings_path_curve_max_distance_from_corner as optional number [10] +-- settings_allow_enter_on_route as optional boolean [true] +function M.map_set_properties(settings_go_threshold, settings_path_curve_tightness, settings_path_curve_roundness + , settings_path_curve_max_distance_from_corner, settings_allow_enter_on_route) + if settings_go_threshold ~= nil then + settings_main_go_threshold = settings_go_threshold + end + if settings_path_curve_tightness ~= nil then + settings_main_path_curve_tightness = settings_path_curve_tightness + end + if settings_path_curve_roundness ~= nil then + settings_main_path_curve_roundness = settings_path_curve_roundness + end + if settings_path_curve_max_distance_from_corner ~= nil then + settings_main_path_curve_max_distance_from_corner = settings_path_curve_max_distance_from_corner + end + if settings_allow_enter_on_route ~= nil then + settings_main_allow_enter_on_route = settings_allow_enter_on_route + end +end --- global: set debug drawing properties --- arguments: node_color as vector4, route_color as vector4, draw_scale as number -function M.debug_set_properties(node_color, route_color, draw_scale) - debug_node_color = node_color - debug_route_color = route_color - debug_draw_scale = 5 +-- global: Update an existing node position. +-- arguments: node_id as number +-- position as vecotr3 +function M.map_update_node_position(node_id, position) + if map_node_list[node_id] ~= nil then + map_node_list[node_id].position = position + end + for from_id, routes in pairs(map_route_list) do + for to_id, route in pairs(routes) do + if from_id == node_id or to_id == node_id then + -- line equation: ax + by + c = 0 + local a, b, c + local from_pos = map_node_list[from_id].position + local to_pos = map_node_list[to_id].position + if from_pos.x ~= to_pos.x then + --non vertical + a = (from_pos.y - to_pos.y)/(to_pos.x - from_pos.x) + b = 1 + c = ((from_pos.x * to_pos.y) - (to_pos.x * from_pos.y))/(to_pos.x - from_pos.x) + else + --vertical + a = 1 + b = 0 + c = -from_pos.x + end + map_route_list[from_id][to_id] = { + a = a, + b = b, + c = c, + distance = sqrt(pow(from_pos.x - to_pos.x, 2) + pow(from_pos.y - to_pos.y, 2)) + } + end + end + end + -- map shape is changed + map_change_iterator = map_change_iterator + 1 end --- local: count size of non-sequences table +-- global: Set the debug drawing properties, nil inputs will fall back to default values. +-- arguments: node_color as optional vector4 [vector4(1, 0, 1, 1)] +-- two_way_route_color as optional vector4 [vector4(0, 1, 0, 1)] +-- one_way_route_color as optional vector4 [vector4(0, 1, 1, 1)] +-- draw_scale as optional number [5] +function M.debug_set_properties(node_color, two_way_route_color, one_way_route_color, draw_scale) + if node_color ~= nil then + debug_node_color = node_color + end + if two_way_route_color ~= nil then + debug_two_way_route_color = two_way_route_color + end + if one_way_route_color ~= nil then + debug_one_way_route_color = one_way_route_color + end + if draw_scale ~= nil then + debug_draw_scale = draw_scale + end +end + +-- local: Count size of non-sequential table. +-- arguments: table as table +-- return: size of table as number local function table_size(table) local count = 0 for _ in pairs(table) do @@ -51,105 +136,187 @@ local function table_size(table) return count end --- local: Add one way route from node source_id to node destination_id -local function map_add_oneway_route(source_id, destination_id) +-- local: Add one way route from one node to another. +-- arguments: source_id as number +-- destination_id as number +-- route_info as table +-- return: route info entry as table +local function map_add_oneway_route(source_id, destination_id, route_info) if map_route_list[source_id] == nil then map_route_list[source_id] = {} end if map_route_list[source_id][destination_id] == nil then - -- line equation: ax + by + c = 0 - local a, b, c - local from_pos = map_node_list[source_id].position - local to_pos = map_node_list[destination_id].position - if from_pos.x ~= to_pos.x then - --non vertical - a = (from_pos.y - to_pos.y)/(to_pos.x - from_pos.x) - b = 1 - c = ((from_pos.x * to_pos.y) - (to_pos.x * from_pos.y))/(to_pos.x - from_pos.x) + if route_info == nil then + -- line equation: ax + by + c = 0 + local a, b, c + local from_pos = map_node_list[source_id].position + local to_pos = map_node_list[destination_id].position + if from_pos.x ~= to_pos.x then + --non vertical + a = (from_pos.y - to_pos.y)/(to_pos.x - from_pos.x) + b = 1 + c = ((from_pos.x * to_pos.y) - (to_pos.x * from_pos.y))/(to_pos.x - from_pos.x) + else + --vertical + a = 1 + b = 0 + c = -from_pos.x + end + map_route_list[source_id][destination_id] = { + a = a, + b = b, + c = c, + distance = sqrt(pow(from_pos.x - to_pos.x, 2) + pow(from_pos.y - to_pos.y, 2)) + } else - --vertical - a = 1 - b = 0 - c = -from_pos.x + map_route_list[source_id][destination_id] = route_info + end + + if route_info == nil then + local is_found = false + for i = 1, #map_node_list[source_id].neighbor_id do + if map_node_list[source_id].neighbor_id[i] == destination_id then + is_found = true + break + end + end + if is_found == false then + table.insert(map_node_list[source_id].neighbor_id, destination_id) + end + + is_found = false + for i = 1, #map_node_list[destination_id].neighbor_id do + if map_node_list[destination_id].neighbor_id[i] == source_id then + is_found = true + break + end + end + if is_found == false then + table.insert(map_node_list[destination_id].neighbor_id, source_id) + end end - map_route_list[source_id][destination_id] = { - a = a, - b = b, - c = c, - distance = sqrt(pow(from_pos.x - to_pos.x, 2) + pow(from_pos.y - to_pos.y, 2)) - } end + + return map_route_list[source_id][destination_id] end --- local: update node type parameter +-- local: Update node type parameter. +-- arguments: node_id as number local function map_update_node_type(node_id) - if map_route_list[node_id] ~= nil then - local size = table_size(map_route_list[node_id]) - if size == 0 then + if map_node_list[node_id] ~= nil then + if #map_node_list[node_id].neighbor_id == 0 then map_node_list[node_id].type = NODETYPE.single - elseif size == 1 then + elseif #map_node_list[node_id].neighbor_id == 1 then map_node_list[node_id].type = NODETYPE.deadend - elseif size > 1 then + elseif #map_node_list[node_id].neighbor_id > 1 then map_node_list[node_id].type = NODETYPE.intersection end end end --- local: remove an existing route between source_id and destination_id nodes +-- local: Remove an existing route between two nodes. +-- arguments: source_id as number +-- destination_id as number local function map_remove_oneway_route(source_id, destination_id) if map_route_list[source_id] ~= nil then map_route_list[source_id][destination_id] = nil if table_size(map_route_list[source_id]) == 0 then map_route_list[source_id] = nil end + if map_route_list[destination_id] == nil or map_route_list[destination_id][source_id] == nil then + for i = 1, #map_node_list[destination_id].neighbor_id do + if map_node_list[destination_id].neighbor_id[i] == source_id then + table.remove(map_node_list[destination_id].neighbor_id, i) + break + end + end + for i = 1, #map_node_list[source_id].neighbor_id do + if map_node_list[source_id].neighbor_id[i] == destination_id then + table.remove(map_node_list[source_id].neighbor_id, i) + break + end + end + end end end --- global: Adding a node at the given position (position.z will get ignored) +-- global: Adding a node at the given position (position.z will get ignored). -- arguments: position as vector3 -- return: Newly added node id as number function M.map_add_node(position) map_node_id_iterator = map_node_id_iterator + 1 local node_id = map_node_id_iterator - map_node_list[node_id] = { position = vmath.vector3(position.x, position.y, 0), type = NODETYPE.single } + map_node_list[node_id] = { position = vmath.vector3(position.x, position.y, 0), type = NODETYPE.single, neighbor_id = {} } map_change_iterator = map_change_iterator + 1 return node_id end --- global: Adding a two-way route between nodes with ids of source_id and destination_id --- arguments: source_id as number, destination_id as number -function M.map_add_route(source_id, destination_id) +-- global: Adding a two-way route between two nodes, you can set it as one way or two way. +-- arguments: source_id as number +-- destination_id as number +-- is_one_way as optional boolean [false] +function M.map_add_route(source_id, destination_id, is_one_way) + if is_one_way == nil then + is_one_way = false + end if map_node_list[source_id] == nil or map_node_list[destination_id] == nil or source_id == destination_id then return end - map_add_oneway_route(source_id, destination_id) - map_add_oneway_route(destination_id, source_id) + local route_info = map_add_oneway_route(source_id, destination_id, nil) + if is_one_way == false then + map_add_oneway_route(destination_id, source_id, route_info) + end map_update_node_type(source_id) map_update_node_type(destination_id) map_change_iterator = map_change_iterator + 1 end --- global: Removing an existing route between nodes with ids of source_id and destination_id --- arguments: source_id as number, destination_id as number -function M.map_remove_route(source_id, destination_id) +-- global: Removing an existing route between two nodes, you can set it to remove just one way or both ways. +-- arguments: source_id as number +-- destination_id as number +-- is_remove_one_way as optional boolean [false] +function M.map_remove_route(source_id, destination_id, is_remove_one_way) + if is_remove_one_way == nil then + is_remove_one_way = false + end if map_node_list[source_id] == nil or map_node_list[destination_id] == nil or source_id == destination_id then return end map_remove_oneway_route(source_id, destination_id) - map_remove_oneway_route(destination_id, source_id) + if is_remove_one_way == false then + map_remove_oneway_route(destination_id, source_id) + end map_update_node_type(source_id) map_update_node_type(destination_id) map_change_iterator = map_change_iterator + 1 end +-- global: Removing an existing node, attached routes to this node will remove. +-- arguments: node_id as number +function M.map_remove_node(node_id) + if map_node_list[node_id] == nil then + return + end + for from_id, routes in pairs(map_route_list) do + for to_id, route in pairs(routes) do + if from_id == node_id or to_id == node_id then + map_remove_oneway_route(from_id, to_id) + map_update_node_type(from_id) + map_update_node_type(to_id) + end + end + end + map_node_list[node_id] = nil + map_change_iterator = map_change_iterator + 1 +end --- global: debug draw all map nodes and choose to show node ids or not --- arguments: is_show_ids as boolean +-- global: Debug draw all map nodes and choose to show node ids or not. +-- arguments: is_show_ids as optional boolean [false] function M.debug_draw_map_nodes(is_show_ids) for node_id, node in pairs(map_node_list) do if is_show_ids then @@ -177,38 +344,55 @@ function M.debug_draw_map_nodes(is_show_ids) end end --- global: debug draw all map routes +-- global: Debug draw all map routes. function M.debug_draw_map_routes() for from_id, routes in pairs(map_route_list) do for to_id, route in pairs(routes) do - if from_id < to_id then - msg.post("@render:", "draw_line", { start_point = map_node_list[from_id].position, end_point = map_node_list[to_id].position, color = debug_route_color } ) + + if map_route_list[to_id] ~= nil and map_route_list[to_id][from_id] ~= nil then + 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 } ) + else + 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 } ) + + local arrow_postion = 4 / 5 * map_node_list[to_id].position + map_node_list[from_id].position / 5 + 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 } ) + 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 } ) + 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 } ) + 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 } ) end end end end --- global: debug draw player specific movement_data with given color --- arguments: movement_data as table, color as vector4 -function M.debug_draw_player_move(movement_data, color) +-- global: Debug draw player specific path with given color. +-- arguments: movement_data as table +-- color as vector4 +-- is_show_intersection as optional boolean [false] +function M.debug_draw_player_move(movement_data, color, is_show_intersection) if movement_data.path_index ~= 0 then for index = movement_data.path_index, #movement_data.path do if index ~= #movement_data.path then msg.post("@render:", "draw_line", { start_point = movement_data.path[index], end_point = movement_data.path[index + 1], color = color } ) end - 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 } ) - 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 } ) + if is_show_intersection then + 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 } ) + 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 } ) + end end end end - --- local: calculate distance between two vector 3 +-- local: Calculate distance between two vector3. +-- arguments: source as vecotr3 +-- destination as vecotr3 +-- return: distance as number local function distance(source, destination) return sqrt(pow(source.x - destination.x, 2) + pow(source.y - destination.y, 2)) end --- local: shallow copy a table +-- local: Shallow copy a table. +-- arguments: table as table +-- return: duplicated table as table local function shallow_copy(table) local new_table = {} for key, value in pairs(table) do @@ -217,17 +401,23 @@ local function shallow_copy(table) return new_table end --- local: calculate neareset position on nearest route on map to given position --- return: table include vecotr3 for position on a nearest route, distance to that position and node ids for that nearest route +-- local: Calculate the nearest position on the nearest route on the map from the given position. +-- arguments: position as vecotr3 +-- return: near_result as table { +-- position_on_route as vecotr3, +-- distance as number +-- route_from_id as number +-- route_to_id as number } local function calculate_to_nearest_route(position) local min_from_id, min_to_id local min_near_pos_x, min_near_pos_y local min_dist = huge + local already_calculated = {} for from_id, routes in pairs(map_route_list) do for to_id, route in pairs(routes) do - if from_id < to_id then - + if already_calculated[from_id] == nil or already_calculated[from_id][to_id] == nil then + local is_between, near_pos_x, near_pos_y, dist, dist_from_id, dist_to_id local from_pos = map_node_list[from_id].position local to_pos = map_node_list[to_id].position @@ -291,12 +481,18 @@ local function calculate_to_nearest_route(position) min_from_id = from_id min_to_id = to_id end - + + if already_calculated[to_id] == nil then + already_calculated[to_id] = {} + end + already_calculated[to_id][from_id] = true + end end end if min_dist == huge then + -- if no route exists return nil else return { @@ -308,8 +504,12 @@ local function calculate_to_nearest_route(position) end end --- local: calculate graph path inside map from node id of start_id to finish_id --- return: list of node ids, total distance of path +-- local: Calculate graph path inside map from a node to another node. +-- arguments: start_id as number +-- finish_id as number +-- return: path_result as list of table { +-- id as number, +-- distance as number } local function calculate_path(start_id, finish_id) local previous = {} local distances = {} @@ -340,15 +540,15 @@ local function calculate_path(start_id, finish_id) table.insert(path, 1, { id = smallest, distance = path_distance }) - if map_route_list[smallest] == nil then + if map_route_list[previous[smallest]] == nil then return nil end - if map_route_list[smallest][previous[smallest]] == nil then + if map_route_list[previous[smallest]][smallest] == nil then return nil end - path_distance = path_distance + map_route_list[smallest][previous[smallest]].distance + path_distance = path_distance + map_route_list[previous[smallest]][smallest].distance smallest = previous[smallest]; end table.insert(path, 1, { id = smallest, distance = path_distance }) @@ -359,11 +559,13 @@ local function calculate_path(start_id, finish_id) break; end - for to_id, neighbor in pairs(map_route_list[smallest]) do - local alt = distances[smallest] + neighbor.distance - if alt < distances[to_id] then - distances[to_id] = alt; - previous[to_id] = smallest; + if map_route_list[smallest] ~= nil then + for to_id, neighbor in pairs(map_route_list[smallest]) do + local alt = distances[smallest] + neighbor.distance + if alt < distances[to_id] then + distances[to_id] = alt; + previous[to_id] = smallest; + end end end @@ -372,8 +574,14 @@ local function calculate_path(start_id, finish_id) return path end --- local: retrive path results from cache or update cache --- return: cache includes distance and path table to destination +-- local: Retrive path results from cache or update cache. +-- arguments: change_number as number +-- from_id as number +-- to_id as number +-- return: cache table as table { +-- change_number as number, +-- distance as number +-- path as list of number } local function fetch_path(change_number, from_id, to_id) -- check for same from and to id @@ -421,10 +629,64 @@ local function fetch_path(change_number, from_id, to_id) return pathfinder_cache[from_id][to_id] end --- global: initialize moves from source_position to a node with an id of destination_id inside the created map --- arguments: source_position as vector3, destination_id as number +-- local: Calculate path curvature. +-- arguments: position_list as list table +-- settings_path_curve_tightness as number +-- settings_path_curve_max_distance_from_corner as number +-- return: curve postions as list table of vector3 +local function process_path_curvature(before, current, after, roundness, settings_path_curve_tightness, settings_path_curve_max_distance_from_corner) + + local new_position_list = {} + + local Q_before = (settings_path_curve_tightness - 1) / settings_path_curve_tightness * before + current / settings_path_curve_tightness + local R_before = before / settings_path_curve_tightness + (settings_path_curve_tightness - 1) / settings_path_curve_tightness * current + local Q_after = (settings_path_curve_tightness - 1) / settings_path_curve_tightness * current + after / settings_path_curve_tightness + local R_after = current / settings_path_curve_tightness + (settings_path_curve_tightness - 1) / settings_path_curve_tightness * after + + if distance(Q_before, before) > settings_path_curve_max_distance_from_corner then + Q_before = vmath.lerp(settings_path_curve_max_distance_from_corner/distance(before, current), before, current) + end + if distance(R_before, current) > settings_path_curve_max_distance_from_corner then + R_before = vmath.lerp(settings_path_curve_max_distance_from_corner/distance(before, current), current, before) + end + if distance(Q_after, current) > settings_path_curve_max_distance_from_corner then + Q_after = vmath.lerp(settings_path_curve_max_distance_from_corner/distance(current, after), current, after) + end + if distance(R_after, after) > settings_path_curve_max_distance_from_corner then + R_after = vmath.lerp(settings_path_curve_max_distance_from_corner/distance(current, after), after, current) + end + + if roundness ~= 1 then + local new_list_before = process_path_curvature(Q_before, R_before, Q_after, roundness - 1, settings_path_curve_tightness, settings_path_curve_max_distance_from_corner) + local new_list_after = process_path_curvature(R_before, Q_after, R_after, roundness - 1, settings_path_curve_tightness, settings_path_curve_max_distance_from_corner) + + for key, value in pairs(new_list_before) do + table.insert(new_position_list, value) + end + for key, value in pairs(new_list_after) do + table.insert(new_position_list, value) + end + else + table.insert(new_position_list, R_before) + table.insert(new_position_list, Q_after) + end + return new_position_list, Q_before, R_after +end + +-- local: Initialize moves from source position to a node with an destination node inside the created map. +-- arguments: source_position as vector3 +-- destination_id as number +-- settings_go_threshold as number +-- initial_face_vector as vector3 +-- current_face_vector as vector3 +-- settings_path_curve_tightness as number +-- settings_path_curve_roundness as number +-- settings_path_curve_max_distance_from_corner as number +-- settings_allow_enter_on_route as boolean -- return: special movement data as table -function M.move_initialize(source_position, destination_id) +local function move_internal_initialize(source_position, destination_id, settings_go_threshold, initial_face_vector + , current_face_vector, settings_path_curve_tightness, settings_path_curve_roundness, settings_path_curve_max_distance_from_corner + , settings_allow_enter_on_route) local near_result = calculate_to_nearest_route(source_position) if near_result == nil then -- stay until something changes @@ -432,18 +694,47 @@ function M.move_initialize(source_position, destination_id) change_number = map_change_iterator, destination_id = destination_id, path_index = 0, - path = {} + path = {}, + initial_face_vector = initial_face_vector, + current_face_vector = current_face_vector, + settings_go_threshold = settings_go_threshold, + settings_path_curve_tightness = settings_path_curve_tightness, + settings_path_curve_roundness = settings_path_curve_roundness, + settings_allow_enter_on_route = settings_allow_enter_on_route, + settings_path_curve_max_distance_from_corner = settings_path_curve_max_distance_from_corner } else - local from_path = fetch_path(map_change_iterator, near_result.route_from_id, destination_id) - local to_path = fetch_path(map_change_iterator, near_result.route_to_id, destination_id) + local from_path = nil + local to_path = nil + + if map_route_list[near_result.route_to_id] ~= nil and map_route_list[near_result.route_to_id][near_result.route_from_id] ~= nil then + from_path = fetch_path(map_change_iterator, near_result.route_from_id, destination_id) + end + if map_route_list[near_result.route_from_id] ~= nil and map_route_list[near_result.route_from_id][near_result.route_to_id] ~= nil then + to_path = fetch_path(map_change_iterator, near_result.route_to_id, destination_id) + end local position_list = {} - table.insert(position_list, near_result.position_on_route) + table.insert(position_list, source_position) + + if (near_result.distance > settings_go_threshold + 1) and settings_allow_enter_on_route then + table.insert(position_list, near_result.position_on_route) + end - if from_path ~= nil and to_path ~= nil then - local from_distance = from_path.distance + distance(source_position, map_node_list[near_result.route_from_id].position) - local to_distance = to_path.distance + distance(source_position, map_node_list[near_result.route_to_id].position) + if from_path ~= nil or to_path ~= nil then + local from_distance, to_distance + + if from_path == nil then + from_distance = huge + else + from_distance = from_path.distance + distance(source_position, map_node_list[near_result.route_from_id].position) + end + + if to_path == nil then + to_distance = huge + else + to_distance = to_path.distance + distance(source_position, map_node_list[near_result.route_to_id].position) + end if from_distance <= to_distance then table.insert(position_list, map_node_list[near_result.route_from_id].position) @@ -458,38 +749,141 @@ function M.move_initialize(source_position, destination_id) end end + local new_position_list = {} + if settings_path_curve_roundness ~= 0 then + table.insert(new_position_list, position_list[1]) + + for i = 2, #position_list - 1 do + local partial_position_list, Q_before, R_after = process_path_curvature(position_list[i - 1], position_list[i], position_list[i + 1] + , settings_path_curve_roundness, settings_path_curve_tightness, settings_path_curve_max_distance_from_corner) + + if i == 2 then + table.insert(new_position_list, Q_before) + end + + for key, value in pairs(partial_position_list) do + table.insert(new_position_list, value) + end + + if i == #position_list - 1 then + table.insert(new_position_list, R_after) + end + end + + table.insert(new_position_list, position_list[#position_list]) + else + new_position_list = position_list + end + return { change_number = map_change_iterator, destination_id = destination_id, path_index = 1, - path = position_list + path = new_position_list, + initial_face_vector = initial_face_vector, + current_face_vector = current_face_vector, + settings_go_threshold = settings_go_threshold, + settings_path_curve_tightness = settings_path_curve_tightness, + settings_path_curve_roundness = settings_path_curve_roundness, + settings_allow_enter_on_route = settings_allow_enter_on_route, + settings_path_curve_max_distance_from_corner = settings_path_curve_max_distance_from_corner } end end --- global: calculate movements from current_position of the game object inside the created map considering given speedand threshold, using last calculated movement data --- arguments: current_position as vector3, speed as number, threshold as number, move_data as table --- return: new movement data as table, move result table like { position: next position of game object as vector3, is_reached: is game object reached the destination as boolean } -function M.move_player(current_position, speed, threshold, move_data) +-- global: Initialize moves from a source position to destination node inside the created map and +-- using given threshold and initial face vector as game object initial face direction and path +-- calculate settings, the optional value will fall back to their default values. +-- arguments: source_position as vector3 +-- destination_id as number +-- initial_face_vector as optional vecotr3 +-- settings_go_threshold as optional number +-- settings_path_curve_tightness as optional number +-- settings_path_curve_roundness as optional number +-- settings_path_curve_max_distance_from_corner as optional number +-- settings_allow_enter_on_route as optional boolean +-- return: special movement data as table +function M.move_initialize(source_position, destination_id, initial_face_vector, settings_go_threshold + , settings_path_curve_tightness, settings_path_curve_roundness, settings_path_curve_max_distance_from_corner, settings_allow_enter_on_route) + + if settings_go_threshold == nil then + settings_go_threshold = settings_main_go_threshold + end + + if settings_path_curve_roundness == nil then + settings_path_curve_roundness = settings_main_path_curve_roundness + end + + if settings_path_curve_tightness == nil then + settings_path_curve_tightness = settings_main_path_curve_tightness + end + + if settings_path_curve_max_distance_from_corner == nil then + settings_path_curve_max_distance_from_corner = settings_main_path_curve_max_distance_from_corner + end + + if settings_allow_enter_on_route == nil then + settings_allow_enter_on_route = settings_main_allow_enter_on_route + end + + return move_internal_initialize(source_position, destination_id, settings_go_threshold, initial_face_vector, initial_face_vector + , settings_path_curve_tightness, settings_path_curve_roundness, settings_path_curve_max_distance_from_corner, settings_allow_enter_on_route) +end + +-- global: Calculate movements from current position of the game object inside the created map +-- considering given speed, using last calculated movement data. +-- arguments: current_position as vector3 +-- speed as number +-- move_data as table +-- return: new movement data as table +-- move result table { +-- position as vector3, +-- rotation as vector3, +-- is_reached as boolean } +function M.move_player(current_position, speed, move_data) + -- check for map updates if move_data.change_number ~= map_change_iterator then - move_data = M.move_initialize(current_position, move_data.destination_id) - end + move_data = move_internal_initialize(current_position, move_data.destination_id, move_data.settings_go_threshold, move_data.initial_face_vector + , move_data.current_face_vector, move_data.settings_path_curve_tightness, move_data.settings_path_curve_roundness + , move_data.settings_path_curve_max_distance_from_corner, move_data.settings_allow_enter_on_route) + end + local rotation + -- stand still if no route found if move_data.path_index == 0 then + if move_data.initial_face_vector == nil then + rotation = nil + else + rotation = vmath.quat_from_to(move_data.current_face_vector, move_data.initial_face_vector) + end return move_data, { position = current_position, + rotation = rotation, is_reached = false } end -- check for reaching path section - if distance(current_position, move_data.path[move_data.path_index]) <= threshold then + while distance(current_position, move_data.path[move_data.path_index]) <= move_data.settings_go_threshold + 1 do if move_data.path_index == #move_data.path then + -- reached next path node + if move_data.initial_face_vector == nil then + rotation = nil + else + rotation = vmath.quat_from_to(move_data.current_face_vector, move_data.initial_face_vector) + end + -- reached destination + local is_reached = true + if distance(current_position, map_node_list[move_data.destination_id].position) > move_data.settings_go_threshold + 1 then + is_reached = false + end + return move_data, { position = current_position, - is_reached = true + rotation = rotation, + is_reached = is_reached } else -- go for next section @@ -501,8 +895,19 @@ function M.move_player(current_position, speed, threshold, move_data) local direction_vector = move_data.path[move_data.path_index] - current_position direction_vector.z = 0 direction_vector = vmath.normalize(direction_vector) + if move_data.initial_face_vector == nil then + rotation = nil + else + local rotation_vector = vmath.lerp(0.2 * speed, move_data.current_face_vector, direction_vector) + rotation = vmath.quat_from_to(move_data.initial_face_vector, rotation_vector) + if rotation.x ~= rotation.x then + rotation = vmath.quat_rotation_z(pi) + end + move_data.current_face_vector = rotation_vector + end return move_data, { position = (current_position + direction_vector * speed), + rotation = rotation, is_reached = false } end diff --git a/example/dynamic-routing.gif b/example/dynamic-routing.gif deleted file mode 100644 index fda181c..0000000 Binary files a/example/dynamic-routing.gif and /dev/null differ diff --git a/example/example.tilesource b/example/example.tilesource deleted file mode 100644 index 837ade2..0000000 --- a/example/example.tilesource +++ /dev/null @@ -1,19 +0,0 @@ -image: "/example/example.png" -tile_width: 25 -tile_height: 25 -tile_margin: 0 -tile_spacing: 0 -collision: "" -material_tag: "tile" -collision_groups: "default" -animations { - id: "anim" - start_tile: 1 - end_tile: 1 - playback: PLAYBACK_ONCE_FORWARD - fps: 30 - flip_horizontal: 0 - flip_vertical: 0 -} -extrude_borders: 2 -inner_padding: 0 diff --git a/example/routing.gif b/example/routing.gif deleted file mode 100644 index 2661b88..0000000 Binary files a/example/routing.gif and /dev/null differ diff --git a/examples/assets/atlas.atlas b/examples/assets/atlas.atlas new file mode 100644 index 0000000..9b2d354 --- /dev/null +++ b/examples/assets/atlas.atlas @@ -0,0 +1,9 @@ +images { + image: "/examples/raw/dot.png" +} +images { + image: "/examples/raw/pointy_dot.png" +} +margin: 0 +extrude_borders: 2 +inner_padding: 0 diff --git a/examples/example_dynamic_nodes/example_dynamic_nodes_dot.go b/examples/example_dynamic_nodes/example_dynamic_nodes_dot.go new file mode 100644 index 0000000..d566cc4 --- /dev/null +++ b/examples/example_dynamic_nodes/example_dynamic_nodes_dot.go @@ -0,0 +1,35 @@ +components { + id: "dot" + component: "/examples/example_dynamic_nodes/example_dynamic_nodes_dot.script" + position { + x: 0.0 + y: 0.0 + z: 0.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } +} +embedded_components { + id: "sprite" + type: "sprite" + data: "tile_set: \"/examples/assets/atlas.atlas\"\n" + "default_animation: \"pointy_dot\"\n" + "material: \"/builtins/materials/sprite.material\"\n" + "blend_mode: BLEND_MODE_ALPHA\n" + "" + position { + x: 0.0 + y: 0.0 + z: 0.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } +} diff --git a/examples/example_dynamic_nodes/example_dynamic_nodes_dot.script b/examples/example_dynamic_nodes/example_dynamic_nodes_dot.script new file mode 100644 index 0000000..ed6755b --- /dev/null +++ b/examples/example_dynamic_nodes/example_dynamic_nodes_dot.script @@ -0,0 +1,30 @@ +-- require defgraph +local defgraph = require("defgraph.defgraph") + +function init(self) + -- initilize variables + local my_position = go.get_position() + local destination_id = 36 + self.speed = 200.0 + + -- initialize movement for go inside given map + self.movement_data = defgraph.move_initialize(my_position, destination_id, vmath.vector3(0, 1, 0)) +end + +function update(self, dt) + -- move go inside given map and update move_data + local my_position = go.get_position() + self.movement_data, self.move_result = defgraph.move_player(my_position, self.speed * dt, self.movement_data) + + -- update go postion based of returned result + go.set_position(self.move_result.position) + + -- update go rotation based of returned result + go.set_rotation(self.move_result.rotation) + + -- you can check if go reached to the destination + if self.move_result.is_reached then + print("I'm at destination!") + go.delete() + end +end \ No newline at end of file diff --git a/examples/example_dynamic_nodes/example_dynamic_nodes_main.collection b/examples/example_dynamic_nodes/example_dynamic_nodes_main.collection new file mode 100644 index 0000000..3debb3d --- /dev/null +++ b/examples/example_dynamic_nodes/example_dynamic_nodes_main.collection @@ -0,0 +1,55 @@ +name: "default" +scale_along_z: 0 +embedded_instances { + id: "go" + data: "components {\n" + " id: \"script\"\n" + " component: \"/examples/example_dynamic_nodes/example_dynamic_nodes_main.script\"\n" + " position {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + "}\n" + "embedded_components {\n" + " id: \"factory\"\n" + " type: \"factory\"\n" + " data: \"prototype: \\\"/examples/example_dynamic_nodes/example_dynamic_nodes_dot.go\\\"\\n" + "load_dynamically: false\\n" + "\"\n" + " position {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + "}\n" + "" + position { + x: 0.0 + y: 0.0 + z: 0.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale3 { + x: 1.0 + y: 1.0 + z: 1.0 + } +} diff --git a/examples/example_dynamic_nodes/example_dynamic_nodes_main.script b/examples/example_dynamic_nodes/example_dynamic_nodes_main.script new file mode 100644 index 0000000..2fe9a1a --- /dev/null +++ b/examples/example_dynamic_nodes/example_dynamic_nodes_main.script @@ -0,0 +1,70 @@ +-- require defgraph +local defgraph = require("defgraph.defgraph") + +function init(self) + msg.post(".", "acquire_input_focus") + + -- change map properties + defgraph.map_set_properties(15, 3, 1, 100, true) + + self.node_ids = {} + self.node_positions = {} + self.node_goas_up = {} + + -- defining graph nodes of the map + local is_up = true + local start_position = vmath.vector3(100, 200, 0) + for i = 1, 36 do + if is_up then + start_position = start_position + vmath.vector3(20, 20, 0) + else + start_position = start_position + vmath.vector3(20, -20, 0) + end + + if start_position.y >= 400 then + is_up = false + elseif start_position.y <= 200 then + is_up = true + end + + table.insert(self.node_ids, defgraph.map_add_node(start_position)) + table.insert(self.node_positions, start_position) + table.insert(self.node_goas_up, is_up) + end + + -- defining routes between nodes + for i = 1, #self.node_ids - 1 do + defgraph.map_add_route(self.node_ids[i], self.node_ids[i + 1], true) + end +end + +function on_input(self, action_id, action) + if action_id == hash("left_click") and action.pressed then + factory.create("#factory", vmath.vector3(action.x, action.y, 0), nil, nil, 0.3) + end +end + +function update(self, dt) + for i = 1, 36 do + if self.node_goas_up[i] then + self.node_positions[i] = self.node_positions[i] + vmath.vector3(0, 1, 0) + if self.node_positions[i].y >= 400 then + self.node_positions[i].y = 400 + self.node_goas_up[i] = false + end + else + self.node_positions[i] = self.node_positions[i] - vmath.vector3(0, 1, 0) + if self.node_positions[i].y <= 200 then + self.node_positions[i].y = 200 + self.node_goas_up[i] = true + end + end + defgraph.map_update_node_position(self.node_ids[i], self.node_positions[i]) + end + + -- draw debug info of nodes and routes + defgraph.debug_draw_map_routes() + + msg.post("@render:", "draw_text", { text = "example dynamic node", position = vmath.vector3(20, 620, 0) } ) + msg.post("@render:", "draw_text", { text = "left click: deploy dot", position = vmath.vector3(20, 600, 0) } ) +end diff --git a/example/runner.go b/examples/example_static_nodes/example_static_nodes_dot.go similarity index 68% rename from example/runner.go rename to examples/example_static_nodes/example_static_nodes_dot.go index 0f15701..539661b 100644 --- a/example/runner.go +++ b/examples/example_static_nodes/example_static_nodes_dot.go @@ -1,6 +1,6 @@ components { - id: "runner" - component: "/example/runner.script" + id: "dot" + component: "/examples/example_static_nodes/example_static_nodes_dot.script" position { x: 0.0 y: 0.0 @@ -16,8 +16,8 @@ components { embedded_components { id: "sprite" type: "sprite" - data: "tile_set: \"/example/example.tilesource\"\n" - "default_animation: \"anim\"\n" + data: "tile_set: \"/examples/assets/atlas.atlas\"\n" + "default_animation: \"dot\"\n" "material: \"/builtins/materials/sprite.material\"\n" "blend_mode: BLEND_MODE_ALPHA\n" "" diff --git a/example/runner.script b/examples/example_static_nodes/example_static_nodes_dot.script similarity index 93% rename from example/runner.script rename to examples/example_static_nodes/example_static_nodes_dot.script index d01a746..1614149 100644 --- a/example/runner.script +++ b/examples/example_static_nodes/example_static_nodes_dot.script @@ -14,7 +14,7 @@ end function update(self, dt) -- move go inside given map and update move_data local my_position = go.get_position() - self.movement_data, self.move_result = defgraph.move_player(my_position, self.speed * dt, 1, self.movement_data) + self.movement_data, self.move_result = defgraph.move_player(my_position, self.speed * dt, self.movement_data) -- update go postion based of returned result go.set_position(self.move_result.position) @@ -22,6 +22,7 @@ function update(self, dt) -- you can check if go reached to the destination if self.move_result.is_reached then print("I'm at destination!") + go.delete() end -- debug draw movement_data with specific color diff --git a/example/main.collection b/examples/example_static_nodes/example_static_nodes_main.collection similarity index 78% rename from example/main.collection rename to examples/example_static_nodes/example_static_nodes_main.collection index 9d6cd6f..bfe0424 100644 --- a/example/main.collection +++ b/examples/example_static_nodes/example_static_nodes_main.collection @@ -3,8 +3,8 @@ scale_along_z: 0 embedded_instances { id: "go" data: "components {\n" - " id: \"example\"\n" - " component: \"/example/example.script\"\n" + " id: \"script\"\n" + " component: \"/examples/example_static_nodes/example_static_nodes_main.script\"\n" " position {\n" " x: 0.0\n" " y: 0.0\n" @@ -20,7 +20,7 @@ embedded_instances { "embedded_components {\n" " id: \"factory\"\n" " type: \"factory\"\n" - " data: \"prototype: \\\"/example/runner.go\\\"\\n" + " data: \"prototype: \\\"/examples/example_static_nodes/example_static_nodes_dot.go\\\"\\n" "load_dynamically: false\\n" "\"\n" " position {\n" diff --git a/example/example.script b/examples/example_static_nodes/example_static_nodes_main.script similarity index 65% rename from example/example.script rename to examples/example_static_nodes/example_static_nodes_main.script index cb32a9d..e560f19 100644 --- a/example/example.script +++ b/examples/example_static_nodes/example_static_nodes_main.script @@ -4,8 +4,11 @@ local defgraph = require("defgraph.defgraph") function init(self) msg.post(".", "acquire_input_focus") + -- change map properties + defgraph.map_set_properties(1, 3, 3, 15, true) + -- change debug properties - defgraph.debug_set_properties(vmath.vector4(1, 0, 1, 1), vmath.vector4(0, 1, 0, 1), 5) + defgraph.debug_set_properties(vmath.vector4(1, 0, 1, 1), vmath.vector4(0, 1, 0, 1), vmath.vector4(0, 1, 1, 1), 5) -- defining graph nodes of the map node01 = defgraph.map_add_node(vmath.vector3(100, 550, 0)) @@ -37,7 +40,7 @@ function init(self) defgraph.map_add_route(node03, node05) defgraph.map_add_route(node05, node06) defgraph.map_add_route(node06, node09) - defgraph.map_add_route(node07, node08) + defgraph.map_add_route(node08, node07, true) defgraph.map_add_route(node09, node20) defgraph.map_add_route(node05, node12) defgraph.map_add_route(node04, node10) @@ -57,15 +60,38 @@ function init(self) defgraph.map_add_route(node19, node20) defgraph.map_add_route(node20, node21) defgraph.map_add_route(node21, node10) + + self.map_trigger = true end function on_input(self, action_id, action) - if action_id == hash("mouse_click") and action.pressed then + if action_id == hash("left_click") and action.pressed then factory.create("#factory", vmath.vector3(action.x, action.y, 0), nil, nil, 0.3) end if action_id == hash("right_click") and action.pressed then - defgraph.map_add_route(node08, node18) - defgraph.map_remove_route(node05, node06) + if self.map_trigger then + defgraph.map_add_route(node08, node18) + defgraph.map_add_route(node02, node10) + defgraph.map_add_route(node05, node01, true) + defgraph.map_add_route(node07, node05, true) + defgraph.map_remove_route(node05, node06) + defgraph.map_remove_route(node17, node16) + defgraph.map_remove_route(node19, node16) + defgraph.map_remove_route(node05, node03) + defgraph.map_remove_route(node04, node10) + self.map_trigger = false + else + defgraph.map_remove_route(node08, node18) + defgraph.map_remove_route(node02, node10) + defgraph.map_remove_route(node05, node01, true) + defgraph.map_remove_route(node07, node05, true) + defgraph.map_add_route(node05, node06) + defgraph.map_add_route(node17, node16) + defgraph.map_add_route(node19, node16) + defgraph.map_add_route(node05, node03) + defgraph.map_add_route(node04, node10) + self.map_trigger = true + end end end @@ -73,4 +99,7 @@ function update(self, dt) -- draw debug info of nodes and routes defgraph.debug_draw_map_nodes(true) defgraph.debug_draw_map_routes() + + msg.post("@render:", "draw_text", { text = "example static node", position = vmath.vector3(20, 620, 0) } ) + msg.post("@render:", "draw_text", { text = "left click: deploy dot - right click: change routes", position = vmath.vector3(20, 600, 0) } ) end diff --git a/example/main.input_binding b/examples/main.input_binding similarity index 81% rename from example/main.input_binding rename to examples/main.input_binding index e3816e3..3ee7818 100644 --- a/example/main.input_binding +++ b/examples/main.input_binding @@ -1,6 +1,6 @@ mouse_trigger { input: MOUSE_BUTTON_1 - action: "mouse_click" + action: "left_click" } mouse_trigger { input: MOUSE_BUTTON_2 diff --git a/examples/raw/allow_false.jpg b/examples/raw/allow_false.jpg new file mode 100644 index 0000000..0c706b1 Binary files /dev/null and b/examples/raw/allow_false.jpg differ diff --git a/examples/raw/allow_true.jpg b/examples/raw/allow_true.jpg new file mode 100644 index 0000000..f73e8ac Binary files /dev/null and b/examples/raw/allow_true.jpg differ diff --git a/example/banner.jpg b/examples/raw/banner.jpg similarity index 100% rename from example/banner.jpg rename to examples/raw/banner.jpg diff --git a/examples/raw/debug_draw_one_way_route.jpg b/examples/raw/debug_draw_one_way_route.jpg new file mode 100644 index 0000000..2d422a5 Binary files /dev/null and b/examples/raw/debug_draw_one_way_route.jpg differ diff --git a/example/debug_draw_player_move.png b/examples/raw/debug_draw_player_move.png similarity index 100% rename from example/debug_draw_player_move.png rename to examples/raw/debug_draw_player_move.png diff --git a/example/example.png b/examples/raw/dot.png similarity index 100% rename from example/example.png rename to examples/raw/dot.png diff --git a/examples/raw/dynamic_routing_v3.gif b/examples/raw/dynamic_routing_v3.gif new file mode 100644 index 0000000..72c9117 Binary files /dev/null and b/examples/raw/dynamic_routing_v3.gif differ diff --git a/examples/raw/max_10.jpg b/examples/raw/max_10.jpg new file mode 100644 index 0000000..7169335 Binary files /dev/null and b/examples/raw/max_10.jpg differ diff --git a/examples/raw/max_30.jpg b/examples/raw/max_30.jpg new file mode 100644 index 0000000..ee90857 Binary files /dev/null and b/examples/raw/max_30.jpg differ diff --git a/examples/raw/max_50.jpg b/examples/raw/max_50.jpg new file mode 100644 index 0000000..a5e9a3b Binary files /dev/null and b/examples/raw/max_50.jpg differ diff --git a/examples/raw/pointy_dot.png b/examples/raw/pointy_dot.png new file mode 100644 index 0000000..aefe74a Binary files /dev/null and b/examples/raw/pointy_dot.png differ diff --git a/examples/raw/round_0.jpg b/examples/raw/round_0.jpg new file mode 100644 index 0000000..8b80559 Binary files /dev/null and b/examples/raw/round_0.jpg differ diff --git a/examples/raw/round_1.jpg b/examples/raw/round_1.jpg new file mode 100644 index 0000000..3edfa62 Binary files /dev/null and b/examples/raw/round_1.jpg differ diff --git a/examples/raw/round_5.jpg b/examples/raw/round_5.jpg new file mode 100644 index 0000000..5c312a5 Binary files /dev/null and b/examples/raw/round_5.jpg differ diff --git a/examples/raw/static_routing_v3.gif b/examples/raw/static_routing_v3.gif new file mode 100644 index 0000000..0ec7c29 Binary files /dev/null and b/examples/raw/static_routing_v3.gif differ diff --git a/examples/raw/tness_2.jpg b/examples/raw/tness_2.jpg new file mode 100644 index 0000000..8c56cbf Binary files /dev/null and b/examples/raw/tness_2.jpg differ diff --git a/examples/raw/tness_3.jpg b/examples/raw/tness_3.jpg new file mode 100644 index 0000000..ec5a89a Binary files /dev/null and b/examples/raw/tness_3.jpg differ diff --git a/examples/raw/tness_8.jpg b/examples/raw/tness_8.jpg new file mode 100644 index 0000000..9e0de4e Binary files /dev/null and b/examples/raw/tness_8.jpg differ diff --git a/game.project b/game.project index 2eb9f89..f1de549 100644 --- a/game.project +++ b/game.project @@ -1,14 +1,15 @@ [project] title = DefGraph +version = 3.0 [library] include_dirs = defgraph [input] -game_binding = /example/main.input_bindingc +game_binding = /examples/main.input_bindingc [bootstrap] -main_collection = /example/main.collectionc +main_collection = /examples/example_static_nodes/example_static_nodes_main.collectionc [script] shared_state = 1