Loads of stuff
This commit is contained in:
parent
f3af522683
commit
66ce3ff503
413 changed files with 14802 additions and 0 deletions
715
Utilities/MapGeneration/MapGeneration.gd
Normal file
715
Utilities/MapGeneration/MapGeneration.gd
Normal file
|
|
@ -0,0 +1,715 @@
|
|||
class_name MapGenerationClass
|
||||
extends Node
|
||||
|
||||
# Map settings
|
||||
@export var map_width: int = 500
|
||||
@export var map_height: int = 500
|
||||
|
||||
# Path generation settings
|
||||
@export var num_horizontal_paths: int = 3
|
||||
@export var num_vertical_paths: int = 2
|
||||
@export var path_wander_strength: float = 0.3 # How much paths can deviate (0-1)
|
||||
@export var path_smoothing_passes: int = 2 # Number of smoothing iterations
|
||||
@export var branch_probability: float = 0.6 # Chance of creating branches (0-1)
|
||||
@export var max_branches_per_path: int = 3 # Maximum branches per main path
|
||||
@export var dead_end_probability: float = 0.3 # Chance for branches to be dead ends (0-1)
|
||||
@export var min_branch_length_ratio: float = 0.2 # Minimum branch length as ratio of main path
|
||||
@export var max_branch_length_ratio: float = 0.4 # Maximum branch length as ratio of main path
|
||||
|
||||
# Water generation settings
|
||||
@export var num_water_bodies: int = 5 # Number of water bodies to generate
|
||||
@export var min_water_size: int = 15 # Minimum radius of water bodies
|
||||
@export var max_water_size: int = 34 # Maximum radius of water bodies
|
||||
@export var water_placement_attempts: int = 50 # Max attempts to place each water body
|
||||
|
||||
# Image export settings
|
||||
@export var tile_size: int = 8 # Size of each tile in pixels (square)
|
||||
@export var export_image: bool = true # Whether to export image
|
||||
@export var image_filename: String = "generated_map.png" # Output filename
|
||||
|
||||
var noise: FastNoiseLite
|
||||
var map: Array = []
|
||||
var map_data: Array = []
|
||||
var path_data: Array = [] # Separate array to track paths
|
||||
var water_data: Array = [] # Separate array to track water bodies
|
||||
|
||||
# New data structures for enhanced path generation
|
||||
var path_segments: Array = [] # Store all path segments for better branching
|
||||
var path_id_counter: int = 0
|
||||
|
||||
func _ready():
|
||||
pass
|
||||
generate_map()
|
||||
generate_paths()
|
||||
generate_water_bodies()
|
||||
generate_final_map_data()
|
||||
#print_visual_map_to_console()
|
||||
|
||||
#if export_image:
|
||||
# export_map_as_image()
|
||||
|
||||
func generate_final_map_data():
|
||||
for y in range(map_height):
|
||||
for x in range(map_width):
|
||||
MapData.setup_cell_data(y, x, map_data[y][x], is_path_at(x, y), is_water_at(x, y))
|
||||
|
||||
func generate_map():
|
||||
# Initialize noise
|
||||
noise = FastNoiseLite.new()
|
||||
noise.seed = randi()
|
||||
noise.frequency = 0.01
|
||||
noise.noise_type = FastNoiseLite.TYPE_SIMPLEX_SMOOTH
|
||||
|
||||
# Fractal settings for organic paths
|
||||
noise.fractal_type = FastNoiseLite.FRACTAL_RIDGED
|
||||
noise.fractal_octaves = 2
|
||||
noise.fractal_lacunarity = 4.0
|
||||
noise.fractal_gain = 0.2
|
||||
noise.fractal_weighted_strength = 1
|
||||
noise.fractal_ping_pong_strength = 2.0
|
||||
|
||||
# Domain warp for natural winding
|
||||
noise.domain_warp_enabled = true
|
||||
noise.domain_warp_type = FastNoiseLite.DOMAIN_WARP_BASIC_GRID
|
||||
noise.domain_warp_amplitude = 20
|
||||
noise.domain_warp_frequency = 0.01
|
||||
|
||||
# Generate map data
|
||||
map_data.resize(map_height)
|
||||
path_data.resize(map_height)
|
||||
water_data.resize(map_height)
|
||||
|
||||
for y in range(map_height):
|
||||
map_data[y] = []
|
||||
path_data[y] = []
|
||||
water_data[y] = []
|
||||
map_data[y].resize(map_width)
|
||||
path_data[y].resize(map_width)
|
||||
water_data[y].resize(map_width)
|
||||
for x in range(map_width):
|
||||
# Get noise value (-1 to 1) and normalize to (0 to 1)
|
||||
var noise_value = noise.get_noise_2d(x, y)
|
||||
var normalized_value = (noise_value + 1.0) / 2.0
|
||||
map_data[y][x] = round(normalized_value * 10.0) / 10.0
|
||||
path_data[y][x] = false # Initialize path data
|
||||
water_data[y][x] = false # Initialize water data
|
||||
|
||||
|
||||
func generate_paths():
|
||||
print("Generating natural paths with branching...")
|
||||
path_segments.clear()
|
||||
path_id_counter = 0
|
||||
|
||||
# Generate main organic paths
|
||||
for i in range(num_horizontal_paths):
|
||||
generate_main_path(i, "horizontal")
|
||||
|
||||
for i in range(num_vertical_paths):
|
||||
generate_main_path(i, "vertical")
|
||||
|
||||
# Generate branches for existing paths
|
||||
generate_path_branches()
|
||||
|
||||
# Smooth and connect paths
|
||||
smooth_paths()
|
||||
|
||||
func generate_main_path(path_index: int, direction: String):
|
||||
var path_points = []
|
||||
|
||||
# Set up start and end points based on direction
|
||||
var start_point: Vector2
|
||||
var end_point: Vector2
|
||||
|
||||
if direction == "horizontal":
|
||||
# Horizontal paths go from left edge to right edge
|
||||
var y_pos = (map_height / (num_horizontal_paths + 1)) * (path_index + 1)
|
||||
y_pos += randi_range(-map_height / 8, map_height / 8) # Add some vertical variation
|
||||
start_point = Vector2(0, clamp(y_pos, 5, map_height - 5))
|
||||
end_point = Vector2(map_width - 1, clamp(y_pos + randi_range(-map_height / 4, map_height / 4), 5, map_height - 5))
|
||||
else:
|
||||
# Vertical paths go from top edge to bottom edge
|
||||
var x_pos = (map_width / (num_vertical_paths + 1)) * (path_index + 1)
|
||||
x_pos += randi_range(-map_width / 8, map_width / 8) # Add some horizontal variation
|
||||
start_point = Vector2(clamp(x_pos, 5, map_width - 5), 0)
|
||||
end_point = Vector2(clamp(x_pos + randi_range(-map_width / 4, map_width / 4), 5, map_width - 5), map_height - 1)
|
||||
|
||||
# Generate control points for smooth curves
|
||||
var control_points = [start_point]
|
||||
|
||||
# Add several intermediate control points for natural curves
|
||||
var num_control_points = randi_range(3, 6)
|
||||
for i in range(1, num_control_points):
|
||||
var t = float(i) / float(num_control_points)
|
||||
|
||||
# Interpolate between start and end
|
||||
var base_point = start_point.lerp(end_point, t)
|
||||
|
||||
# Add random offset for natural wandering
|
||||
var max_offset = min(map_width, map_height) * 0.3 * path_wander_strength
|
||||
var offset = Vector2(
|
||||
randf_range(-max_offset, max_offset),
|
||||
randf_range(-max_offset, max_offset)
|
||||
)
|
||||
|
||||
var control_point = base_point + offset
|
||||
control_point.x = clamp(control_point.x, 0, map_width - 1)
|
||||
control_point.y = clamp(control_point.y, 0, map_height - 1)
|
||||
|
||||
control_points.append(control_point)
|
||||
|
||||
control_points.append(end_point)
|
||||
|
||||
# Create main path and store segment info
|
||||
var path_id = path_id_counter
|
||||
path_id_counter += 1
|
||||
|
||||
var segment_info = {
|
||||
"id": path_id,
|
||||
"type": "main",
|
||||
"direction": direction,
|
||||
"control_points": control_points,
|
||||
"parent_id": - 1,
|
||||
"branch_points": [] # Points where branches can spawn
|
||||
}
|
||||
|
||||
path_segments.append(segment_info)
|
||||
create_spline_path(control_points, path_id, true) # true = is_main_path
|
||||
|
||||
func generate_path_branches():
|
||||
print("Generating path branches...")
|
||||
|
||||
# Create branches for each main path
|
||||
for segment in path_segments:
|
||||
if segment.type == "main":
|
||||
generate_branches_for_segment(segment)
|
||||
|
||||
func generate_branches_for_segment(main_segment: Dictionary):
|
||||
var num_branches = 0
|
||||
var max_attempts = 5
|
||||
|
||||
# Try to create branches up to the maximum allowed
|
||||
while num_branches < max_branches_per_path and max_attempts > 0:
|
||||
max_attempts -= 1
|
||||
|
||||
if randf() < branch_probability:
|
||||
create_branch_from_segment(main_segment, num_branches)
|
||||
num_branches += 1
|
||||
|
||||
func create_branch_from_segment(parent_segment: Dictionary, branch_index: int):
|
||||
# Pick a random point along the parent path to branch from
|
||||
var branch_start_t = randf_range(0.2, 0.8) # Don't branch too close to ends
|
||||
var branch_start_point = evaluate_catmull_rom_spline(parent_segment.control_points, branch_start_t)
|
||||
|
||||
# Determine branch direction based on parent direction and some randomness
|
||||
var branch_direction = get_branch_direction(parent_segment.direction, branch_start_point)
|
||||
|
||||
# Calculate branch length
|
||||
var main_path_length = estimate_path_length(parent_segment.control_points)
|
||||
var branch_length_ratio = randf_range(min_branch_length_ratio, max_branch_length_ratio)
|
||||
var target_branch_length = main_path_length * branch_length_ratio
|
||||
|
||||
# Determine if this should be a dead end
|
||||
var is_dead_end = randf() < dead_end_probability
|
||||
|
||||
# Generate branch end point
|
||||
var branch_end_point: Vector2
|
||||
if is_dead_end:
|
||||
# Dead end - create a point that doesn't reach map edge
|
||||
branch_end_point = create_dead_end_point(branch_start_point, branch_direction, target_branch_length)
|
||||
else:
|
||||
# Through branch - try to reach map edge or connect to another path
|
||||
branch_end_point = create_through_branch_point(branch_start_point, branch_direction, target_branch_length)
|
||||
|
||||
# Generate branch control points
|
||||
var branch_control_points = generate_branch_control_points(branch_start_point, branch_end_point, branch_direction)
|
||||
|
||||
# Create the branch
|
||||
var branch_id = path_id_counter
|
||||
path_id_counter += 1
|
||||
|
||||
var branch_segment = {
|
||||
"id": branch_id,
|
||||
"type": "branch",
|
||||
"direction": branch_direction,
|
||||
"control_points": branch_control_points,
|
||||
"parent_id": parent_segment.id,
|
||||
"is_dead_end": is_dead_end
|
||||
}
|
||||
|
||||
path_segments.append(branch_segment)
|
||||
create_spline_path(branch_control_points, branch_id, false) # false = not main path
|
||||
|
||||
func get_branch_direction(parent_direction: String, branch_point: Vector2) -> String:
|
||||
# Determine branch direction based on parent and position
|
||||
var directions = []
|
||||
|
||||
if parent_direction == "horizontal":
|
||||
directions = ["north", "south"]
|
||||
# Add some bias based on position
|
||||
if branch_point.y < map_height * 0.3:
|
||||
directions.append("south") # Bias toward south if in upper area
|
||||
elif branch_point.y > map_height * 0.7:
|
||||
directions.append("north") # Bias toward north if in lower area
|
||||
else: # vertical
|
||||
directions = ["east", "west"]
|
||||
# Add some bias based on position
|
||||
if branch_point.x < map_width * 0.3:
|
||||
directions.append("east") # Bias toward east if in left area
|
||||
elif branch_point.x > map_width * 0.7:
|
||||
directions.append("west") # Bias toward west if in right area
|
||||
|
||||
return directions[randi() % directions.size()]
|
||||
|
||||
func create_dead_end_point(start_point: Vector2, direction: String, target_length: float) -> Vector2:
|
||||
# Create a point for a dead end that doesn't reach the map edge
|
||||
var direction_vector = get_direction_vector(direction)
|
||||
var max_distance = target_length * randf_range(0.5, 0.9) # Dead ends are shorter
|
||||
|
||||
# Add some randomness to the direction
|
||||
var angle_variation = randf_range(-PI / 4, PI / 4) # ±45 degrees
|
||||
direction_vector = direction_vector.rotated(angle_variation)
|
||||
|
||||
var end_point = start_point + direction_vector * max_distance
|
||||
|
||||
# Ensure the end point stays within map bounds with some margin
|
||||
var margin = 20
|
||||
end_point.x = clamp(end_point.x, margin, map_width - margin)
|
||||
end_point.y = clamp(end_point.y, margin, map_height - margin)
|
||||
|
||||
return end_point
|
||||
|
||||
func create_through_branch_point(start_point: Vector2, direction: String, target_length: float) -> Vector2:
|
||||
# Create a point that tries to reach toward a map edge
|
||||
var direction_vector = get_direction_vector(direction)
|
||||
|
||||
# Add some wandering to make it more natural
|
||||
var angle_variation = randf_range(-PI / 6, PI / 6) # ±30 degrees
|
||||
direction_vector = direction_vector.rotated(angle_variation)
|
||||
|
||||
var end_point = start_point + direction_vector * target_length
|
||||
|
||||
# Try to reach toward the appropriate map edge
|
||||
match direction:
|
||||
"north":
|
||||
end_point.y = max(5, end_point.y)
|
||||
"south":
|
||||
end_point.y = min(map_height - 5, end_point.y)
|
||||
"east":
|
||||
end_point.x = min(map_width - 5, end_point.x)
|
||||
"west":
|
||||
end_point.x = max(5, end_point.x)
|
||||
|
||||
# Clamp to map bounds
|
||||
end_point.x = clamp(end_point.x, 5, map_width - 5)
|
||||
end_point.y = clamp(end_point.y, 5, map_height - 5)
|
||||
|
||||
return end_point
|
||||
|
||||
func get_direction_vector(direction: String) -> Vector2:
|
||||
match direction:
|
||||
"north":
|
||||
return Vector2(0, -1)
|
||||
"south":
|
||||
return Vector2(0, 1)
|
||||
"east":
|
||||
return Vector2(1, 0)
|
||||
"west":
|
||||
return Vector2(-1, 0)
|
||||
_:
|
||||
return Vector2(0, 1) # Default to south
|
||||
|
||||
func generate_branch_control_points(start_point: Vector2, end_point: Vector2, direction: String) -> Array:
|
||||
var control_points = [start_point]
|
||||
|
||||
# Add fewer control points for branches to make them simpler
|
||||
var num_control_points = randi_range(2, 4)
|
||||
|
||||
for i in range(1, num_control_points):
|
||||
var t = float(i) / float(num_control_points)
|
||||
var base_point = start_point.lerp(end_point, t)
|
||||
|
||||
# Less wandering for branches
|
||||
var max_offset = min(map_width, map_height) * 0.15 * path_wander_strength
|
||||
var offset = Vector2(
|
||||
randf_range(-max_offset, max_offset),
|
||||
randf_range(-max_offset, max_offset)
|
||||
)
|
||||
|
||||
var control_point = base_point + offset
|
||||
control_point.x = clamp(control_point.x, 0, map_width - 1)
|
||||
control_point.y = clamp(control_point.y, 0, map_height - 1)
|
||||
|
||||
control_points.append(control_point)
|
||||
|
||||
control_points.append(end_point)
|
||||
return control_points
|
||||
|
||||
func estimate_path_length(control_points: Array) -> float:
|
||||
# Rough estimate of path length for branch sizing
|
||||
var total_length = 0.0
|
||||
for i in range(control_points.size() - 1):
|
||||
total_length += control_points[i].distance_to(control_points[i + 1])
|
||||
return total_length
|
||||
|
||||
func create_spline_path(control_points: Array, path_id: int, is_main_path: bool = false):
|
||||
# Create a smooth path using Catmull-Rom spline interpolation
|
||||
var path_resolution = max(map_width, map_height) * 2 # High resolution for smooth curves
|
||||
|
||||
# Reduce resolution for branches to make them less dense
|
||||
if not is_main_path:
|
||||
path_resolution = int(path_resolution * 0.7)
|
||||
|
||||
for i in range(path_resolution):
|
||||
var t = float(i) / float(path_resolution - 1)
|
||||
var point = evaluate_catmull_rom_spline(control_points, t)
|
||||
|
||||
# Place path segments along the spline
|
||||
var x = int(round(point.x))
|
||||
var y = int(round(point.y))
|
||||
|
||||
if x >= 0 and x < map_width and y >= 0 and y < map_height:
|
||||
place_organic_path_segment(x, y, path_id, is_main_path)
|
||||
|
||||
func evaluate_catmull_rom_spline(points: Array, t: float) -> Vector2:
|
||||
var n = points.size()
|
||||
if n < 2:
|
||||
return points[0] if n > 0 else Vector2.ZERO
|
||||
|
||||
# Handle edge cases
|
||||
if t <= 0.0:
|
||||
return points[0]
|
||||
if t >= 1.0:
|
||||
return points[n - 1]
|
||||
|
||||
# Find the segment
|
||||
var segment_length = 1.0 / float(n - 1)
|
||||
var segment_index = int(t / segment_length)
|
||||
segment_index = clamp(segment_index, 0, n - 2)
|
||||
|
||||
# Local t within the segment
|
||||
var local_t = (t - segment_index * segment_length) / segment_length
|
||||
|
||||
# Get control points (with clamping for edge segments)
|
||||
var p0 = points[max(0, segment_index - 1)]
|
||||
var p1 = points[segment_index]
|
||||
var p2 = points[min(n - 1, segment_index + 1)]
|
||||
var p3 = points[min(n - 1, segment_index + 2)]
|
||||
|
||||
# Catmull-Rom spline interpolation
|
||||
var t2 = local_t * local_t
|
||||
var t3 = t2 * local_t
|
||||
|
||||
var result = Vector2.ZERO
|
||||
result += p0 * (-0.5 * t3 + t2 - 0.5 * local_t)
|
||||
result += p1 * (1.5 * t3 - 2.5 * t2 + 1.0)
|
||||
result += p2 * (-1.5 * t3 + 2.0 * t2 + 0.5 * local_t)
|
||||
result += p3 * (0.5 * t3 - 0.5 * t2)
|
||||
|
||||
return result
|
||||
|
||||
func place_organic_path_segment(x: int, y: int, path_id: int, is_main_path: bool = false):
|
||||
# Base path placement - always place the center tile
|
||||
path_data[y][x] = true
|
||||
|
||||
if is_main_path:
|
||||
# Main paths can be wider (2-3 tiles)
|
||||
var width_noise = FastNoiseLite.new()
|
||||
width_noise.seed = randi() + path_id * 5000
|
||||
width_noise.frequency = 0.1
|
||||
width_noise.noise_type = FastNoiseLite.TYPE_SIMPLEX_SMOOTH
|
||||
|
||||
# Add organic width variation for main paths only
|
||||
var width_value = width_noise.get_noise_2d(x, y)
|
||||
var base_width = 1 + int((width_value + 1.0) * 1.0) # Width 1-3 tiles for main paths
|
||||
var path_width = max(1, base_width)
|
||||
|
||||
# Place path tiles in organic pattern around the center for main paths
|
||||
for dy in range(-path_width, path_width + 1):
|
||||
for dx in range(-path_width, path_width + 1):
|
||||
var distance = sqrt(dx * dx + dy * dy)
|
||||
if distance <= path_width:
|
||||
var nx = x + dx
|
||||
var ny = y + dy
|
||||
|
||||
if nx >= 0 and nx < map_width and ny >= 0 and ny < map_height:
|
||||
# Use noise to create organic edges
|
||||
var edge_noise = width_noise.get_noise_2d(nx, ny)
|
||||
var edge_chance = 1.0 - (distance / float(path_width))
|
||||
edge_chance += edge_noise * 0.3
|
||||
|
||||
if edge_chance > 0.4:
|
||||
path_data[ny][nx] = true
|
||||
# For branches (is_main_path = false), only the center tile is placed above
|
||||
|
||||
func smooth_paths():
|
||||
# Apply smoothing passes to make paths more natural
|
||||
for pass_num in range(path_smoothing_passes):
|
||||
var new_path_data = path_data.duplicate(true)
|
||||
|
||||
for y in range(1, map_height - 1):
|
||||
for x in range(1, map_width - 1):
|
||||
if path_data[y][x]:
|
||||
# Check neighbors and potentially expand path
|
||||
var neighbor_count = 0
|
||||
for dy in range(-1, 2):
|
||||
for dx in range(-1, 2):
|
||||
if dx == 0 and dy == 0:
|
||||
continue
|
||||
if path_data[y + dy][x + dx]:
|
||||
neighbor_count += 1
|
||||
|
||||
# If isolated path point, try to connect it
|
||||
if neighbor_count < 2 and randf() > 0.5:
|
||||
# Find nearest path point and create connection
|
||||
for radius in range(1, 4):
|
||||
var connected = false
|
||||
for dy in range(-radius, radius + 1):
|
||||
for dx in range(-radius, radius + 1):
|
||||
var ny = y + dy
|
||||
var nx = x + dx
|
||||
if ny >= 0 and ny < map_height and nx >= 0 and nx < map_width:
|
||||
if path_data[ny][nx] and (abs(dx) + abs(dy)) <= radius:
|
||||
# Create connection
|
||||
var steps = max(abs(dx), abs(dy))
|
||||
for step in range(steps + 1):
|
||||
var interp_x = x + int(float(dx * step) / float(steps))
|
||||
var interp_y = y + int(float(dy * step) / float(steps))
|
||||
if interp_x >= 0 and interp_x < map_width and interp_y >= 0 and interp_y < map_height:
|
||||
new_path_data[interp_y][interp_x] = true
|
||||
connected = true
|
||||
break
|
||||
if connected:
|
||||
break
|
||||
if connected:
|
||||
break
|
||||
|
||||
path_data = new_path_data
|
||||
|
||||
func generate_water_bodies():
|
||||
print("Generating water bodies...")
|
||||
|
||||
for i in range(num_water_bodies):
|
||||
generate_single_water_body(i)
|
||||
|
||||
func generate_single_water_body(body_index: int):
|
||||
var attempts = 0
|
||||
var placed = false
|
||||
|
||||
while attempts < water_placement_attempts and not placed:
|
||||
# Random center position
|
||||
var center_x = randi_range(max_water_size, map_width - max_water_size)
|
||||
var center_y = randi_range(max_water_size, map_height - max_water_size)
|
||||
|
||||
# Random size within configured range
|
||||
var water_radius = randi_range(min_water_size, max_water_size)
|
||||
|
||||
# Check if this location is valid (no paths in the area)
|
||||
if is_valid_water_location(center_x, center_y, water_radius):
|
||||
place_water_body(center_x, center_y, water_radius, body_index)
|
||||
placed = true
|
||||
print("Water body ", body_index + 1, " placed at (", center_x, ",", center_y, ") with radius ", water_radius)
|
||||
|
||||
attempts += 1
|
||||
|
||||
if not placed:
|
||||
print("Could not place water body ", body_index + 1, " after ", water_placement_attempts, " attempts")
|
||||
|
||||
func is_valid_water_location(center_x: int, center_y: int, radius: int) -> bool:
|
||||
# Check if any paths would be intersected by this water body
|
||||
# Use a slightly larger radius for safety buffer
|
||||
var safety_buffer = 2
|
||||
var check_radius = radius + safety_buffer
|
||||
|
||||
for y in range(center_y - check_radius, center_y + check_radius + 1):
|
||||
for x in range(center_x - check_radius, center_x + check_radius + 1):
|
||||
if x >= 0 and x < map_width and y >= 0 and y < map_height:
|
||||
# Check if this position has a path
|
||||
if path_data[y][x]:
|
||||
var distance = sqrt(pow(x - center_x, 2) + pow(y - center_y, 2))
|
||||
if distance <= check_radius:
|
||||
return false
|
||||
|
||||
# Also check if there's already water here (prevent overlap)
|
||||
if water_data[y][x]:
|
||||
var distance = sqrt(pow(x - center_x, 2) + pow(y - center_y, 2))
|
||||
if distance <= check_radius:
|
||||
return false
|
||||
|
||||
return true
|
||||
|
||||
func place_water_body(center_x: int, center_y: int, radius: int, body_index: int):
|
||||
# Simple approach: start with basic circle, then use noise to make edges organic
|
||||
var water_noise = FastNoiseLite.new()
|
||||
water_noise.seed = randi() + body_index * 3000
|
||||
water_noise.frequency = 0.1
|
||||
water_noise.noise_type = FastNoiseLite.TYPE_SIMPLEX_SMOOTH
|
||||
|
||||
# Place water in roughly circular area with noise-modified edges
|
||||
for y in range(center_y - radius - 2, center_y + radius + 3):
|
||||
for x in range(center_x - radius - 2, center_x + radius + 3):
|
||||
if x >= 0 and x < map_width and y >= 0 and y < map_height:
|
||||
if path_data[y][x]: # Don't place water on paths
|
||||
continue
|
||||
|
||||
# Calculate distance from center
|
||||
var distance = sqrt(pow(x - center_x, 2) + pow(y - center_y, 2))
|
||||
|
||||
# Use noise to modify the effective radius at this position
|
||||
var noise_value = water_noise.get_noise_2d(x, y)
|
||||
var radius_modifier = noise_value * radius * 0.4 # 40% variation
|
||||
var effective_radius = radius + radius_modifier
|
||||
|
||||
# Place water if within the noise-modified radius
|
||||
if distance <= effective_radius:
|
||||
water_data[y][x] = true
|
||||
|
||||
func print_visual_map_to_console():
|
||||
print("")
|
||||
print("==================================================")
|
||||
print("VISUAL MAP WITH BRANCHING PATHS AND DEAD ENDS")
|
||||
print("==================================================")
|
||||
print("Path Statistics:")
|
||||
print("- Total path segments: ", path_segments.size())
|
||||
var main_paths = path_segments.filter(func(seg): return seg.type == "main")
|
||||
var branches = path_segments.filter(func(seg): return seg.type == "branch")
|
||||
var dead_ends = branches.filter(func(seg): return seg.get("is_dead_end", false))
|
||||
print("- Main paths: ", main_paths.size())
|
||||
print("- Branches: ", branches.size())
|
||||
print("- Dead ends: ", dead_ends.size())
|
||||
print("")
|
||||
|
||||
print_block_map_with_paths_safe()
|
||||
|
||||
func print_block_map_with_paths_safe():
|
||||
print("Block character map with paths (X = paths, W = water):")
|
||||
print("")
|
||||
|
||||
# Print in smaller chunks
|
||||
var chunk_size = 10
|
||||
for chunk_start in range(0, map_height, chunk_size):
|
||||
var chunk_end = min(chunk_start + chunk_size, map_height)
|
||||
|
||||
for y in range(chunk_start, chunk_end):
|
||||
var row_string = ""
|
||||
for x in range(map_width):
|
||||
# Check priority: paths first, then water, then terrain
|
||||
if path_data[y][x]:
|
||||
row_string += "X"
|
||||
elif water_data[y][x]:
|
||||
row_string += "W"
|
||||
else:
|
||||
var value = map_data[y][x]
|
||||
var block_char = get_block_character(value)
|
||||
row_string += block_char
|
||||
print(row_string)
|
||||
|
||||
# Brief pause between chunks
|
||||
if chunk_end < map_height:
|
||||
await get_tree().process_frame
|
||||
|
||||
func get_block_character(value: float) -> String:
|
||||
# Use Unicode block characters for different densities
|
||||
if value >= 0.8:
|
||||
return "█" # Full block
|
||||
elif value >= 0.6:
|
||||
return "▓" # Dark shade
|
||||
elif value >= 0.4:
|
||||
return "▒" # Medium shade
|
||||
elif value >= 0.2:
|
||||
return "░" # Light shade
|
||||
else:
|
||||
return " " # Empty space
|
||||
|
||||
# Enhanced utility functions
|
||||
func is_path_at(x: int, y: int) -> bool:
|
||||
if x >= 0 and x < map_width and y >= 0 and y < map_height:
|
||||
return path_data[y][x]
|
||||
return false
|
||||
|
||||
func is_water_at(x: int, y: int) -> bool:
|
||||
if x >= 0 and x < map_width and y >= 0 and y < map_height:
|
||||
return water_data[y][x]
|
||||
return false
|
||||
|
||||
func get_terrain_at(x: int, y: int) -> float:
|
||||
if x >= 0 and x < map_width and y >= 0 and y < map_height:
|
||||
return map_data[y][x]
|
||||
return 0.0
|
||||
|
||||
func get_tile_type_at(x: int, y: int) -> String:
|
||||
if is_path_at(x, y):
|
||||
return "path"
|
||||
elif is_water_at(x, y):
|
||||
return "water"
|
||||
else:
|
||||
return "terrain"
|
||||
|
||||
func get_path_segments() -> Array:
|
||||
return path_segments
|
||||
|
||||
func get_main_paths() -> Array:
|
||||
return path_segments.filter(func(seg): return seg.type == "main")
|
||||
|
||||
func get_branches() -> Array:
|
||||
return path_segments.filter(func(seg): return seg.type == "branch")
|
||||
|
||||
func get_dead_ends() -> Array:
|
||||
var branches = get_branches()
|
||||
return branches.filter(func(seg): return seg.get("is_dead_end", false))
|
||||
|
||||
func export_map_as_image():
|
||||
print("Exporting map as image...")
|
||||
|
||||
# Calculate image dimensions
|
||||
var image_width = map_width * tile_size
|
||||
var image_height = map_height * tile_size
|
||||
|
||||
# Create image
|
||||
var image = Image.create(image_width, image_height, false, Image.FORMAT_RGB8)
|
||||
|
||||
# Generate the image pixel by pixel
|
||||
for map_y in range(map_height):
|
||||
for map_x in range(map_width):
|
||||
var color = get_tile_color(map_x, map_y)
|
||||
|
||||
# Fill the tile area with the color (each tile is tile_size x tile_size pixels)
|
||||
for pixel_y in range(tile_size):
|
||||
for pixel_x in range(tile_size):
|
||||
var image_x = map_x * tile_size + pixel_x
|
||||
var image_y = map_y * tile_size + pixel_y
|
||||
image.set_pixel(image_x, image_y, color)
|
||||
|
||||
# Save the image - you can choose different locations:
|
||||
var file_path = "user://" + image_filename
|
||||
|
||||
print("User data directory: ", OS.get_user_data_dir())
|
||||
print("Saving image to: ", file_path)
|
||||
|
||||
var error = image.save_png(file_path)
|
||||
|
||||
if error == OK:
|
||||
print("Map image saved successfully!")
|
||||
print("Image dimensions: ", image_width, "x", image_height, " pixels")
|
||||
print("Map dimensions: ", map_width, "x", map_height, " tiles")
|
||||
print("Tile size: ", tile_size, "x", tile_size, " pixels per tile")
|
||||
else:
|
||||
print("Error saving image: ", error)
|
||||
|
||||
func get_tile_color(x: int, y: int) -> Color:
|
||||
if is_path_at(x, y):
|
||||
# Brown color for paths
|
||||
return Color(0.6, 0.4, 0.2) # Brown
|
||||
elif is_water_at(x, y):
|
||||
# Blue color for water
|
||||
return Color(0.2, 0.4, 0.8) # Blue
|
||||
else:
|
||||
# Terrain - dark green to light green based on height
|
||||
var terrain_value = get_terrain_at(x, y)
|
||||
|
||||
# Create gradient from dark green to light green
|
||||
# Dark green for low values, light green for high values
|
||||
var dark_green = Color(0.1, 0.3, 0.1) # Dark green
|
||||
var light_green = Color(0.4, 0.7, 0.3) # Light green
|
||||
|
||||
# Interpolate between dark and light green based on terrain value
|
||||
return dark_green.lerp(light_green, terrain_value)
|
||||
Loading…
Add table
Add a link
Reference in a new issue