Files
g-world/scripts/game/vault_view.gd
T

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)