class_name VaultView extends Node2D ## Отрисовка убежища в разрезе: небо, порода, лифтовая шахта, комнаты ## и призрак строительства. ## ## Ассетов у проекта пока нет, поэтому всё рисуется процедурно в _draw(). ## Когда появится графика, заменять нужно только этот файл — модель ## (VaultGrid) и логика сцены от него не зависят. const SKY_TOP := Color("#050912") const SKY_HORIZON := Color("#16333f") const DIRT_TOP := Color("#2a231d") const DIRT_BOTTOM := Color("#100d0b") const SURFACE := Color("#3d4f3a") const SHELL := Color("#1b262c") const WALL := Color("#2c3b45") const OUTLINE := Color("#080d11") const SHAFT := Color("#141c21") const RUNG := Color("#c9a227", 0.45) const CABIN := Color("#c9a227", 0.22) const EMPTY_FILL := Color("#ffffff", 0.02) const EMPTY_LINE := Color("#ffffff", 0.06) const GHOST_OK := Color("#2ee6c5") const GHOST_BAD := Color("#f2704a") const MARGIN := 1200.0 const SKY_HEIGHT := 900.0 const DIRT_DEPTH := 460.0 const PEBBLE_SEED := 20260810 ## Модель, которую рисуем. Ставится сценой сразу после создания. var grid: VaultGrid var _ghost_kind := "" var _ghost_cell := Vector2i(-1, -1) var _demolish := false var _pebbles: Array[Dictionary] = [] func _ready() -> void: _generate_pebbles() ## Подсветка под курсором: тип строящейся комнаты (пустая строка — ничего), ## ячейка и режим сноса. func set_ghost(kind: String, cell: Vector2i, demolish: bool) -> void: if kind == _ghost_kind and cell == _ghost_cell and demolish == _demolish: return _ghost_kind = kind _ghost_cell = cell _demolish = demolish queue_redraw() func _draw() -> void: if grid == null: return _draw_sky() _draw_dirt() _draw_shell() _draw_empty_cells() _draw_shaft() _draw_floor_numbers() for room: Dictionary in grid.rooms: _draw_room(room) _draw_ghost() # --- Фон -------------------------------------------------------------------- func _draw_sky() -> void: var bounds := VaultGrid.bounds() var left := -MARGIN var right := bounds.size.x + MARGIN _fill_gradient(Rect2(Vector2(left, -SKY_HEIGHT), Vector2(right - left, SKY_HEIGHT)), SKY_TOP, SKY_HORIZON) func _draw_dirt() -> void: var bounds := VaultGrid.bounds() var left := -MARGIN var right := bounds.size.x + MARGIN var depth := bounds.size.y + DIRT_DEPTH _fill_gradient(Rect2(Vector2(left, 0.0), Vector2(right - left, depth)), DIRT_TOP, DIRT_BOTTOM) for pebble: Dictionary in _pebbles: draw_circle(pebble["position"], float(pebble["radius"]), pebble["color"]) # Полоса поверхности — граница земли и неба. draw_line(Vector2(left, 0.0), Vector2(right, 0.0), SURFACE, 5.0) ## Заливка прямоугольника вертикальным градиентом: draw_rect умеет только ## сплошной цвет, поэтому идём через полигон с цветами вершин. func _fill_gradient(rect: Rect2, top: Color, bottom: Color) -> void: var points := PackedVector2Array([ rect.position, Vector2(rect.end.x, rect.position.y), rect.end, Vector2(rect.position.x, rect.end.y), ]) draw_polygon(points, PackedColorArray([top, top, bottom, bottom])) func _generate_pebbles() -> void: var rng := RandomNumberGenerator.new() rng.seed = PEBBLE_SEED var bounds := VaultGrid.bounds() for i in 260: _pebbles.append({ "position": Vector2( rng.randf_range(-MARGIN, bounds.size.x + MARGIN), rng.randf_range(6.0, bounds.size.y + DIRT_DEPTH) ), "radius": rng.randf_range(1.5, 5.0), "color": Color("#000000", rng.randf_range(0.10, 0.28)), }) # --- Убежище ---------------------------------------------------------------- func _draw_shell() -> void: draw_rect(VaultGrid.bounds().grow(10.0), SHELL) func _draw_empty_cells() -> void: for a_floor in VaultGrid.FLOORS: for column in range(VaultGrid.ELEVATOR_COLUMN + 1, VaultGrid.COLUMNS): if grid.room_index_at(a_floor, column) >= 0: continue var rect := VaultGrid.cell_rect(a_floor, column).grow(-3.0) draw_rect(rect, EMPTY_FILL) draw_rect(rect, EMPTY_LINE, false, 1.0) func _draw_shaft() -> void: var rect := Rect2( Vector2(VaultGrid.ELEVATOR_COLUMN * VaultGrid.CELL.x, 0.0), Vector2(VaultGrid.CELL.x, VaultGrid.FLOORS * VaultGrid.CELL.y) ) draw_rect(rect, SHAFT) var y := 16.0 while y < rect.size.y: draw_line(Vector2(rect.position.x + 18.0, y), Vector2(rect.end.x - 18.0, y), RUNG, 2.0) y += 26.0 # Кабина стоит на верхнем этаже. var cabin := Rect2(rect.position + Vector2(22.0, 14.0), Vector2(rect.size.x - 44.0, 92.0)) draw_rect(cabin, CABIN) draw_rect(cabin, RUNG, false, 2.0) draw_rect(rect, OUTLINE, false, 2.0) func _draw_floor_numbers() -> void: var font := ThemeDB.fallback_font for a_floor in VaultGrid.FLOORS: var pos := Vector2(-46.0, a_floor * VaultGrid.CELL.y + VaultGrid.CELL.y * 0.5 + 7.0) draw_string(font, pos, str(a_floor + 1), HORIZONTAL_ALIGNMENT_LEFT, -1, 20, Color("#ffffff", 0.28)) func _draw_room(room: Dictionary) -> void: var rect := VaultGrid.room_rect(room) var kind: Dictionary = VaultGrid.KINDS[String(room["kind"])] var accent := Color(String(kind["color"])) draw_rect(rect, WALL) var inner := rect.grow(-7.0) _fill_gradient(inner, accent.lerp(Color("#0b1116"), 0.58), accent.lerp(Color("#060a0d"), 0.86)) # Свет от потолочной лампы. var lamp := Vector2(inner.position.x + inner.size.x * 0.5, inner.position.y + 2.0) draw_polygon( PackedVector2Array([ lamp + Vector2(-16.0, 0.0), lamp + Vector2(16.0, 0.0), Vector2(inner.end.x - 10.0, inner.end.y), Vector2(inner.position.x + 10.0, inner.end.y), ]), PackedColorArray([ Color(accent, 0.13), Color(accent, 0.13), Color(accent, 0.0), Color(accent, 0.0), ]) ) _draw_equipment(inner, accent, VaultGrid.kind_width(String(room["kind"]))) # Пол. draw_rect( Rect2(Vector2(inner.position.x, inner.end.y - 7.0), Vector2(inner.size.x, 7.0)), accent.lerp(Color("#05080a"), 0.55) ) draw_rect(rect, OUTLINE, false, 2.0) draw_string(ThemeDB.fallback_font, rect.position + Vector2(14.0, 30.0), String(kind["label"]), HORIZONTAL_ALIGNMENT_LEFT, rect.size.x - 20.0, 17, Color("#e6f1f7", 0.92)) ## Силуэты оборудования у пола — чтобы комнаты не выглядели пустыми коробками. func _draw_equipment(inner: Rect2, accent: Color, width: int) -> void: var body := accent.lerp(Color("#04070a"), 0.62) var glow := Color(accent, 0.55) var baseline := inner.end.y - 7.0 var count := 3 if width > 1 else 2 var step := inner.size.x / float(count + 1) for i in range(1, count + 1): var center_x := inner.position.x + step * i var height := 26.0 + float((i * 13) % 22) var machine := Rect2(Vector2(center_x - 17.0, baseline - height), Vector2(34.0, height)) draw_rect(machine, body) draw_rect(Rect2(machine.position + Vector2(6.0, 6.0), Vector2(22.0, 4.0)), glow) # --- Строительство ---------------------------------------------------------- func _draw_ghost() -> void: if _ghost_cell.x < 0: return if _demolish: var index := grid.room_index_at(_ghost_cell.y, _ghost_cell.x) if index < 0: return var target := VaultGrid.room_rect(grid.rooms[index]) draw_rect(target, Color(GHOST_BAD, 0.22)) draw_rect(target, GHOST_BAD, false, 3.0) return if _ghost_kind.is_empty(): return var width := VaultGrid.kind_width(_ghost_kind) var rect := VaultGrid.cell_rect(_ghost_cell.y, _ghost_cell.x, width) var allowed := grid.can_place(_ghost_kind, _ghost_cell.y, _ghost_cell.x) var tint := GHOST_OK if allowed else GHOST_BAD draw_rect(rect, Color(tint, 0.16)) draw_rect(rect, tint, false, 3.0)