먼 바다 배경·하루의 빛·층 구조·감상 모드
네 가지 작업이 같은 함수들을 겹쳐 고쳐서 한 커밋으로 묶는다. 쪼개려면 hunk 단위로 갈라야 하는데 서로 얽혀 있어 오히려 위험하다. 먼 바다 배경 (backdrop.gd) - 3/4 부감에서 화면 위쪽은 더 먼 곳이다. 거기까지 모래를 깔면 바다가 아니라 사막처럼 보여서, 위쪽 띠를 물로 비웠다. - 먼 물 그라디언트 → 능선 → 먼 실루엣 순으로 깊이를 만든다. - Seabed가 뒷줄일수록 색을 물빛 쪽으로 당겨 공기원근을 준다. - 빛줄기를 알파를 더하는 방향으로 바꿨다. 걷어내는 방향이면 바닥만 밝아지고 물기둥은 텅 빈 채로 남는다. 하루의 빛 (day_cycle.gd) - 실제 시각에 맞춰 새벽/아침/낮/저녁/밤. 이름은 구간으로 끊고 색과 밝기는 계속 보간한다. - 밤에는 물결과 빛줄기가 잦아들고, 스스로 빛나는 것은 어둠을 되밀어 오히려 도드라진다. - godot --path . -- --hour=21 로 특정 시각을 눈으로 확인할 수 있다. 한 화면에 한 층 - 가로 연속 스크롤을 버리고 층 전환으로 바꿨다. 위층(얕은 바다)에서 아래층(열수구)으로 내려간다. 층 선택기·휠·↑↓ 키. - 층은 내부적으로 여전히 옆으로 늘어서 있고 화면이 한 구간만 비춘다. 갈아탈 때만 세로로 미끄러뜨려 깊이가 위아래로 읽히게 했다. - 칸 크기를 가로에서 정하도록 뒤집고 ZONE_COLUMNS를 36으로 올렸다. 층당 칸이 96 -> 216이라 정원과 해금 곡선은 다시 봐야 한다. - 한 화면에 한 층만 보이므로 층 색은 이웃과 섞지 않는다. 감상 모드 (F11) - 창을 화면 전체로. 세로로 남는 공간은 격자가 아니라 물기둥에 준다. 격자를 키우면 타일만 커지고 보이는 열은 오히려 줄어든다. - 창을 화면과 픽셀까지 똑같이 맞추면 Windows가 독점 전체화면으로 승격시켜 size 지정이 먹지 않는다. usable_rect까지만 쓴다. 그 밖에 - 저장 window_state: scroll_x -> layer - 알림이 정보 바와 겹치던 것을 바 아래로 옮겼다 - 난파선 선체 폴리곤이 제 몸을 가로질러 삼각분할이 실패하던 것 수정 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+22
-4
@@ -10,7 +10,7 @@ signal menu_pressed
|
||||
signal reserved_top_changed(px: float)
|
||||
|
||||
## 바 아래로 이만큼은 띄워야 첫 줄 칸이 바에 닿지 않는다.
|
||||
const BAR_CLEARANCE := 6.0
|
||||
const BAR_CLEARANCE := 22.0
|
||||
|
||||
@onready var control_bar: PanelContainer = $ControlBar
|
||||
@onready var bubbles_label: Label = %BubblesLabel
|
||||
@@ -20,6 +20,7 @@ const BAR_CLEARANCE := 6.0
|
||||
@onready var shop_button: Button = %ShopButton
|
||||
@onready var menu_button: Button = %MenuButton
|
||||
@onready var toast: Label = %Toast
|
||||
@onready var layer_bar: LayerBar = %LayerBar
|
||||
@onready var trash: PanelContainer = %Trash
|
||||
@onready var trash_label: Label = %TrashLabel
|
||||
|
||||
@@ -27,8 +28,13 @@ var _toast_tween: Tween
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
control_bar.add_theme_stylebox_override("panel", UIStyle.panel(UIStyle.PANEL_BG, 12))
|
||||
UIStyle.label(bubbles_label, 24, UIStyle.INK)
|
||||
# 바를 납작하게 눌러 둔다. 이 높이만큼 격자가 밀리고,
|
||||
# 남는 자리에 먼 바다 배경이 드러난다.
|
||||
var bar_style := UIStyle.panel(UIStyle.PANEL_BG, 11)
|
||||
bar_style.content_margin_top = 6
|
||||
bar_style.content_margin_bottom = 6
|
||||
control_bar.add_theme_stylebox_override("panel", bar_style)
|
||||
UIStyle.label(bubbles_label, 20, UIStyle.INK)
|
||||
UIStyle.label(bps_label, 13, UIStyle.INK_DIM)
|
||||
UIStyle.label(beauty_label, 13, UIStyle.GOLD)
|
||||
UIStyle.label(zone_label, 13, UIStyle.ACCENT)
|
||||
@@ -44,7 +50,10 @@ func _ready() -> void:
|
||||
shop_button.pressed.connect(func(): shop_pressed.emit())
|
||||
menu_button.pressed.connect(func(): menu_pressed.emit())
|
||||
|
||||
control_bar.resized.connect(func(): reserved_top_changed.emit(reserved_top()))
|
||||
control_bar.resized.connect(func():
|
||||
_place_toast()
|
||||
reserved_top_changed.emit(reserved_top()))
|
||||
_place_toast()
|
||||
|
||||
GameState.zone_unlocked.connect(func(_i: int): _refresh_zone_label())
|
||||
GameState.state_changed.connect(_refresh_zone_label)
|
||||
@@ -83,6 +92,15 @@ func _on_offline_earned(amount: float, seconds: int) -> void:
|
||||
], 4.5)
|
||||
|
||||
|
||||
## 알림은 정보 바 바로 아래 왼쪽에 붙인다.
|
||||
## 가운데에 두면 바가 길어졌을 때 글자가 겹쳐 둘 다 못 읽는다.
|
||||
func _place_toast() -> void:
|
||||
# 층 선택기가 왼쪽 아래를 쓰므로 알림은 그 폭만큼 비켜 앉힌다.
|
||||
toast.position = Vector2(
|
||||
control_bar.position.x + layer_bar.size.x + 14.0,
|
||||
control_bar.position.y + control_bar.size.y + 6.0)
|
||||
|
||||
|
||||
func show_toast(text: String, duration := 2.0) -> void:
|
||||
toast.text = text
|
||||
if _toast_tween and _toast_tween.is_valid():
|
||||
|
||||
+22
-12
@@ -1,6 +1,7 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
[gd_scene load_steps=3 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/ui/hud.gd" id="1_hud"]
|
||||
[ext_resource type="Script" path="res://scenes/ui/layer_bar.gd" id="2_layers"]
|
||||
|
||||
[node name="HUD" type="Control"]
|
||||
layout_mode = 3
|
||||
@@ -16,7 +17,7 @@ script = ExtResource("1_hud")
|
||||
layout_mode = 1
|
||||
anchors_preset = 0
|
||||
offset_left = 12.0
|
||||
offset_top = 10.0
|
||||
offset_top = 7.0
|
||||
offset_right = 12.0
|
||||
offset_bottom = 10.0
|
||||
mouse_filter = 2
|
||||
@@ -80,6 +81,20 @@ layout_mode = 2
|
||||
size_flags_vertical = 4
|
||||
text = "···"
|
||||
|
||||
[node name="LayerBar" type="VBoxContainer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 2
|
||||
anchor_top = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 12.0
|
||||
offset_top = -146.0
|
||||
offset_right = 86.0
|
||||
offset_bottom = -10.0
|
||||
grow_vertical = 0
|
||||
mouse_filter = 2
|
||||
script = ExtResource("2_layers")
|
||||
|
||||
[node name="Trash" type="PanelContainer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
@@ -106,16 +121,11 @@ vertical_alignment = 1
|
||||
|
||||
[node name="Toast" type="Label" parent="."]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 5
|
||||
anchor_left = 0.5
|
||||
anchor_right = 0.5
|
||||
offset_left = -240.0
|
||||
offset_top = 16.0
|
||||
offset_right = 240.0
|
||||
offset_bottom = 60.0
|
||||
grow_horizontal = 2
|
||||
layout_mode = 0
|
||||
offset_left = 100.0
|
||||
offset_top = 70.0
|
||||
offset_right = 700.0
|
||||
offset_bottom = 108.0
|
||||
mouse_filter = 2
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
autowrap_mode = 3
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
extends VBoxContainer
|
||||
class_name LayerBar
|
||||
## 층 선택기. 위에서 아래로 얕은 바다 → 열수구 순으로 늘어선다.
|
||||
##
|
||||
## 배치는 실제로는 옆으로 늘어서 있지만, 플레이어에게는 깊이가 위아래다.
|
||||
## 그래서 이 목록의 순서가 곧 깊이의 순서이고, 아래로 갈수록 어두워진다.
|
||||
|
||||
signal layer_picked(index: int)
|
||||
|
||||
## 잠긴 층에 붙는 표시. 이름을 감추는 것은 잠긴 층의 안내가 바다 쪽에 이미 뜨기 때문.
|
||||
const LOCKED_MARK := "🔒"
|
||||
|
||||
var _buttons: Array[Button] = []
|
||||
var _current := 0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
add_theme_constant_override("separation", 3)
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
GameState.zone_unlocked.connect(func(_i: int): rebuild())
|
||||
GameState.reloaded.connect(rebuild)
|
||||
rebuild()
|
||||
|
||||
|
||||
## 층마다 단추 하나. 잠긴 층도 눌러서 조건을 보러 갈 수 있어야 한다.
|
||||
func rebuild() -> void:
|
||||
for child in get_children():
|
||||
child.queue_free()
|
||||
_buttons.clear()
|
||||
|
||||
for i in Catalog.zone_count():
|
||||
var zone := Catalog.zone(i)
|
||||
var open := GameState.is_zone_unlocked(i)
|
||||
|
||||
var b := Button.new()
|
||||
b.text = String(zone.name) if open else LOCKED_MARK
|
||||
b.custom_minimum_size = Vector2(74, 24)
|
||||
b.tooltip_text = String(zone.name) if open else "%s · 아직 잠겨 있어요" % zone.name
|
||||
b.focus_mode = Control.FOCUS_NONE
|
||||
UIStyle.style_button(b, 7)
|
||||
b.add_theme_font_size_override("font_size", 11)
|
||||
# 층 색을 단추에 그대로 입혀, 목록만 봐도 깊이가 읽히게 한다.
|
||||
b.add_theme_stylebox_override("normal", _skin(i, 0.62))
|
||||
b.add_theme_stylebox_override("hover", _skin(i, 0.80))
|
||||
b.add_theme_stylebox_override("pressed", _skin(i, 0.90))
|
||||
b.pressed.connect(func(): layer_picked.emit(i))
|
||||
add_child(b)
|
||||
_buttons.append(b)
|
||||
|
||||
set_current(_current)
|
||||
|
||||
|
||||
func _skin(index: int, alpha: float) -> StyleBoxFlat:
|
||||
var sb := UIStyle.row(Color(0, 0, 0, 0), 7)
|
||||
var c: Color = Catalog.zone(index).tint
|
||||
sb.bg_color = Color(c.r, c.g, c.b, alpha)
|
||||
sb.content_margin_top = 3
|
||||
sb.content_margin_bottom = 3
|
||||
return sb
|
||||
|
||||
|
||||
## 지금 보고 있는 층을 표시한다. 고른 것만 또렷하고 나머지는 물러난다.
|
||||
func set_current(index: int) -> void:
|
||||
_current = clampi(index, 0, _buttons.size() - 1)
|
||||
for i in _buttons.size():
|
||||
var here := i == _current
|
||||
_buttons[i].modulate = Color.WHITE if here else Color(1, 1, 1, 0.55)
|
||||
_buttons[i].add_theme_color_override(
|
||||
"font_color", UIStyle.INK if here else UIStyle.INK_DIM)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bdayflw4cn1co
|
||||
Reference in New Issue
Block a user