243 lines
7.8 KiB
GDScript
243 lines
7.8 KiB
GDScript
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)
|