extends Node2D class_name Backdrop ## 격자 위쪽의 먼 바다. ## ## 3/4 부감이라 화면 위쪽은 "더 먼 곳"이다. 거기까지 모래를 깔면 ## 바다가 아니라 사막처럼 보인다. 그래서 위쪽 띠를 물로 비우고, ## 먼 물 → 흐릿한 능선 → 격자 순으로 이어 깊이를 만든다. ## ## 색은 Seabed와 같은 구역 램프를 쓰므로 좌우로 갈수록 자연스럽게 깊어진다. var rect_size := Vector2(1280, 220) ## 이 높이까지가 먼 바다. 그 아래부터 격자다. var grid_top := 46.0 ## 능선이 격자선 위로 얼마나 솟는지. const RIDGE_HEIGHT := 9.0 ## 능선에 세우는 먼 실루엣 수(구역당). const SILHOUETTES_PER_ZONE := 4 ## 늘 같은 무늬가 나오도록 하는 해시. 다시 그릴 때마다 배경이 흔들리면 안 된다. func _hash(i: int, salt: int) -> float: var h := (i * 73856093) ^ (salt * 83492791) return float(absi(h) % 1000) / 1000.0 ## 스트립 가로 위치 f에서 먼 물의 색. 위쪽 끝(카메라에서 가장 먼 곳)이다. func _far_color(f: float) -> Color: return Catalog.ramp_color("tint", f).darkened(0.30) ## 능선 언저리의 색. 모래가 물빛에 절반쯤 먹힌 상태. func _near_color(f: float) -> Color: var sand := Catalog.ramp_color("sand", f) var tint := Catalog.ramp_color("tint", f) # 깊을수록 모래가 더 많이 먹힌다. var haze := clampf(0.42 + Catalog.ramp_float("murk", f) * 0.55, 0.0, 0.95) return sand.lerp(tint, haze) func _draw() -> void: var cols := GameState.columns() if rect_size.x < 4.0 or cols <= 0 or grid_top < 4.0: return _draw_open_water(cols) _draw_ridge(cols) _draw_silhouettes() ## 먼 물. 열마다 사다리꼴을 그리고 정점 색을 먹여 가로·세로로 동시에 번지게 한다. func _draw_open_water(cols: int) -> void: var tw := rect_size.x / cols for col in cols: var f0 := float(col) / cols var f1 := float(col + 1) / cols var x0 := col * tw var x1 := x0 + tw + 1.0 draw_polygon( PackedVector2Array([ Vector2(x0, 0.0), Vector2(x1, 0.0), Vector2(x1, grid_top), Vector2(x0, grid_top), ]), PackedColorArray([ _far_color(f0), _far_color(f1), _near_color(f1), _near_color(f0), ])) ## 격자 바로 위에 얹히는 먼 바닥. 물결치는 윗선이 수평선 노릇을 한다. func _draw_ridge(cols: int) -> void: var base_y := grid_top - RIDGE_HEIGHT var steps := maxi(int(rect_size.x / 26.0), 12) var points := PackedVector2Array() var colors := PackedColorArray() for i in steps + 1: var f := float(i) / steps var x := rect_size.x * f var y := base_y - sin(x / 96.0) * 3.4 - cos(x / 41.0) * 1.6 points.append(Vector2(x, y)) # 능선 마루는 조금 밝게. 빛을 받는 면처럼 보인다. colors.append(_near_color(f).lightened(0.06)) for i in range(steps, -1, -1): var f := float(i) / steps points.append(Vector2(rect_size.x * f, grid_top + 1.0)) colors.append(_near_color(f)) draw_polygon(points, colors) ## 능선 위에 서 있는 먼 것들. 무엇인지 알아볼 필요는 없고, ## 시선이 닿는 곳에 뭔가 더 있다는 신호만 주면 된다. func _draw_silhouettes() -> void: var zones := Catalog.zone_count() var zone_w := rect_size.x / zones var base_y := grid_top - RIDGE_HEIGHT + 1.0 for z in zones: var tint := Catalog.ramp_color("tint", (z + 0.5) / float(zones)) for i in SILHOUETTES_PER_ZONE: var seed_id := z * 31 + i var x := zone_w * (z + 0.12 + _hash(seed_id, 1) * 0.76) # 낮고 넓게. 가늘고 높으면 비석처럼 서 버려서 먼 풍경으로 안 읽힌다. # 띠가 아무리 높아도 실루엣은 능선 언저리에 낮게 깔린다. # 띠 높이에 그대로 비례시키면 감상 모드에서 산맥이 되어버린다. var band := minf(grid_top, 96.0) var h := band * (0.16 + _hash(seed_id, 2) * 0.26) var w := 5.0 + _hash(seed_id, 3) * 11.0 var shade := tint.darkened(0.16) shade.a = 0.42 if _hash(seed_id, 4) < 0.5: # 먼 수초. 가늘게 위로 뻗는다. for blade in 5: var bx := x + (blade - 2.0) * w * 0.34 var bh := h * (0.55 + _hash(seed_id, 10 + blade) * 0.7) draw_polygon( PackedVector2Array([ Vector2(bx - w * 0.16, base_y), Vector2(bx + w * 0.16, base_y), Vector2(bx + _hash(seed_id, 20 + blade) * 4.0 - 2.0, base_y - bh), ]), PackedColorArray([shade, shade, Color(shade, 0.0)])) else: # 먼 바위. 낮고 둥근 덩어리. var pts := PackedVector2Array() var sides := 7 for s in sides + 1: var a := PI + PI * (float(s) / sides) pts.append(Vector2(x + cos(a) * w * 1.6, base_y + sin(a) * h * 0.62)) pts.append(Vector2(x + w * 1.6, base_y)) pts.append(Vector2(x - w * 1.6, base_y)) draw_colored_polygon(pts, shade)