using System.Collections.Generic;
using System.Globalization;
using UnityEngine;
using UnityEngine.UI;
namespace TKS.OutGame
{
///
/// 로딩 화면 종류(PD 2026-11-13 「원격 다운로드가 필요한 시점 · 로딩이 필요한 시점 2 경우에 사용」) — 표 s_set 이 모드마다 다른 화면을 고른다.
/// Download = 원격 리소스 받기(배경 + 위 타이틀 로고 + 조운·병졸 + 용량) · Loading = 그 밖의 로딩(검은 화면 + 유비·관우·장비 말 타고 질주 · 심플).
///
public enum LoadingMode
{
Download,
Loading
}
///
/// 로딩·리소스 다운로드 화면(PD 2026-11-13) — 진행 바 위에서 무리가 제자리로 달리고(프레임 반복) 말 뒤로 먼지가 일어 흩어진다.
/// 다운로드 = 풀 덮인 땅 바 위를 조운(아두를 품음)이 백마로 질주 · 뒤를 병졸이 따름 · 위에 타이틀 로고 · 진행률·용량.
/// 로딩 = 배경 없이 검은 화면 · 단순 바 위를 유비·관우·장비가 각자 말을 타고 함께 질주 · 「Loading...」.
/// 값(자리·크기·속도·문구 · 세트별) = 표 Resources/UI/loading_screen.csv(LoadingTable · s_set = download | loading | 빈칸 = 공통 · C45) · 그림 = Resources/UI/Loading/.
/// 캔버스(P41 · 갱신 빈도별): 루트 Static(배경 · 로고 · 바 틀) → Live = Frame(바 채움 · 먼지 · 달리는 무리 · 점 3개) → Info = Event(상태 문구 · 진행률 · 용량).
/// 연결점: Show(cam, mode) → SetProgress(0~1) 또는 SetDownload(받은 바이트, 전체 바이트) → Hide(). 모드를 바꾸면 그 세트로 다시 만든다(같은 객체).
/// 게임에는 아직 리소스 다운로드·씬 비동기 로딩 흐름이 없다(Addressables 미도입 · 2026-11-13 실측) — 부르는 자리는 그 흐름을 만들 때 연결.
///
public class LoadingScreen : MonoBehaviour
{
public static LoadingScreen Current { get; private set; }
///
/// 이 화면의 시계(초) — 기본 = Time.unscaledTime(로딩 중 timeScale 과 무관). 확인 캡처만 Time.time + Time.captureDeltaTime 으로 바꿔
/// 원본 크기 화면 읽기가 느려도 캡처 한 장 = 게임 1/16 초(영상 간격 = 실제 재생 · UICaptureDriver.LoadingShots).
///
public static System.Func Clock;
public static float Now
{
get { return Clock != null ? Clock() : Time.unscaledTime; }
}
/// 모드 → 표 세트 이름.
public static string SetOf(LoadingMode m)
{
return m == LoadingMode.Download ? "download" : "loading";
}
float lastNow = -1f;
Camera cam;
public LoadingMode Mode { get; private set; }
/// 지금 화면을 만든 표 세트(download | loading).
public string Set { get; private set; }
/// 목표 진행률(0~1 · 마지막으로 받은 값).
public float Target { get; private set; }
/// 화면에 보이는 진행률(목표를 향해 부드럽게 따라감).
public float Shown { get; private set; }
public long DoneBytes { get; private set; }
public long TotalBytes { get; private set; }
public Canvas RootCanvas { get; private set; }
public RectTransform Live { get; private set; }
public RectTransform Info { get; private set; }
public Image Fill { get; private set; }
public Image Logo { get; private set; }
public Text StatusText { get; private set; }
public Text PercentText { get; private set; }
public Text SizeText { get; private set; }
public Text DotsText { get; private set; }
public readonly List Runners = new List();
public readonly List Dusts = new List();
LoadingTable.Row rowStatus;
LoadingTable.Row rowPercent;
LoadingTable.Row rowSize;
LoadingTable.Row rowDots;
float smooth = 1.5f;
float dotStep = 0.4f;
int dotMax = 3;
int shownPct = -1;
int dots = -1;
/// 화면을 띄운다(이미 있으면 그것을 씀 · 모드가 다른 세트면 다시 만듦) · cam = 아웃게임과 같은 카메라(Screen Space - Camera · 캡처에도 찍힘).
public static LoadingScreen Show(Camera cam, LoadingMode mode)
{
if (Current == null)
{
var go = new GameObject("LoadingScreen");
Current = go.AddComponent();
Current.cam = cam;
}
Current.SetMode(mode);
Current.gameObject.SetActive(true);
return Current;
}
public static void HideCurrent()
{
if (Current != null) Current.Hide();
}
public void Hide()
{
if (Current == this) Current = null;
Destroy(gameObject);
}
void OnDestroy()
{
if (Current == this) Current = null;
}
/// 진행률 0~1 · instant = 부드럽게 따라가지 않고 바로(첫 표시 · 테스트).
public void SetProgress(float p01, bool instant = false)
{
Target = Mathf.Clamp01(p01);
if (instant) Shown = Target;
Apply();
}
/// 받은 바이트 / 전체 바이트 → 용량 글자(그 세트에 size 줄이 있을 때) + 진행률(전체 0 이면 진행률은 그대로).
public void SetDownload(long doneBytes, long totalBytes, bool instant = false)
{
DoneBytes = System.Math.Max(0L, doneBytes);
TotalBytes = System.Math.Max(0L, totalBytes);
ApplySize();
if (TotalBytes > 0) SetProgress((float)((double)DoneBytes / TotalBytes), instant);
}
/// 모드 바꾸기 — 표 세트가 다르면 같은 객체 안에서 화면을 다시 만든다(진행률·용량 값은 유지).
public void SetMode(LoadingMode m)
{
Mode = m;
string set = SetOf(m);
if (Set != set) Rebuild(set);
}
/// 1 MB = 1024² 바이트 · 소수 한 자리.
public static string Bytes(long b)
{
double mb = b / (1024.0 * 1024.0);
if (mb >= 1024.0) return (mb / 1024.0).ToString("0.0", CultureInfo.InvariantCulture) + " GB";
if (mb >= 0.1) return mb.ToString("0.0", CultureInfo.InvariantCulture) + " MB";
return (b / 1024.0).ToString("0", CultureInfo.InvariantCulture) + " KB";
}
void Rebuild(string set)
{
// 이전 세트의 자식은 지움(Destroy = 이번 프레임 그리기 전에 사라짐) · 루트 캔버스 컴포넌트는 그대로 다시 씀
for (int i = transform.childCount - 1; i >= 0; i--)
{
var c = transform.GetChild(i).gameObject;
c.SetActive(false);
Destroy(c);
}
Runners.Clear();
Dusts.Clear();
Fill = null;
Logo = null;
StatusText = PercentText = SizeText = DotsText = null;
rowStatus = rowPercent = rowSize = rowDots = null;
shownPct = -1;
dots = -1;
Set = set;
Build(set);
ApplySize();
}
void Build(string set)
{
var opt = LoadingTable.First("option", "canvas", set);
int order = opt != null ? (int)opt.a : 900;
float plane = opt != null && opt.b > 0f ? opt.b : 0.3f;
RootCanvas = UiLayer.Root(gameObject, cam, order, plane, UiLayer.Reference, UiRate.Static);
var o = LoadingTable.First("option", "smooth", set);
if (o != null && o.a > 0f) smooth = o.a;
o = LoadingTable.First("option", "dots", set);
if (o != null)
{
if (o.a > 0f) dotStep = o.a;
if (o.size > 0) dotMax = o.size;
}
var root = (RectTransform)transform;
// 배경(화면을 덮음 · 아래 게임 화면 누름을 막음) · 그림이 없으면 색만(로딩 = 검정)
foreach (var r in LoadingTable.All("bg", set))
{
var img = UiKit.Fill("Bg", root).gameObject.AddComponent();
img.sprite = LoadingTable.Sprite(r.sprite);
img.color = r.color;
img.raycastTarget = true;
if (img.sprite != null)
{
var fit = img.gameObject.AddComponent();
fit.aspectMode = AspectRatioFitter.AspectMode.EnvelopeParent;
fit.aspectRatio = img.sprite.rect.width / img.sprite.rect.height;
}
}
// 타이틀 로고(다운로드 · 비율 유지 · f_w×f_h 상자 안)
foreach (var r in LoadingTable.All("logo", set))
{
Logo = Pic("Logo", root, r.sprite);
Place(Logo, r);
Logo.preserveAspect = true;
}
foreach (var r in LoadingTable.All("bar", set))
Place(Pic("Bar", root, r.sprite), r);
// 매 프레임: 바 채움 · 먼지 · 달리는 무리 · 점
Live = UiLayer.Group("Live", root, UiRate.Frame);
foreach (var r in LoadingTable.All("fill", set))
{
Fill = Pic("Fill", Live, r.sprite);
Place(Fill, r);
Fill.type = Image.Type.Filled;
Fill.fillMethod = Image.FillMethod.Horizontal;
Fill.fillOrigin = (int)Image.OriginHorizontal.Left;
Fill.fillAmount = 0f;
}
var dustRoot = UiKit.Fill("Dust", Live);
var runnerRoot = UiKit.Fill("Runners", Live);
var runnerByID = new Dictionary();
foreach (var r in LoadingTable.All("runner", set))
{
var frames = LoadingTable.Frames(r.sprite);
if (frames.Length == 0) continue;
var img = Pic("Runner_" + r.id, runnerRoot, null);
img.sprite = frames[0];
var rt = (RectTransform)img.transform;
rt.pivot = new Vector2(0.5f, 0f); // 발(그림 아래 가운데) = 표 좌표
float aspect = frames[0].rect.width / frames[0].rect.height;
rt.sizeDelta = new Vector2(r.h * aspect * Mathf.Abs(r.scale), r.h * Mathf.Abs(r.scale));
rt.anchoredPosition = Pos(r.x, r.y);
if (r.scale < 0f) rt.localScale = new Vector3(-1f, 1f, 1f);
var loop = UiSpriteLoop.Attach(img, frames, r.fps, r.phase);
loop.name = "Runner_" + r.id;
Runners.Add(loop);
runnerByID[r.id] = loop;
}
foreach (var r in LoadingTable.All("dust", set))
{
UiSpriteLoop host;
runnerByID.TryGetValue(r.reference, out host);
// 먼지 = 달리는 무리 전부의 뒤(앞 말 먼지가 뒤따르는 말 다리를 덮지 않게 · QA r3 M1 · r2 M2 · PD 2026-11-13 「경미한 수정사항 모두 반영」)
var d = LoadingDust.Create(dustRoot, r, host, Pos);
if (d == null) continue;
Dusts.Add(d);
}
// 진행률·상태 글자(값이 바뀔 때만)
Info = UiLayer.Group("Info", root, UiRate.Event);
rowStatus = LoadingTable.First("text", "status", set);
rowPercent = LoadingTable.First("text", "percent", set);
rowSize = LoadingTable.First("text", "size", set);
rowDots = LoadingTable.First("text", "dots", set);
if (rowStatus != null) StatusText = Txt("Status", Info, rowStatus);
if (rowPercent != null) PercentText = Txt("Percent", Info, rowPercent);
if (rowSize != null) SizeText = Txt("Size", Info, rowSize);
if (rowDots != null)
{
DotsText = Txt("Dots", Live, rowDots);
DotsText.alignment = TextAnchor.MiddleLeft;
var drt = (RectTransform)DotsText.transform;
drt.pivot = new Vector2(0f, 0.5f);
DotsText.text = "";
// 점 = 상태 문구 오른쪽 끝 바로 뒤(문구는 가운데 정렬)
if (StatusText != null)
{
var srt = (RectTransform)StatusText.transform;
drt.anchoredPosition = new Vector2(srt.anchoredPosition.x + StatusText.preferredWidth * 0.5f + rowDots.x, srt.anchoredPosition.y + rowDots.y);
}
}
Apply();
}
void Update()
{
if (RootCanvas == null) return; // Play 중 재컴파일 = 직렬화 안 된 값이 비면 멈춤
float now = Now;
float dt = lastNow < 0f ? 0f : Mathf.Max(0f, now - lastNow);
lastNow = now;
if (!Mathf.Approximately(Shown, Target))
{
Shown = Mathf.MoveTowards(Shown, Target, smooth * dt);
Apply();
}
if (DotsText != null && rowDots != null)
{
int n = (int)(now / dotStep) % (dotMax + 1);
if (n != dots)
{
dots = n;
DotsText.text = new string(rowDots.text.Length > 0 ? rowDots.text[0] : '.', n);
}
}
}
void Apply()
{
if (Fill != null && !Mathf.Approximately(Fill.fillAmount, Shown)) Fill.fillAmount = Shown;
int pct = Mathf.FloorToInt(Shown * 100f + 0.0001f);
if (PercentText != null && rowPercent != null && pct != shownPct)
{
shownPct = pct;
PercentText.text = string.Format(CultureInfo.InvariantCulture, rowPercent.text, pct);
}
}
void ApplySize()
{
if (SizeText != null && rowSize != null)
SizeText.text = string.Format(CultureInfo.InvariantCulture, rowSize.text, Bytes(DoneBytes), Bytes(TotalBytes));
}
// ---------- 만들기 도움 ----------
/// 표 좌표(기준 화면 1080×1920 px · 왼쪽 위 0,0 · 아래로 +) → 가운데 기준 anchoredPosition.
static Vector2 Pos(float x, float y)
{
return new Vector2(x - UiLayer.Reference.x * 0.5f, UiLayer.Reference.y * 0.5f - y);
}
static Image Pic(string name, Transform parent, string sprite)
{
var rt = UiKit.Rect(name, parent, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, new Vector2(100f, 100f));
var img = rt.gameObject.AddComponent();
if (!string.IsNullOrEmpty(sprite)) img.sprite = LoadingTable.Sprite(sprite);
img.raycastTarget = false;
return img;
}
static void Place(Image img, LoadingTable.Row r)
{
var rt = (RectTransform)img.transform;
rt.anchoredPosition = Pos(r.x, r.y);
rt.sizeDelta = new Vector2(r.w, r.h);
img.color = r.color;
}
static Text Txt(string name, Transform parent, LoadingTable.Row r)
{
var t = UiKit.Label(parent, r.text, r.size, r.color, TextAnchor.MiddleCenter, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Pos(r.x, r.y), new Vector2(r.w, r.h));
t.name = name;
t.horizontalOverflow = HorizontalWrapMode.Overflow;
UiKit.Title(t);
if (r.b > 0f)
{
Color sc;
if (!ColorUtility.TryParseHtmlString(r.reference, out sc)) sc = UiKit.Stroke;
UISkin.Stroke(t, sc, r.b);
}
return t;
}
}
/// UI 이미지 프레임 반복(로딩 화면 달리기 · 고른 간격 fps · 시작 위상 초) — 매 프레임 바뀌므로 Frame 캔버스 안에서만(UiLayer.Isolate).
public class UiSpriteLoop : MonoBehaviour
{
public Image Img { get; private set; }
public Sprite[] Frames { get; private set; }
public float Fps { get; private set; }
public int Index { get; private set; }
float t0;
public static UiSpriteLoop Attach(Image img, Sprite[] frames, float fps, float phase)
{
if (img == null || frames == null || frames.Length == 0) return null;
var a = img.gameObject.AddComponent();
a.Img = img;
a.Frames = frames;
a.Fps = fps > 0f ? fps : 12f;
a.t0 = LoadingScreen.Now - phase;
UiLayer.Isolate(img.gameObject);
return a;
}
/// 지금 시각의 프레임 번호(0 ~ n-1).
public int FrameAt(float now)
{
int n = Frames.Length;
int k = Mathf.FloorToInt((now - t0) * Fps) % n;
return k < 0 ? k + n : k;
}
void Update()
{
if (Img == null || Frames == null || Frames.Length == 0) return;
Index = FrameAt(LoadingScreen.Now);
var sp = Frames[Index];
if (Img.sprite != sp) Img.sprite = sp;
}
}
///
/// 말 뒤 먼지(표 dust 줄) — 달리는 그림의 정해진 프레임(발굽이 땅을 찰 때 · s_text 「on:3,10」)마다 또는 초당 f_fps 개로 먼지 덩이를 내고,
/// 덩이는 뒤(왼쪽)로 밀리며 떠올라 커지고 옅어진다. 덩이 수 = i_size 개 재사용(만들고 지우지 않음).
///
public class LoadingDust : MonoBehaviour
{
class Puff
{
public RectTransform rt;
public Image img;
public float t = -1f;
public float x;
public float y;
public float s;
public float rot;
public float vx;
}
readonly List pool = new List();
LoadingTable.Row row;
UiSpriteLoop host;
Vector2 origin;
Sprite[] sprites;
int[] onFrames;
int lastFrame = -1;
float acc;
int next;
float lastNow = -1f;
System.Random rng;
public int Alive
{
get
{
int n = 0;
foreach (var p in pool)
if (p.t >= 0f)
n++;
return n;
}
}
public static LoadingDust Create(Transform parent, LoadingTable.Row r, UiSpriteLoop host, System.Func pos)
{
var sprites = LoadingTable.SpriteList(r.sprite);
if (sprites.Length == 0) return null;
var rt = UiKit.Fill("Dust_" + r.id, parent);
var d = rt.gameObject.AddComponent();
d.row = r;
d.host = host;
d.sprites = sprites;
d.rng = new System.Random(r.id.GetHashCode());
// 먼지 기준점 = 표 f_x,f_y(기준 화면 px) · 달리는 그림을 따라 붙이지 않고 고정(무리도 제자리)
d.origin = pos(r.x, r.y);
if (r.text.StartsWith("on:"))
{
var parts = r.text.Substring(3).Split(new[] { ',', ';' }, System.StringSplitOptions.RemoveEmptyEntries);
var list = new List();
foreach (var s in parts)
{
int v;
if (int.TryParse(s.Trim(), out v)) list.Add(v);
}
d.onFrames = list.ToArray();
}
int n = Mathf.Max(1, r.size);
for (int i = 0; i < n; i++)
{
var prt = UiKit.Rect("Puff", rt, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, new Vector2(r.w, r.w));
var img = prt.gameObject.AddComponent();
img.raycastTarget = false;
img.sprite = sprites[i % sprites.Length];
img.preserveAspect = true;
img.color = new Color(r.color.r, r.color.g, r.color.b, 0f);
prt.gameObject.SetActive(false);
d.pool.Add(new Puff { rt = prt, img = img });
}
UiLayer.Isolate(rt.gameObject);
return d;
}
void Emit()
{
var p = pool[next];
next = (next + 1) % pool.Count;
float j = row.phase; // 흩어짐 px
p.t = 0f;
p.x = origin.x + (float)(rng.NextDouble() * 2 - 1) * j;
p.y = origin.y + (float)(rng.NextDouble() * 2 - 1) * j * 0.4f;
p.s = 0.75f + (float)rng.NextDouble() * 0.5f;
p.rot = (float)(rng.NextDouble() * 2 - 1) * 25f;
p.vx = row.a * (0.8f + (float)rng.NextDouble() * 0.4f);
p.img.sprite = sprites[rng.Next(sprites.Length)];
p.rt.gameObject.SetActive(true);
}
void Update()
{
if (row == null || pool.Count == 0) return;
float now = LoadingScreen.Now;
float dt = lastNow < 0f ? 0f : Mathf.Max(0f, now - lastNow);
lastNow = now;
if (onFrames != null && onFrames.Length > 0 && host != null)
{
int f = host.Index;
if (f != lastFrame)
{
foreach (var k in onFrames)
if (k == f)
Emit();
lastFrame = f;
}
}
else if (row.fps > 0f)
{
acc += dt * row.fps;
while (acc >= 1f)
{
acc -= 1f;
Emit();
}
}
float life = row.b > 0f ? row.b : 0.6f;
foreach (var p in pool)
{
if (p.t < 0f) continue;
p.t += dt;
float u = p.t / life;
if (u >= 1f)
{
p.t = -1f;
p.rt.gameObject.SetActive(false);
continue;
}
float ease = 1f - (1f - u) * (1f - u);
p.rt.anchoredPosition = new Vector2(p.x + p.vx * p.t, p.y + row.c * ease);
float size = Mathf.Lerp(row.w, row.h, ease) * p.s;
p.rt.sizeDelta = new Vector2(size, size);
p.rt.localEulerAngles = new Vector3(0f, 0f, p.rot);
// 처음 15 % 는 나타나고 그 뒤로 옅어짐
float a = u < 0.15f ? u / 0.15f : 1f - (u - 0.15f) / 0.85f;
p.img.color = new Color(row.color.r, row.color.g, row.color.b, row.color.a * a);
}
}
}
///
/// 로딩 화면 표(Resources/UI/loading_screen.csv · utf-8-sig) — 좌표 = 기준 화면 1080×1920 px · 왼쪽 위 0,0 · 아래로 +.
/// s_kind: bg · logo · bar · fill · runner · dust · text · option · s_set = download | loading | 빈칸(공통). 칸 설명 = 표 머리 주석.
///
public static class LoadingTable
{
public const string TablePath = "UI/loading_screen";
public class Row
{
public string kind = "";
/// 표 세트(download | loading · 빈칸 = 모든 세트 공통).
public string set = "";
public string id = "";
public string sprite = "";
public string reference = "";
public string text = "";
public string note = "";
public Color color = Color.white;
public float x;
public float y;
public float w;
public float h;
public float fps;
public float phase;
public float scale = 1f;
public float a;
public float b;
public float c;
public int size;
}
static List rows;
public static void Reload()
{
rows = null;
}
public static List Rows
{
get
{
if (rows == null) rows = Load(TablePath);
return rows;
}
}
static bool InSet(Row r, string set)
{
return set == null || r.set.Length == 0 || r.set == set;
}
/// kind(·id) 의 첫 줄 · set = 그 세트 줄 + 공통 줄만(null = 모든 줄).
public static Row First(string kind, string id = null, string set = null)
{
foreach (var r in Rows)
if (r.kind == kind && (id == null || r.id == id) && InSet(r, set))
return r;
return null;
}
public static List All(string kind, string set = null)
{
var list = new List();
foreach (var r in Rows)
if (r.kind == kind && InSet(r, set))
list.Add(r);
return list;
}
public static Sprite Sprite(string path)
{
return string.IsNullOrEmpty(path) ? null : Resources.Load(path);
}
/// | 로 나눈 스프라이트 경로들.
public static Sprite[] SpriteList(string paths)
{
var list = new List();
foreach (var p in paths.Split(new[] { '|' }, System.StringSplitOptions.RemoveEmptyEntries))
{
var s = Sprite(p.Trim());
if (s != null) list.Add(s);
}
return list.ToArray();
}
/// 폴더 안 스프라이트 전부(이름 순 = 프레임 순).
public static Sprite[] Frames(string folder)
{
var all = Resources.LoadAll(folder);
System.Array.Sort(all, (p, q) => string.CompareOrdinal(p.name, q.name));
return all;
}
public static List Load(string path)
{
var list = new List();
var ta = Resources.Load(path);
if (ta == null) return list;
string[] head = null;
foreach (var raw in ta.text.Replace("\r", "").Split('\n'))
{
var line = raw.TrimStart('');
if (line.Trim().Length == 0 || line.TrimStart().StartsWith("#")) continue;
var cells = SkillVfx.SplitCsv(line);
if (head == null)
{
head = cells;
continue;
}
var r = new Row();
for (int i = 0; i < head.Length && i < cells.Length; i++)
{
string k = head[i].Trim(), v = cells[i].Trim();
if (v.Length == 0) continue;
switch (k)
{
case "s_kind": r.kind = v.ToLowerInvariant(); break;
case "s_set": r.set = v.ToLowerInvariant(); break;
case "s_id": r.id = v; break;
case "s_sprite": r.sprite = v; break;
case "s_ref": r.reference = v; break;
case "s_text": r.text = v; break;
case "s_note": r.note = v; break;
case "s_color":
Color c;
if (ColorUtility.TryParseHtmlString(v, out c)) r.color = c;
break;
case "f_x": r.x = F(v); break;
case "f_y": r.y = F(v); break;
case "f_w": r.w = F(v); break;
case "f_h": r.h = F(v); break;
case "f_fps": r.fps = F(v); break;
case "f_phase": r.phase = F(v); break;
case "f_scale": r.scale = F(v); break;
case "f_a": r.a = F(v); break;
case "f_b": r.b = F(v); break;
case "f_c": r.c = F(v); break;
case "i_size": r.size = (int)F(v); break;
}
}
if (r.kind.Length > 0) list.Add(r);
}
return list;
}
static float F(string v)
{
float f;
return float.TryParse(v, NumberStyles.Float, CultureInfo.InvariantCulture, out f) ? f : 0f;
}
}
}