Salles de donjon

Des salles placées sans se chevaucher, puis une marche aléatoire qui creuse le couloir entre chaque paire.

L'éditeur ci-dessus fait tourner ce projet. Changez une ligne et il se recharge.

Classes Godot utilisées

Polygon2DLine2DRect2i

Le code

scripts/main.gd
extends Node

const COLUMNS: int = 52
const ROWS: int = 28
const ROOMS: int = 12
const TRIES: int = 160
const WANDER: float = 0.18
const SEED: int = 20260814

const DIRECTIONS: Array = [Vector2i.RIGHT, Vector2i.LEFT, Vector2i.DOWN, Vector2i.UP]

var rng: RandomNumberGenerator = RandomNumberGenerator.new()
var origin: Vector2
var cell: float


func _ready() -> void:
	_add_background()

	rng.seed = SEED

	var view: Vector2 = get_viewport().get_visible_rect().size
	cell = minf(view.x / COLUMNS, view.y / ROWS)
	origin = (view - Vector2(COLUMNS, ROWS) * cell) * 0.5

	var rooms: Array[Rect2i] = _place_rooms()

	for i in rooms.size() - 1:
		_add_corridor(_walk(rooms[i].get_center(), rooms[i + 1].get_center()))

	for i in rooms.size():
		_add_room(rooms[i], i)

	print('rooms first, then a random walk digs between them, change SEED for another dungeon')


func _place_rooms() -> Array[Rect2i]:
	var rooms: Array[Rect2i] = []

	for attempt in TRIES:
		if rooms.size() == ROOMS:
			break

		var size: Vector2i = Vector2i(rng.randi_range(5, 10), rng.randi_range(4, 7))
		var candidate: Rect2i = Rect2i(
			Vector2i(rng.randi_range(2, COLUMNS - size.x - 2), rng.randi_range(2, ROWS - size.y - 2)),
			size
		)
		var free: bool = true

		for room in rooms:
			if room.grow(2).intersects(candidate):
				free = false
				break

		if free:
			rooms.append(candidate)

	rooms.sort_custom(func(a: Rect2i, b: Rect2i) -> bool: return a.position.x < b.position.x)

	return rooms


func _walk(from: Vector2i, to: Vector2i) -> PackedVector2Array:
	var path: PackedVector2Array = PackedVector2Array()
	var at: Vector2i = from
	path.append(_to_point(at))

	while at != to:
		var step: Vector2i = Vector2i(signi(to.x - at.x), 0)

		if rng.randf() < WANDER:
			step = DIRECTIONS[rng.randi_range(0, DIRECTIONS.size() - 1)]
		elif at.x == to.x:
			step = Vector2i(0, signi(to.y - at.y))

		at = (at + step).clamp(Vector2i.ONE, Vector2i(COLUMNS - 2, ROWS - 2))
		path.append(_to_point(at))

	return path


func _add_corridor(path: PackedVector2Array) -> void:
	var corridor: Line2D = Line2D.new()
	corridor.points = path
	corridor.width = cell
	corridor.default_color = Color('#2a2f3d')
	corridor.joint_mode = Line2D.LINE_JOINT_ROUND
	corridor.begin_cap_mode = Line2D.LINE_CAP_BOX
	corridor.end_cap_mode = Line2D.LINE_CAP_BOX
	add_child(corridor)


func _add_room(room: Rect2i, index: int) -> void:
	var top_left: Vector2 = origin + Vector2(room.position) * cell
	var size: Vector2 = Vector2(room.size) * cell

	var floor_rect: Polygon2D = Polygon2D.new()
	floor_rect.polygon = PackedVector2Array([top_left, top_left + Vector2(size.x, 0), top_left + size, top_left + Vector2(0, size.y)])
	floor_rect.color = Color('#39415a')
	add_child(floor_rect)

	var border: Line2D = Line2D.new()
	border.points = floor_rect.polygon
	border.closed = true
	border.width = 2.0
	border.default_color = Color('#8da5f3')
	add_child(border)

	var label: Label = Label.new()
	label.text = str(index + 1)
	label.size = size
	label.position = top_left
	label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
	label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
	label.add_theme_color_override('font_color', Color('#8da5f3'))
	add_child(label)


func _to_point(at: Vector2i) -> Vector2:
	return origin + (Vector2(at) + Vector2(0.5, 0.5)) * cell


func _add_background() -> void:
	var background: ColorRect = ColorRect.new()
	background.color = Color('#12141a')
	add_child(background)
	background.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)

Plus d'exemples de Procédural