Slay the Spire Style Map Generation in Godot 4

mail@pastecode.io avatar
unknown
plain_text
8 months ago
2.8 kB
372
Indexable
Never
################################################
# Map World
################################################

extends Node2D

@onready var map_node = preload("res://map_node.tscn")
@onready var map_nodes = $MapNodes

func _ready():
    var map = MapGenerator.new().generate_map()
	var map_height = map.size()
	var map_width = map[0].size()
	var floors = map
	for y in map_height:
		for x in map_width:
			var n = map_node.instantiate()
			n.room_data = floors[y][x]
			map_nodes.add_child(n)











################################################
# Map Generator
################################################

extends Node
class_name MapGenerator

# https://kosgames.com/slay-the-spire-map-generation-guide-26769/

var distance_x = 60 # x distance between nodes
var distance_y = 50 # y distance between nodes
var wackiness = Vector2(25, 25) #a random factor so nodes aren't exactly lined up
var map_height = 15 # how many floors
var map_width = 7 # how many rooms on each floor
var total_paths = 6 # how many paths should generate
var floors = [] # this is the map layout!

func generate_map():
	floors = []
	# load an empty map
	for y in map_height:
		var room = []
		room.resize(map_width)
		room.fill(null)
		floors.push_back(room)
		for x in map_width:
			var wack = Vector2(randf_range(0, wackiness.x), randf_range(0, wackiness.y))
			
			floors[y][x] = {
				"y": y,
				"x": x,
				"position": Vector2((x * distance_x) + wack.x , (y * distance_y) + wack.y),
				"next_nodes": []
			}
			if y == map_height - 1:
				floors[y][x].position.y = (y * distance_y) + distance_y

	# load paths throughout the map
	for i in _rand_starting_points():
		var next_node = i
		for y in map_height - 1:
			next_node = _set_path(next_node, y)
	
	# load boss room
	for x in map_width:
		if floors[map_height - 2][x].next_nodes.size():
			floors[map_height - 2][x].next_nodes = [ Vector2(floor(map_width * 0.5), map_height - 1) ]
	
	return floors

func _set_path(x, y):
	var next_room_x
	while next_room_x == null or _would_path_cross_existing_path(x, y, next_room_x):
		next_room_x = clamp(randi_range(x - 1, x + 1), 0, map_width - 1)
	floors[y][x].next_nodes.push_back(Vector2(next_room_x, y + 1))
	return next_room_x

func _would_path_cross_existing_path(x, y, next_room_x):
	var left_node
	var right_node
	if x > 0:
		left_node = floors[y][x - 1]
	if x < map_width - 1:
		right_node = floors[y][x + 1]

	if right_node and next_room_x > x:
		for next in right_node.next_nodes:
			if next.x < next_room_x:
				return true
				
	if left_node and next_room_x < x:
		for next in left_node.next_nodes:
			if next.x > next_room_x:
				return true
	return false

func _rand_starting_points():
	var points = []
	for i in total_paths:
		points.push_back(randi_range(0, map_width - 1))
	return points