技能主要更新,修复卡顿并加入动画,以及各种其他更新。
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,116 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class BottomBarButtonHover : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerDownHandler, IPointerUpHandler, IPointerMoveHandler
|
||||
{
|
||||
public float hoverScale = 1.06f;
|
||||
public float pressScale = 0.92f;
|
||||
public float hoverWidthScale = 1.08f;
|
||||
public float scaleLerp = 12f;
|
||||
public float gyroAngle = 6f;
|
||||
public float gyroLerp = 12f;
|
||||
|
||||
private RectTransform rect;
|
||||
private LayoutElement layout;
|
||||
private float basePreferredWidth;
|
||||
private Vector3 baseScale;
|
||||
private bool hovering;
|
||||
private bool pressed;
|
||||
private Vector3 targetScale;
|
||||
private Quaternion targetRotation = Quaternion.identity;
|
||||
private Camera eventCamera;
|
||||
private RectTransform layoutRoot;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
rect = GetComponent<RectTransform>();
|
||||
baseScale = transform.localScale;
|
||||
targetScale = baseScale;
|
||||
|
||||
layout = GetComponent<LayoutElement>();
|
||||
if (layout == null)
|
||||
layout = gameObject.AddComponent<LayoutElement>();
|
||||
|
||||
basePreferredWidth = layout.preferredWidth;
|
||||
if (basePreferredWidth <= 0f && rect != null)
|
||||
basePreferredWidth = rect.rect.width;
|
||||
|
||||
if (basePreferredWidth <= 0f)
|
||||
basePreferredWidth = 100f;
|
||||
|
||||
layout.preferredWidth = basePreferredWidth;
|
||||
|
||||
var group = GetComponentInParent<LayoutGroup>();
|
||||
if (group != null)
|
||||
layoutRoot = group.GetComponent<RectTransform>();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
float dt = Time.unscaledDeltaTime;
|
||||
transform.localScale = Vector3.Lerp(transform.localScale, targetScale, 1f - Mathf.Exp(-scaleLerp * dt));
|
||||
|
||||
if (hovering)
|
||||
{
|
||||
Vector2 localPoint;
|
||||
if (rect != null && RectTransformUtility.ScreenPointToLocalPointInRectangle(rect, Input.mousePosition, eventCamera, out localPoint))
|
||||
{
|
||||
Vector2 size = rect.rect.size;
|
||||
float nx = size.x > 0f ? localPoint.x / size.x : 0f;
|
||||
float ny = size.y > 0f ? localPoint.y / size.y : 0f;
|
||||
float rotX = -ny * gyroAngle * 2f;
|
||||
float rotY = nx * gyroAngle * 2f;
|
||||
targetRotation = Quaternion.Euler(rotX, rotY, 0f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
targetRotation = Quaternion.identity;
|
||||
}
|
||||
|
||||
transform.localRotation = Quaternion.Slerp(transform.localRotation, targetRotation, 1f - Mathf.Exp(-gyroLerp * dt));
|
||||
}
|
||||
|
||||
public void OnPointerEnter(PointerEventData eventData)
|
||||
{
|
||||
hovering = true;
|
||||
eventCamera = eventData.pressEventCamera;
|
||||
layout.preferredWidth = basePreferredWidth * hoverWidthScale;
|
||||
RebuildLayout();
|
||||
if (!pressed)
|
||||
targetScale = baseScale * hoverScale;
|
||||
}
|
||||
|
||||
public void OnPointerExit(PointerEventData eventData)
|
||||
{
|
||||
hovering = false;
|
||||
layout.preferredWidth = basePreferredWidth;
|
||||
RebuildLayout();
|
||||
if (!pressed)
|
||||
targetScale = baseScale;
|
||||
}
|
||||
|
||||
public void OnPointerDown(PointerEventData eventData)
|
||||
{
|
||||
pressed = true;
|
||||
targetScale = baseScale * pressScale;
|
||||
}
|
||||
|
||||
public void OnPointerUp(PointerEventData eventData)
|
||||
{
|
||||
pressed = false;
|
||||
targetScale = baseScale * (hovering ? hoverScale : 1f);
|
||||
}
|
||||
|
||||
public void OnPointerMove(PointerEventData eventData)
|
||||
{
|
||||
eventCamera = eventData.pressEventCamera;
|
||||
}
|
||||
|
||||
private void RebuildLayout()
|
||||
{
|
||||
if (layoutRoot != null)
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(layoutRoot);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a3c858fc56d8aa84daefcd0121bcadd8
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,13 @@ public class btmandtopController : MonoBehaviour
|
||||
public Button email_display;
|
||||
public Button notice_display;
|
||||
|
||||
public Button button_Music;
|
||||
[Header("MusicPic")]
|
||||
public RectTransform musicPicRoot;
|
||||
public bool showMusicPicInUI = false;
|
||||
public string uiSceneName = "UI_UI";
|
||||
public float musicPicFadeTime = 0.25f;
|
||||
|
||||
[Header("sig prefabs")]
|
||||
public GameObject showLevel_prefab;
|
||||
public GameObject store_prefab;
|
||||
@@ -33,15 +40,37 @@ public class btmandtopController : MonoBehaviour
|
||||
|
||||
// cached reference to the settings instance to ensure only one exists
|
||||
private GameObject settingsInstance;
|
||||
private GameObject showLevelInstance;
|
||||
private GameObject emailInstance;
|
||||
private GameObject noticeInstance;
|
||||
private CanvasGroup musicPicGroup;
|
||||
private Coroutine musicPicFade;
|
||||
private bool musicPicVisible = true;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
EnsureMusicPicRoot();
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (button_Music == null)
|
||||
{
|
||||
var btn = transform.Find("Button_Music") as RectTransform;
|
||||
if (btn != null)
|
||||
button_Music = btn.GetComponent<Button>();
|
||||
if (button_Music == null)
|
||||
{
|
||||
var any = FindByName("Button_Music");
|
||||
if (any != null) button_Music = any.GetComponent<Button>();
|
||||
}
|
||||
}
|
||||
instantiateSettings = () => ToggleSettingsPrefab();
|
||||
instantiateUserInfo = () => InstantiatePrefab(userInfo_prefab);
|
||||
instantiateStore = () => InstantiatePrefab(store_prefab);
|
||||
instantiateShowLevel = () => InstantiatePrefab(showLevel_prefab);
|
||||
instantiateEmail = () => InstantiatePrefab(email_prefab);
|
||||
instantiateNotice = () => InstantiatePrefab(notice_prefab);
|
||||
instantiateShowLevel = () => ShowLevelPrefab();
|
||||
instantiateEmail = () => ShowEmailPrefab();
|
||||
instantiateNotice = () => ShowNoticePrefab();
|
||||
|
||||
if (settings_launch != null)
|
||||
settings_launch.onClick.AddListener(instantiateSettings);
|
||||
@@ -56,6 +85,17 @@ public class btmandtopController : MonoBehaviour
|
||||
if (notice_display != null)
|
||||
notice_display.onClick.AddListener(instantiateNotice);
|
||||
|
||||
EnsureMusicPicRoot();
|
||||
SetupMusicPicDefault();
|
||||
if (button_Music != null)
|
||||
button_Music.onClick.AddListener(ToggleMusicPicLocal);
|
||||
|
||||
var binder = BgmUiBinder.Instance != null ? BgmUiBinder.Instance : Object.FindAnyObjectByType<BgmUiBinder>();
|
||||
if (binder != null && musicPicRoot != null)
|
||||
{
|
||||
binder.SetMusicRoot(musicPicRoot);
|
||||
}
|
||||
|
||||
// Pre-instantiate settings prefab and keep it inactive, ensuring only one exists
|
||||
if (settings_prefab != null && putPrefabsHere != null)
|
||||
{
|
||||
@@ -80,7 +120,7 @@ public class btmandtopController : MonoBehaviour
|
||||
Canvas c = settingsInstance.GetComponentInChildren<Canvas>(true);
|
||||
if (c != null)
|
||||
{
|
||||
Camera[] cams = GameObject.FindObjectsOfType<Camera>();
|
||||
Camera[] cams = Object.FindObjectsByType<Camera>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
|
||||
if (cams != null && cams.Length > 0)
|
||||
{
|
||||
c.worldCamera = cams[0];
|
||||
@@ -100,8 +140,160 @@ public class btmandtopController : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
if(Input.GetKeyDown(KeyCode.Escape))
|
||||
{
|
||||
//ToggleSettingsPrefab();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void ToggleMusicPic()
|
||||
{
|
||||
var binder = BgmUiBinder.Instance != null ? BgmUiBinder.Instance : Object.FindAnyObjectByType<BgmUiBinder>();
|
||||
if (binder != null)
|
||||
{
|
||||
binder.ToggleMusicPic();
|
||||
var spectrum = binder.GetSpectrumRoot();
|
||||
if (spectrum != null && binder.transform != null)
|
||||
{
|
||||
spectrum.localRotation = binder.transform.localRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureMusicPicRoot()
|
||||
{
|
||||
if (musicPicRoot != null)
|
||||
return;
|
||||
var child = transform.Find("MusicPic");
|
||||
if (child != null)
|
||||
{
|
||||
musicPicRoot = child as RectTransform;
|
||||
return;
|
||||
}
|
||||
var sceneMusic = GameObject.Find("MusicPic");
|
||||
if (sceneMusic != null)
|
||||
{
|
||||
musicPicRoot = sceneMusic.GetComponent<RectTransform>();
|
||||
if (musicPicRoot != null)
|
||||
{
|
||||
musicPicRoot.SetParent(transform, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupMusicPicDefault()
|
||||
{
|
||||
if (musicPicRoot == null)
|
||||
return;
|
||||
EnsureMusicPicGroup();
|
||||
SetMusicPicVisible(false, true);
|
||||
}
|
||||
|
||||
private void EnsureMusicPicGroup()
|
||||
{
|
||||
if (musicPicRoot == null)
|
||||
return;
|
||||
if (musicPicGroup == null)
|
||||
musicPicGroup = musicPicRoot.GetComponent<CanvasGroup>();
|
||||
if (musicPicGroup == null)
|
||||
musicPicGroup = musicPicRoot.gameObject.AddComponent<CanvasGroup>();
|
||||
}
|
||||
|
||||
private void ToggleMusicPicLocal()
|
||||
{
|
||||
EnsureMusicPicRoot();
|
||||
var binder = BgmUiBinder.Instance != null ? BgmUiBinder.Instance : Object.FindAnyObjectByType<BgmUiBinder>();
|
||||
if (musicPicRoot == null)
|
||||
{
|
||||
if (binder != null)
|
||||
binder.ToggleMusicPic();
|
||||
return;
|
||||
}
|
||||
|
||||
SetMusicPicVisible(!musicPicVisible, false);
|
||||
}
|
||||
|
||||
private void SetMusicPicVisible(bool visible, bool instant)
|
||||
{
|
||||
if (musicPicRoot == null)
|
||||
return;
|
||||
EnsureMusicPicGroup();
|
||||
musicPicVisible = visible;
|
||||
|
||||
if (musicPicFade != null)
|
||||
{
|
||||
StopCoroutine(musicPicFade);
|
||||
musicPicFade = null;
|
||||
}
|
||||
|
||||
if (instant)
|
||||
{
|
||||
musicPicGroup.alpha = visible ? 1f : 0f;
|
||||
musicPicGroup.interactable = visible;
|
||||
musicPicGroup.blocksRaycasts = visible;
|
||||
musicPicRoot.gameObject.SetActive(visible);
|
||||
return;
|
||||
}
|
||||
|
||||
musicPicRoot.gameObject.SetActive(true);
|
||||
musicPicFade = StartCoroutine(FadeMusicPic(visible));
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator FadeMusicPic(bool visibleAtEnd)
|
||||
{
|
||||
EnsureMusicPicGroup();
|
||||
if (musicPicGroup == null)
|
||||
yield break;
|
||||
float start = musicPicGroup != null ? musicPicGroup.alpha : 0f;
|
||||
float target = visibleAtEnd ? 1f : 0f;
|
||||
float t = 0f;
|
||||
while (t < 1f)
|
||||
{
|
||||
if (musicPicGroup == null || musicPicRoot == null)
|
||||
yield break;
|
||||
t += Time.unscaledDeltaTime / Mathf.Max(0.0001f, musicPicFadeTime);
|
||||
float eased = Mathf.SmoothStep(0f, 1f, t);
|
||||
musicPicGroup.alpha = Mathf.LerpUnclamped(start, target, eased);
|
||||
yield return null;
|
||||
}
|
||||
if (musicPicGroup != null)
|
||||
{
|
||||
musicPicGroup.alpha = target;
|
||||
musicPicGroup.interactable = visibleAtEnd;
|
||||
musicPicGroup.blocksRaycasts = visibleAtEnd;
|
||||
}
|
||||
if (!visibleAtEnd && musicPicRoot != null)
|
||||
{
|
||||
musicPicRoot.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
GameObject FindByName(string name)
|
||||
{
|
||||
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;
|
||||
if (t.name == name) return t.gameObject;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
if (musicPicFade != null)
|
||||
{
|
||||
StopCoroutine(musicPicFade);
|
||||
musicPicFade = null;
|
||||
}
|
||||
musicPicGroup = null;
|
||||
if (settings_launch != null)
|
||||
settings_launch.onClick.RemoveListener(instantiateSettings);
|
||||
if (userInfo_launch != null)
|
||||
@@ -114,6 +306,9 @@ public class btmandtopController : MonoBehaviour
|
||||
email_display.onClick.RemoveListener(instantiateEmail);
|
||||
if (notice_display != null)
|
||||
notice_display.onClick.RemoveListener(instantiateNotice);
|
||||
|
||||
if (button_Music != null)
|
||||
button_Music.onClick.RemoveListener(ToggleMusicPicLocal);
|
||||
}
|
||||
|
||||
private void ToggleSettingsPrefab()
|
||||
@@ -131,7 +326,7 @@ public class btmandtopController : MonoBehaviour
|
||||
Canvas c = settingsInstance.GetComponentInChildren<Canvas>(true);
|
||||
if (c != null)
|
||||
{
|
||||
Camera[] cams = GameObject.FindObjectsOfType<Camera>();
|
||||
Camera[] cams = Object.FindObjectsByType<Camera>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
|
||||
if (cams != null && cams.Length > 0)
|
||||
{
|
||||
c.worldCamera = cams[0];
|
||||
@@ -151,6 +346,97 @@ public class btmandtopController : MonoBehaviour
|
||||
settingsInstance.SetActive(!active);
|
||||
}
|
||||
|
||||
private void ShowLevelPrefab()
|
||||
{
|
||||
ShowPrefab(showLevel_prefab, ref showLevelInstance);
|
||||
CloseInfoPanels(showLevelInstance);
|
||||
}
|
||||
|
||||
private void ShowEmailPrefab()
|
||||
{
|
||||
ShowPrefab(email_prefab, ref emailInstance);
|
||||
CloseInfoPanels(emailInstance);
|
||||
}
|
||||
|
||||
private void ShowNoticePrefab()
|
||||
{
|
||||
ShowPrefab(notice_prefab, ref noticeInstance);
|
||||
CloseInfoPanels(noticeInstance);
|
||||
}
|
||||
|
||||
private void ShowPrefab(GameObject prefab, ref GameObject instance)
|
||||
{
|
||||
if (prefab == null || putPrefabsHere == null) return;
|
||||
|
||||
if (instance == null)
|
||||
{
|
||||
instance = FindExistingInstance(prefab);
|
||||
if (instance == null)
|
||||
{
|
||||
instance = Instantiate(prefab, putPrefabsHere.transform);
|
||||
TryAssignCanvasCamera(instance);
|
||||
}
|
||||
else
|
||||
{
|
||||
instance.transform.SetParent(putPrefabsHere.transform, false);
|
||||
}
|
||||
}
|
||||
|
||||
instance.SetActive(true);
|
||||
instance.transform.SetAsLastSibling();
|
||||
}
|
||||
|
||||
private void CloseInfoPanels(GameObject keep)
|
||||
{
|
||||
CloseInstance(ref showLevelInstance, keep);
|
||||
CloseInstance(ref emailInstance, keep);
|
||||
CloseInstance(ref noticeInstance, keep);
|
||||
}
|
||||
|
||||
private void CloseInstance(ref GameObject instance, GameObject keep)
|
||||
{
|
||||
if (instance == null || instance == keep) return;
|
||||
instance.SetActive(false);
|
||||
}
|
||||
|
||||
private GameObject FindExistingInstance(GameObject prefab)
|
||||
{
|
||||
if (prefab == null || putPrefabsHere == null) return null;
|
||||
|
||||
Transform parent = putPrefabsHere.transform;
|
||||
for (int i = 0; i < parent.childCount; i++)
|
||||
{
|
||||
Transform child = parent.GetChild(i);
|
||||
if (child == null) continue;
|
||||
if (child.name == prefab.name || child.name == prefab.name + "(Clone)")
|
||||
{
|
||||
return child.gameObject;
|
||||
}
|
||||
}
|
||||
|
||||
GameObject existing = GameObject.Find(prefab.name + "(Clone)");
|
||||
return existing;
|
||||
}
|
||||
|
||||
private void TryAssignCanvasCamera(GameObject instance)
|
||||
{
|
||||
if (instance == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
Canvas c = instance.GetComponentInChildren<Canvas>(true);
|
||||
if (c != null)
|
||||
{
|
||||
Camera[] cams = Object.FindObjectsByType<Camera>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
|
||||
if (cams != null && cams.Length > 0)
|
||||
{
|
||||
c.worldCamera = cams[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void InstantiatePrefab(GameObject prefab)
|
||||
{
|
||||
if (prefab != null && putPrefabsHere != null)
|
||||
|
||||
@@ -5,6 +5,7 @@ public class loadtopandbottomPrefab: MonoBehaviour
|
||||
[Header("father obj")]
|
||||
public GameObject top_and_bottom_Panel;
|
||||
public GameObject gameobject_to_Instantiate_below;
|
||||
[SerializeField] bool notify_Main_Panel = true;
|
||||
|
||||
void Start()
|
||||
{
|
||||
@@ -17,11 +18,34 @@ public class loadtopandbottomPrefab: MonoBehaviour
|
||||
Camera cam = Camera.main;
|
||||
if (cam == null)
|
||||
{
|
||||
cam = FindObjectOfType<Camera>();
|
||||
cam = Object.FindAnyObjectByType<Camera>();
|
||||
}
|
||||
canvas.worldCamera = cam;
|
||||
cam = null;
|
||||
}
|
||||
if (notify_Main_Panel)
|
||||
{
|
||||
Bind_Main_Panel(instantiated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Bind_Main_Panel(GameObject instantiated)
|
||||
{
|
||||
if (instantiated == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
UI_Panel_Main main = Object.FindAnyObjectByType<UI_Panel_Main>();
|
||||
if (main == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
RectTransform top = instantiated.transform.Find("TOP") as RectTransform;
|
||||
RectTransform bottom = instantiated.transform.Find("BTM") as RectTransform;
|
||||
if (top != null || bottom != null)
|
||||
{
|
||||
main.Bind_Top_Bottom(top, bottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,11 +28,15 @@ public class MusicPlayer : MonoBehaviour
|
||||
Image_Cover.sprite = data.Cover;
|
||||
Text_Title.text = data.Name;
|
||||
}
|
||||
private System.Text.StringBuilder _sb = new System.Text.StringBuilder();
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Music_Source.clip != null)
|
||||
{
|
||||
Text_Time.text = Music_Mgr.Get_Time_Cur(Music_Source) + "/" + Music_Mgr.Get_Time_Max(Music_Source.clip);
|
||||
_sb.Clear();
|
||||
_sb.Append(Music_Mgr.Get_Time_Cur(Music_Source)).Append("/").Append(Music_Mgr.Get_Time_Max(Music_Source.clip));
|
||||
Text_Time.text = _sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,14 +14,21 @@ public class Music_Mgr : Singleton_Mono<Music_Mgr>
|
||||
{
|
||||
return Get_Time((int)aus.time);
|
||||
}
|
||||
private static System.Text.StringBuilder _timeSb = new System.Text.StringBuilder();
|
||||
|
||||
public static string Get_Time(int time)
|
||||
{
|
||||
var currentHour = time / 3600;
|
||||
var currentMinute = (time - currentHour * 3600) / 60;
|
||||
var currentSecond = time - currentHour * 3600 - currentMinute * 60;
|
||||
|
||||
return string.Format("{0:D2}:{1:D2}:{2:D2} ",
|
||||
currentHour, currentMinute, currentSecond);
|
||||
_timeSb.Clear();
|
||||
if (currentHour > 0)
|
||||
{
|
||||
_timeSb.Append(currentHour.ToString("D2")).Append(":");
|
||||
}
|
||||
_timeSb.Append(currentMinute.ToString("D2")).Append(":").Append(currentSecond.ToString("D2")).Append(" ");
|
||||
return _timeSb.ToString();
|
||||
}
|
||||
public static float Get_Time_Cur_Prv(AudioSource aus)
|
||||
{
|
||||
@@ -37,7 +44,21 @@ public class Music_Mgr : Singleton_Mono<Music_Mgr>
|
||||
{
|
||||
AudioSource = gameObject.AddComponent<AudioSource>();
|
||||
}
|
||||
Music_Data_List = Resources.LoadAll<Music_Data>("Data/Music").ToList();
|
||||
StartCoroutine(LoadMusicDataRoutine());
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator LoadMusicDataRoutine()
|
||||
{
|
||||
var rawData = Resources.LoadAll<Music_Data>("Data/Music");
|
||||
Music_Data_List = new List<Music_Data>();
|
||||
|
||||
for (int i = 0; i < rawData.Length; i++)
|
||||
{
|
||||
Music_Data_List.Add(rawData[i]);
|
||||
// 每 10 个数据等待一帧
|
||||
if (i > 0 && i % 10 == 0) yield return null;
|
||||
}
|
||||
|
||||
if (Music_Data_List.Count > 0)
|
||||
{
|
||||
Play(Music_Data_List[0]);
|
||||
|
||||
@@ -38,7 +38,7 @@ public class SimpleAxisTween : MonoBehaviour
|
||||
|
||||
while (elapsed < duration)
|
||||
{
|
||||
elapsed += Time.deltaTime;
|
||||
elapsed += Time.unscaledDeltaTime;
|
||||
float t = Mathf.Clamp01(elapsed / duration);
|
||||
float easedT = ease.Evaluate(t);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine.SceneManagement;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
@@ -19,7 +20,7 @@ public class PauseManager : MonoBehaviour
|
||||
|
||||
public bool IsPaused { get; private set; } = false;
|
||||
|
||||
// 添加暂停状态改变事件
|
||||
// ������ͣ״̬�ı��¼�
|
||||
public event Action<bool> OnPauseStateChanged;
|
||||
|
||||
void Awake()
|
||||
@@ -52,13 +53,13 @@ public class PauseManager : MonoBehaviour
|
||||
TogglePause();
|
||||
}
|
||||
|
||||
// 当处于暂停状态且按下 Backspace 时返回主界面
|
||||
// ��������ͣ״̬�Ұ��� Backspace ʱ����������
|
||||
if (IsPaused && Input.GetKeyDown(KeyCode.Backspace))
|
||||
{
|
||||
// 恢复时间流并触发事件,然后加载主场景
|
||||
// �ָ�ʱ�����������¼���Ȼ�����������
|
||||
Pause(false);
|
||||
// 确保场景名称与项目中的主场景匹配
|
||||
SceneManager.LoadScene("Main_main");
|
||||
// ȷ��������������Ŀ�е�������ƥ��
|
||||
StartCoroutine(LoadSceneAsync("Main_main"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,4 +91,13 @@ public class PauseManager : MonoBehaviour
|
||||
{
|
||||
Pause(!IsPaused);
|
||||
}
|
||||
|
||||
private IEnumerator LoadSceneAsync(string sceneName)
|
||||
{
|
||||
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
|
||||
while (!asyncLoad.isDone)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
[Serializable]
|
||||
public class UI_FunctionHoverEntry
|
||||
{
|
||||
public string path;
|
||||
[TextArea(2, 6)]
|
||||
public string text;
|
||||
public string iconPath;
|
||||
public Sprite icon;
|
||||
}
|
||||
|
||||
[CreateAssetMenu(menuName = "UI/Function Hover Config", fileName = "UI_FunctionHoverConfig")]
|
||||
public class UI_FunctionHoverConfig : ScriptableObject
|
||||
{
|
||||
public List<UI_FunctionHoverEntry> entries = new List<UI_FunctionHoverEntry>();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[ContextMenu("Import From 素材/指向")]
|
||||
private void ImportFromDefaultFile()
|
||||
{
|
||||
ImportFromFile("素材/指向");
|
||||
}
|
||||
|
||||
[ContextMenu("Import From UI_FunctionHoverConfig.json")]
|
||||
private void ImportFromDefaultJson()
|
||||
{
|
||||
ImportFromJsonFile("Assets/Resources/UI_FunctionHoverConfig.json");
|
||||
}
|
||||
|
||||
public void ImportFromFile(string relativePath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(relativePath)) return;
|
||||
string fullPath = Path.Combine(Application.dataPath, "..", relativePath);
|
||||
if (!File.Exists(fullPath))
|
||||
{
|
||||
Debug.LogWarning("UI_FunctionHoverConfig: file not found: " + fullPath);
|
||||
return;
|
||||
}
|
||||
|
||||
string content = File.ReadAllText(fullPath, Encoding.UTF8);
|
||||
var parsed = ParseConfig(content);
|
||||
if (parsed.Count > 0)
|
||||
{
|
||||
entries = parsed;
|
||||
EditorUtility.SetDirty(this);
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
}
|
||||
|
||||
public void ImportFromJsonFile(string relativePath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(relativePath)) return;
|
||||
string fullPath = Path.Combine(Application.dataPath, "..", relativePath);
|
||||
if (!File.Exists(fullPath))
|
||||
{
|
||||
Debug.LogWarning("UI_FunctionHoverConfig: json not found: " + fullPath);
|
||||
return;
|
||||
}
|
||||
|
||||
string content = File.ReadAllText(fullPath, Encoding.UTF8);
|
||||
var parsed = ParseJson(content);
|
||||
if (parsed.Count > 0)
|
||||
{
|
||||
entries = parsed;
|
||||
EditorUtility.SetDirty(this);
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private static List<UI_FunctionHoverEntry> ParseConfig(string content)
|
||||
{
|
||||
var result = new List<UI_FunctionHoverEntry>();
|
||||
if (string.IsNullOrEmpty(content)) return result;
|
||||
|
||||
var blocks = content.Split(new[] { "\r\n\r\n", "\n\n" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var block in blocks)
|
||||
{
|
||||
string pathLine = null;
|
||||
string textLine = null;
|
||||
string iconLine = null;
|
||||
var lines = block.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line.StartsWith("路径")) pathLine = line;
|
||||
else if (line.StartsWith("文本")) textLine = line;
|
||||
else if (line.StartsWith("显示icon")) iconLine = line;
|
||||
}
|
||||
|
||||
if (pathLine == null || textLine == null) continue;
|
||||
|
||||
string pathsRaw = GetValueAfterColon(pathLine);
|
||||
string text = GetValueAfterColon(textLine);
|
||||
string iconPath = iconLine != null ? GetValueAfterColon(iconLine) : null;
|
||||
|
||||
var paths = SplitPaths(pathsRaw);
|
||||
var icon = LoadIcon(iconPath);
|
||||
foreach (var p in paths)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(p)) continue;
|
||||
result.Add(new UI_FunctionHoverEntry
|
||||
{
|
||||
path = p.Trim(),
|
||||
text = text,
|
||||
iconPath = iconPath,
|
||||
icon = icon
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private class JsonRoot
|
||||
{
|
||||
public List<JsonEntry> entries;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private class JsonEntry
|
||||
{
|
||||
public List<string> paths;
|
||||
public string text;
|
||||
public string icon;
|
||||
}
|
||||
|
||||
private static List<UI_FunctionHoverEntry> ParseJson(string content)
|
||||
{
|
||||
var result = new List<UI_FunctionHoverEntry>();
|
||||
if (string.IsNullOrEmpty(content)) return result;
|
||||
|
||||
JsonRoot root = null;
|
||||
try
|
||||
{
|
||||
root = JsonUtility.FromJson<JsonRoot>(content);
|
||||
}
|
||||
catch
|
||||
{
|
||||
root = null;
|
||||
}
|
||||
|
||||
if (root == null || root.entries == null) return result;
|
||||
|
||||
for (int i = 0; i < root.entries.Count; i++)
|
||||
{
|
||||
var entry = root.entries[i];
|
||||
if (entry == null || entry.paths == null) continue;
|
||||
Sprite icon = LoadIcon(entry.icon);
|
||||
for (int p = 0; p < entry.paths.Count; p++)
|
||||
{
|
||||
var path = entry.paths[p];
|
||||
if (string.IsNullOrWhiteSpace(path)) continue;
|
||||
result.Add(new UI_FunctionHoverEntry
|
||||
{
|
||||
path = path.Trim(),
|
||||
text = entry.text,
|
||||
iconPath = entry.icon,
|
||||
icon = icon
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string GetValueAfterColon(string line)
|
||||
{
|
||||
int idx = line.IndexOf(':');
|
||||
if (idx < 0) idx = line.IndexOf(':');
|
||||
if (idx < 0) return line.Trim();
|
||||
return line.Substring(idx + 1).Trim();
|
||||
}
|
||||
|
||||
private static string[] SplitPaths(string raw)
|
||||
{
|
||||
if (string.IsNullOrEmpty(raw)) return Array.Empty<string>();
|
||||
string tmp = raw.Replace("和", "|")
|
||||
.Replace("、", "|")
|
||||
.Replace(",", "|")
|
||||
.Replace(",", "|")
|
||||
.Replace(";", "|")
|
||||
.Replace(";", "|");
|
||||
return tmp.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
private static Sprite LoadIcon(string iconPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(iconPath)) return null;
|
||||
string path = NormalizeAssetPath(iconPath);
|
||||
if (!path.StartsWith("Assets", StringComparison.OrdinalIgnoreCase))
|
||||
path = "Assets/" + path;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
Sprite direct = TryLoadByPath(path);
|
||||
if (direct != null) return direct;
|
||||
|
||||
string name = Path.GetFileName(path);
|
||||
string folder = Path.GetDirectoryName(path);
|
||||
List<string> searchFolders = new List<string>();
|
||||
if (!string.IsNullOrEmpty(folder) && Directory.Exists(Path.Combine(Application.dataPath, "..", folder)))
|
||||
searchFolders.Add(folder);
|
||||
|
||||
string uiUiFolder = "Assets/artworks/UI_UI";
|
||||
if (Directory.Exists(Path.Combine(Application.dataPath, "..", uiUiFolder)))
|
||||
searchFolders.Add(uiUiFolder);
|
||||
|
||||
string artworksFolder = "Assets/artworks";
|
||||
if (Directory.Exists(Path.Combine(Application.dataPath, "..", artworksFolder)))
|
||||
searchFolders.Add(artworksFolder);
|
||||
|
||||
string[] folders = searchFolders.Count > 0 ? searchFolders.ToArray() : null;
|
||||
var guids = AssetDatabase.FindAssets(name + " t:Sprite", folders);
|
||||
if (guids.Length == 0)
|
||||
guids = AssetDatabase.FindAssets(name + " t:Texture2D", folders);
|
||||
if (guids.Length > 0)
|
||||
{
|
||||
string best = PickBestAssetPath(guids, name, folder);
|
||||
var sprite = AssetDatabase.LoadAssetAtPath<Sprite>(best);
|
||||
if (sprite != null) return sprite;
|
||||
var tex = AssetDatabase.LoadAssetAtPath<Texture2D>(best);
|
||||
if (tex != null)
|
||||
return Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f));
|
||||
}
|
||||
#endif
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string NormalizeAssetPath(string raw)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(raw)) return string.Empty;
|
||||
string path = raw.Replace('>', '/').Replace('\\', '/').Trim();
|
||||
path = path.Replace("Ul_Ul", "UI_UI");
|
||||
path = path.Replace("(", " (").Replace(")", ")");
|
||||
path = path.Replace(" ", " ").Trim();
|
||||
return path;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private static Sprite TryLoadByPath(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path)) return null;
|
||||
string[] candidates = path.IndexOf('.') >= 0
|
||||
? new[] { path }
|
||||
: new[]
|
||||
{
|
||||
path + ".png",
|
||||
path + ".jpg",
|
||||
path + ".jpeg"
|
||||
};
|
||||
|
||||
for (int i = 0; i < candidates.Length; i++)
|
||||
{
|
||||
string p = candidates[i];
|
||||
var sprite = AssetDatabase.LoadAssetAtPath<Sprite>(p);
|
||||
if (sprite != null) return sprite;
|
||||
var tex = AssetDatabase.LoadAssetAtPath<Texture2D>(p);
|
||||
if (tex != null)
|
||||
return Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string PickBestAssetPath(string[] guids, string name, string preferredFolder)
|
||||
{
|
||||
if (guids == null || guids.Length == 0) return null;
|
||||
string best = AssetDatabase.GUIDToAssetPath(guids[0]);
|
||||
if (guids.Length == 1) return best;
|
||||
|
||||
string preferredLower = (preferredFolder ?? string.Empty).Replace('\\', '/').ToLowerInvariant();
|
||||
string uiUiFolder = "assets/artworks/ui_ui";
|
||||
for (int i = 0; i < guids.Length; i++)
|
||||
{
|
||||
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
|
||||
if (string.IsNullOrEmpty(path)) continue;
|
||||
string lower = path.ToLowerInvariant();
|
||||
if (!string.IsNullOrEmpty(preferredLower) && lower.Contains(preferredLower))
|
||||
return path;
|
||||
if (lower.Contains(uiUiFolder))
|
||||
best = path;
|
||||
}
|
||||
return best;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0db6d24442085814ca9a66f17b584840
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa941f3246d11454c9bb0f52d6a3087b
|
||||
@@ -8,20 +8,25 @@ public class UI_Show_Anim : MonoBehaviour
|
||||
private void OnEnable()
|
||||
{
|
||||
Clear();
|
||||
transform.localScale = new();
|
||||
transform.DOScale(1, animTime_Scale);
|
||||
transform.localScale = Vector3.zero;
|
||||
transform.DOScale(1, animTime_Scale).SetUpdate(true);
|
||||
|
||||
gameObject.Try_Get_Component(out CanvasGroup canvasGroup);
|
||||
float a = 0;
|
||||
DOTween.To(() => a, (f) => { a = f; canvasGroup.alpha = f; }, 1, animTime_Alpha);
|
||||
if (gameObject.Try_Get_Component(out CanvasGroup canvasGroup))
|
||||
{
|
||||
canvasGroup.alpha = 0;
|
||||
float a = 0;
|
||||
DOTween.To(() => a, (f) => { a = f; canvasGroup.alpha = f; }, 1, animTime_Alpha).SetUpdate(true);
|
||||
}
|
||||
}
|
||||
public void Close()
|
||||
{
|
||||
Clear();
|
||||
transform.DOScale(0, animTime_Scale);
|
||||
gameObject.Try_Get_Component(out CanvasGroup canvasGroup);
|
||||
float a = 1;
|
||||
DOTween.To(() => a, (f) => { a = f; canvasGroup.alpha = f; }, 0, animTime_Alpha);
|
||||
transform.DOScale(0, animTime_Scale).SetUpdate(true);
|
||||
if (gameObject.Try_Get_Component(out CanvasGroup canvasGroup))
|
||||
{
|
||||
float a = 1;
|
||||
DOTween.To(() => a, (f) => { a = f; canvasGroup.alpha = f; }, 0, animTime_Alpha).SetUpdate(true);
|
||||
}
|
||||
}
|
||||
void Clear()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user