浮动小游戏已经基本搞好

新做了两个设置界面
一些bug修复和优化
This commit is contained in:
FloatGaming
2025-12-28 04:55:18 +08:00
parent eb9b6ba78a
commit 0963263ff4
557 changed files with 296886 additions and 487 deletions
@@ -2,6 +2,7 @@ using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System;
using System.IO;
public class game_card_Prefab : MonoBehaviour, IPointerClickHandler
{
@@ -9,6 +10,7 @@ public class game_card_Prefab : MonoBehaviour, IPointerClickHandler
public Image card_icon_image;
public Text card_name_text;
public Text authorName_text;
public Text versionCode_text;
public Text description_text;
public Text _isOfficial_text;
public Text source_text;
@@ -21,7 +23,7 @@ public class game_card_Prefab : MonoBehaviour, IPointerClickHandler
private string indexFilePath;
// 给 loadLittleGamesPrefab 调用以填充文本和路径
public void SetData(string indexPath, string cardName, string authorName, string description, string isOfficial, string source, string yyyyMmDd)
public void SetData(string indexPath, string cardName, string authorName, string description, string isOfficial, string source, string yyyyMmDd, string version = null)
{
indexFilePath = indexPath;
if (card_name_text != null) card_name_text.text = cardName ?? "";
@@ -31,7 +33,11 @@ public class game_card_Prefab : MonoBehaviour, IPointerClickHandler
if (source_text != null) source_text.text = source ?? "";
if (yyyy_mm_dd != null) yyyy_mm_dd.text = yyyyMmDd ?? "";
UnityEngine.Debug.Log($"game_card_Prefab.SetData: indexPath='{indexFilePath}', card_name='{cardName}', author='{authorName}', isOfficial='{isOfficial}', source='{source}'");
// version
string ver = string.IsNullOrEmpty(version) ? "0.0.0" : version;
if (versionCode_text != null) versionCode_text.text = ver;
UnityEngine.Debug.Log($"game_card_Prefab.SetData: indexPath='{indexFilePath}', card_name='{cardName}', author='{authorName}', isOfficial='{isOfficial}', source='{source}', version='{ver}'");
}
void Awake()
@@ -110,6 +116,21 @@ public class game_card_Prefab : MonoBehaviour, IPointerClickHandler
{
UnityEngine.Debug.Log("Calling WebViewLauncher.OpenUrl with: " + indexFilePath);
WebViewLauncher.Instance.OpenUrl(indexFilePath);
// After initiating navigation, set the input field display to localhost::parent/index.html
try
{
if (WebViewLauncher.Instance != null && WebViewLauncher.Instance.urlInputField != null)
{
string parent = Path.GetFileName(Path.GetDirectoryName(indexFilePath));
string display = $"localhost::{parent}/{Path.GetFileName(indexFilePath)}";
WebViewLauncher.Instance.urlInputField.text = display;
}
}
catch (Exception e)
{
UnityEngine.Debug.LogWarning("Failed to set url input display: " + e.Message);
}
}
else
{
@@ -120,6 +141,20 @@ public class game_card_Prefab : MonoBehaviour, IPointerClickHandler
{
UnityEngine.Debug.Log("Found launcher in scene, calling OpenUrl: " + indexFilePath);
launcherObj.OpenUrl(indexFilePath);
try
{
if (launcherObj.urlInputField != null)
{
string parent = Path.GetFileName(Path.GetDirectoryName(indexFilePath));
string display = $"localhost::{parent}/{Path.GetFileName(indexFilePath)}";
launcherObj.urlInputField.text = display;
}
}
catch (Exception e)
{
UnityEngine.Debug.LogWarning("Failed to set url input display: " + e.Message);
}
}
else
{
@@ -129,6 +164,20 @@ public class game_card_Prefab : MonoBehaviour, IPointerClickHandler
var launcher = go.AddComponent<WebViewLauncher>();
launcher.OpenUrl(indexFilePath);
GameObject.DontDestroyOnLoad(go);
try
{
if (launcher.urlInputField != null)
{
string parent = Path.GetFileName(Path.GetDirectoryName(indexFilePath));
string display = $"localhost::{parent}/{Path.GetFileName(indexFilePath)}";
launcher.urlInputField.text = display;
}
}
catch (Exception e)
{
UnityEngine.Debug.LogWarning("Failed to set url input display: " + e.Message);
}
}
}
}
@@ -34,8 +34,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 10.717, y: 14.07}
m_SizeDelta: {x: 252.359, y: 30}
m_AnchoredPosition: {x: -12.6, y: 14.07}
m_SizeDelta: {x: 204.359, y: 30}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &7480212223569318349
CanvasRenderer:
@@ -316,6 +316,85 @@ MonoBehaviour:
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!1 &3596489648732047302
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6607639485689222063}
- component: {fileID: 552011013437464851}
- component: {fileID: 6022216317311205539}
m_Layer: 5
m_Name: version_code
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &6607639485689222063
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3596489648732047302}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 3566709735925945696}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 105.7, y: 15.6}
m_SizeDelta: {x: 47.359, y: 30}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &552011013437464851
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3596489648732047302}
m_CullTransparentMesh: 1
--- !u!114 &6022216317311205539
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3596489648732047302}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: cc180dff846d13a4d88ddaed6f77e5cd, type: 3}
m_FontSize: 10
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
m_MaxSize: 40
m_Alignment: 8
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: 1.0.1
--- !u!1 &5110857998175197173
GameObject:
m_ObjectHideFlags: 0
@@ -429,6 +508,7 @@ RectTransform:
- {fileID: 4583698199465520998}
- {fileID: 604392950021451895}
- {fileID: 872492842950718926}
- {fileID: 6607639485689222063}
- {fileID: 2019306417107768352}
- {fileID: 2622440068852620012}
- {fileID: 2138782682665805939}
@@ -653,6 +733,7 @@ MonoBehaviour:
card_icon_image: {fileID: 6004099248657222439}
card_name_text: {fileID: 3389633104739560455}
authorName_text: {fileID: 9193153990832272737}
versionCode_text: {fileID: 6022216317311205539}
description_text: {fileID: 8118505406259206449}
_isOfficial_text: {fileID: 5166008895911254744}
source_text: {fileID: 4780041332280513338}
@@ -1,7 +1,9 @@
using UnityEngine;
using UnityEngine.UI;
using System.IO;
using System.Collections.Generic;
using System.Xml;
using System.Text.RegularExpressions;
public class loadLittleGamesPrefab : MonoBehaviour
{
@@ -12,44 +14,66 @@ public class loadLittleGamesPrefab : MonoBehaviour
[Header("Prefab")]
public GameObject littleGames_card_Prefab;
[Tooltip("默认icon图")]
public Sprite default_card_icon_sprite;
[Header("Refresh")]
public Button refreshButton; // 点击触发重新扫描并刷新左侧列表
[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();
}
/// <summary>
/// 在 WebViewLauncher 的 h5 目录下扫描 three categories 的所有子文件夹(递归一层),
/// 找到含有 index.html 的目录(例如: ...\h5LG\_thirdPartyGames\kjb\index.html),
/// 读取同目录下的 info.xml(如果存在),并把 prefab 实例化到对应的 content 中。
///
/// info.xml 示例格式(放在同目录下,编码 UTF-8):
/// <?xml version="1.0" encoding="utf-8"?>
/// <Info>
/// <card_name>示例游戏名</card_name>
/// <authorName>作者名</authorName>
/// <description>这里是描述文本</description>
/// <isOfficial>True</isOfficial>
/// <source>来源信息</source>
/// <yyyy_mm_dd>2025-12-23</yyyy_mm_dd>
/// </Info>
///
/// 请保证节点名如上所示以便能被正确读取。
/// </summary>
// 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 = GetH5RootPath();
string root = ResolveH5RootPath();
if (string.IsNullOrEmpty(root) || !Directory.Exists(root))
{
Debug.LogWarning("H5 root path not found: " + root);
return;
}
// 目标一级子目录名称(与 WebViewLauncher 中保持一致)
string[] categories = { "_officialDocs", "_officialGames", "_thirdPartyGames" };
string[] categories = new string[] { "_officialDocs", "_officialGames", "_thirdPartyGames" };
foreach (string cat in categories)
{
@@ -57,95 +81,148 @@ public class loadLittleGamesPrefab : MonoBehaviour
if (!Directory.Exists(catPath))
continue;
// 遍历 catPath 下的第一层子目录,检查是否含有 index.html
string[] subdirs = Directory.GetDirectories(catPath);
foreach (string subdir in subdirs)
{
string indexPath = Path.Combine(subdir, "index.html");
if (!File.Exists(indexPath))
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)
{
// 也可能 index.html 在更深一层,但按照约定通常是 subdir/index.html
Debug.LogWarning("Missing target content or prefab for category: " + cat);
continue;
}
// 读取 info.xml(可选)
string infoXmlPath = Path.Combine(subdir, "info.xml");
var meta = ReadInfoXml(infoXmlPath);
// 根据分类选择目标 content
GameObject targetContent = GetContentByCategory(cat);
if (targetContent == null || littleGames_card_Prefab == null)
{
Debug.LogWarning("Missing prefab or content for category: " + cat);
GameObject item = Instantiate(littleGames_card_Prefab, target.transform);
var card = item.GetComponent<game_card_Prefab>();
if (card == null)
continue;
// defaults
string defName = "未知Html文件";
string defAuthor = "未知作者";
string defDesc = "暂无相关描述信息";
string defIsOfficial = "未知来源";
string defSource = "自定义渠道";
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 = "官方";
source = "小游戏DLC";
}
else
{
if (string.IsNullOrEmpty(isOfficial)) isOfficial = "第三方来源";
if (string.IsNullOrEmpty(source)) source = defSource;
}
// 实例化 prefab
GameObject go = Instantiate(littleGames_card_Prefab, targetContent.transform);
// 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;
// 设置数据(indexPath 使用本地文件绝对路径)
string absIndexPath = indexPath; // 使用绝对路径,WebViewLauncher.OpenUrl 会添加 file://
var prefabComp = go.GetComponent<game_card_Prefab>();
if (prefabComp != null)
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)
{
// 若来自官方分类,覆盖 isOfficial 与 source 字段为固定值
string finalIsOfficial = meta.GetValueOrDefault("isOfficial");
string finalSource = meta.GetValueOrDefault("source");
card.card_icon_image.sprite = default_card_icon_sprite;
card.card_icon_image.enabled = true;
}
if (cat == "_officialDocs" || cat == "_officialGames")
// 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))
{
finalIsOfficial = "官方";
finalSource = "小游戏DLC";
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
{
// 非官方分类统一标记为第三方来源
finalIsOfficial = "第三方来源";
// 若 info.xml 没有 source,则可以保留原值或设置默认,这里若无则为空
if (string.IsNullOrEmpty(finalSource))
finalSource = "";
Debug.LogWarning("card_icon not found: " + iconPath + ". Using default sprite if available.");
}
// 格式化日期为 yyyy\nmm\ndd 的形式
string rawDate = meta.GetValueOrDefault("yyyy_mm_dd");
string formattedDate = FormatDateWithNewlines(rawDate);
prefabComp.SetData(absIndexPath,
meta.GetValueOrDefault("card_name"),
meta.GetValueOrDefault("authorName"),
meta.GetValueOrDefault("description"),
finalIsOfficial,
finalSource,
formattedDate
);
}
// 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 GetH5RootPath()
string ResolveH5RootPath()
{
if (!string.IsNullOrEmpty(h5RootPath) && Directory.Exists(h5RootPath))
return h5RootPath;
// 从 WebViewLauncher 获取编辑器路径
var w = GameObject.FindObjectOfType<WebViewLauncher>();
if (w != null)
var launcher = GameObject.FindObjectOfType<WebViewLauncher>();
if (launcher != null)
{
#if UNITY_EDITOR
return w.h5EditorPath;
return launcher.h5EditorPath;
#else
return Path.Combine(Application.streamingAssetsPath, "h5LG");
#endif
}
// 作为回退,使用 StreamingAssets/h5LG
return Path.Combine(Application.streamingAssetsPath, "h5LG");
}
GameObject GetContentByCategory(string categoryName)
GameObject GetTargetContent(string category)
{
switch (categoryName)
switch (category)
{
case "_officialDocs": return officialDocs_scrollView_content;
case "_officialGames": return officialGames_scrollView_content;
@@ -162,54 +239,43 @@ public class loadLittleGamesPrefab : MonoBehaviour
try
{
var doc = new XmlDocument();
XmlDocument doc = new XmlDocument();
doc.Load(xmlPath);
var root = doc.DocumentElement;
if (root == null)
return dict;
XmlElement root = doc.DocumentElement;
if (root == null) return dict;
// 读取预定义字段
string[] keys = { "card_name", "authorName", "description", "isOfficial", "source", "yyyy_mm_dd" };
foreach (var k in keys)
string[] keys = new string[] { "card_name", "authorName", "description", "isOfficial", "source", "yyyy_mm_dd", "card_icon", "versionCode" };
foreach (string k in keys)
{
var node = root.SelectSingleNode(k);
if (node != null)
dict[k] = node.InnerText;
XmlNode node = root.SelectSingleNode(k);
if (node != null) dict[k] = node.InnerText;
}
}
catch (System.Exception e)
catch (System.Exception ex)
{
Debug.LogWarning("Failed to read info.xml: " + xmlPath + " error: " + e.Message);
Debug.LogWarning("ReadInfoXml failed: " + ex.Message);
}
return dict;
}
// 将日期文本尝试解析成 yyyy\nmm\ndd 格式,若解析失败则做简单替换(支持 2025-12-23, 2025/12/23, 2025_12_23, 20251223 等)
string FormatDateWithNewlines(string raw)
string GetMeta(Dictionary<string, string> meta, string key)
{
if (string.IsNullOrEmpty(raw))
return "";
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];
}
if (parts.Length == 3) return parts[0] + "\n" + parts[1] + "\n" + parts[2];
// 尝试连续数字 8 位
var digits = System.Text.RegularExpressions.Regex.Replace(raw, "\\D", "");
if (digits.Length == 8)
{
string y = digits.Substring(0, 4);
string m = digits.Substring(4, 2);
string d = digits.Substring(6, 2);
return y + "\n" + m + "\n" + d;
}
// 回退:尝试用第一个 4 位作为年,接下来的两位为月,之后为日
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);
@@ -218,13 +284,8 @@ public class loadLittleGamesPrefab : MonoBehaviour
return y + "\n" + m + "\n" + d;
}
// 最后回退直接把原文本按字符换行(有限长度)
return raw.Replace("-", "\n").Replace("/", "\n").Replace("_", "\n");
}
// Update is called once per frame
void Update()
{
}
void Update() { }
}