using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace TKS.OutGame
{
///
/// 로비 배경 연출(PD 2026-11-11 「펄럭이는 깃발 · 환하게 비쳐진 관청의 불빛이나 등불 · 가끔 날아다니는 새」) — 값 = 표 lobby_scene.csv(LobbyScene · C45).
/// 한 Frame 캔버스(「Ambient」 · 배경 그림 위 · 영웅 아래)에 모두 담는다 — 매 프레임 바뀌는 것끼리만 다시 묶임(P41 · 화면 캔버스는 그대로).
/// 좌표 = 배경 그림 px(그림 크기 artW × artH) → 이 층(그림을 꽉 채움)의 단위로 매 프레임 환산(그림이 화면 폭에 맞춰 커져도 같은 자리).
///
public class LobbyAmbient : MonoBehaviour
{
class Glow
{
public Graphic g;
public LobbyScene.Row row;
public float baseA;
}
class Mover
{
public RectTransform rt;
public Image img;
public Sprite[] frames;
public float x;
public float y;
public float vx;
public float vy;
public float size;
public float t;
public float life;
public float ph;
public float rot;
public float vrot;
public bool on;
}
RectTransform area;
RectTransform back;
RectTransform front;
float artW;
float artH;
readonly List glows = new List();
readonly List birds = new List();
readonly List petals = new List();
LobbyScene.Row birdRow;
LobbyScene.Row petalRow;
float nextFlock;
float nextPetal;
float openedAt;
/// 영웅 자리 층(그림을 꽉 채움 · 그림 좌표 = 정규화 앵커) — 뒤 연출(깃발·불빛)과 앞 연출(새·꽃잎) 사이.
public RectTransform Lineup { get; private set; }
///
/// 그림(art) 위에 장면 층(Frame 캔버스 1장)을 만든다 — 순서 = Back(깃발·불빛) → Lineup(그림자·영웅 · 대기 프레임 재생) → Front(새·꽃잎 · PD 2026-11-11 「벚꽃잎은 유닛보다 앞」).
/// 영웅 대기 프레임이 매 프레임 바뀌므로 영웅도 이 Frame 층에 둔다(화면 캔버스 = Event 그대로 · P41).
///
public static LobbyAmbient Build(RectTransform art, float artW, float artH)
{
var layer = UiLayer.Group("Ambient", art, UiRate.Frame);
var a = layer.gameObject.AddComponent();
a.area = layer;
a.back = UiKit.Fill("Back", layer);
a.Lineup = UiKit.Fill("Lineup", layer);
a.front = UiKit.Fill("Front", layer);
a.artW = artW;
a.artH = artH;
a.Setup();
return a;
}
/// 그림 px 사각형(왼쪽 위 x,y · 폭 w · 높이 h) = 정규화 앵커(그림과 함께 늘어남) · 뒤 층.
RectTransform ArtRect(string name, float x, float y, float w, float h)
{
var rt = UiKit.Rect(name, back, new Vector2(0f, 1f), new Vector2(0.5f, 0.5f), Vector2.zero, Vector2.zero);
rt.anchorMin = new Vector2(x / artW, 1f - (y + h) / artH);
rt.anchorMax = new Vector2((x + w) / artW, 1f - y / artH);
rt.offsetMin = rt.offsetMax = Vector2.zero;
return rt;
}
static Sprite[] Load(LobbyScene.Row r)
{
var list = new List();
foreach (var id in r.Sprites)
{
var sp = UISkin.Icon(id);
if (sp != null) list.Add(sp);
}
return list.ToArray();
}
void Setup()
{
// 깃발(원래 깃발 위에 같은 자리 · UiWaveImage)
foreach (var r in LobbyScene.All("flag"))
{
var sp = Load(r);
if (sp.Length == 0) continue;
var rt = ArtRect("Flag_" + sp[0].name, r.x, r.y, r.w, r.h);
var wv = rt.gameObject.AddComponent();
wv.sprite = sp[0];
wv.raycastTarget = false;
wv.amp = r.amp;
wv.period = r.period;
wv.waves = r.waves;
wv.phase = r.phase;
wv.billow = Mathf.Max(0f, r.billow);
if (r.shade >= 0f) wv.shade = r.shade;
wv.color = r.color;
}
// 불빛(관청 창 · 문 앞 번짐 · 석등) = 알파 깜박임
foreach (var r in LobbyScene.All("light"))
{
var sp = Load(r);
if (sp.Length == 0) continue;
var img = ArtRect("Light_" + sp[0].name, r.x, r.y, r.w, r.h).gameObject.AddComponent();
img.sprite = sp[0];
img.raycastTarget = false;
img.color = r.color;
glows.Add(new Glow { g = img, row = r, baseA = r.color.a });
}
birdRow = LobbyScene.First("bird");
petalRow = LobbyScene.First("petal");
openedAt = Time.unscaledTime;
if (birdRow != null)
{
var fr = Load(birdRow);
for (int i = 0; i < Mathf.Max(0, birdRow.count) && fr.Length > 0; i++) birds.Add(NewMover("Bird" + i, fr));
nextFlock = openedAt + birdRow.first;
}
if (petalRow != null)
{
var fr = Load(petalRow);
for (int i = 0; i < Mathf.Max(0, petalRow.count) && fr.Length > 0; i++) petals.Add(NewMover("Petal" + i, fr));
nextPetal = openedAt + petalRow.first;
}
}
Mover NewMover(string name, Sprite[] frames)
{
var rt = UiKit.Rect(name, front, new Vector2(0f, 1f), new Vector2(0.5f, 0.5f), Vector2.zero, Vector2.zero);
var img = rt.gameObject.AddComponent();
img.raycastTarget = false;
img.sprite = frames[0];
rt.gameObject.SetActive(false);
return new Mover { rt = rt, img = img, frames = frames };
}
void OnEnable()
{
// 화면을 다시 열면 새 떼 · 꽃잎 시계를 다시 시작(열자마자 하나는 보이게 = 표 f_first)
float now = Time.unscaledTime;
if (birdRow != null) nextFlock = now + birdRow.first;
if (petalRow != null) nextPetal = now + petalRow.first;
foreach (var m in birds) Hide(m);
foreach (var m in petals) Hide(m);
}
static void Hide(Mover m)
{
m.on = false;
if (m.rt != null) m.rt.gameObject.SetActive(false);
}
static float Flicker(LobbyScene.Row r, float t)
{
// 촛불 = 느린 물결 + 빠른 떨림(두 사인의 합 · 0~1)
float p = Mathf.Max(0.05f, r.period);
float s = 0.62f * Mathf.Sin((t / p + r.phase) * Mathf.PI * 2f) + 0.38f * Mathf.Sin((t / p * 2.71f + r.phase * 1.7f) * Mathf.PI * 2f);
return Mathf.Lerp(r.min, r.max, 0.5f + 0.5f * s);
}
void Update()
{
float t = Time.unscaledTime;
float dt = Mathf.Min(0.1f, Time.unscaledDeltaTime);
float unit = area.rect.width / Mathf.Max(1f, artW);
if (unit <= 0f) return;
foreach (var g in glows)
{
var c = g.g.color;
c.a = g.baseA * Flicker(g.row, t);
g.g.color = c;
}
UpdateBirds(t, dt, unit);
UpdatePetals(t, dt, unit);
}
void UpdateBirds(float t, float dt, float unit)
{
if (birdRow == null || birds.Count == 0) return;
bool any = false;
foreach (var b in birds) any |= b.on;
if (!any && t >= nextFlock)
{
// 새 떼 1번 — 왼→오 또는 오→왼 · 앞장선 새 뒤로 비스듬히
int n = Random.Range(1, birds.Count + 1);
float dir = Random.value < 0.5f ? 1f : -1f;
float y0 = birdRow.y + Random.value * birdRow.h;
float sp = birdRow.speed * Random.Range(0.85f, 1.15f);
for (int i = 0; i < n; i++)
{
var b = birds[i];
b.on = true;
b.size = birdRow.w * Random.Range(0.85f, 1.05f);
b.x = dir > 0f ? -b.size - i * b.size * 1.3f : artW + b.size + i * b.size * 1.3f;
// 뒤따르는 새 = 위·아래 번갈아 조금씩(떼가 세로로 퍼지면 좌우 버튼 열 사이 틈에 걸림 · QA r1 M5)
b.y = y0 + (i % 2 == 1 ? -1f : 1f) * ((i + 1) / 2) * b.size * 0.3f;
b.vx = dir * sp;
b.ph = Random.value;
b.rt.gameObject.SetActive(true);
b.rt.localScale = new Vector3(dir, 1f, 1f); // 그림 = 오른쪽을 봄
}
nextFlock = t + Random.Range(birdRow.gapMin, birdRow.gapMax);
}
foreach (var b in birds)
{
if (!b.on) continue;
b.x += b.vx * dt;
float bob = birdRow.amp * Mathf.Sin((t / Mathf.Max(0.05f, birdRow.period) * 0.5f + b.ph) * Mathf.PI * 2f);
int f = (int)((t / Mathf.Max(0.05f, birdRow.period) + b.ph) * b.frames.Length) % b.frames.Length;
b.img.sprite = b.frames[f];
var sz = b.frames[f].rect.size;
float k = b.size / Mathf.Max(1f, sz.x);
b.rt.sizeDelta = sz * k * unit;
b.rt.anchoredPosition = new Vector2(b.x * unit, -(b.y + bob) * unit);
if ((b.vx > 0f && b.x > artW + b.size * 2f) || (b.vx < 0f && b.x < -b.size * 2f))
{
Hide(b);
nextFlock = Mathf.Max(nextFlock, t + Random.Range(birdRow.gapMin, birdRow.gapMax));
}
}
}
void UpdatePetals(float t, float dt, float unit)
{
if (petalRow == null || petals.Count == 0) return;
if (t >= nextPetal)
{
foreach (var p in petals)
{
if (p.on) continue;
p.on = true;
p.x = petalRow.x + Random.value * petalRow.w;
p.y = petalRow.y + Random.value * petalRow.h;
p.vy = petalRow.speed * Random.Range(0.8f, 1.2f);
p.vx = petalRow.speed * Random.Range(0.35f, 0.7f);
p.size = petalRow.scale * Random.Range(0.8f, 1.15f);
p.ph = Random.value;
p.t = 0f;
p.life = Mathf.Max(0.5f, petalRow.period * Random.Range(0.8f, 1.2f));
p.rot = Random.value * 360f;
p.vrot = Random.Range(-90f, 90f);
p.img.sprite = p.frames[Random.Range(0, p.frames.Length)];
p.rt.gameObject.SetActive(true);
break;
}
nextPetal = t + Random.Range(petalRow.gapMin, petalRow.gapMax);
}
foreach (var p in petals)
{
if (!p.on) continue;
p.t += dt;
if (p.t >= p.life)
{
Hide(p);
continue;
}
p.x += p.vx * dt;
p.y += p.vy * dt;
p.rot += p.vrot * dt;
float sway = petalRow.amp * Mathf.Sin((p.t * 1.3f + p.ph) * Mathf.PI * 2f);
var sz = p.img.sprite.rect.size;
float k = p.size / Mathf.Max(1f, Mathf.Max(sz.x, sz.y));
p.rt.sizeDelta = sz * k * unit;
p.rt.anchoredPosition = new Vector2((p.x + sway) * unit, -p.y * unit);
p.rt.localEulerAngles = new Vector3(0f, 0f, p.rot);
// 나타남(f_min 초) · 사라짐(f_max 초) — PD 2026-11-11 「짧게 떨어지는 느낌으로 보이다 투명하게 없어지도록」
float fin = Mathf.Max(0.05f, petalRow.min), fout = Mathf.Max(0.05f, petalRow.max);
float a = Mathf.Clamp01(Mathf.Min(p.t / fin, (p.life - p.t) / fout));
var c = p.img.color;
if (!Mathf.Approximately(c.a, a))
{
c.a = a;
p.img.color = c;
}
}
}
}
}