2024-11-19 11:51:36 +01:00
|
|
|
extends Node2D
|
|
|
|
|
|
|
|
@onready var ground_layer: TileMapLayer = $GroundLayer
|
|
|
|
@onready var path_layer: TileMapLayer = $PathLayer
|
2024-11-19 13:52:51 +01:00
|
|
|
@onready var data_layer: TileMapLayer = $DataLayer
|
2024-11-19 11:51:36 +01:00
|
|
|
@onready var navigation_graph: MPNavigationGraph = $NavigationGraph
|
2024-11-19 13:52:51 +01:00
|
|
|
#
|
|
|
|
@onready var player: CharacterBody2D = $Player
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
var node_positions: Dictionary = {}
|
2024-11-19 11:51:36 +01:00
|
|
|
|
|
|
|
|
|
|
|
func _ready() -> void:
|
2024-11-19 13:52:51 +01:00
|
|
|
global_transform_size = Vector2(16, 16) * data_layer.transform.get_scale()
|
|
|
|
|
2024-11-19 11:51:36 +01:00
|
|
|
build_graph()
|
|
|
|
|
2024-11-19 13:52:51 +01:00
|
|
|
# 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
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
# 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)
|
|
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
|
|
func local_to_world(location: Vector2) -> Vector2:
|
|
|
|
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
|
|
|
|
|
2024-11-19 11:51:36 +01:00
|
|
|
|
|
|
|
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:
|
2024-11-19 13:52:51 +01:00
|
|
|
var node: MPNavigationNode = navigation_graph.add_node(position.x, position.y)
|
2024-11-19 11:51:36 +01:00
|
|
|
node_positions[position] = node
|
2024-11-19 13:52:51 +01:00
|
|
|
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)
|
2024-11-19 11:51:36 +01:00
|
|
|
|
|
|
|
# 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
|
2024-11-19 13:52:51 +01:00
|
|
|
# print("Connecting node at ", start_position)
|
2024-11-19 11:51:36 +01:00
|
|
|
|
|
|
|
# For each direction, perform flood-fill
|
|
|
|
for dir_name in DIRECTIONS.keys():
|
|
|
|
var direction = DIRECTIONS[dir_name]
|
|
|
|
var next_position = start_position + direction
|
|
|
|
|
2024-11-19 13:52:51 +01:00
|
|
|
# print("Checking direction ", dir_name, " from ", start_position, " to ", next_position)
|
2024-11-19 11:51:36 +01:00
|
|
|
|
|
|
|
# Ensure the first tile respects the direction
|
|
|
|
if not is_valid_direction(next_position, dir_name):
|
|
|
|
continue
|
2024-11-19 13:52:51 +01:00
|
|
|
# print("Flood-fill in direction ", dir_name, " from ", next_position)
|
2024-11-19 11:51:36 +01:00
|
|
|
|
|
|
|
# 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)
|
|
|
|
|
|
|
|
|
|
|
|
func flood_fill(start_position: Vector2, visited: Dictionary) -> Array:
|
|
|
|
var stack: Array[Vector2] = [start_position]
|
|
|
|
var connected_nodes: Array[Vector2] = []
|
|
|
|
|
|
|
|
while stack.size() > 0:
|
|
|
|
var current_position = stack.pop_back()
|
2024-11-19 13:52:51 +01:00
|
|
|
# print(" - Visiting ", current_position)
|
2024-11-19 11:51:36 +01:00
|
|
|
|
|
|
|
# Skip if already visited
|
|
|
|
if visited.has(current_position):
|
|
|
|
continue
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
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:
|
2024-11-19 13:52:51 +01:00
|
|
|
# print(" - Found node tile at ", current_position)
|
2024-11-19 11:51:36 +01:00
|
|
|
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
|
|
|
|
|
|
|
|
if not visited.has(neighbor_position):
|
|
|
|
if is_valid_direction(current_position, dir_name):
|
|
|
|
stack.append(neighbor_position)
|
|
|
|
|
|
|
|
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 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"
|
|
|
|
|
2024-11-19 13:52:51 +01:00
|
|
|
# print(" L ", position, " ", is_walkable, " ", walk_dir, " ", required_dir)
|
2024-11-19 11:51:36 +01:00
|
|
|
|
|
|
|
# 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")
|
2024-11-19 13:52:51 +01:00
|
|
|
|
|
|
|
|
|
|
|
# the first function that evaluates the finished graph
|
|
|
|
# it is given a starting position and a distance in integers, which represent the amount of nodes to travel to reach the target node(s)
|
|
|
|
# it will find any nodes that are the given distance away from the starting node and return them in an array
|
|
|
|
# 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 []
|
|
|
|
|
|
|
|
# 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]
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
|
|
|
# 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
|