技能主要更新,修复卡顿并加入动画,以及各种其他更新。

This commit is contained in:
FloatGaming
2026-02-07 21:05:17 +08:00
parent abeca51be5
commit 1ac3cd0104
1349 changed files with 1526749 additions and 24850 deletions
+681
View File
@@ -0,0 +1,681 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using TMPro;
using DG.Tweening;
public class UI_FunctionHoverGuide : MonoBehaviour
{
[Header("Scene")]
public bool onlyInUiUi = true;
public string uiUiSceneName = "UI_UI";
[Header("Config")]
public UI_FunctionHoverConfig config;
public string configResourcesName = "UI_FunctionHoverConfig";
public bool debugLogs = false;
public bool useManualHover = true;
[Header("Function References")]
public string functionRootName = "Function";
public string functionRootPath = "UI_Panel_Main_main/Content/Function";
public string targetBasePath = "UI_Panel_Main_main";
public string iconNameContains = "ICON";
public string textNameContains = "TEXT";
public RectTransform functionRoot;
public Image functionIcon;
public RawImage functionIconRaw;
public TMP_Text functionTextTmp;
public Text functionTextLegacy;
[Header("Animation")]
public float iconStartScale = 0.6f;
public float iconPopDuration = 0.35f;
public int iconFlashCount = 2;
public float iconFlashInterval = 0.05f;
public float textTypeInterval = 0.02f;
public bool useUnscaledTime = true;
[Header("Gyro")]
public bool enableGyro = false;
public float gyroMaxRotation = 4f;
public float gyroSmooth = 10f;
private Sprite defaultIcon;
private Texture defaultIconTexture;
private string defaultText;
private Vector3 iconBaseScale = Vector3.one;
private bool iconBaseScaleCached;
private bool initialized;
private Coroutine typeRoutine;
private Sequence iconSeq;
private UI_FunctionHoverEntry currentEntry;
private Coroutine retryRoutine;
private readonly List<HoverTarget> hoverTargets = new();
private class HoverTarget
{
public UI_FunctionHoverEntry entry;
public RectTransform rect;
public float area;
}
private void OnEnable()
{
SceneManager.sceneLoaded += OnSceneLoaded;
TryInit();
}
private void OnDisable()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
}
private void Update()
{
if (!initialized || !useManualHover) return;
if (hoverTargets.Count == 0) return;
if (!IsUiUiScene()) return;
UI_FunctionHoverEntry entry = GetHoveredEntry();
if (entry == currentEntry) return;
if (entry != null)
ApplyEntry(entry);
else if (currentEntry != null)
ClearEntry(currentEntry);
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
initialized = false;
TryInit();
}
private void TryInit()
{
if (initialized) return;
if (onlyInUiUi && !IsUiUiScene()) return;
if (config == null && !string.IsNullOrEmpty(configResourcesName))
config = Resources.Load<UI_FunctionHoverConfig>(configResourcesName);
if (config == null)
{
Debug.LogWarning("UI_FunctionHoverGuide: config is missing. Create a UI_FunctionHoverConfig asset and assign it.");
return;
}
ResolveFunctionRefs();
if (functionRoot == null || functionIcon == null || (functionTextTmp == null && functionTextLegacy == null))
{
StartRetry();
return;
}
CacheDefault();
EnsureWrapping();
EnsureGyro();
int wired = WireTargets();
if (debugLogs)
Debug.Log($"UI_FunctionHoverGuide: wired targets = {wired}, functionRoot = {functionRoot?.name}");
if (wired == 0)
{
StartRetry();
return;
}
initialized = true;
}
private void StartRetry()
{
if (retryRoutine != null) return;
retryRoutine = StartCoroutine(RetryInit());
}
private IEnumerator RetryInit()
{
int tries = 0;
while (!initialized)
{
ResolveFunctionRefs();
if (functionRoot != null && functionIcon != null && (functionTextTmp != null || functionTextLegacy != null))
{
CacheDefault();
EnsureWrapping();
EnsureGyro();
int wired = WireTargets();
if (debugLogs)
Debug.Log($"UI_FunctionHoverGuide: retry wired targets = {wired}");
if (wired > 0)
{
initialized = true;
break;
}
}
tries++;
yield return new WaitForSecondsRealtime(0.25f);
}
retryRoutine = null;
}
private bool IsUiUiScene()
{
if (string.IsNullOrEmpty(uiUiSceneName)) return true;
var active = SceneManager.GetActiveScene().name;
return active.IndexOf(uiUiSceneName, StringComparison.OrdinalIgnoreCase) >= 0;
}
private void ResolveFunctionRefs()
{
if (functionRoot == null)
{
GameObject go = null;
if (!string.IsNullOrWhiteSpace(functionRootPath))
go = GameObject.Find(functionRootPath);
if (go == null && !string.IsNullOrWhiteSpace(targetBasePath))
go = GameObject.Find(CombinePath(targetBasePath, functionRootName));
if (go == null)
go = FindByNameContains(functionRootName);
if (go != null)
functionRoot = go.GetComponent<RectTransform>();
}
if (functionRoot == null) return;
if (functionIcon == null)
{
var icon = FindChildGraphic(functionRoot, iconNameContains);
if (icon != null) functionIcon = icon;
}
if (functionIconRaw == null && functionIcon == null)
{
var raw = FindChildRawImage(functionRoot, iconNameContains);
if (raw != null) functionIconRaw = raw;
}
if (functionTextTmp == null && functionTextLegacy == null)
{
var tmp = FindChildTmp(functionRoot, textNameContains);
if (tmp != null)
functionTextTmp = tmp;
else
functionTextLegacy = FindChildLegacyText(functionRoot, textNameContains);
}
}
private void CacheDefault()
{
if (functionIcon != null)
defaultIcon = functionIcon.sprite;
if (functionIconRaw != null)
defaultIconTexture = functionIconRaw.texture;
var iconGraphic = functionIcon != null ? (Graphic)functionIcon : functionIconRaw;
if (iconGraphic != null && iconGraphic.rectTransform != null)
{
var scale = iconGraphic.rectTransform.localScale;
if (IsFinite(scale) && scale != Vector3.zero)
{
iconBaseScale = scale;
iconBaseScaleCached = true;
}
}
if (functionTextTmp != null)
defaultText = functionTextTmp.text;
else if (functionTextLegacy != null)
defaultText = functionTextLegacy.text;
}
private void EnsureWrapping()
{
if (functionTextTmp != null)
{
functionTextTmp.textWrappingMode = TextWrappingModes.Normal;
functionTextTmp.overflowMode = TextOverflowModes.Overflow;
}
if (functionTextLegacy != null)
{
functionTextLegacy.horizontalOverflow = HorizontalWrapMode.Wrap;
functionTextLegacy.verticalOverflow = VerticalWrapMode.Overflow;
}
}
private void EnsureGyro()
{
if (functionRoot == null) return;
var gyro = functionRoot.GetComponent<BgmGyroRotator>();
if (gyro != null)
{
if (Application.isPlaying)
Destroy(gyro);
else
DestroyImmediate(gyro);
}
}
private int WireTargets()
{
if (config == null || config.entries == null) return 0;
hoverTargets.Clear();
int wired = 0;
for (int i = 0; i < config.entries.Count; i++)
{
var entry = config.entries[i];
if (entry == null || string.IsNullOrWhiteSpace(entry.path)) continue;
var target = FindByPath(entry.path);
if (target == null)
{
if (debugLogs)
Debug.LogWarning("UI_FunctionHoverGuide: target not found for path: " + entry.path);
continue;
}
if (AttachHoverTargets(target.transform, entry))
wired++;
}
return wired;
}
public void ApplyEntry(UI_FunctionHoverEntry entry)
{
if (entry == null) return;
if (currentEntry == entry) return;
currentEntry = entry;
if (functionIcon != null && entry.icon != null)
functionIcon.sprite = entry.icon;
if (functionIconRaw != null)
functionIconRaw.texture = entry.icon != null ? entry.icon.texture : defaultIconTexture;
PlayIconAnim();
PlayTypewriter(entry.text);
}
public void ClearEntry(UI_FunctionHoverEntry entry)
{
if (currentEntry != entry) return;
currentEntry = null;
if (functionIcon != null && defaultIcon != null)
functionIcon.sprite = defaultIcon;
if (functionIconRaw != null && defaultIconTexture != null)
functionIconRaw.texture = defaultIconTexture;
PlayIconAnim();
PlayTypewriter(defaultText);
}
private void PlayIconAnim()
{
Graphic iconGraphic = functionIcon != null ? (Graphic)functionIcon : functionIconRaw;
if (iconGraphic == null) return;
var rt = iconGraphic.rectTransform;
var baseScale = iconBaseScaleCached ? iconBaseScale : Vector3.one;
if (!iconBaseScaleCached && rt != null)
baseScale = rt.localScale;
if (!IsFinite(baseScale))
baseScale = Vector3.one;
if (iconSeq != null && iconSeq.IsActive())
iconSeq.Kill();
var color = iconGraphic.color;
iconGraphic.color = new Color(color.r, color.g, color.b, 0f);
if (rt != null)
rt.localScale = baseScale * iconStartScale;
iconSeq = DOTween.Sequence();
for (int i = 0; i < Mathf.Max(1, iconFlashCount); i++)
{
iconSeq.Append(iconGraphic.DOFade(1f, iconFlashInterval));
iconSeq.Append(iconGraphic.DOFade(0.25f, iconFlashInterval));
}
iconSeq.Append(iconGraphic.DOFade(1f, iconFlashInterval));
if (rt != null)
iconSeq.Join(rt.DOScale(baseScale, iconPopDuration).SetEase(Ease.OutBack));
}
private static bool IsFinite(Vector3 v)
{
return !(float.IsNaN(v.x) || float.IsNaN(v.y) || float.IsNaN(v.z)
|| float.IsInfinity(v.x) || float.IsInfinity(v.y) || float.IsInfinity(v.z));
}
private void PlayTypewriter(string text)
{
if (string.IsNullOrEmpty(text)) text = string.Empty;
if (typeRoutine != null)
StopCoroutine(typeRoutine);
if (functionTextTmp != null)
typeRoutine = StartCoroutine(TypeTmp(functionTextTmp, text));
else if (functionTextLegacy != null)
typeRoutine = StartCoroutine(TypeLegacy(functionTextLegacy, text));
}
private IEnumerator TypeTmp(TMP_Text tmp, string text)
{
tmp.text = text;
tmp.maxVisibleCharacters = 0;
tmp.ForceMeshUpdate();
int total = tmp.textInfo.characterCount;
for (int i = 0; i <= total; i++)
{
tmp.maxVisibleCharacters = i;
yield return Wait(textTypeInterval);
}
}
private IEnumerator TypeLegacy(Text txt, string text)
{
txt.text = string.Empty;
for (int i = 0; i <= text.Length; i++)
{
txt.text = text.Substring(0, i);
yield return Wait(textTypeInterval);
}
}
private IEnumerator Wait(float t)
{
if (useUnscaledTime)
yield return new WaitForSecondsRealtime(t);
else
yield return new WaitForSeconds(t);
}
private GameObject FindByPath(string path)
{
if (string.IsNullOrWhiteSpace(path)) return null;
var go = GameObject.Find(path);
if (go != null) return go;
if (!string.IsNullOrWhiteSpace(targetBasePath))
{
var combined = CombinePath(targetBasePath, path);
go = GameObject.Find(combined);
if (go != null) return go;
}
string normalized = NormalizePath(path);
if (string.IsNullOrEmpty(normalized)) return null;
var all = Resources.FindObjectsOfTypeAll<Transform>();
for (int i = 0; i < all.Length; i++)
{
var t = all[i];
if (t == null) continue;
if (!t.gameObject.scene.IsValid() || !t.gameObject.scene.isLoaded) continue;
string full = NormalizePath(GetFullPath(t));
if (string.Equals(full, normalized, StringComparison.OrdinalIgnoreCase) ||
full.EndsWith("/" + normalized, StringComparison.OrdinalIgnoreCase))
return t.gameObject;
}
string[] parts = path.Split('/');
string last = parts[parts.Length - 1];
return FindByNameContains(last);
}
private GameObject FindByNameContains(string name)
{
if (string.IsNullOrWhiteSpace(name)) return null;
var all = Resources.FindObjectsOfTypeAll<Transform>();
string target = name.Replace(" ", "");
for (int i = 0; i < all.Length; i++)
{
var t = all[i];
if (t == null) continue;
if (!t.gameObject.scene.IsValid() || !t.gameObject.scene.isLoaded) continue;
string n = t.name.Replace(" ", "");
if (n.IndexOf(target, StringComparison.OrdinalIgnoreCase) >= 0)
return t.gameObject;
}
return null;
}
private static string CombinePath(string basePath, string path)
{
if (string.IsNullOrWhiteSpace(basePath)) return path;
if (string.IsNullOrWhiteSpace(path)) return basePath;
string trimmedBase = basePath.Trim().TrimEnd('/', '\\');
string trimmedPath = path.Trim().TrimStart('/', '\\');
if (trimmedPath.StartsWith(trimmedBase, StringComparison.OrdinalIgnoreCase))
return trimmedPath;
return trimmedBase + "/" + trimmedPath;
}
private static string NormalizePath(string path)
{
if (string.IsNullOrEmpty(path)) return string.Empty;
return path.Replace('\\', '/').Replace(" ", "");
}
private static string GetFullPath(Transform t)
{
if (t == null) return string.Empty;
var parts = new List<string>();
var cur = t;
while (cur != null)
{
parts.Add(cur.name);
cur = cur.parent;
}
parts.Reverse();
return string.Join("/", parts);
}
private static Image FindChildGraphic(Transform root, string nameContains)
{
var imgs = root.GetComponentsInChildren<Image>(true);
if (imgs == null || imgs.Length == 0) return null;
if (!string.IsNullOrEmpty(nameContains))
{
string target = nameContains.Replace(" ", "");
for (int i = 0; i < imgs.Length; i++)
{
var img = imgs[i];
if (img == null) continue;
string n = img.name.Replace(" ", "");
if (n.IndexOf(target, StringComparison.OrdinalIgnoreCase) >= 0)
return img;
}
}
return imgs[0];
}
private static RawImage FindChildRawImage(Transform root, string nameContains)
{
var raws = root.GetComponentsInChildren<RawImage>(true);
if (raws == null || raws.Length == 0) return null;
if (!string.IsNullOrEmpty(nameContains))
{
string target = nameContains.Replace(" ", "");
for (int i = 0; i < raws.Length; i++)
{
var img = raws[i];
if (img == null) continue;
string n = img.name.Replace(" ", "");
if (n.IndexOf(target, StringComparison.OrdinalIgnoreCase) >= 0)
return img;
}
}
return raws[0];
}
private static TMP_Text FindChildTmp(Transform root, string nameContains)
{
var tmps = root.GetComponentsInChildren<TMP_Text>(true);
if (tmps == null || tmps.Length == 0) return null;
if (!string.IsNullOrEmpty(nameContains))
{
string target = nameContains.Replace(" ", "");
for (int i = 0; i < tmps.Length; i++)
{
var t = tmps[i];
if (t == null) continue;
string n = t.name.Replace(" ", "");
if (n.IndexOf(target, StringComparison.OrdinalIgnoreCase) >= 0)
return t;
}
}
return tmps[0];
}
private static Text FindChildLegacyText(Transform root, string nameContains)
{
var texts = root.GetComponentsInChildren<Text>(true);
if (texts == null || texts.Length == 0) return null;
if (!string.IsNullOrEmpty(nameContains))
{
string target = nameContains.Replace(" ", "");
for (int i = 0; i < texts.Length; i++)
{
var t = texts[i];
if (t == null) continue;
string n = t.name.Replace(" ", "");
if (n.IndexOf(target, StringComparison.OrdinalIgnoreCase) >= 0)
return t;
}
}
return texts[0];
}
private static void EnsureRaycastTarget(GameObject go)
{
var graphics = go.GetComponentsInChildren<Graphic>(true);
if (graphics != null && graphics.Length > 0)
{
for (int i = 0; i < graphics.Length; i++)
{
if (graphics[i] != null)
graphics[i].raycastTarget = true;
}
return;
}
var img = go.AddComponent<Image>();
img.color = new Color(1f, 1f, 1f, 0f);
img.raycastTarget = true;
}
private bool AttachHoverTargets(Transform root, UI_FunctionHoverEntry entry)
{
if (root == null || entry == null) return false;
Transform attachRoot = root;
Button button = root.GetComponent<Button>();
if (button == null)
button = root.GetComponentInParent<Button>();
if (button != null)
attachRoot = button.transform;
var hover = attachRoot.GetComponent<UI_FunctionHoverTarget>();
if (hover == null)
hover = attachRoot.gameObject.AddComponent<UI_FunctionHoverTarget>();
hover.guide = this;
hover.entry = entry;
if (button != null)
{
if (button.targetGraphic != null)
button.targetGraphic.raycastTarget = true;
else
EnsureRaycastTarget(attachRoot.gameObject);
}
else
{
var graphic = attachRoot.GetComponent<Graphic>();
if (graphic != null)
graphic.raycastTarget = true;
else
EnsureRaycastTarget(attachRoot.gameObject);
}
var rect = attachRoot as RectTransform;
if (rect != null)
{
hoverTargets.Add(new HoverTarget
{
entry = entry,
rect = rect,
area = Mathf.Abs(rect.rect.width * rect.rect.height)
});
}
return true;
}
private UI_FunctionHoverEntry GetHoveredEntry()
{
if (hoverTargets.Count == 0) return null;
Vector2 mouse = Input.mousePosition;
UI_FunctionHoverEntry best = null;
float bestArea = float.MaxValue;
for (int i = 0; i < hoverTargets.Count; i++)
{
var target = hoverTargets[i];
if (target == null || target.rect == null) continue;
if (!target.rect.gameObject.activeInHierarchy) continue;
var canvas = target.rect.GetComponentInParent<Canvas>();
Camera cam = canvas != null && canvas.renderMode != RenderMode.ScreenSpaceOverlay
? canvas.worldCamera
: null;
if (RectTransformUtility.RectangleContainsScreenPoint(target.rect, mouse, cam))
{
float area = target.area > 0f ? target.area : Mathf.Abs(target.rect.rect.width * target.rect.rect.height);
if (area < bestArea)
{
bestArea = area;
best = target.entry;
}
}
}
return best;
}
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
private static void Bootstrap()
{
SceneManager.sceneLoaded += BootstrapOnSceneLoaded;
int count = SceneManager.sceneCount;
for (int i = 0; i < count; i++)
{
var scene = SceneManager.GetSceneAt(i);
if (scene.isLoaded)
BootstrapOnSceneLoaded(scene, LoadSceneMode.Single);
}
}
private static void BootstrapOnSceneLoaded(Scene scene, LoadSceneMode mode)
{
if (!scene.IsValid()) return;
if (scene.name.IndexOf("UI_UI", StringComparison.OrdinalIgnoreCase) < 0) return;
if (UnityEngine.Object.FindAnyObjectByType<UI_FunctionHoverGuide>() != null) return;
var go = new GameObject("UI_FunctionHoverGuide");
go.AddComponent<UI_FunctionHoverGuide>();
}
}
public class UI_FunctionHoverTarget : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
public UI_FunctionHoverGuide guide;
public UI_FunctionHoverEntry entry;
public void OnPointerEnter(PointerEventData eventData)
{
if (guide != null && entry != null)
guide.ApplyEntry(entry);
}
public void OnPointerExit(PointerEventData eventData)
{
if (guide != null && entry != null)
guide.ClearEntry(entry);
}
}