main
Yan Wittmann 2024-11-21 18:32:27 +01:00
parent 2a22d3865c
commit 63aa1c0cb5
20 changed files with 584 additions and 437 deletions

View File

@ -6,6 +6,7 @@ const ACCELERATION: int = 2400
func _physics_process(delta: float) -> void:
await get_tree().physics_frame
var movement: Vector2 = Vector2()
nav.target_position = get_global_mouse_position()

View File

@ -15,6 +15,10 @@ run/main_scene="res://scenes/mario-party/MarioParty.tscn"
config/features=PackedStringArray("4.3", "Forward Plus")
config/icon="res://icon.svg"
[display]
window/stretch/mode="viewport"
[input]
draw_toggle_polygon={

View File

@ -5,17 +5,17 @@ var polygon: PackedVector2Array
var center_node: NavigationNode
func _init() -> void:
polygon = PackedVector2Array()
polygon = PackedVector2Array()
func set_polygon(new_polygon: PackedVector2Array) -> PackedVector2Array:
var new_polygon_clone: PackedVector2Array = new_polygon.duplicate()
new_polygon_clone.append(new_polygon_clone[0])
polygon = new_polygon_clone
return polygon
var new_polygon_clone: PackedVector2Array = new_polygon.duplicate()
new_polygon_clone.append(new_polygon_clone[0])
polygon = new_polygon_clone
return polygon
func center() -> Vector2:
var center: Vector2 = Vector2()
for point in polygon:
center += Vector2(point.x, point.y)
center /= polygon.size()
return center
var center: Vector2 = Vector2()
for point in polygon:
center += Vector2(point.x, point.y)
center /= polygon.size()
return center

View File

@ -8,26 +8,26 @@ var frozen: bool = false
func _physics_process(delta: float) -> void:
# check if space is pressed
if Input.is_action_just_pressed("ui_select"):
frozen = !frozen
# check if space is pressed
if Input.is_action_just_pressed("ui_select"):
frozen = !frozen
var movement: Vector2 = Vector2()
if frozen:
return
var movement: Vector2 = Vector2()
if frozen:
return
var pathfinding_result: PathfindingResult = graph.determine_next_position(position, get_global_mouse_position())
graph.draw_pathfinding_result(pathfinding_result)
var pathfinding_result: PathfindingResult = graph.determine_next_position(position, get_global_mouse_position())
graph.draw_pathfinding_result(pathfinding_result)
movement = pathfinding_result.next_position - global_position
movement = pathfinding_result.next_position - global_position
if pathfinding_result.is_next_target and movement.length() < 20:
movement = -velocity * 0.2
else:
movement = movement.normalized() * ACCELERATION * delta
if pathfinding_result.is_next_target and movement.length() < 20:
movement = -velocity * 0.2
else:
movement = movement.normalized() * ACCELERATION * delta
velocity += movement
if velocity.length() > MAX_SPEED:
velocity = velocity.normalized() * MAX_SPEED
velocity += movement
if velocity.length() > MAX_SPEED:
velocity = velocity.normalized() * MAX_SPEED
move_and_slide()
move_and_slide()

View File

@ -5,6 +5,7 @@ extends Node2D
var navigation_nodes: Dictionary = {}
# type is Dictionary[CNavigationPolygon, Array[NavigationNode]]
var polygon_nodes: Dictionary = {}
var latest_navigation_result: PathfindingResult = null
var draw_polygons: bool = true
var draw_nodes: bool = false
@ -12,297 +13,294 @@ var draw_edges: bool = false
func all_nodes() -> Array[NavigationNode]:
# i've had a problem where godot would not allow me to directly return navigation_nodes.keys()
# because it wasn't able to cast the keys to Array[NavigationNode] directly because the type is not explicit
# on the dictionary, so i had to do this workaround.
var keys: Array = navigation_nodes.keys()
var nodes: Array[NavigationNode] = []
for key in keys:
if key is NavigationNode:
nodes.append(key)
else:
push_error("Key is not a NavigationNode: %s" % key)
return nodes
# i've had a problem where godot would not allow me to directly return navigation_nodes.keys()
# because it wasn't able to cast the keys to Array[NavigationNode] directly because the type is not explicit
# on the dictionary, so i had to do this workaround.
var keys: Array = navigation_nodes.keys()
var nodes: Array[NavigationNode] = []
for key in keys:
if key is NavigationNode:
nodes.append(key)
else:
push_error("Key is not a NavigationNode: %s" % key)
return nodes
func get_connections(from: NavigationNode) -> Array[NavigationNode]:
# the same problem as the all_nodes() function
var connections: Array = navigation_nodes[from]
var nodes: Array[NavigationNode] = []
for connection in connections:
if connection is NavigationNode:
nodes.append(connection)
else:
push_error("Connection is not a NavigationNode: %s" % connection)
return nodes
# the same problem as the all_nodes() function
var connections: Array = navigation_nodes[from]
var nodes: Array[NavigationNode] = []
for connection in connections:
if connection is NavigationNode:
nodes.append(connection)
else:
push_error("Connection is not a NavigationNode: %s" % connection)
return nodes
func add_connection(from: NavigationNode, to: NavigationNode) -> void:
if all_nodes().has(from):
navigation_nodes[from].append(to)
else:
navigation_nodes[from] = [to]
if all_nodes().has(from):
navigation_nodes[from].append(to)
else:
navigation_nodes[from] = [to]
func add_node(x: float, y: float, merge_threshold: float = -1.0) -> NavigationNode:
if merge_threshold > 0:
var closest_node: NavigationNode = find_closest_node_with_threshold(Vector2(x, y), merge_threshold)
if closest_node:
closest_node.was_merged = true
return closest_node
var node: NavigationNode = NavigationNode.new()
node.set_position(Vector2(x, y))
navigation_nodes[node] = []
return node
if merge_threshold > 0:
var closest_node: NavigationNode = find_closest_node_with_threshold(Vector2(x, y), merge_threshold)
if closest_node:
closest_node.was_merged = true
return closest_node
var node: NavigationNode = NavigationNode.new()
node.set_position(Vector2(x, y))
navigation_nodes[node] = []
return node
func find_closest_node_with_threshold(position: Vector2, threshold: float) -> NavigationNode:
var closest_node: NavigationNode = null
var closest_distance: float = threshold
for node in all_nodes():
var distance: float = position.distance_to(node.position)
if distance < closest_distance:
closest_node = node
closest_distance = distance
return closest_node
var closest_node: NavigationNode = null
var closest_distance: float = threshold
for node in all_nodes():
var distance: float = position.distance_to(node.position)
if distance < closest_distance:
closest_node = node
closest_distance = distance
return closest_node
func remove_connection(from: NavigationNode, to: NavigationNode) -> void:
if all_nodes().has(from):
navigation_nodes[from].erase(to)
if all_nodes().has(from):
navigation_nodes[from].erase(to)
func remove_node(node: NavigationNode) -> void:
navigation_nodes.erase(node)
for other_node in all_nodes():
if other_node != node:
remove_connection(other_node, node)
for poly in all_polygons():
if node in polygon_nodes[poly]:
polygon_nodes[poly].erase(node)
navigation_nodes.erase(node)
for other_node in all_nodes():
if other_node != node:
remove_connection(other_node, node)
for poly in all_polygons():
if node in polygon_nodes[poly]:
polygon_nodes[poly].erase(node)
func all_polygons() -> Array[CNavigationPolygon]:
var keys: Array = polygon_nodes.keys()
var polygons: Array[CNavigationPolygon] = []
for key in keys:
if key is CNavigationPolygon:
polygons.append(key)
else:
push_error("Key is not a NavigationPolygon: %s" % key)
return polygons
var keys: Array = polygon_nodes.keys()
var polygons: Array[CNavigationPolygon] = []
for key in keys:
if key is CNavigationPolygon:
polygons.append(key)
else:
push_error("Key is not a NavigationPolygon: %s" % key)
return polygons
func get_nodes_in_polygon(poly: CNavigationPolygon) -> Array[NavigationNode]:
var relevant_nodes: Array = polygon_nodes[poly]
var nodes: Array[NavigationNode] = []
for node in relevant_nodes:
if node is NavigationNode:
nodes.append(node)
else:
push_error("Node is not a NavigationNode: %s" % node)
return nodes
var relevant_nodes: Array = polygon_nodes[poly]
var nodes: Array[NavigationNode] = []
for node in relevant_nodes:
if node is NavigationNode:
nodes.append(node)
else:
push_error("Node is not a NavigationNode: %s" % node)
return nodes
func erase_and_create_nodes_from_polygons(new_polys: Array[PackedVector2Array]) -> void:
navigation_nodes.clear()
polygon_nodes.clear()
navigation_nodes.clear()
polygon_nodes.clear()
for poly in new_polys:
if poly.size() == 0:
continue
for poly in new_polys:
if poly.size() == 0:
continue
var navpoly: CNavigationPolygon = CNavigationPolygon.new()
poly = navpoly.set_polygon(poly)
polygon_nodes[navpoly] = []
var navpoly: CNavigationPolygon = CNavigationPolygon.new()
poly = navpoly.set_polygon(poly)
polygon_nodes[navpoly] = []
polygon_nodes[navpoly] = []
# create one in the center of each polygon that is kept no matter what
# var poly_center: Vector2 = navpoly.center()
# var center_node: NavigationNode = add_node(poly_center.x, poly_center.y, -1)
# center_node.was_merged = true
# polygon_nodes[navpoly].append(center_node)
# navpoly.center_node = center_node
# create one in the center of each polygon that is kept no matter what
# var poly_center: Vector2 = navpoly.center()
# var center_node: NavigationNode = add_node(poly_center.x, poly_center.y, -1)
# center_node.was_merged = true
# polygon_nodes[navpoly].append(center_node)
# navpoly.center_node = center_node
for i in range(len(poly) - 1):
var center: Vector2 = (poly[i] + poly[i + 1]) / 2
polygon_nodes[navpoly].append(add_node(center.x, center.y, 10))
var quater: Vector2 = (poly[i] + center) / 2
polygon_nodes[navpoly].append(add_node(quater.x, quater.y, 10))
var three_quater: Vector2 = (center + poly[i + 1]) / 2
polygon_nodes[navpoly].append(add_node(three_quater.x, three_quater.y, 10))
for i in range(len(poly) - 1):
var center: Vector2 = (poly[i] + poly[i + 1]) / 2
polygon_nodes[navpoly].append(add_node(center.x, center.y, 10))
var quater: Vector2 = (poly[i] + center) / 2
polygon_nodes[navpoly].append(add_node(quater.x, quater.y, 10))
var three_quater: Vector2 = (center + poly[i + 1]) / 2
polygon_nodes[navpoly].append(add_node(three_quater.x, three_quater.y, 10))
# clear any that were not merged
for node in all_nodes():
if not node.was_merged:
remove_node(node)
# clear any that were not merged
for node in all_nodes():
if not node.was_merged:
remove_node(node)
# connect all within a polygon
for poly in all_polygons():
var nodes_in_polygon: Array[NavigationNode] = get_nodes_in_polygon(poly)
connect_all_nodes(nodes_in_polygon)
# connect all within a polygon
for poly in all_polygons():
var nodes_in_polygon: Array[NavigationNode] = get_nodes_in_polygon(poly)
connect_all_nodes(nodes_in_polygon)
func connect_all_nodes(nodes: Array[NavigationNode]) -> void:
for i in range(len(nodes)):
for j in range(len(nodes)):
if i != j:
add_connection(nodes[i], nodes[j])
for i in range(len(nodes)):
for j in range(len(nodes)):
if i != j:
add_connection(nodes[i], nodes[j])
func _draw() -> void:
if draw_nodes or draw_edges:
for from in all_nodes():
if draw_edges:
for to in get_connections(from):
draw_line(from.position, to.position, Color.RED, 1, false)
if draw_nodes:
draw_circle(from.position, 5, Color.RED)
if draw_nodes or draw_edges:
for from in all_nodes():
if draw_edges:
for to in get_connections(from):
draw_line(from.position, to.position, Color.RED, 1, false)
if draw_nodes:
draw_circle(from.position, 5, Color.RED)
if draw_polygons:
for poly in all_polygons():
draw_colored_polygon(poly.polygon, Color(0.5, 0.4, 0.9, 0.3))
draw_polyline(poly.polygon, Color.WHITE, 1, true)
if draw_polygons:
for poly in all_polygons():
draw_colored_polygon(poly.polygon, Color(0.5, 0.4, 0.9, 0.3))
draw_polyline(poly.polygon, Color.WHITE, 1, true)
if latest_navigation_result != null:
if latest_navigation_result.path.size() > 1:
for i in range(latest_navigation_result.path.size() - 1):
draw_line(latest_navigation_result.path[i].position, latest_navigation_result.path[i + 1].position, Color.GREEN, 1, false)
draw_circle(latest_navigation_result.next_position, 5, Color.GREEN)
if latest_navigation_result != null:
if latest_navigation_result.path.size() > 1:
for i in range(latest_navigation_result.path.size() - 1):
draw_line(latest_navigation_result.path[i].position, latest_navigation_result.path[i + 1].position, Color.GREEN, 1, false)
draw_circle(latest_navigation_result.next_position, 5, Color.GREEN)
func draw_pathfinding_result(result: PathfindingResult) -> void:
if latest_navigation_result and latest_navigation_result.is_identical_to(result):
return
latest_navigation_result = result
queue_redraw()
if latest_navigation_result and latest_navigation_result.is_identical_to(result):
return
latest_navigation_result = result
queue_redraw()
func determine_next_position(current_position: Vector2, target_position: Vector2) -> PathfindingResult:
var result: PathfindingResult = PathfindingResult.new()
var result: PathfindingResult = PathfindingResult.new()
# find both polygons containing the current and target positions
var current_polygon: CNavigationPolygon = null
var target_polygon: CNavigationPolygon = null
# find both polygons containing the current and target positions
var current_polygon: CNavigationPolygon = null
var target_polygon: CNavigationPolygon = null
for poly in all_polygons():
if Geometry2D.is_point_in_polygon(current_position, poly.polygon):
current_polygon = poly
if Geometry2D.is_point_in_polygon(target_position, poly.polygon):
target_polygon = poly
for poly in all_polygons():
if Geometry2D.is_point_in_polygon(current_position, poly.polygon):
current_polygon = poly
if Geometry2D.is_point_in_polygon(target_position, poly.polygon):
target_polygon = poly
# if the current position is not in any polygon, navigate to the closest node
if not current_polygon:
var closest_node: NavigationNode = find_closest_node_with_threshold(current_position, 100000)
result.next_position = closest_node.position
return result
# if the current position is not in any polygon, navigate to the closest node
if not current_polygon:
var closest_node: NavigationNode = find_closest_node_with_threshold(current_position, 100000)
result.next_position = closest_node.position
return result
# if the target position is not in any polygon, return current position (cannot navigate)
if not target_polygon:
result.is_next_target = true
result.next_position = current_position
return result
# if the target position is not in any polygon, return current position (cannot navigate)
if not target_polygon:
result.is_next_target = true
result.next_position = current_position
return result
# if the current and target positions are in the same polygon, return the target position
if current_polygon == target_polygon:
result.is_next_target = true
result.next_position = target_position
return result
# if the current and target positions are in the same polygon, return the target position
if current_polygon == target_polygon:
result.is_next_target = true
result.next_position = target_position
return result
# we will have to insert the start node into the graph and connect it to the nodes within the polygon,
# and remove it later on again
var start_node: NavigationNode = add_node(current_position.x, current_position.y, -1)
var nodes_in_current_polygon: Array[NavigationNode] = get_nodes_in_polygon(current_polygon)
polygon_nodes[current_polygon].append(start_node)
for node in nodes_in_current_polygon:
add_connection(start_node, node)
# we will have to insert the start node into the graph and connect it to the nodes within the polygon,
# and remove it later on again
var start_node: NavigationNode = add_node(current_position.x, current_position.y, -1)
var nodes_in_current_polygon: Array[NavigationNode] = get_nodes_in_polygon(current_polygon)
polygon_nodes[current_polygon].append(start_node)
for node in nodes_in_current_polygon:
add_connection(start_node, node)
# the target position is simple, just find the closest node in the polygon to the target position,
# the alternate algorithm for within a polygon above will take care of the rest
var end_node: NavigationNode = null
var min_distance: float = INF
# the target position is simple, just find the closest node in the polygon to the target position,
# the alternate algorithm for within a polygon above will take care of the rest
var end_node: NavigationNode = null
var min_distance: float = INF
var nodes_in_target_polygon: Array[NavigationNode] = get_nodes_in_polygon(target_polygon)
for node in nodes_in_target_polygon:
var distance: float = target_position.distance_to(node.position)
if distance < min_distance:
min_distance = distance
end_node = node
var nodes_in_target_polygon: Array[NavigationNode] = get_nodes_in_polygon(target_polygon)
for node in nodes_in_target_polygon:
var distance: float = target_position.distance_to(node.position)
if distance < min_distance:
min_distance = distance
end_node = node
var path: Array[NavigationNode] = dijkstra(start_node, end_node)
result.path = path
var path: Array[NavigationNode] = dijkstra(start_node, end_node)
result.path = path
# remove the start node again
remove_node(start_node)
# remove the start node again
remove_node(start_node)
# if a path is found, return the position of the next node in the path
if path.size() > 1:
# next node in the path
result.next_position = path[1].position
return result
elif path.size() == 1:
# directly reachable
result.is_next_target = true
result.next_position = target_position
else:
# no path found; return current position
result.is_next_target = true
result.next_position = current_position
# if a path is found, return the position of the next node in the path
if path.size() > 1:
# next node in the path
result.next_position = path[1].position
return result
elif path.size() == 1:
# directly reachable
result.is_next_target = true
result.next_position = target_position
else:
# no path found; return current position
result.is_next_target = true
result.next_position = current_position
return result
return result
func array_contains_node(arr: Array, node: NavigationNode) -> bool:
for item in arr:
if item == node:
return true
return false
for item in arr:
if item == node:
return true
return false
func dijkstra(start_node: NavigationNode, end_node: NavigationNode) -> Array[NavigationNode]:
var open_set: Array[NavigationNode] = []
var closed_set: Array[NavigationNode] = []
var distances: Dictionary = {}
var previous_nodes: Dictionary = {}
var open_set: Array[NavigationNode] = []
var closed_set: Array[NavigationNode] = []
var distances: Dictionary = {}
var previous_nodes: Dictionary = {}
distances[start_node] = 0
open_set.append(start_node)
distances[start_node] = 0
open_set.append(start_node)
while open_set.size() > 0:
# Find the node with the smallest tentative distance
var current_node: NavigationNode = open_set[0]
var current_distance: float = distances[current_node]
for node in open_set:
if distances[node] < current_distance:
current_node = node
current_distance = distances[node]
while open_set.size() > 0:
var current_node: NavigationNode = open_set[0]
var current_distance: float = distances[current_node]
for node in open_set:
if distances[node] < current_distance:
current_node = node
current_distance = distances[node]
# If the end node is reached, reconstruct the path
if current_node == end_node:
var path: Array[NavigationNode] = []
var node: NavigationNode = end_node
while node != null:
path.insert(0, node)
node = previous_nodes.get(node, null)
return path
# if the end node is reached, reconstruct path
if current_node == end_node:
var path: Array[NavigationNode] = []
var node: NavigationNode = end_node
while node != null:
path.insert(0, node)
node = previous_nodes.get(node, null)
return path
open_set.erase(current_node)
closed_set.append(current_node)
open_set.erase(current_node)
closed_set.append(current_node)
# Examine neighbors of the current node
var neighbors = get_connections(current_node)
for neighbor in neighbors:
if array_contains_node(closed_set, neighbor):
continue
# neighbors of the current node
var neighbors = get_connections(current_node)
for neighbor in neighbors:
if array_contains_node(closed_set, neighbor):
continue
var tentative_distance: float = distances[current_node] + current_node.position.distance_to(neighbor.position)
var tentative_distance: float = distances[current_node] + current_node.position.distance_to(neighbor.position)
if not array_contains_node(open_set, neighbor) or tentative_distance < distances.get(neighbor, INF):
distances[neighbor] = tentative_distance
previous_nodes[neighbor] = current_node
if not array_contains_node(open_set, neighbor):
open_set.append(neighbor)
if not array_contains_node(open_set, neighbor) or tentative_distance < distances.get(neighbor, INF):
distances[neighbor] = tentative_distance
previous_nodes[neighbor] = current_node
if not array_contains_node(open_set, neighbor):
open_set.append(neighbor)
# No path found; return an empty array
return []
# no path found
return []

View File

@ -14,10 +14,10 @@ script = ExtResource("1_tis1c")
navigation_polygon = [NodePath("NavPolygon2D_1"), NodePath("SmallPolygon2D"), NodePath("NavPolygon2D_2")]
[node name="NavPolygon2D_2" type="Polygon2D" parent="."]
polygon = PackedVector2Array(474, 298, 379, 295, 381, 261, 479, 98, 422, 71, 336, 203, 334, 47, 255, 43, 49, 42, 48, 119, 55, 247, 109, 244, 112, 126, 253, 119, 249, 176, 153, 177, 153, 240, 253, 236, 289, 322, 69, 300, 74, 390, 475, 400, 484, 463, 104, 458, 108, 630, 222, 633, 222, 542, 476, 569, 748, 642, 984, 642, 979, 580, 911, 563, 910, 495, 831, 495, 847, 563, 701, 493, 656, 402, 926, 414, 995, 516, 1110, 517, 985, 319, 774, 292, 765, 244, 884, 266, 896, 206, 764, 167, 675, 226, 703, 295, 652, 295, 554, 179, 805, 106, 935, 128, 998, 249, 1105, 257, 934, 19, 616, 61, 476, 167)
visible = false
polygon = PackedVector2Array(474, 298, 379, 295, 381, 261, 479, 98, 422, 71, 336, 203, 334, 47, 255, 43, 49, 42, 48, 119, 55, 247, 109, 244, 134, 127, 253, 119, 249, 176, 153, 177, 153, 240, 253, 236, 289, 322, 69, 300, 74, 390, 475, 400, 619, 511, 222, 449, 108, 630, 222, 633, 297, 544, 476, 569, 748, 642, 984, 642, 979, 580, 911, 563, 910, 495, 831, 495, 847, 563, 701, 493, 656, 402, 926, 414, 995, 516, 1110, 517, 985, 319, 774, 292, 765, 244, 884, 266, 896, 206, 764, 167, 675, 226, 703, 295, 652, 295, 600, 200, 805, 106, 935, 128, 998, 249, 1105, 257, 934, 19, 616, 61, 476, 167)
[node name="NavPolygon2D_1" type="Polygon2D" parent="."]
visible = false
polygon = PackedVector2Array(164, 56, 379, 23, 603, 53, 684, 152, 759, 255, 572, 293, 598, 166, 422, 106, 344, 158, 509, 312, 438, 454, 489, 504, 610, 342, 734, 323, 834, 199, 786, 85, 958, 43, 1117, 48, 1109, 194, 1137, 565, 916, 550, 887, 386, 965, 359, 962, 473, 1000, 494, 1002, 204, 928, 184, 916, 304, 837, 382, 835, 541, 752, 563, 732, 421, 627, 450, 592, 618, 335, 540, 295, 412, 361, 311, 190, 169, 194, 329, 278, 510, 132, 589, 133, 459, 77, 311, 48, 130)
[node name="SmallPolygon2D" type="Polygon2D" parent="."]

View File

@ -9,33 +9,33 @@ var seleted_polygon: int = 0
func _ready() -> void:
for polygon in navigation_polygon:
polygon.hide()
for polygon in navigation_polygon:
polygon.hide()
update_polygon()
update_polygon()
func update_polygon():
var polygons: Array[PackedVector2Array] = Geometry2D.decompose_polygon_in_convex(navigation_polygon[seleted_polygon].polygon)
graph.erase_and_create_nodes_from_polygons(polygons)
var polygons: Array[PackedVector2Array] = Geometry2D.decompose_polygon_in_convex(navigation_polygon[seleted_polygon].polygon)
graph.erase_and_create_nodes_from_polygons(polygons)
func _process(delta: float) -> void:
# check for left and right arrow keys to change the selected polygon (0, len(navigation_polygon)-1)
if Input.is_action_just_pressed("ui_right"):
seleted_polygon += 1
if seleted_polygon >= len(navigation_polygon):
seleted_polygon = 0
update_polygon()
# check for left and right arrow keys to change the selected polygon (0, len(navigation_polygon)-1)
if Input.is_action_just_pressed("ui_right"):
seleted_polygon += 1
if seleted_polygon >= len(navigation_polygon):
seleted_polygon = 0
update_polygon()
if Input.is_action_just_pressed("ui_left"):
seleted_polygon -= 1
if seleted_polygon < 0:
seleted_polygon = len(navigation_polygon) - 1
update_polygon()
if Input.is_action_just_pressed("ui_left"):
seleted_polygon -= 1
if seleted_polygon < 0:
seleted_polygon = len(navigation_polygon) - 1
update_polygon()
if Input.is_action_just_pressed("draw_toggle_polygon"):
graph.draw_polygons = !graph.draw_polygons
if Input.is_action_just_pressed("draw_toggle_nodes") or Input.is_action_just_pressed("draw_toggle_edges"):
graph.draw_nodes = !graph.draw_nodes
graph.draw_edges = !graph.draw_edges
if Input.is_action_just_pressed("draw_toggle_polygon"):
graph.draw_polygons = !graph.draw_polygons
if Input.is_action_just_pressed("draw_toggle_nodes") or Input.is_action_just_pressed("draw_toggle_edges"):
graph.draw_nodes = !graph.draw_nodes
graph.draw_edges = !graph.draw_edges

File diff suppressed because one or more lines are too long

View File

@ -12,187 +12,191 @@ var last_dice_result: int = 0
var global_transform_size: Vector2 = Vector2(0, 0)
#
const DIRECTIONS: Dictionary = {
"up": Vector2(0, -1),
"down": Vector2(0, 1),
"left": Vector2(-1, 0),
"right": Vector2(1, 0)
}
"up": Vector2(0, -1),
"down": Vector2(0, 1),
"left": Vector2(-1, 0),
"right": Vector2(1, 0)
}
var node_positions: Dictionary = {}
func _ready() -> void:
global_transform_size = Vector2(16, 16) * data_layer.transform.get_scale()
global_transform_size = Vector2(16, 16) * data_layer.transform.get_scale()
build_graph()
build_graph()
# place the player at the first node
var current_player_position: Vector2 = node_positions.keys()[0]
player.global_position = local_to_world(current_player_position)
# place the player at the first node
var current_player_position: Vector2 = node_positions.keys()[0]
player.global_position = local_to_world(current_player_position)
# bring player to top
player.z_index = 1
# bring player to top
player.z_index = 1
roll_dice()
roll_dice()
player.finished_movement.connect(roll_dice)
player.finished_movement.connect(roll_dice)
func _physics_process(delta: float) -> void:
if Input.is_action_just_pressed("draw_toggle_nodes") or Input.is_action_just_pressed("draw_toggle_edges"):
navigation_graph.draw_nodes = !navigation_graph.draw_nodes
navigation_graph.draw_edges = !navigation_graph.draw_edges
navigation_graph.queue_redraw()
if Input.is_action_just_pressed("draw_toggle_nodes") or Input.is_action_just_pressed("draw_toggle_edges"):
navigation_graph.draw_nodes = !navigation_graph.draw_nodes
navigation_graph.draw_edges = !navigation_graph.draw_edges
navigation_graph.queue_redraw()
var dice_controls_min: int = 2
var dice_controls_max: int = 8
func roll_dice() -> void:
last_dice_result = randi() % (dice_controls_max - dice_controls_min + 1) + dice_controls_min
print("Dice result: ", last_dice_result)
$ResultLabel.text = "Rolled a " + str(last_dice_result)
last_dice_result = randi() % (dice_controls_max - dice_controls_min + 1) + dice_controls_min
print("Dice result: ", last_dice_result)
$ResultLabel.text = "Rolled a " + str(last_dice_result)
# Find possible movement options
var movement_options: Array[MPNavigationNode] = find_nodes_with_distance(world_to_local(player.global_position), last_dice_result)
# visualize by spawning an indicator at each possible node, with a click event to move the player
for node in movement_options:
# res://scenes/mario-party/TileIndicator.tscn
var indicator_scene: Node2D = preload("res://scenes/mario-party/TileIndicator.tscn").instantiate()
indicator_scene.scale = Vector2(2, 2)
var area_node: Area2D = indicator_scene.get_node("area")
area_node.set_data(node.position, "player_movement")
area_node.indicator_clicked.connect(_on_indicator_clicked)
indicator_scene.global_position = local_to_world(node.get_position())
add_child(indicator_scene)
# Find possible movement options
var movement_options: Array[MPNavigationNode] = find_nodes_with_distance(world_to_local(player.global_position), last_dice_result)
# visualize by spawning an indicator at each possible node, with a click event to move the player
for node in movement_options:
# res://scenes/mario-party/TileIndicator.tscn
var indicator_scene: Node2D = preload("res://scenes/mario-party/TileIndicator.tscn").instantiate()
indicator_scene.scale = Vector2(2, 2)
var area_node: Area2D = indicator_scene.get_node("area")
area_node.set_data(node.position, "player_movement")
area_node.indicator_clicked.connect(_on_indicator_clicked)
indicator_scene.global_position = local_to_world(node.get_position())
add_child(indicator_scene)
# await get_tree().create_timer(1.0).timeout
# var picked = movement_options[randi() % (len(movement_options))]
# _on_indicator_clicked(picked.position, "player_movement")
func _on_indicator_clicked(pos: Vector2, type: String) -> void:
print("Indicator clicked: ", type, " ", pos)
if type == "player_movement":
player.move_to(pos)
# delete all indicators
for child in get_children():
if child is Node2D:
var area_node: Area2D = child.get_node("area")
if area_node != null:
if area_node.get_type() == "player_movement":
child.queue_free()
print("Indicator clicked: ", type, " ", pos)
if type == "player_movement":
player.move_to(pos)
# delete all indicators
for child in get_children():
if child is Node2D:
var area_node: Area2D = child.get_node("area")
if area_node != null:
if area_node.get_type() == "player_movement":
child.queue_free()
func local_to_world(location: Vector2) -> Vector2:
return location * global_transform_size + global_transform_size / 2
return location * global_transform_size + global_transform_size / 2
func world_to_local(location: Vector2) -> Vector2:
return (location - global_transform_size / 2) / global_transform_size
return (location - global_transform_size / 2) / global_transform_size
func build_graph() -> void:
print("Identifying nodes")
# Step 1: Place nodes at positions where is_tile is true
for position in data_layer.get_used_cells():
var tile_data: TileData = data_layer.get_cell_tile_data(position)
var is_tile: bool = tile_data.get_custom_data("is_tile")
if is_tile:
var node: MPNavigationNode = navigation_graph.add_node(position.x, position.y)
node_positions[position] = node
var indicator_scene: Node2D = preload("res://scenes/mario-party/TileIndicator.tscn").instantiate()
var area_node: Area2D = indicator_scene.get_node("area")
area_node.set_display_type("node")
indicator_scene.global_position = local_to_world(position)
add_child(indicator_scene)
print("Identifying nodes")
# Step 1: Place nodes at positions where is_tile is true
for position in data_layer.get_used_cells():
var tile_data: TileData = data_layer.get_cell_tile_data(position)
var is_tile: bool = tile_data.get_custom_data("is_tile")
if is_tile:
var node: MPNavigationNode = navigation_graph.add_node(position.x, position.y)
node_positions[position] = node
var indicator_scene: Node2D = preload("res://scenes/mario-party/TileIndicator.tscn").instantiate()
var area_node: Area2D = indicator_scene.get_node("area")
area_node.set_display_type("node")
indicator_scene.global_position = local_to_world(position)
add_child(indicator_scene)
# Step 2: Connect nodes using flood-fill based on walkable tiles
print("Connecting nodes")
for position in node_positions.keys():
connect_node(position)
# Step 2: Connect nodes using flood-fill based on walkable tiles
print("Connecting nodes")
for position in node_positions.keys():
connect_node(position)
func connect_node(start_position: Vector2) -> void:
var start_node = node_positions.get(Vector2i(start_position))
var visited: Dictionary = {}
visited[start_position] = true
# print("Connecting node at ", start_position)
var start_node = node_positions.get(Vector2i(start_position))
var visited: Dictionary = {}
visited[start_position] = true
# print("Connecting node at ", start_position)
# For each direction, perform flood-fill
for dir_name in DIRECTIONS.keys():
var direction = DIRECTIONS[dir_name]
var next_position = start_position + direction
# For each direction, perform flood-fill
for dir_name in DIRECTIONS.keys():
var direction = DIRECTIONS[dir_name]
var next_position = start_position + direction
# print("Checking direction ", dir_name, " from ", start_position, " to ", next_position)
# print("Checking direction ", dir_name, " from ", start_position, " to ", next_position)
# Ensure the first tile respects the direction
if not is_valid_direction(next_position, dir_name):
continue
# print("Flood-fill in direction ", dir_name, " from ", next_position)
# Ensure the first tile respects the direction
if not is_valid_direction(next_position, dir_name):
continue
# print("Flood-fill in direction ", dir_name, " from ", next_position)
# Perform flood-fill from the valid tile
var connected_nodes: Array = flood_fill(next_position, visited)
# Perform flood-fill from the valid tile
var connected_nodes: Array = flood_fill(next_position, visited)
# Add connections between the start node and found nodes
for target_position in connected_nodes:
if target_position != start_position:
if node_positions.has(Vector2i(target_position)):
var target_node = node_positions.get(Vector2i(target_position))
navigation_graph.add_connection(start_node, target_node)
print(start_position, " --> ", target_position)
# Add connections between the start node and found nodes
for target_position in connected_nodes:
if target_position != start_position:
if node_positions.has(Vector2i(target_position)):
var target_node = node_positions.get(Vector2i(target_position))
navigation_graph.add_connection(start_node, target_node)
print(start_position, " --> ", target_position)
func flood_fill(start_position: Vector2, visited: Dictionary) -> Array:
var stack: Array[Vector2] = [start_position]
var connected_nodes: Array[Vector2] = []
var stack: Array[Vector2] = [start_position]
var connected_nodes: Array[Vector2] = []
while stack.size() > 0:
var current_position = stack.pop_back()
# print(" - Visiting ", current_position)
while stack.size() > 0:
var current_position = stack.pop_back()
# print(" - Visiting ", current_position)
# Skip if already visited
if visited.has(current_position):
continue
# Skip if already visited
if visited.has(current_position):
continue
visited[current_position] = true
visited[current_position] = true
# Skip if not walkable
var tile_data: TileData = data_layer.get_cell_tile_data(current_position)
if tile_data == null:
continue
# Skip if not walkable
var tile_data: TileData = data_layer.get_cell_tile_data(current_position)
if tile_data == null:
continue
var is_walkable: bool = tile_data.get_custom_data("is_walkable")
var is_tile: bool = tile_data.get_custom_data("is_tile")
if (not is_walkable) and (not is_tile):
continue
var is_walkable: bool = tile_data.get_custom_data("is_walkable")
var is_tile: bool = tile_data.get_custom_data("is_tile")
if (not is_walkable) and (not is_tile):
continue
# If this position is a node, add it to the result
if is_tile:
# print(" - Found node tile at ", current_position)
connected_nodes.append(current_position)
# If this position is a node, add it to the result
if is_tile:
# print(" - Found node tile at ", current_position)
connected_nodes.append(current_position)
# Add neighboring tiles to the stack if they respect the direction
for dir_name in DIRECTIONS.keys():
var direction = DIRECTIONS[dir_name]
var neighbor_position = current_position + direction
# Add neighboring tiles to the stack if they respect the direction
for dir_name in DIRECTIONS.keys():
var direction = DIRECTIONS[dir_name]
var neighbor_position = current_position + direction
if not visited.has(neighbor_position):
if is_valid_direction(current_position, dir_name):
stack.append(neighbor_position)
if not visited.has(neighbor_position):
if is_valid_direction(current_position, dir_name):
stack.append(neighbor_position)
return connected_nodes
return connected_nodes
func is_valid_direction(position: Vector2, required_dir: String) -> bool:
var tile_data: TileData = data_layer.get_cell_tile_data(position)
if tile_data == null:
return false
var tile_data: TileData = data_layer.get_cell_tile_data(position)
if tile_data == null:
return false
var is_walkable: bool = tile_data.get_custom_data("is_walkable")
var walk_dir: String = tile_data.get_custom_data("walk_dir")
if walk_dir == "":
walk_dir = "any"
var is_walkable: bool = tile_data.get_custom_data("is_walkable")
var walk_dir: String = tile_data.get_custom_data("walk_dir")
if walk_dir == "":
walk_dir = "any"
# print(" L ", position, " ", is_walkable, " ", walk_dir, " ", required_dir)
# print(" L ", position, " ", is_walkable, " ", walk_dir, " ", required_dir)
# Check if the tile is walkable and allows movement in the required direction
return is_walkable and (walk_dir == required_dir or walk_dir == "any")
# Check if the tile is walkable and allows movement in the required direction
return is_walkable and (walk_dir == required_dir or walk_dir == "any")
# the first function that evaluates the finished graph
@ -201,35 +205,35 @@ func is_valid_direction(position: Vector2, required_dir: String) -> bool:
# distance here is the amount of nodes to travel, not the actual distance in pixels
# must use the MPNavigationNode class and the navigation_graph.get_connections(node: MPNavigationNode) function
func find_nodes_with_distance(start_position: Vector2, distance: int) -> Array[MPNavigationNode]:
var start_node = node_positions.get(Vector2i(start_position))
if start_node == null:
print("Error: No node found at starting position ", start_position)
return []
var start_node = node_positions.get(Vector2i(start_position))
if start_node == null:
print("Error: No node found at starting position ", start_position)
return []
# Initialize BFS
var queue: Array = [[start_node, 0]] # Each element is [node, current_distance]
var visited: Dictionary = {start_node: true}
var result_nodes: Array[MPNavigationNode] = []
# Initialize BFS
var queue: Array = [[start_node, 0]] # Each element is [node, current_distance]
var visited: Dictionary = {start_node: true}
var result_nodes: Array[MPNavigationNode] = []
while queue.size() > 0:
var current = queue.pop_front()
var current_node: MPNavigationNode = current[0]
var current_distance: int = current[1]
while queue.size() > 0:
var current = queue.pop_front()
var current_node: MPNavigationNode = current[0]
var current_distance: int = current[1]
# If the target distance is reached, add the node to the result
if current_distance == distance:
result_nodes.append(current_node)
continue # Do not explore further from this node
# If the target distance is reached, add the node to the result
if current_distance == distance:
result_nodes.append(current_node)
continue # Do not explore further from this node
# If the current distance exceeds the target, stop exploring
if current_distance > distance:
break
# If the current distance exceeds the target, stop exploring
if current_distance > distance:
break
# Explore neighbors of the current node
var neighbors: Array[MPNavigationNode] = navigation_graph.get_connections(current_node)
for neighbor in neighbors:
if not visited.has(neighbor):
visited[neighbor] = true
queue.append([neighbor, current_distance + 1])
# Explore neighbors of the current node
var neighbors: Array[MPNavigationNode] = navigation_graph.get_connections(current_node)
for neighbor in neighbors:
if not visited.has(neighbor):
visited[neighbor] = true
queue.append([neighbor, current_distance + 1])
return result_nodes
return result_nodes

View File

@ -1,9 +1,17 @@
[gd_resource type="TileSet" load_steps=7 format=3 uid="uid://cm6wi7fjgfk56"]
[gd_resource type="TileSet" load_steps=9 format=3 uid="uid://cm6wi7fjgfk56"]
[ext_resource type="Texture2D" uid="uid://bif0n5c12bwrh" path="res://addons/sprout_lands_tilemap/assets/Tilesets/Water.png" id="1_j6mfj"]
[ext_resource type="Texture2D" uid="uid://cmb6k735nhxmy" path="res://scenes/mario-party/img/data_layer.png" id="1_pdyr2"]
[ext_resource type="Texture2D" uid="uid://bngfh7nslvij1" path="res://addons/sprout_lands_tilemap/assets/Tilesets/Grass.png" id="2_ev1xx"]
[ext_resource type="Texture2D" uid="uid://cvemyer4jq6we" path="res://addons/sprout_lands_tilemap/assets/Objects/Paths.png" id="3_eigvu"]
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_wcjgp"]
texture = ExtResource("1_j6mfj")
0:0/0 = 0
1:0/0 = 0
2:0/0 = 0
3:0/0 = 0
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_tp5r2"]
texture = ExtResource("1_pdyr2")
0:0/0 = 0
@ -120,3 +128,4 @@ custom_data_layer_2/type = 4
sources/1 = SubResource("TileSetAtlasSource_tp5r2")
sources/2 = SubResource("TileSetAtlasSource_m45pk")
sources/3 = SubResource("TileSetAtlasSource_xi3gi")
sources/0 = SubResource("TileSetAtlasSource_wcjgp")

View File

@ -2,7 +2,6 @@ extends Node2D
@onready var floor: NavigationRegion2D = $BaseNavigationRegion2D
@onready var road: NavigationRegion2D = $CheapPathNavigationRegion2D
# @onready var road_poly: Polygon2D = $CheapPathNavigationRegion2D/CheapPath
@onready var road_poly: Polygon2D = $BaseNavigationRegion2D/CheapPath
# Called when the node enters the scene tree for the first time.

File diff suppressed because one or more lines are too long

3
state/.gitignore vendored 100644
View File

@ -0,0 +1,3 @@
# Godot 4+ specific ignores
.godot/
/android/

1
state/icon.svg 100644
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>

After

Width:  |  Height:  |  Size: 994 B

View File

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://beggt5ndi6j4p"
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.svg"
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@ -0,0 +1,15 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="state"
config/features=PackedStringArray("4.3", "Forward Plus")
config/icon="res://icon.svg"

View File

@ -0,0 +1,18 @@
class_name State
extends Node
var state_machine: StateMachine
var character: CharacterBody2D
func _ready() -> void:
pass
func state_enter() -> void:
pass
func state_process(delta: float) -> void:
pass
func state_exit() -> void:
pass

View File

@ -0,0 +1,19 @@
class_name StateMachine
extends Node
var current_state: State:
set(value):
if current_state:
current_state.state_exit()
current_state = value
current_state.state_enter()
func _ready() -> void:
for child in self.get_children():
if true:
print(child)
func _process(delta: float) -> void:
pass

View File

@ -0,0 +1,25 @@
[gd_scene load_steps=5 format=3 uid="uid://cgdvptekjbpgq"]
[ext_resource type="Texture2D" uid="uid://beggt5ndi6j4p" path="res://icon.svg" id="1_jgx5y"]
[ext_resource type="Script" path="res://scenes/state/StateMachine.gd" id="2_d1xqo"]
[ext_resource type="Script" path="res://scenes/state/state_idle.gd" id="3_r1btx"]
[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_tr1gq"]
radius = 60.0
height = 122.0
[node name="StateMachineWorld" type="Node2D"]
[node name="CharacterBody2D" type="CharacterBody2D" parent="."]
[node name="Sprite2D" type="Sprite2D" parent="CharacterBody2D"]
texture = ExtResource("1_jgx5y")
[node name="CollisionShape2D" type="CollisionShape2D" parent="CharacterBody2D"]
shape = SubResource("CapsuleShape2D_tr1gq")
[node name="StateMachine" type="Node" parent="CharacterBody2D"]
script = ExtResource("2_d1xqo")
[node name="idle" type="Node" parent="CharacterBody2D/StateMachine"]
script = ExtResource("3_r1btx")

View File

@ -0,0 +1,10 @@
extends State
func state_enter():
print("idle enter")
func state_process(delta: float) -> void:
print("idle process")
func state_exit() -> void:
print("idle exit")