295 lines
11 KiB
C#
295 lines
11 KiB
C#
// 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<string, string> 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<game_card_Prefab>();
|
|
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<WebViewLauncher>();
|
|
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<string, string> ReadInfoXml(string xmlPath)
|
|
{
|
|
var dict = new Dictionary<string, string>();
|
|
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<string, string> 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
|