// NOTE: Windows-only. Depends on WebViewLauncher (external Flutter .exe / native // window control), which is Windows-only. Wrapped in UNITY_STANDALONE_WIN so the // Windows build/logic is unchanged while Android compiles this class out. #if UNITY_STANDALONE_WIN using UnityEngine; using UnityEngine.UI; using System.IO; using System.Collections.Generic; using System.Xml; using System.Text.RegularExpressions; public class loadLittleGamesPrefab : MonoBehaviour { [Header("Scroll Views")] public GameObject officialDocs_scrollView_content; public GameObject officialGames_scrollView_content; public GameObject thirdPartyGames_scrollView_content; [Header("Prefab")] public GameObject littleGames_card_Prefab; [Tooltip("Documentation text normalized.")] public Sprite default_card_icon_sprite; [Header("Refresh")] public Button refreshButton; // Documentation text normalized. [Header("H5 Root Path (optional, leave empty to use WebViewLauncher settings)")] public string h5RootPath; void Awake() { // attach refresh listener if button provided if (refreshButton != null) { refreshButton.onClick.AddListener(RefreshList); } } void OnDestroy() { if (refreshButton != null) refreshButton.onClick.RemoveListener(RefreshList); } void Start() { LoadAllLittleGames(); } // Clear children of a content transform void ClearContent(GameObject content) { if (content == null) return; for (int i = content.transform.childCount - 1; i >= 0; i--) { var child = content.transform.GetChild(i).gameObject; Destroy(child); } } // Public method to allow external callers to refresh public void RefreshList() { // clear all three contents then reload ClearContent(officialDocs_scrollView_content); ClearContent(officialGames_scrollView_content); ClearContent(thirdPartyGames_scrollView_content); LoadAllLittleGames(); } public void LoadAllLittleGames() { string root = ResolveH5RootPath(); if (string.IsNullOrEmpty(root) || !Directory.Exists(root)) { Debug.LogWarning("H5 root path not found: " + root); return; } string[] categories = new string[] { "_officialDocs", "_officialGames", "_thirdPartyGames" }; foreach (string cat in categories) { string catPath = Path.Combine(root, cat); if (!Directory.Exists(catPath)) continue; string[] subdirs = Directory.GetDirectories(catPath); foreach (string subdir in subdirs) { string indexHtml = Path.Combine(subdir, "index.html"); if (!File.Exists(indexHtml)) continue; // read xml if exists string xmlPath = Path.Combine(subdir, "info.xml"); Dictionary meta = null; if (File.Exists(xmlPath)) meta = ReadInfoXml(xmlPath); // target content GameObject target = GetTargetContent(cat); if (target == null || littleGames_card_Prefab == null) { Debug.LogWarning("Missing target content or prefab for category: " + cat); continue; } GameObject item = Instantiate(littleGames_card_Prefab, target.transform); var card = item.GetComponent(); if (card == null) continue; // defaults string defName = "Unknown HTML Page"; string defAuthor = "Unknown Author"; string defDesc = "No description provided."; string defIsOfficial = "Unknown Source"; string defSource = "Unknown"; string defDateRaw = "19700101"; string cardName = GetMeta(meta, "card_name") ?? defName; string author = GetMeta(meta, "authorName") ?? defAuthor; string desc = GetMeta(meta, "description") ?? defDesc; string isOfficial = GetMeta(meta, "isOfficial"); string source = GetMeta(meta, "source"); string dateRaw = GetMeta(meta, "yyyy_mm_dd"); string cardIcon = GetMeta(meta, "card_icon"); string versionCode = GetMeta(meta, "versionCode"); if (string.IsNullOrEmpty(dateRaw)) dateRaw = defDateRaw; if (cat == "_officialDocs" || cat == "_officialGames") { isOfficial = "Official"; source = "MiniGame DLC"; } else { if (string.IsNullOrEmpty(isOfficial)) isOfficial = "Community"; if (string.IsNullOrEmpty(source)) source = defSource; } // ensure no nulls if (string.IsNullOrEmpty(cardName)) cardName = defName; if (string.IsNullOrEmpty(author)) author = defAuthor; if (string.IsNullOrEmpty(desc)) desc = defDesc; if (string.IsNullOrEmpty(isOfficial)) isOfficial = defIsOfficial; if (string.IsNullOrEmpty(source)) source = defSource; string formattedDate = FormatDate(dateRaw); card.SetData(indexHtml, cardName, author, desc, isOfficial, source, formattedDate, versionCode); // Set default icon first (if provided) if (card.card_icon_image != null && default_card_icon_sprite != null) { card.card_icon_image.sprite = default_card_icon_sprite; card.card_icon_image.enabled = true; } // load icon if specified (override default) if (!string.IsNullOrEmpty(cardIcon)) { string iconPath = cardIcon; if (!Path.IsPathRooted(iconPath)) iconPath = Path.Combine(subdir, iconPath); if (File.Exists(iconPath)) { try { byte[] bytes = File.ReadAllBytes(iconPath); Texture2D tex = new Texture2D(2, 2); if (tex.LoadImage(bytes)) { Sprite sp = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f)); if (card.card_icon_image != null) { card.card_icon_image.sprite = sp; card.card_icon_image.enabled = true; } } else { Debug.LogWarning("Failed to LoadImage for card_icon: " + iconPath + ". Using default sprite if available."); } } catch (System.Exception e) { Debug.LogWarning("Failed to load card_icon: " + e.Message + ". Using default sprite if available."); } } else { Debug.LogWarning("card_icon not found: " + iconPath + ". Using default sprite if available."); } } // if no cardIcon specified and no default was assigned earlier, ensure image disabled else { if (card.card_icon_image != null && card.card_icon_image.sprite == null) card.card_icon_image.enabled = false; } } } } string ResolveH5RootPath() { if (!string.IsNullOrEmpty(h5RootPath) && Directory.Exists(h5RootPath)) return h5RootPath; var launcher = SceneObjectLookupCache.FindAny(); if (launcher != null) { #if UNITY_EDITOR return launcher.h5EditorPath; #else return Path.Combine(Application.streamingAssetsPath, "h5LG"); #endif } return Path.Combine(Application.streamingAssetsPath, "h5LG"); } GameObject GetTargetContent(string category) { switch (category) { case "_officialDocs": return officialDocs_scrollView_content; case "_officialGames": return officialGames_scrollView_content; case "_thirdPartyGames": return thirdPartyGames_scrollView_content; default: return null; } } Dictionary ReadInfoXml(string xmlPath) { var dict = new Dictionary(); if (string.IsNullOrEmpty(xmlPath) || !File.Exists(xmlPath)) return dict; try { XmlDocument doc = new XmlDocument(); doc.Load(xmlPath); XmlElement root = doc.DocumentElement; if (root == null) return dict; string[] keys = new string[] { "card_name", "authorName", "description", "isOfficial", "source", "yyyy_mm_dd", "card_icon", "versionCode" }; foreach (string k in keys) { XmlNode node = root.SelectSingleNode(k); if (node != null) dict[k] = node.InnerText; } } catch (System.Exception ex) { Debug.LogWarning("ReadInfoXml failed: " + ex.Message); } return dict; } string GetMeta(Dictionary meta, string key) { if (meta == null) return null; string v; if (meta.TryGetValue(key, out v)) return v; return null; } string FormatDate(string raw) { if (string.IsNullOrEmpty(raw)) return ""; char[] seps = new char[] { '-', '/', '_', ' ' }; var parts = raw.Split(seps, System.StringSplitOptions.RemoveEmptyEntries); if (parts.Length == 3) return parts[0] + "\n" + parts[1] + "\n" + parts[2]; string digits = Regex.Replace(raw, "\\D", ""); if (digits.Length == 8) return digits.Substring(0, 4) + "\n" + digits.Substring(4, 2) + "\n" + digits.Substring(6, 2); if (digits.Length >= 6) { string y = digits.Substring(0, 4); string m = digits.Length >= 6 ? digits.Substring(4, 2) : ""; string d = digits.Length >= 8 ? digits.Substring(6, 2) : ""; return y + "\n" + m + "\n" + d; } return raw.Replace("-", "\n").Replace("/", "\n").Replace("_", "\n"); } } #endif