using System.Collections.Generic; using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif namespace SplashEdit.RuntimeCode { /// /// Collects all PSXCanvas hierarchies in the scene, bakes RectTransform /// coordinates into PS1 pixel space, and produces /// arrays ready for binary serialization. /// public static class PSXUIExporter { /// /// Collect all PSXCanvas components and their child UI elements, /// converting RectTransform coordinates to PS1 pixel space. /// Also collects and deduplicates custom fonts. /// /// Target PS1 resolution (e.g. 320×240). /// Output: collected custom font data (max 3). /// Array of canvas data ready for binary writing. public static PSXCanvasData[] CollectCanvases(Vector2 resolution, out PSXFontData[] fonts) { // Collect and deduplicate all custom fonts used by text elements List uniqueFonts = new List(); #if UNITY_EDITOR PSXCanvas[] canvases = Object.FindObjectsByType(FindObjectsSortMode.None); #else PSXCanvas[] canvases = Object.FindObjectsOfType(); #endif if (canvases == null || canvases.Length == 0) { fonts = new PSXFontData[0]; return new PSXCanvasData[0]; } return CollectCanvasesInternal(canvases, resolution, out fonts); } /// /// Collect a single canvas from a prefab instance for loading screen export. /// The prefab must have a PSXCanvas on its root. /// Note: Image elements that reference VRAM textures will NOT work in loading screens /// since the VRAM hasn't been populated yet. Use Box, Text, and ProgressBar only. /// public static PSXCanvasData[] CollectCanvasFromPrefab(GameObject prefab, Vector2 resolution, out PSXFontData[] fonts) { if (prefab == null) { fonts = new PSXFontData[0]; return new PSXCanvasData[0]; } PSXCanvas canvas = prefab.GetComponentInChildren(true); if (canvas == null) { Debug.LogWarning($"PSXUIExporter: Prefab '{prefab.name}' has no PSXCanvas component."); fonts = new PSXFontData[0]; return new PSXCanvasData[0]; } return CollectCanvasesInternal(new[] { canvas }, resolution, out fonts); } /// /// Internal shared implementation for canvas collection. /// Works on an explicit array of PSXCanvas components. /// private static PSXCanvasData[] CollectCanvasesInternal(PSXCanvas[] canvases, Vector2 resolution, out PSXFontData[] fonts) { List uniqueFonts = new List(); // First pass: collect unique fonts foreach (PSXCanvas canvas in canvases) { PSXUIText[] texts = canvas.GetComponentsInChildren(true); foreach (PSXUIText txt in texts) { PSXFontAsset font = txt.GetEffectiveFont(); if (font != null && !uniqueFonts.Contains(font) && uniqueFonts.Count < 3) uniqueFonts.Add(font); } } // Build font data with VRAM positions. // Each font gets its own texture page to avoid V-coordinate overflow. // Font textures go at x=960: // Font 1: y=0 (page 15,0) - 256px available // Font 2: y=256 (page 15,1) - 208px available (system font at y=464) // Font 3: not supported (would need different VRAM column) // System font: (960, 464) in page (15,1), occupies y=464-511. List fontDataList = new List(); ushort[] fontPageStarts = { 0, 256 }; // one per texture page int fontPageIndex = 0; foreach (PSXFontAsset fa in uniqueFonts) { byte[] pixelData = fa.ConvertTo4BPP(); if (pixelData == null) continue; // Read advance widths directly from the font asset. // These were computed during bitmap generation from the exact same // CharacterInfo used to render the glyphs - guaranteed to match. byte[] advances = fa.AdvanceWidths; if (advances == null || advances.Length < 96) { Debug.LogWarning($"PSXUIExporter: Font '{fa.name}' has no stored advance widths. Using cell width as fallback."); advances = new byte[96]; for (int i = 0; i < 96; i++) advances[i] = (byte)fa.GlyphWidth; } ushort texH = (ushort)fa.TextureHeight; if (fontPageIndex >= fontPageStarts.Length) { Debug.LogError($"PSXUIExporter: Max 2 custom fonts supported (need separate texture pages). Skipping '{fa.name}'."); continue; } ushort vramY = fontPageStarts[fontPageIndex]; int maxHeight = (fontPageIndex == 1) ? 208 : 256; // page 1 shares with system font if (texH > maxHeight) { Debug.LogWarning($"PSXUIExporter: Font '{fa.name}' texture ({texH}px) exceeds page limit ({maxHeight}px). May be clipped."); } fontDataList.Add(new PSXFontData { Source = fa, GlyphWidth = (byte)fa.GlyphWidth, GlyphHeight = (byte)fa.GlyphHeight, VramX = 960, VramY = vramY, TextureHeight = texH, PixelData = pixelData, AdvanceWidths = advances }); fontPageIndex++; } fonts = fontDataList.ToArray(); // Second pass: collect canvases with font index assignment List result = new List(); foreach (PSXCanvas canvas in canvases) { Canvas unityCanvas = canvas.GetComponent(); if (unityCanvas == null) continue; RectTransform canvasRect = canvas.GetComponent(); float canvasW = canvasRect.rect.width; float canvasH = canvasRect.rect.height; if (canvasW <= 0) canvasW = resolution.x; if (canvasH <= 0) canvasH = resolution.y; float scaleX = resolution.x / canvasW; float scaleY = resolution.y / canvasH; List elements = new List(); Debug.Log($"[UIExporter] Canvas '{canvas.CanvasName}' on '{canvas.gameObject.name}' " + $"canvasW={canvasW} canvasH={canvasH} childCount={canvas.transform.childCount}"); // Collect all UI elements in hierarchy order (depth-first, sibling index). // GetComponentsInChildren returns top-of-hierarchy first, but the C++ // renderer draws index 0 first (back). Reverse so that top-of-hierarchy // elements are rendered last (on top), matching Unity's visual stacking. CollectAllElementsInHierarchyOrder(canvas.transform, canvasRect, scaleX, scaleY, resolution, elements, uniqueFonts); elements.Reverse(); Debug.Log($"[UIExporter] TOTAL elements: {elements.Count}"); string name = canvas.CanvasName ?? "canvas"; if (name.Length > 24) name = name.Substring(0, 24); result.Add(new PSXCanvasData { Name = name, StartVisible = canvas.StartVisible, SortOrder = canvas.SortOrder, Elements = elements.ToArray() }); } return result.ToArray(); } // ─── Coordinate baking helpers ─── /// /// Convert a RectTransform into PS1 pixel-space layout values. /// Handles anchor-based positioning and Y inversion. /// private static void BakeLayout( RectTransform rt, RectTransform canvasRect, float scaleX, float scaleY, Vector2 resolution, out short x, out short y, out short w, out short h, out byte anchorMinX, out byte anchorMinY, out byte anchorMaxX, out byte anchorMaxY) { // Anchor values in 8.8 fixed point (0-255 maps to 0.0-~1.0) anchorMinX = (byte)Mathf.Clamp(Mathf.RoundToInt(rt.anchorMin.x * 255f), 0, 255); anchorMinY = (byte)Mathf.Clamp(Mathf.RoundToInt((1f - rt.anchorMax.y) * 255f), 0, 255); // Y invert anchorMaxX = (byte)Mathf.Clamp(Mathf.RoundToInt(rt.anchorMax.x * 255f), 0, 255); anchorMaxY = (byte)Mathf.Clamp(Mathf.RoundToInt((1f - rt.anchorMin.y) * 255f), 0, 255); // Y invert if (Mathf.Approximately(rt.anchorMin.x, rt.anchorMax.x) && Mathf.Approximately(rt.anchorMin.y, rt.anchorMax.y)) { // Fixed-size element with single anchor point // anchoredPosition is the offset from the anchor in canvas pixels float px = rt.anchoredPosition.x * scaleX; float py = -rt.anchoredPosition.y * scaleY; // Y invert float pw = rt.rect.width * scaleX; float ph = rt.rect.height * scaleY; // Adjust for pivot (anchoredPosition is at the pivot point) px -= rt.pivot.x * pw; py -= (1f - rt.pivot.y) * ph; // pivot Y inverted x = (short)Mathf.RoundToInt(px); y = (short)Mathf.RoundToInt(py); w = (short)Mathf.Max(1, Mathf.RoundToInt(pw)); h = (short)Mathf.Max(1, Mathf.RoundToInt(ph)); } else { // Stretched element: offsets from anchored edges // offsetMin = distance from anchorMin corner, offsetMax = distance from anchorMax corner float leftOff = rt.offsetMin.x * scaleX; float rightOff = rt.offsetMax.x * scaleX; float topOff = -rt.offsetMax.y * scaleY; // Y invert float bottomOff = -rt.offsetMin.y * scaleY; // Y invert // For stretched elements, x/y store the offset from the anchor start, // and w/h store the combined inset (negative = shrink) x = (short)Mathf.RoundToInt(leftOff); y = (short)Mathf.RoundToInt(topOff); w = (short)Mathf.RoundToInt(rightOff - leftOff); h = (short)Mathf.RoundToInt(bottomOff - topOff); } } private static string TruncateName(string name, int maxLen = 24) { if (string.IsNullOrEmpty(name)) return ""; return name.Length > maxLen ? name.Substring(0, maxLen) : name; } // ─── Collectors ─── /// /// Walk the hierarchy depth-first in sibling order, collecting every /// PSX UI component into so that draw order /// matches the Unity scene tree (top-to-bottom = back-to-front). /// private static void CollectAllElementsInHierarchyOrder( Transform root, RectTransform canvasRect, float scaleX, float scaleY, Vector2 resolution, List elements, List uniqueFonts) { // GetComponentsInChildren iterates depth-first in sibling order — // exactly the hierarchy ordering we want. Transform[] allTransforms = root.GetComponentsInChildren(true); foreach (Transform t in allTransforms) { if (t == root) continue; // skip the canvas root itself // Check each supported component type on this transform. // A single GameObject should only have one PSX UI component, // but we check all to be safe. PSXUIImage img = t.GetComponent(); if (img != null) { CollectSingleImage(img, canvasRect, scaleX, scaleY, resolution, elements); continue; } PSXUIBox box = t.GetComponent(); if (box != null) { CollectSingleBox(box, canvasRect, scaleX, scaleY, resolution, elements); continue; } PSXUIText txt = t.GetComponent(); if (txt != null) { CollectSingleText(txt, canvasRect, scaleX, scaleY, resolution, elements, uniqueFonts); continue; } PSXUIProgressBar bar = t.GetComponent(); if (bar != null) { CollectSingleProgressBar(bar, canvasRect, scaleX, scaleY, resolution, elements); continue; } } } private static void CollectSingleImage( PSXUIImage img, RectTransform canvasRect, float scaleX, float scaleY, Vector2 resolution, List elements) { RectTransform rt = img.GetComponent(); if (rt == null) return; BakeLayout(rt, canvasRect, scaleX, scaleY, resolution, out short x, out short y, out short w, out short h, out byte amin_x, out byte amin_y, out byte amax_x, out byte amax_y); var data = new PSXUIElementData { Type = PSXUIElementType.Image, StartVisible = img.StartVisible, Name = TruncateName(img.ElementName), X = x, Y = y, W = w, H = h, AnchorMinX = amin_x, AnchorMinY = amin_y, AnchorMaxX = amax_x, AnchorMaxY = amax_y, ColorR = (byte)Mathf.Clamp(Mathf.RoundToInt(img.TintColor.r * 255f), 0, 255), ColorG = (byte)Mathf.Clamp(Mathf.RoundToInt(img.TintColor.g * 255f), 0, 255), ColorB = (byte)Mathf.Clamp(Mathf.RoundToInt(img.TintColor.b * 255f), 0, 255), }; if (img.PackedTexture != null) { PSXTexture2D tex = img.PackedTexture; int expander = 16 / (int)tex.BitDepth; data.TexpageX = tex.TexpageX; data.TexpageY = tex.TexpageY; data.ClutX = (ushort)tex.ClutPackingX; data.ClutY = (ushort)tex.ClutPackingY; data.U0 = (byte)(tex.PackingX * expander); data.V0 = (byte)tex.PackingY; data.U1 = (byte)(tex.PackingX * expander + tex.Width - 1); data.V1 = (byte)(tex.PackingY + tex.Height - 1); data.BitDepthIndex = tex.BitDepth switch { PSXBPP.TEX_4BIT => 0, PSXBPP.TEX_8BIT => 1, PSXBPP.TEX_16BIT => 2, _ => 2 }; Debug.Log($"[UIImage] '{img.ElementName}' src='{(tex.OriginalTexture ? tex.OriginalTexture.name : "null")}' " + $"bpp={(int)tex.BitDepth} W={tex.Width} H={tex.Height} QW={tex.QuantizedWidth} " + $"packXY=({tex.PackingX},{tex.PackingY}) tpage=({tex.TexpageX},{tex.TexpageY}) " + $"clutXY=({tex.ClutPackingX},{tex.ClutPackingY}) " + $"UV=({data.U0},{data.V0})->({data.U1},{data.V1}) expander={expander} bitIdx={data.BitDepthIndex}"); } else { Debug.LogWarning($"[UIImage] '{img.ElementName}' has NULL PackedTexture!"); } elements.Add(data); } private static void CollectSingleBox( PSXUIBox box, RectTransform canvasRect, float scaleX, float scaleY, Vector2 resolution, List elements) { RectTransform rt = box.GetComponent(); if (rt == null) return; BakeLayout(rt, canvasRect, scaleX, scaleY, resolution, out short x, out short y, out short w, out short h, out byte amin_x, out byte amin_y, out byte amax_x, out byte amax_y); elements.Add(new PSXUIElementData { Type = PSXUIElementType.Box, StartVisible = box.StartVisible, Name = TruncateName(box.ElementName), X = x, Y = y, W = w, H = h, AnchorMinX = amin_x, AnchorMinY = amin_y, AnchorMaxX = amax_x, AnchorMaxY = amax_y, ColorR = (byte)Mathf.Clamp(Mathf.RoundToInt(box.BoxColor.r * 255f), 0, 255), ColorG = (byte)Mathf.Clamp(Mathf.RoundToInt(box.BoxColor.g * 255f), 0, 255), ColorB = (byte)Mathf.Clamp(Mathf.RoundToInt(box.BoxColor.b * 255f), 0, 255), }); } private static void CollectSingleText( PSXUIText txt, RectTransform canvasRect, float scaleX, float scaleY, Vector2 resolution, List elements, List uniqueFonts) { RectTransform rt = txt.GetComponent(); if (rt == null) return; BakeLayout(rt, canvasRect, scaleX, scaleY, resolution, out short x, out short y, out short w, out short h, out byte amin_x, out byte amin_y, out byte amax_x, out byte amax_y); string defaultText = txt.DefaultText ?? ""; if (defaultText.Length > 63) defaultText = defaultText.Substring(0, 63); byte fontIndex = 0; PSXFontAsset effectiveFont = txt.GetEffectiveFont(); if (effectiveFont != null && uniqueFonts != null) { int idx = uniqueFonts.IndexOf(effectiveFont); if (idx >= 0) fontIndex = (byte)(idx + 1); } elements.Add(new PSXUIElementData { Type = PSXUIElementType.Text, StartVisible = txt.StartVisible, Name = TruncateName(txt.ElementName), X = x, Y = y, W = w, H = h, AnchorMinX = amin_x, AnchorMinY = amin_y, AnchorMaxX = amax_x, AnchorMaxY = amax_y, ColorR = (byte)Mathf.Clamp(Mathf.RoundToInt(txt.TextColor.r * 255f), 0, 255), ColorG = (byte)Mathf.Clamp(Mathf.RoundToInt(txt.TextColor.g * 255f), 0, 255), ColorB = (byte)Mathf.Clamp(Mathf.RoundToInt(txt.TextColor.b * 255f), 0, 255), DefaultText = defaultText, FontIndex = fontIndex, }); } private static void CollectSingleProgressBar( PSXUIProgressBar bar, RectTransform canvasRect, float scaleX, float scaleY, Vector2 resolution, List elements) { RectTransform rt = bar.GetComponent(); if (rt == null) return; BakeLayout(rt, canvasRect, scaleX, scaleY, resolution, out short x, out short y, out short w, out short h, out byte amin_x, out byte amin_y, out byte amax_x, out byte amax_y); elements.Add(new PSXUIElementData { Type = PSXUIElementType.Progress, StartVisible = bar.StartVisible, Name = TruncateName(bar.ElementName), X = x, Y = y, W = w, H = h, AnchorMinX = amin_x, AnchorMinY = amin_y, AnchorMaxX = amax_x, AnchorMaxY = amax_y, ColorR = (byte)Mathf.Clamp(Mathf.RoundToInt(bar.FillColor.r * 255f), 0, 255), ColorG = (byte)Mathf.Clamp(Mathf.RoundToInt(bar.FillColor.g * 255f), 0, 255), ColorB = (byte)Mathf.Clamp(Mathf.RoundToInt(bar.FillColor.b * 255f), 0, 255), BgR = (byte)Mathf.Clamp(Mathf.RoundToInt(bar.BackgroundColor.r * 255f), 0, 255), BgG = (byte)Mathf.Clamp(Mathf.RoundToInt(bar.BackgroundColor.g * 255f), 0, 255), BgB = (byte)Mathf.Clamp(Mathf.RoundToInt(bar.BackgroundColor.b * 255f), 0, 255), ProgressValue = (byte)bar.InitialValue, }); } // ─── Legacy per-type collectors (kept for reference, no longer called) ─── private static void CollectImages( Transform root, RectTransform canvasRect, float scaleX, float scaleY, Vector2 resolution, List elements) { PSXUIImage[] images = root.GetComponentsInChildren(true); foreach (PSXUIImage img in images) { RectTransform rt = img.GetComponent(); if (rt == null) continue; BakeLayout(rt, canvasRect, scaleX, scaleY, resolution, out short x, out short y, out short w, out short h, out byte amin_x, out byte amin_y, out byte amax_x, out byte amax_y); var data = new PSXUIElementData { Type = PSXUIElementType.Image, StartVisible = img.StartVisible, Name = TruncateName(img.ElementName), X = x, Y = y, W = w, H = h, AnchorMinX = amin_x, AnchorMinY = amin_y, AnchorMaxX = amax_x, AnchorMaxY = amax_y, ColorR = (byte)Mathf.Clamp(Mathf.RoundToInt(img.TintColor.r * 255f), 0, 255), ColorG = (byte)Mathf.Clamp(Mathf.RoundToInt(img.TintColor.g * 255f), 0, 255), ColorB = (byte)Mathf.Clamp(Mathf.RoundToInt(img.TintColor.b * 255f), 0, 255), }; // Image texture data is filled in after VRAM packing by // FillImageTextureData() — see PSXSceneExporter integration if (img.PackedTexture != null) { PSXTexture2D tex = img.PackedTexture; // Convert PackingX from VRAM halfwords to texture-pixel U coords. // 4bpp: 4 pixels per halfword, 8bpp: 2, 16bpp: 1 int expander = 16 / (int)tex.BitDepth; data.TexpageX = tex.TexpageX; data.TexpageY = tex.TexpageY; data.ClutX = (ushort)tex.ClutPackingX; data.ClutY = (ushort)tex.ClutPackingY; data.U0 = (byte)(tex.PackingX * expander); data.V0 = (byte)tex.PackingY; // U1/V1 are the LAST texel (inclusive), not one-past-end. // Without -1, values >= 256 overflow byte to 0. data.U1 = (byte)(tex.PackingX * expander + tex.Width - 1); data.V1 = (byte)(tex.PackingY + tex.Height - 1); data.BitDepthIndex = tex.BitDepth switch { PSXBPP.TEX_4BIT => 0, PSXBPP.TEX_8BIT => 1, PSXBPP.TEX_16BIT => 2, _ => 2 }; Debug.Log($"[UIImage] '{img.ElementName}' src='{(tex.OriginalTexture ? tex.OriginalTexture.name : "null")}' " + $"bpp={(int)tex.BitDepth} W={tex.Width} H={tex.Height} QW={tex.QuantizedWidth} " + $"packXY=({tex.PackingX},{tex.PackingY}) tpage=({tex.TexpageX},{tex.TexpageY}) " + $"clutXY=({tex.ClutPackingX},{tex.ClutPackingY}) " + $"UV=({data.U0},{data.V0})->({data.U1},{data.V1}) expander={expander} bitIdx={data.BitDepthIndex}"); } else { Debug.LogWarning($"[UIImage] '{img.ElementName}' has NULL PackedTexture!"); } elements.Add(data); } } private static void CollectBoxes( Transform root, RectTransform canvasRect, float scaleX, float scaleY, Vector2 resolution, List elements) { PSXUIBox[] boxes = root.GetComponentsInChildren(true); foreach (PSXUIBox box in boxes) { RectTransform rt = box.GetComponent(); if (rt == null) continue; BakeLayout(rt, canvasRect, scaleX, scaleY, resolution, out short x, out short y, out short w, out short h, out byte amin_x, out byte amin_y, out byte amax_x, out byte amax_y); elements.Add(new PSXUIElementData { Type = PSXUIElementType.Box, StartVisible = box.StartVisible, Name = TruncateName(box.ElementName), X = x, Y = y, W = w, H = h, AnchorMinX = amin_x, AnchorMinY = amin_y, AnchorMaxX = amax_x, AnchorMaxY = amax_y, ColorR = (byte)Mathf.Clamp(Mathf.RoundToInt(box.BoxColor.r * 255f), 0, 255), ColorG = (byte)Mathf.Clamp(Mathf.RoundToInt(box.BoxColor.g * 255f), 0, 255), ColorB = (byte)Mathf.Clamp(Mathf.RoundToInt(box.BoxColor.b * 255f), 0, 255), }); } } private static void CollectTexts( Transform root, RectTransform canvasRect, float scaleX, float scaleY, Vector2 resolution, List elements, List uniqueFonts = null) { PSXUIText[] texts = root.GetComponentsInChildren(true); foreach (PSXUIText txt in texts) { RectTransform rt = txt.GetComponent(); if (rt == null) continue; BakeLayout(rt, canvasRect, scaleX, scaleY, resolution, out short x, out short y, out short w, out short h, out byte amin_x, out byte amin_y, out byte amax_x, out byte amax_y); string defaultText = txt.DefaultText ?? ""; if (defaultText.Length > 63) defaultText = defaultText.Substring(0, 63); // Resolve font index: 0 = system font, 1+ = custom font byte fontIndex = 0; PSXFontAsset effectiveFont = txt.GetEffectiveFont(); if (effectiveFont != null && uniqueFonts != null) { int idx = uniqueFonts.IndexOf(effectiveFont); if (idx >= 0) fontIndex = (byte)(idx + 1); // 1-based for custom fonts } elements.Add(new PSXUIElementData { Type = PSXUIElementType.Text, StartVisible = txt.StartVisible, Name = TruncateName(txt.ElementName), X = x, Y = y, W = w, H = h, AnchorMinX = amin_x, AnchorMinY = amin_y, AnchorMaxX = amax_x, AnchorMaxY = amax_y, ColorR = (byte)Mathf.Clamp(Mathf.RoundToInt(txt.TextColor.r * 255f), 0, 255), ColorG = (byte)Mathf.Clamp(Mathf.RoundToInt(txt.TextColor.g * 255f), 0, 255), ColorB = (byte)Mathf.Clamp(Mathf.RoundToInt(txt.TextColor.b * 255f), 0, 255), DefaultText = defaultText, FontIndex = fontIndex, }); } } private static void CollectProgressBars( Transform root, RectTransform canvasRect, float scaleX, float scaleY, Vector2 resolution, List elements) { PSXUIProgressBar[] bars = root.GetComponentsInChildren(true); foreach (PSXUIProgressBar bar in bars) { RectTransform rt = bar.GetComponent(); if (rt == null) continue; BakeLayout(rt, canvasRect, scaleX, scaleY, resolution, out short x, out short y, out short w, out short h, out byte amin_x, out byte amin_y, out byte amax_x, out byte amax_y); elements.Add(new PSXUIElementData { Type = PSXUIElementType.Progress, StartVisible = bar.StartVisible, Name = TruncateName(bar.ElementName), X = x, Y = y, W = w, H = h, AnchorMinX = amin_x, AnchorMinY = amin_y, AnchorMaxX = amax_x, AnchorMaxY = amax_y, // Fill color goes into primary color (used for the fill bar) ColorR = (byte)Mathf.Clamp(Mathf.RoundToInt(bar.FillColor.r * 255f), 0, 255), ColorG = (byte)Mathf.Clamp(Mathf.RoundToInt(bar.FillColor.g * 255f), 0, 255), ColorB = (byte)Mathf.Clamp(Mathf.RoundToInt(bar.FillColor.b * 255f), 0, 255), // Background color goes into progress-specific fields BgR = (byte)Mathf.Clamp(Mathf.RoundToInt(bar.BackgroundColor.r * 255f), 0, 255), BgG = (byte)Mathf.Clamp(Mathf.RoundToInt(bar.BackgroundColor.g * 255f), 0, 255), BgB = (byte)Mathf.Clamp(Mathf.RoundToInt(bar.BackgroundColor.b * 255f), 0, 255), ProgressValue = (byte)bar.InitialValue, }); } } } }