1906 lines
60 KiB
C#
1906 lines
60 KiB
C#
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using UnityEngine.Events;
|
||
using UnityEngine.SceneManagement;
|
||
using UnityEngine.EventSystems;
|
||
using System.Collections.Generic;
|
||
using UnityEngine.Audio;
|
||
using DG.Tweening;
|
||
using Bansonic;
|
||
#if UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || UNITY_STANDALONE_OSX || STEAMWORKS_WIN || STEAMWORKS_LIN_OSX
|
||
using Steamworks;
|
||
#endif
|
||
using GameServer.Client;
|
||
|
||
public class btmandtopController : MonoBehaviour, ICancelHandler
|
||
{
|
||
private const string MusicLockedMessage = "暂无可选择的音乐";
|
||
|
||
public static event System.Action<bool> GlobalSettingsVisibilityChanged;
|
||
public static event System.Action<bool> GlobalOverlayPanelVisibilityChanged;
|
||
public static bool CurrentSettingsVisible { get; private set; }
|
||
public static bool CurrentOverlayPanelsVisible { get; private set; }
|
||
|
||
public event System.Action<bool> SettingsVisibilityChanged;
|
||
|
||
[Header("player unique so")]
|
||
public Player_SO player_SO;
|
||
public Text playerCoins_legacy;
|
||
public Text player_mmrFragment;
|
||
public Text player_rks;
|
||
public Image rksProgressImage;
|
||
[Header("currency hover")]
|
||
public Button mmrFragmentHoverButton;
|
||
public Button coinsHoverButton;
|
||
public CanvasGroup mmrFragmentHoverCanvasGroup;
|
||
public CanvasGroup coinsHoverCanvasGroup;
|
||
[Min(0.01f)] public float currencyHoverFadeDuration = 0.25f;
|
||
[Header("put prefabs here")]
|
||
public GameObject putPrefabsHere;
|
||
public GameObject putSettingsPrefabHere;
|
||
[Header("son buttons")]
|
||
public Button settings_launch;
|
||
public Button userInfo_launch;
|
||
public Button store_launch;
|
||
public Button showLevel_display;
|
||
public Button email_display;
|
||
public Button notice_display;
|
||
public Button market_launch;
|
||
public Button button_userBag;
|
||
|
||
[Header("User Guide")]
|
||
[Tooltip("是否启用指引 sprite 展示。true:点击指引按钮时按场景展示 guideMappings 的 sprite;false:点击无反应,不展示任何指引。")]
|
||
[SerializeField] private bool enableGuideDisplay = true;
|
||
[SerializeField] private Button userGuideButton;
|
||
[SerializeField] private Image guideDisplayImage;
|
||
[SerializeField] private Button preIMGb;
|
||
[SerializeField] private Button aftIMGb;
|
||
[SerializeField] private Button closeGuideButton;
|
||
[SerializeField] private Text guideIndexLegacyText;
|
||
[SerializeField] private List<GuideMapping> guideMappings;
|
||
|
||
[System.Serializable]
|
||
public struct GuideMapping
|
||
{
|
||
public string sceneName;
|
||
public List<Sprite> guideSprites;
|
||
}
|
||
|
||
public Button button_Music;
|
||
[Header("MusicPic")]
|
||
public RectTransform musicPicRoot;
|
||
public AudioMixerGroup bgmMixerGroup;
|
||
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;
|
||
public GameObject settings_prefab;
|
||
public GameObject userInfo_prefab;
|
||
public GameObject email_prefab;
|
||
public GameObject notice_prefab;
|
||
public GameObject userBag_prefab;
|
||
|
||
[Header("mail red pot")]
|
||
public Image mailRedPot;
|
||
|
||
[Header("New Back Buttons")]
|
||
[SerializeField] private Button back_navButton;
|
||
[SerializeField] private Button home_navButton;
|
||
[SerializeField] private Button settings_navButton;
|
||
[SerializeField] private string homeSceneName = "UI_UI";
|
||
[SerializeField] private string backFallbackSceneName = "Main_main";
|
||
|
||
[Header("Scene Navigation Rules")]
|
||
[Tooltip("Define explicit back-navigation targets for specific scenes. This overrides the history-based navigation.")]
|
||
[SerializeField] private List<SceneNavigationRule> navigationRules;
|
||
|
||
[System.Serializable]
|
||
public struct SceneNavigationRule
|
||
{
|
||
public string sceneName;
|
||
[Tooltip("The scene to return to. If empty, falls back to history.")]
|
||
public string backTargetScene;
|
||
[Tooltip("Optional: Visual depth indicator (0=Root, 1=Chapter, etc)")]
|
||
public int depth;
|
||
}
|
||
|
||
[Header("Steam Integration")]
|
||
public Material steamMaterial;
|
||
public Text steamUserNameText;
|
||
public Text steamUserID64Text;
|
||
public Text steamStatusText;
|
||
public Image steamUserAvatarImage;
|
||
|
||
private UnityAction instantiateSettings;
|
||
private UnityAction instantiateUserInfo;
|
||
private UnityAction instantiateStore;
|
||
private UnityAction instantiateShowLevel;
|
||
private UnityAction instantiateEmail;
|
||
private UnityAction instantiateNotice;
|
||
private UnityAction navBackAction;
|
||
private UnityAction navHomeAction;
|
||
private UnityAction navSettingsAction;
|
||
private UnityAction navGuideAction;
|
||
private UnityAction instantiateMarket;
|
||
private UnityAction instantiateUserBag;
|
||
|
||
// cached reference to the settings instance to ensure only one exists
|
||
private GameObject settingsInstance;
|
||
private GameObject userInfoInstance;
|
||
private GameObject storeInstance;
|
||
private GameObject showLevelInstance;
|
||
private GameObject emailInstance;
|
||
private GameObject noticeInstance;
|
||
private GameObject userBagInstance;
|
||
private CanvasGroup musicPicGroup;
|
||
private Coroutine musicPicFade;
|
||
private bool musicPicVisible = true;
|
||
private bool navSceneLoading = false;
|
||
private Coroutine deferredUiRefreshRoutine;
|
||
private bool lastSettingsVisibilityState;
|
||
private bool lastOverlayPanelsVisibilityState;
|
||
private readonly Dictionary<string, int> guideIndexByScene = new Dictionary<string, int>();
|
||
private string currentGuideScene = string.Empty;
|
||
private static btmandtopController activeInstance;
|
||
private RectTransform topNavigationRoot;
|
||
private bool topNavigationGeometryDirty = true;
|
||
private int lastTopNavigationParentChildCount = -1;
|
||
private Tween mmrFragmentHoverTween;
|
||
private Tween coinsHoverTween;
|
||
|
||
private static readonly List<string> sceneHistory = new List<string>();
|
||
private static bool sceneHistoryHooked = false;
|
||
private static string trackedCurrentScene = string.Empty;
|
||
|
||
void Awake()
|
||
{
|
||
activeInstance = this;
|
||
EnsureMusicPicRoot();
|
||
InitializeSceneHistoryIfNeeded();
|
||
}
|
||
|
||
void Start()
|
||
{
|
||
PlayerEconomyLedger.EnsureInstance().AttachPlayerData(player_SO);
|
||
PlayerEconomyLedger.EnsureInstance().OnCoinsChanged += HandleCoinsChanged;
|
||
PlayerEconomyLedger.EnsureInstance().OnMaterialChanged += HandleMaterialChanged;
|
||
PlayerRksService.EnsureLoaded(player_SO);
|
||
PlayerRksService.OnRksChanged += HandleRksChanged;
|
||
UpdatePlayerCoinsLegacyText(PlayerEconomyLedger.EnsureInstance().GetCoins());
|
||
UpdatePlayerMmrFragmentText(PlayerEconomyLedger.EnsureInstance().GetMaterial());
|
||
float currentRks = PlayerRksService.GetBestOverallRks(player_SO);
|
||
UpdatePlayerRksText(currentRks);
|
||
UpdateRksProgressImage(currentRks);
|
||
SetupCurrencyHoverIndicators();
|
||
|
||
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 = SceneObjectLookupCache.Find("Button_Music");
|
||
if (any != null) button_Music = any.GetComponent<Button>();
|
||
}
|
||
}
|
||
instantiateSettings = () => ToggleSettingsPrefab();
|
||
instantiateUserInfo = () => ToggleUserInfoPrefab();
|
||
instantiateStore = () => { };
|
||
instantiateShowLevel = () => { gNotice.error.display("功能即将下线,禁止访问"); };
|
||
// instantiateShowLevel = () => ShowLevelPrefab();
|
||
instantiateEmail = () => ShowEmailPrefab();
|
||
instantiateNotice = () => { gNotice.warning.display("此版本该功能暂不可用"); };
|
||
// instantiateNotice = () => ShowNoticePrefab();
|
||
navGuideAction = () => ToggleGuideDisplay();
|
||
instantiateMarket = () => ShowStorePrefab();
|
||
instantiateUserBag = () => ShowUserBagPrefab();
|
||
|
||
if (settings_launch != null)
|
||
settings_launch.onClick.AddListener(instantiateSettings);
|
||
if (userInfo_launch != null)
|
||
userInfo_launch.onClick.AddListener(instantiateUserInfo);
|
||
if (store_launch != null)
|
||
store_launch.onClick.AddListener(instantiateStore);
|
||
if (showLevel_display != null)
|
||
showLevel_display.onClick.AddListener(instantiateShowLevel);
|
||
if (email_display != null)
|
||
email_display.onClick.AddListener(instantiateEmail);
|
||
if (notice_display != null)
|
||
notice_display.onClick.AddListener(instantiateNotice);
|
||
if (market_launch != null)
|
||
market_launch.onClick.AddListener(instantiateMarket);
|
||
if (button_userBag != null)
|
||
button_userBag.onClick.AddListener(instantiateUserBag);
|
||
if (userGuideButton != null)
|
||
userGuideButton.onClick.AddListener(navGuideAction);
|
||
if (preIMGb != null)
|
||
preIMGb.onClick.AddListener(ShowPreviousGuideImage);
|
||
if (aftIMGb != null)
|
||
aftIMGb.onClick.AddListener(ShowNextGuideImage);
|
||
if (closeGuideButton != null)
|
||
closeGuideButton.onClick.AddListener(CloseGuideDisplay);
|
||
|
||
BindNewBackButtons();
|
||
EnsureTopNavigationFront();
|
||
ScheduleDeferredUiRefresh();
|
||
|
||
EnsureMusicPicRoot();
|
||
SetupMusicPicDefault();
|
||
if (button_Music != null)
|
||
button_Music.onClick.AddListener(HandleMusicButtonLockedClick);
|
||
|
||
UpdateSteamUserInfo();
|
||
InitializeMailRedPot();
|
||
|
||
var binder = BgmUiBinder.Instance != null ? BgmUiBinder.Instance : SceneObjectLookupCache.FindAny<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 && GetSettingsParentTransform() != null)
|
||
{
|
||
// Try to find existing one in scene first
|
||
if (settingsInstance == null)
|
||
{
|
||
var existing = SceneObjectLookupCache.Find(settings_prefab.name + "(Clone)");
|
||
if (existing != null)
|
||
{
|
||
settingsInstance = existing;
|
||
}
|
||
}
|
||
|
||
if (settingsInstance == null)
|
||
{
|
||
settingsInstance = Instantiate(settings_prefab, GetSettingsParentTransform());
|
||
AttachManagedPanelVisibilityRelay(settingsInstance, true);
|
||
EnsureSettingsEnterAnimator(settingsInstance);
|
||
EnsureSettingsLastSibling();
|
||
settingsInstance.SetActive(false);
|
||
|
||
// try to set canvas camera now as in previous behavior
|
||
try
|
||
{
|
||
Canvas c = settingsInstance.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];
|
||
}
|
||
cams = null;
|
||
c = null;
|
||
}
|
||
}
|
||
catch { }
|
||
}
|
||
else
|
||
{
|
||
// ensure it's parented correctly beside putPrefabsHere
|
||
settingsInstance.transform.SetParent(GetSettingsParentTransform(), false);
|
||
AttachManagedPanelVisibilityRelay(settingsInstance, true);
|
||
EnsureSettingsLastSibling();
|
||
EnsureSettingsEnterAnimator(settingsInstance);
|
||
settingsInstance.SetActive(false);
|
||
}
|
||
}
|
||
|
||
if (userInfo_prefab != null && putPrefabsHere != null)
|
||
{
|
||
if (userInfoInstance == null)
|
||
{
|
||
var existing = SceneObjectLookupCache.Find(userInfo_prefab.name + "(Clone)");
|
||
if (existing != null)
|
||
{
|
||
userInfoInstance = existing;
|
||
}
|
||
}
|
||
|
||
if (userInfoInstance == null)
|
||
{
|
||
userInfoInstance = Instantiate(userInfo_prefab, putPrefabsHere.transform);
|
||
AttachManagedPanelVisibilityRelay(userInfoInstance, false);
|
||
userInfoInstance.SetActive(false);
|
||
}
|
||
else
|
||
{
|
||
userInfoInstance.transform.SetParent(putPrefabsHere.transform, false);
|
||
AttachManagedPanelVisibilityRelay(userInfoInstance, false);
|
||
userInfoInstance.SetActive(false);
|
||
}
|
||
}
|
||
|
||
SyncManagedPanelVisibilityState(true);
|
||
}
|
||
|
||
private void OnTransformChildrenChanged()
|
||
{
|
||
if (!IsUiUiSceneActive())
|
||
{
|
||
return;
|
||
}
|
||
|
||
topNavigationGeometryDirty = true;
|
||
EnsureTopNavigationFront();
|
||
}
|
||
|
||
internal void NotifyManagedPanelVisibilityChanged(bool includesSettingsPanel)
|
||
{
|
||
if (!ReferenceEquals(activeInstance, this))
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (!IsUiUiSceneActive())
|
||
{
|
||
SetSettingsVisibilityState(false);
|
||
SetOverlayPanelsVisibilityState(false);
|
||
return;
|
||
}
|
||
|
||
if (includesSettingsPanel)
|
||
{
|
||
SetSettingsVisibilityState(IsSettingsPanelVisible);
|
||
}
|
||
|
||
SetOverlayPanelsVisibilityState(AreOverlayPanelsVisible());
|
||
}
|
||
|
||
private void SyncManagedPanelVisibilityState(bool forceNotify)
|
||
{
|
||
if (!IsUiUiSceneActive())
|
||
{
|
||
SetSettingsVisibilityState(false, forceNotify);
|
||
SetOverlayPanelsVisibilityState(false, forceNotify);
|
||
return;
|
||
}
|
||
|
||
SetSettingsVisibilityState(IsSettingsPanelVisible, forceNotify);
|
||
SetOverlayPanelsVisibilityState(AreOverlayPanelsVisible(), forceNotify);
|
||
}
|
||
|
||
private void AttachManagedPanelVisibilityRelay(GameObject panel, bool includesSettingsPanel)
|
||
{
|
||
if (panel == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
btmandtopPanelVisibilityRelay relay = panel.GetComponent<btmandtopPanelVisibilityRelay>();
|
||
if (relay == null)
|
||
{
|
||
relay = panel.AddComponent<btmandtopPanelVisibilityRelay>();
|
||
}
|
||
|
||
relay.Initialize(this, includesSettingsPanel);
|
||
}
|
||
|
||
private void SetSettingsVisibilityState(bool visible, bool forceNotify = false)
|
||
{
|
||
lastSettingsVisibilityState = visible;
|
||
if (!forceNotify && CurrentSettingsVisible == visible)
|
||
{
|
||
return;
|
||
}
|
||
|
||
CurrentSettingsVisible = visible;
|
||
SettingsVisibilityChanged?.Invoke(visible);
|
||
GlobalSettingsVisibilityChanged?.Invoke(visible);
|
||
}
|
||
|
||
private void SetOverlayPanelsVisibilityState(bool visible, bool forceNotify = false)
|
||
{
|
||
lastOverlayPanelsVisibilityState = visible;
|
||
if (!forceNotify && CurrentOverlayPanelsVisible == visible)
|
||
{
|
||
return;
|
||
}
|
||
|
||
CurrentOverlayPanelsVisible = visible;
|
||
GlobalOverlayPanelVisibilityChanged?.Invoke(visible);
|
||
topNavigationGeometryDirty = true;
|
||
EnsureTopNavigationFront();
|
||
}
|
||
|
||
|
||
|
||
private void ToggleMusicPic()
|
||
{
|
||
var binder = BgmUiBinder.Instance != null ? BgmUiBinder.Instance : SceneObjectLookupCache.FindAny<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 = SceneObjectLookupCache.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 : SceneObjectLookupCache.FindAny<BgmUiBinder>();
|
||
if (musicPicRoot == null)
|
||
{
|
||
if (binder != null)
|
||
binder.ToggleMusicPic();
|
||
return;
|
||
}
|
||
|
||
SetMusicPicVisible(!musicPicVisible, false);
|
||
}
|
||
|
||
private void HandleMusicButtonLockedClick()
|
||
{
|
||
gNotice.error.display(MusicLockedMessage);
|
||
}
|
||
|
||
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 cached = SceneObjectLookupCache.Find(name);
|
||
if (cached != null) return cached;
|
||
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;
|
||
}
|
||
KillCurrencyHoverTweens();
|
||
musicPicGroup = null;
|
||
if (settings_launch != null)
|
||
settings_launch.onClick.RemoveListener(instantiateSettings);
|
||
if (userInfo_launch != null)
|
||
userInfo_launch.onClick.RemoveListener(instantiateUserInfo);
|
||
if (store_launch != null)
|
||
store_launch.onClick.RemoveListener(instantiateStore);
|
||
if (showLevel_display != null)
|
||
showLevel_display.onClick.RemoveListener(instantiateShowLevel);
|
||
if (email_display != null)
|
||
email_display.onClick.RemoveListener(instantiateEmail);
|
||
if (notice_display != null)
|
||
notice_display.onClick.RemoveListener(instantiateNotice);
|
||
if (market_launch != null)
|
||
market_launch.onClick.RemoveListener(instantiateMarket);
|
||
if (button_userBag != null)
|
||
button_userBag.onClick.RemoveListener(instantiateUserBag);
|
||
if (userGuideButton != null)
|
||
userGuideButton.onClick.RemoveListener(navGuideAction);
|
||
if (preIMGb != null)
|
||
preIMGb.onClick.RemoveListener(ShowPreviousGuideImage);
|
||
if (aftIMGb != null)
|
||
aftIMGb.onClick.RemoveListener(ShowNextGuideImage);
|
||
if (closeGuideButton != null)
|
||
closeGuideButton.onClick.RemoveListener(CloseGuideDisplay);
|
||
|
||
if (button_Music != null)
|
||
button_Music.onClick.RemoveListener(HandleMusicButtonLockedClick);
|
||
|
||
if (back_navButton != null && navBackAction != null)
|
||
back_navButton.onClick.RemoveListener(navBackAction);
|
||
if (home_navButton != null && navHomeAction != null)
|
||
home_navButton.onClick.RemoveListener(navHomeAction);
|
||
if (settings_navButton != null && navSettingsAction != null)
|
||
settings_navButton.onClick.RemoveListener(navSettingsAction);
|
||
|
||
if (PlayerEconomyLedger.Instance != null)
|
||
{
|
||
PlayerEconomyLedger.Instance.OnCoinsChanged -= HandleCoinsChanged;
|
||
PlayerEconomyLedger.Instance.OnMaterialChanged -= HandleMaterialChanged;
|
||
}
|
||
|
||
PlayerRksService.OnRksChanged -= HandleRksChanged;
|
||
UI_Panel_Mail.OnUnreadStateChanged -= HandleMailUnreadStateChanged;
|
||
if (MailHttpService.Instance != null)
|
||
{
|
||
MailHttpService.Instance.OnMailListChanged -= HandleMailListChanged;
|
||
}
|
||
|
||
if (ReferenceEquals(activeInstance, this))
|
||
{
|
||
activeInstance = null;
|
||
}
|
||
}
|
||
|
||
private void SetupCurrencyHoverIndicators()
|
||
{
|
||
InitializeCurrencyHoverCanvasGroup(mmrFragmentHoverCanvasGroup);
|
||
InitializeCurrencyHoverCanvasGroup(coinsHoverCanvasGroup);
|
||
|
||
AttachCurrencyHoverListener(mmrFragmentHoverButton, true);
|
||
AttachCurrencyHoverListener(coinsHoverButton, false);
|
||
}
|
||
|
||
private void InitializeCurrencyHoverCanvasGroup(CanvasGroup targetGroup)
|
||
{
|
||
if (targetGroup == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
targetGroup.DOKill();
|
||
targetGroup.alpha = 0f;
|
||
targetGroup.interactable = false;
|
||
targetGroup.blocksRaycasts = false;
|
||
}
|
||
|
||
private void AttachCurrencyHoverListener(Button targetButton, bool isFragmentButton)
|
||
{
|
||
if (targetButton == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
btmandtopCurrencyHoverRelay relay = targetButton.GetComponent<btmandtopCurrencyHoverRelay>();
|
||
if (relay == null)
|
||
{
|
||
relay = targetButton.gameObject.AddComponent<btmandtopCurrencyHoverRelay>();
|
||
}
|
||
|
||
relay.Initialize(this, isFragmentButton);
|
||
}
|
||
|
||
internal void SetCurrencyHoverVisible(bool isFragmentGroup, bool visible)
|
||
{
|
||
CanvasGroup targetGroup = isFragmentGroup ? mmrFragmentHoverCanvasGroup : coinsHoverCanvasGroup;
|
||
if (targetGroup == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
Tween currentTween = isFragmentGroup ? mmrFragmentHoverTween : coinsHoverTween;
|
||
if (currentTween != null && currentTween.IsActive())
|
||
{
|
||
currentTween.Kill();
|
||
}
|
||
|
||
float duration = Mathf.Max(0.01f, currencyHoverFadeDuration);
|
||
targetGroup.interactable = false;
|
||
targetGroup.blocksRaycasts = false;
|
||
|
||
Tween nextTween = targetGroup
|
||
.DOFade(visible ? 1f : 0f, duration)
|
||
.SetEase(Ease.Linear)
|
||
.SetUpdate(true)
|
||
.SetLink(targetGroup.gameObject)
|
||
.OnComplete(() =>
|
||
{
|
||
targetGroup.alpha = visible ? 1f : 0f;
|
||
targetGroup.interactable = false;
|
||
targetGroup.blocksRaycasts = false;
|
||
});
|
||
|
||
if (isFragmentGroup)
|
||
{
|
||
mmrFragmentHoverTween = nextTween;
|
||
}
|
||
else
|
||
{
|
||
coinsHoverTween = nextTween;
|
||
}
|
||
}
|
||
|
||
private void KillCurrencyHoverTweens()
|
||
{
|
||
if (mmrFragmentHoverTween != null && mmrFragmentHoverTween.IsActive())
|
||
{
|
||
mmrFragmentHoverTween.Kill();
|
||
}
|
||
|
||
if (coinsHoverTween != null && coinsHoverTween.IsActive())
|
||
{
|
||
coinsHoverTween.Kill();
|
||
}
|
||
|
||
if (mmrFragmentHoverCanvasGroup != null)
|
||
{
|
||
mmrFragmentHoverCanvasGroup.DOKill();
|
||
}
|
||
|
||
if (coinsHoverCanvasGroup != null)
|
||
{
|
||
coinsHoverCanvasGroup.DOKill();
|
||
}
|
||
}
|
||
|
||
private void HandleCoinsChanged(int coinAmount)
|
||
{
|
||
UpdatePlayerCoinsLegacyText(coinAmount);
|
||
}
|
||
|
||
private void HandleMaterialChanged(int materialAmount)
|
||
{
|
||
UpdatePlayerMmrFragmentText(materialAmount);
|
||
}
|
||
|
||
private void HandleRksChanged(float rksAmount)
|
||
{
|
||
UpdatePlayerRksText(rksAmount);
|
||
UpdateRksProgressImage(rksAmount);
|
||
}
|
||
|
||
private void InitializeMailRedPot()
|
||
{
|
||
if (mailRedPot == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
bool useServerMail = false;
|
||
if (email_prefab != null)
|
||
{
|
||
UI_Panel_Mail mailPanel = email_prefab.GetComponent<UI_Panel_Mail>();
|
||
if (mailPanel != null)
|
||
{
|
||
useServerMail = mailPanel.UsesServerMail;
|
||
}
|
||
}
|
||
|
||
if (useServerMail && MailHttpService.Instance != null)
|
||
{
|
||
MailHttpService.Instance.SetServerModeEnabled(true);
|
||
MailHttpService.Instance.OnMailListChanged -= HandleMailListChanged;
|
||
MailHttpService.Instance.OnMailListChanged += HandleMailListChanged;
|
||
}
|
||
|
||
UI_Panel_Mail.OnUnreadStateChanged -= HandleMailUnreadStateChanged;
|
||
UI_Panel_Mail.OnUnreadStateChanged += HandleMailUnreadStateChanged;
|
||
UpdateMailRedPot();
|
||
}
|
||
|
||
private void HandleMailUnreadStateChanged()
|
||
{
|
||
UpdateMailRedPot();
|
||
}
|
||
|
||
private void HandleMailListChanged(IReadOnlyList<MailApiEntry> _)
|
||
{
|
||
UpdateMailRedPot();
|
||
}
|
||
|
||
private void UpdateMailRedPot()
|
||
{
|
||
if (mailRedPot == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
bool useServerMail = false;
|
||
if (email_prefab != null)
|
||
{
|
||
UI_Panel_Mail mailPanel = email_prefab.GetComponent<UI_Panel_Mail>();
|
||
if (mailPanel != null)
|
||
{
|
||
useServerMail = mailPanel.UsesServerMail;
|
||
}
|
||
}
|
||
|
||
mailRedPot.enabled = UI_Panel_Mail.HasUnreadMails(useServerMail);
|
||
}
|
||
|
||
private void UpdatePlayerCoinsLegacyText(int coinAmount)
|
||
{
|
||
if (playerCoins_legacy != null)
|
||
{
|
||
playerCoins_legacy.text = coinAmount.ToString();
|
||
}
|
||
}
|
||
|
||
private void UpdatePlayerMmrFragmentText(int fragmentAmount)
|
||
{
|
||
if (player_mmrFragment != null)
|
||
{
|
||
player_mmrFragment.text = fragmentAmount.ToString();
|
||
}
|
||
}
|
||
|
||
private void UpdatePlayerRksText(float rksAmount)
|
||
{
|
||
if (player_rks == null)
|
||
{
|
||
player_rks = FindLegacyTextByLiteral("player_rks");
|
||
}
|
||
|
||
if (player_rks != null)
|
||
{
|
||
player_rks.text = rksAmount.ToString("F2");
|
||
}
|
||
}
|
||
|
||
private void UpdateRksProgressImage(float rksAmount)
|
||
{
|
||
if (rksProgressImage == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
rksProgressImage.fillAmount = Mathf.Clamp01(Mathf.InverseLerp(0f, 100f, rksAmount));
|
||
}
|
||
|
||
private Text FindLegacyTextByLiteral(string literal)
|
||
{
|
||
if (string.IsNullOrEmpty(literal))
|
||
{
|
||
return null;
|
||
}
|
||
|
||
Text[] texts = GetComponentsInChildren<Text>(true);
|
||
for (int i = 0; i < texts.Length; i++)
|
||
{
|
||
if (texts[i] != null && texts[i].text == literal)
|
||
{
|
||
return texts[i];
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
private void ToggleGuideDisplay()
|
||
{
|
||
// 关闭指引展示时点击无反应;若指引已在显示中则允许关闭,避免残留面板卡住。
|
||
if (!enableGuideDisplay)
|
||
{
|
||
if (guideDisplayImage != null && guideDisplayImage.gameObject.activeSelf)
|
||
{
|
||
CloseGuideDisplay();
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (guideDisplayImage == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (guideDisplayImage.gameObject.activeSelf)
|
||
{
|
||
CloseGuideDisplay();
|
||
}
|
||
else
|
||
{
|
||
string currentScene = SceneManager.GetActiveScene().name;
|
||
ShowGuideForScene(currentScene, true);
|
||
}
|
||
}
|
||
|
||
private void CloseGuideDisplay()
|
||
{
|
||
if (guideDisplayImage == null) return;
|
||
guideDisplayImage.gameObject.SetActive(false);
|
||
UpdateGuideIndexText(-1, 0);
|
||
SetGuideButtonsInteractable(false);
|
||
}
|
||
|
||
public void OnCancel(BaseEventData eventData)
|
||
{
|
||
if (guideDisplayImage != null && guideDisplayImage.gameObject.activeSelf)
|
||
CloseGuideDisplay();
|
||
}
|
||
|
||
private void ShowGuideForScene(string sceneName, bool allowStoredIndex)
|
||
{
|
||
if (!TryGetGuideMapping(sceneName, out var mapping))
|
||
{
|
||
gNotice.warning.display("未找到指引信息");
|
||
return;
|
||
}
|
||
|
||
int count = GetGuideSpriteCount(mapping);
|
||
if (count <= 0)
|
||
{
|
||
gNotice.warning.display("未找到指引信息");
|
||
return;
|
||
}
|
||
|
||
int index = 0;
|
||
if (allowStoredIndex && guideIndexByScene.TryGetValue(sceneName, out var storedIndex))
|
||
index = storedIndex;
|
||
index = Mathf.Clamp(index, 0, count - 1);
|
||
ApplyGuideSprite(sceneName, mapping, index, count);
|
||
}
|
||
|
||
private void ShowPreviousGuideImage()
|
||
{
|
||
if (guideDisplayImage == null || !guideDisplayImage.gameObject.activeSelf) return;
|
||
if (string.IsNullOrEmpty(currentGuideScene))
|
||
currentGuideScene = SceneManager.GetActiveScene().name;
|
||
if (!TryGetGuideMapping(currentGuideScene, out var mapping)) return;
|
||
int count = GetGuideSpriteCount(mapping);
|
||
if (count <= 0) return;
|
||
int index = 0;
|
||
guideIndexByScene.TryGetValue(currentGuideScene, out index);
|
||
index = Mathf.Clamp(index - 1, 0, count - 1);
|
||
ApplyGuideSprite(currentGuideScene, mapping, index, count);
|
||
}
|
||
|
||
private void ShowNextGuideImage()
|
||
{
|
||
if (guideDisplayImage == null || !guideDisplayImage.gameObject.activeSelf) return;
|
||
if (string.IsNullOrEmpty(currentGuideScene))
|
||
currentGuideScene = SceneManager.GetActiveScene().name;
|
||
if (!TryGetGuideMapping(currentGuideScene, out var mapping)) return;
|
||
int count = GetGuideSpriteCount(mapping);
|
||
if (count <= 0) return;
|
||
int index = 0;
|
||
guideIndexByScene.TryGetValue(currentGuideScene, out index);
|
||
index = Mathf.Clamp(index + 1, 0, count - 1);
|
||
ApplyGuideSprite(currentGuideScene, mapping, index, count);
|
||
}
|
||
|
||
private void ApplyGuideSprite(string sceneName, GuideMapping mapping, int index, int count)
|
||
{
|
||
Sprite targetSprite = GetGuideSpriteAt(mapping, index);
|
||
if (targetSprite == null)
|
||
{
|
||
gNotice.warning.display("未找到指引信息");
|
||
return;
|
||
}
|
||
guideDisplayImage.sprite = targetSprite;
|
||
guideDisplayImage.gameObject.SetActive(true);
|
||
currentGuideScene = sceneName;
|
||
guideIndexByScene[sceneName] = index;
|
||
UpdateGuideIndexText(index, count);
|
||
SetGuideButtonsInteractable(count > 1);
|
||
}
|
||
|
||
private void UpdateGuideIndexText(int index, int count)
|
||
{
|
||
string text = count > 0 && index >= 0 ? $"{index + 1}/{count}" : string.Empty;
|
||
if (guideIndexLegacyText != null) guideIndexLegacyText.text = text;
|
||
}
|
||
|
||
private void SetGuideButtonsInteractable(bool enabled)
|
||
{
|
||
if (preIMGb != null) preIMGb.interactable = enabled;
|
||
if (aftIMGb != null) aftIMGb.interactable = enabled;
|
||
}
|
||
|
||
private bool TryGetGuideMapping(string sceneName, out GuideMapping mapping)
|
||
{
|
||
mapping = default;
|
||
if (guideMappings == null) return false;
|
||
for (int i = 0; i < guideMappings.Count; i++)
|
||
{
|
||
if (guideMappings[i].sceneName == sceneName)
|
||
{
|
||
mapping = guideMappings[i];
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
private int GetGuideSpriteCount(GuideMapping mapping)
|
||
{
|
||
if (mapping.guideSprites != null) return mapping.guideSprites.Count;
|
||
return 0;
|
||
}
|
||
|
||
private Sprite GetGuideSpriteAt(GuideMapping mapping, int index)
|
||
{
|
||
if (mapping.guideSprites == null || mapping.guideSprites.Count == 0) return null;
|
||
return mapping.guideSprites[Mathf.Clamp(index, 0, mapping.guideSprites.Count - 1)];
|
||
}
|
||
|
||
private void ToggleSettingsPrefab()
|
||
{
|
||
if (settings_prefab == null || GetSettingsParentTransform() == null) return;
|
||
|
||
// If we don't have an instance (it may have been destroyed), instantiate one
|
||
if (settingsInstance == null)
|
||
{
|
||
settingsInstance = Instantiate(settings_prefab, GetSettingsParentTransform());
|
||
AttachManagedPanelVisibilityRelay(settingsInstance, true);
|
||
EnsureSettingsEnterAnimator(settingsInstance);
|
||
EnsureSettingsLastSibling();
|
||
|
||
// attempt to set canvas camera
|
||
try
|
||
{
|
||
Canvas c = settingsInstance.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];
|
||
}
|
||
cams = null;
|
||
c = null;
|
||
}
|
||
}
|
||
catch { }
|
||
|
||
settingsInstance.SetActive(true);
|
||
EnsureSettingsLastSibling();
|
||
RegisterManagedPanel(settingsInstance, CloseSettingsWithExitAnim);
|
||
NotifyManagedPanelVisibilityChanged(true);
|
||
return;
|
||
}
|
||
|
||
AttachManagedPanelVisibilityRelay(settingsInstance, true);
|
||
EnsureSettingsEnterAnimator(settingsInstance);
|
||
settingsInstance.transform.SetParent(GetSettingsParentTransform(), false);
|
||
EnsureSettingsLastSibling();
|
||
|
||
bool active = settingsInstance.activeSelf;
|
||
if (active)
|
||
{
|
||
CloseSettingsWithExitAnim();
|
||
return;
|
||
}
|
||
|
||
settingsInstance.SetActive(true);
|
||
if (settingsInstance.activeSelf)
|
||
{
|
||
EnsureSettingsLastSibling();
|
||
RegisterManagedPanel(settingsInstance, CloseSettingsWithExitAnim);
|
||
}
|
||
NotifyManagedPanelVisibilityChanged(true);
|
||
}
|
||
|
||
private void CloseSettingsWithExitAnim()
|
||
{
|
||
if (settingsInstance == null)
|
||
{
|
||
NotifyManagedPanelVisibilityChanged(true);
|
||
return;
|
||
}
|
||
|
||
UI_PanelExitUtility.PlayExitThen(settingsInstance, () =>
|
||
{
|
||
if (settingsInstance != null)
|
||
{
|
||
settingsInstance.SetActive(false);
|
||
}
|
||
|
||
NotifyManagedPanelVisibilityChanged(true);
|
||
});
|
||
}
|
||
|
||
public bool IsSettingsPanelVisible
|
||
{
|
||
get { return settingsInstance != null && settingsInstance.activeInHierarchy; }
|
||
}
|
||
|
||
private void ToggleUserInfoPrefab()
|
||
{
|
||
if (ToggleIfAlreadyOpen(ref userInfoInstance))
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (OpenPrefab(userInfo_prefab, ref userInfoInstance))
|
||
{
|
||
CloseInfoPanels(userInfoInstance);
|
||
RegisterManagedPanel(userInfoInstance, () => CloseManagedOverlay(userInfoInstance));
|
||
}
|
||
}
|
||
|
||
private void BroadcastSettingsVisibility(bool visible)
|
||
{
|
||
SetSettingsVisibilityState(visible);
|
||
}
|
||
|
||
private bool IsUiUiSceneActive()
|
||
{
|
||
string sceneName = SceneManager.GetActiveScene().name;
|
||
string targetScene = string.IsNullOrWhiteSpace(uiSceneName) ? "UI_UI" : uiSceneName;
|
||
return string.Equals(sceneName, targetScene, System.StringComparison.OrdinalIgnoreCase);
|
||
}
|
||
|
||
private bool AreOverlayPanelsVisible()
|
||
{
|
||
return (settingsInstance != null && settingsInstance.activeInHierarchy)
|
||
|| (userInfoInstance != null && userInfoInstance.activeInHierarchy)
|
||
|| (storeInstance != null && storeInstance.activeInHierarchy)
|
||
|| (showLevelInstance != null && showLevelInstance.activeInHierarchy)
|
||
|| (emailInstance != null && emailInstance.activeInHierarchy)
|
||
|| (noticeInstance != null && noticeInstance.activeInHierarchy)
|
||
|| (userBagInstance != null && userBagInstance.activeInHierarchy);
|
||
}
|
||
|
||
private void BroadcastOverlayPanelsVisibility()
|
||
{
|
||
SetOverlayPanelsVisibilityState(AreOverlayPanelsVisible());
|
||
}
|
||
|
||
private void RegisterManagedPanel(GameObject panel, System.Action closeAction)
|
||
{
|
||
if (panel == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
UIBackStack.RegisterOrBump(
|
||
panel,
|
||
() => panel != null && panel.activeInHierarchy,
|
||
() =>
|
||
{
|
||
closeAction?.Invoke();
|
||
return true;
|
||
});
|
||
}
|
||
|
||
private void ShowLevelPrefab()
|
||
{
|
||
if (ToggleIfAlreadyOpen(ref showLevelInstance))
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (OpenPrefab(showLevel_prefab, ref showLevelInstance))
|
||
{
|
||
CloseInfoPanels(showLevelInstance);
|
||
RegisterManagedPanel(showLevelInstance, () => CloseManagedOverlay(showLevelInstance));
|
||
}
|
||
}
|
||
|
||
private void ShowStorePrefab()
|
||
{
|
||
if (ToggleIfAlreadyOpen(ref storeInstance))
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (OpenPrefab(store_prefab, ref storeInstance))
|
||
{
|
||
CloseInfoPanels(storeInstance);
|
||
RegisterManagedPanel(storeInstance, () => CloseManagedOverlay(storeInstance));
|
||
}
|
||
}
|
||
|
||
private void ShowEmailPrefab()
|
||
{
|
||
if (ToggleIfAlreadyOpen(ref emailInstance))
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (OpenPrefab(email_prefab, ref emailInstance))
|
||
{
|
||
CloseInfoPanels(emailInstance);
|
||
RegisterManagedPanel(emailInstance, () => CloseManagedOverlay(emailInstance));
|
||
}
|
||
}
|
||
|
||
private void ShowNoticePrefab()
|
||
{
|
||
if (ToggleIfAlreadyOpen(ref noticeInstance))
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (OpenPrefab(notice_prefab, ref noticeInstance))
|
||
{
|
||
CloseInfoPanels(noticeInstance);
|
||
RegisterManagedPanel(noticeInstance, () => CloseManagedOverlay(noticeInstance));
|
||
}
|
||
}
|
||
|
||
private void ShowUserBagPrefab()
|
||
{
|
||
if (ToggleIfAlreadyOpen(ref userBagInstance))
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (OpenPrefab(userBag_prefab, ref userBagInstance))
|
||
{
|
||
CloseInfoPanels(userBagInstance);
|
||
RegisterManagedPanel(userBagInstance, () => CloseManagedOverlay(userBagInstance));
|
||
}
|
||
}
|
||
|
||
private void CloseManagedOverlay(GameObject instance)
|
||
{
|
||
if (instance == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
CloseManagedOverlayWithExitAnim(instance);
|
||
}
|
||
|
||
private bool ToggleIfAlreadyOpen(ref GameObject instance)
|
||
{
|
||
if (instance == null)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (!instance.activeInHierarchy)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
CloseManagedOverlayWithExitAnim(instance);
|
||
return true;
|
||
}
|
||
|
||
private bool OpenPrefab(GameObject prefab, ref GameObject instance)
|
||
{
|
||
if (prefab == null || putPrefabsHere == null) return false;
|
||
|
||
if (instance == null)
|
||
{
|
||
instance = FindExistingInstance(prefab);
|
||
if (instance == null)
|
||
{
|
||
instance = Instantiate(prefab, putPrefabsHere.transform);
|
||
}
|
||
else
|
||
{
|
||
instance.transform.SetParent(putPrefabsHere.transform, false);
|
||
}
|
||
}
|
||
|
||
if (instance == null)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (!instance.activeSelf)
|
||
{
|
||
instance.SetActive(true);
|
||
}
|
||
|
||
AttachManagedPanelVisibilityRelay(instance, false);
|
||
EnsurePanelPopupTween(instance);
|
||
TryAssignCanvasCamera(instance);
|
||
BroadcastOverlayPanelsVisibility();
|
||
EnsureTopNavigationFront();
|
||
return true;
|
||
}
|
||
|
||
private UI_PanelPopupTween EnsurePanelPopupTween(GameObject instance)
|
||
{
|
||
if (instance == null)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
if (instance.GetComponent<UI_SettingsPanelEnterAnim>() != null)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
UI_PanelPopupTween popupTween = instance.GetComponent<UI_PanelPopupTween>();
|
||
if (popupTween == null)
|
||
{
|
||
popupTween = instance.AddComponent<UI_PanelPopupTween>();
|
||
}
|
||
|
||
return popupTween;
|
||
}
|
||
|
||
private void EnsureSettingsLastSibling()
|
||
{
|
||
if (settingsInstance == null || GetSettingsParentTransform() == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
settingsInstance.transform.SetParent(GetSettingsParentTransform(), false);
|
||
settingsInstance.transform.SetAsLastSibling();
|
||
}
|
||
|
||
private Transform GetSettingsParentTransform()
|
||
{
|
||
if (putSettingsPrefabHere != null)
|
||
{
|
||
return putSettingsPrefabHere.transform;
|
||
}
|
||
|
||
if (putPrefabsHere == null)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
Transform siblingParent = putPrefabsHere.transform.parent;
|
||
return siblingParent != null ? siblingParent : putPrefabsHere.transform;
|
||
}
|
||
|
||
private void CloseInfoPanels(GameObject keep)
|
||
{
|
||
CloseInstance(ref userInfoInstance, keep);
|
||
CloseInstance(ref storeInstance, keep);
|
||
CloseInstance(ref showLevelInstance, keep);
|
||
CloseInstance(ref emailInstance, keep);
|
||
CloseInstance(ref noticeInstance, keep);
|
||
CloseInstance(ref userBagInstance, keep);
|
||
}
|
||
|
||
private void CloseInstance(ref GameObject instance, GameObject keep)
|
||
{
|
||
if (instance == null || instance == keep) return;
|
||
CloseManagedOverlayWithExitAnim(instance);
|
||
}
|
||
|
||
private void CloseManagedOverlayWithExitAnim(GameObject instance)
|
||
{
|
||
if (instance == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
UI_PanelExitUtility.PlayExitThen(instance, () =>
|
||
{
|
||
if (instance != null)
|
||
{
|
||
instance.SetActive(false);
|
||
}
|
||
|
||
BroadcastOverlayPanelsVisibility();
|
||
});
|
||
}
|
||
|
||
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 = SceneObjectLookupCache.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 { }
|
||
}
|
||
|
||
public static void RefreshOverlayVisibilityState()
|
||
{
|
||
if (activeInstance != null)
|
||
{
|
||
activeInstance.SyncManagedPanelVisibilityState(false);
|
||
return;
|
||
}
|
||
|
||
CurrentOverlayPanelsVisible = false;
|
||
GlobalOverlayPanelVisibilityChanged?.Invoke(false);
|
||
}
|
||
|
||
private void InstantiatePrefab(GameObject prefab)
|
||
{
|
||
if (prefab != null && putPrefabsHere != null)
|
||
{
|
||
GameObject instance = Instantiate(prefab, putPrefabsHere.transform);
|
||
instance = null;
|
||
}
|
||
}
|
||
|
||
private void EnsureSettingsEnterAnimator(GameObject instance)
|
||
{
|
||
if (instance == null) return;
|
||
|
||
UI_SettingsPanelEnterAnim anim = instance.GetComponent<UI_SettingsPanelEnterAnim>();
|
||
if (anim == null)
|
||
anim = instance.AddComponent<UI_SettingsPanelEnterAnim>();
|
||
anim.enabled = true;
|
||
}
|
||
|
||
private void BindNewBackButtons()
|
||
{
|
||
ResolveNewBackButtons();
|
||
|
||
navBackAction = OnBackNavClicked;
|
||
navHomeAction = OnHomeNavClicked;
|
||
navSettingsAction = OnSettingsNavClicked;
|
||
|
||
if (back_navButton != null)
|
||
{
|
||
back_navButton.onClick.RemoveListener(navBackAction);
|
||
back_navButton.onClick.AddListener(navBackAction);
|
||
}
|
||
|
||
if (home_navButton != null)
|
||
{
|
||
home_navButton.onClick.RemoveListener(navHomeAction);
|
||
home_navButton.onClick.AddListener(navHomeAction);
|
||
}
|
||
|
||
if (settings_navButton != null)
|
||
{
|
||
settings_navButton.onClick.RemoveListener(navSettingsAction);
|
||
settings_navButton.onClick.AddListener(navSettingsAction);
|
||
}
|
||
|
||
EnsureTopNavigationFront();
|
||
}
|
||
|
||
private void ResolveNewBackButtons()
|
||
{
|
||
Transform topRoot = FindChildRecursive(transform, "TOP");
|
||
if (topRoot != null)
|
||
{
|
||
topNavigationRoot = topRoot as RectTransform;
|
||
}
|
||
|
||
Transform navRoot = FindChildRecursive(transform, "New back");
|
||
if (navRoot == null)
|
||
{
|
||
GameObject sceneRoot = FindByName("New back");
|
||
if (sceneRoot != null)
|
||
navRoot = sceneRoot.transform;
|
||
}
|
||
|
||
if (back_navButton == null)
|
||
back_navButton = FindButtonByName(navRoot, "back");
|
||
if (home_navButton == null)
|
||
home_navButton = FindButtonByName(navRoot, "home");
|
||
if (settings_navButton == null)
|
||
settings_navButton = FindButtonByName(navRoot, "settings");
|
||
}
|
||
|
||
private void EnsureTopNavigationFront()
|
||
{
|
||
ResolveTopNavigationRoot();
|
||
|
||
if (topNavigationRoot == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
Transform parent = topNavigationRoot.parent;
|
||
if (parent == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (topNavigationRoot.GetSiblingIndex() != parent.childCount - 1)
|
||
{
|
||
topNavigationRoot.SetAsLastSibling();
|
||
topNavigationGeometryDirty = true;
|
||
}
|
||
|
||
// The parent's child count changing means a panel was shown/hidden, which can
|
||
// shift the top-bar layout. Use it as a cheap heuristic to re-settle geometry.
|
||
if (parent.childCount != lastTopNavigationParentChildCount)
|
||
{
|
||
lastTopNavigationParentChildCount = parent.childCount;
|
||
topNavigationGeometryDirty = true;
|
||
}
|
||
|
||
if (topNavigationGeometryDirty)
|
||
{
|
||
topNavigationGeometryDirty = false;
|
||
RefreshTopNavigationGeometry();
|
||
}
|
||
}
|
||
|
||
public void MarkTopNavigationGeometryDirty()
|
||
{
|
||
topNavigationGeometryDirty = true;
|
||
}
|
||
|
||
private void ResolveTopNavigationRoot()
|
||
{
|
||
if (topNavigationRoot != null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
Transform topRoot = FindChildRecursive(transform, "TOP");
|
||
if (topRoot == null)
|
||
{
|
||
GameObject sceneTop = FindByName("TOP");
|
||
if (sceneTop != null)
|
||
{
|
||
topRoot = sceneTop.transform;
|
||
}
|
||
}
|
||
|
||
if (topRoot != null)
|
||
{
|
||
topNavigationRoot = topRoot as RectTransform;
|
||
}
|
||
}
|
||
|
||
private void RefreshTopNavigationGeometry()
|
||
{
|
||
if (topNavigationRoot == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
Canvas.ForceUpdateCanvases();
|
||
LayoutRebuilder.ForceRebuildLayoutImmediate(topNavigationRoot);
|
||
|
||
RectTransform navRoot = null;
|
||
if (back_navButton != null)
|
||
{
|
||
navRoot = back_navButton.transform.parent as RectTransform;
|
||
}
|
||
if (navRoot == null)
|
||
{
|
||
Transform candidate = FindChildRecursive(topNavigationRoot, "New back");
|
||
navRoot = candidate as RectTransform;
|
||
}
|
||
if (navRoot != null)
|
||
{
|
||
LayoutRebuilder.ForceRebuildLayoutImmediate(navRoot);
|
||
}
|
||
|
||
if (back_navButton != null)
|
||
{
|
||
RectTransform backRect = back_navButton.transform as RectTransform;
|
||
if (backRect != null)
|
||
{
|
||
LayoutRebuilder.ForceRebuildLayoutImmediate(backRect);
|
||
}
|
||
}
|
||
|
||
Canvas.ForceUpdateCanvases();
|
||
}
|
||
|
||
private static Transform FindChildRecursive(Transform root, string childName)
|
||
{
|
||
if (root == null || string.IsNullOrEmpty(childName)) return null;
|
||
if (root.name == childName) return root;
|
||
|
||
for (int i = 0; i < root.childCount; i++)
|
||
{
|
||
Transform child = root.GetChild(i);
|
||
Transform found = FindChildRecursive(child, childName);
|
||
if (found != null) return found;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
private static Button FindButtonByName(Transform root, string name)
|
||
{
|
||
if (string.IsNullOrEmpty(name)) return null;
|
||
|
||
if (root != null)
|
||
{
|
||
Button[] buttons = root.GetComponentsInChildren<Button>(true);
|
||
for (int i = 0; i < buttons.Length; i++)
|
||
{
|
||
Button btn = buttons[i];
|
||
if (btn != null && string.Equals(btn.gameObject.name, name, System.StringComparison.OrdinalIgnoreCase))
|
||
return btn;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
private static void InitializeSceneHistoryIfNeeded()
|
||
{
|
||
if (sceneHistoryHooked) return;
|
||
sceneHistoryHooked = true;
|
||
trackedCurrentScene = SceneManager.GetActiveScene().name ?? string.Empty;
|
||
SceneManager.sceneLoaded += OnSceneLoadedTrackHistory;
|
||
}
|
||
|
||
private static void OnSceneLoadedTrackHistory(Scene scene, LoadSceneMode mode)
|
||
{
|
||
string incoming = scene.name ?? string.Empty;
|
||
if (!string.IsNullOrEmpty(trackedCurrentScene) &&
|
||
!string.Equals(trackedCurrentScene, incoming, System.StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
if (sceneHistory.Count == 0 ||
|
||
!string.Equals(sceneHistory[sceneHistory.Count - 1], trackedCurrentScene, System.StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
sceneHistory.Add(trackedCurrentScene);
|
||
if (sceneHistory.Count > 24) sceneHistory.RemoveAt(0);
|
||
}
|
||
}
|
||
trackedCurrentScene = incoming;
|
||
|
||
if (activeInstance != null)
|
||
{
|
||
activeInstance.topNavigationGeometryDirty = true;
|
||
activeInstance.EnsureTopNavigationFront();
|
||
activeInstance.SyncManagedPanelVisibilityState(true);
|
||
activeInstance.ScheduleDeferredUiRefresh();
|
||
}
|
||
}
|
||
|
||
private void ScheduleDeferredUiRefresh()
|
||
{
|
||
if (!isActiveAndEnabled)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (deferredUiRefreshRoutine != null)
|
||
{
|
||
StopCoroutine(deferredUiRefreshRoutine);
|
||
}
|
||
|
||
deferredUiRefreshRoutine = StartCoroutine(DeferredUiRefreshRoutine());
|
||
}
|
||
|
||
private System.Collections.IEnumerator DeferredUiRefreshRoutine()
|
||
{
|
||
for (int i = 0; i < 3; i++)
|
||
{
|
||
yield return null;
|
||
topNavigationGeometryDirty = true;
|
||
EnsureTopNavigationFront();
|
||
}
|
||
|
||
deferredUiRefreshRoutine = null;
|
||
}
|
||
|
||
private static string PopPreviousSceneName(string currentScene)
|
||
{
|
||
for (int i = sceneHistory.Count - 1; i >= 0; i--)
|
||
{
|
||
string candidate = sceneHistory[i];
|
||
sceneHistory.RemoveAt(i);
|
||
if (string.IsNullOrEmpty(candidate)) continue;
|
||
if (string.Equals(candidate, currentScene, System.StringComparison.OrdinalIgnoreCase)) continue;
|
||
return candidate;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
private void OnBackNavClicked()
|
||
{
|
||
if (navSceneLoading) return;
|
||
InitializeSceneHistoryIfNeeded();
|
||
|
||
if (UIBackStack.TryHandleTop())
|
||
{
|
||
return;
|
||
}
|
||
|
||
string current = SceneManager.GetActiveScene().name ?? string.Empty;
|
||
string target = null;
|
||
|
||
if (navigationRules != null)
|
||
{
|
||
foreach (var rule in navigationRules)
|
||
{
|
||
if (string.Equals(rule.sceneName, current, System.StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
if (!string.IsNullOrEmpty(rule.backTargetScene))
|
||
{
|
||
target = rule.backTargetScene;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (string.IsNullOrEmpty(target))
|
||
{
|
||
target = PopPreviousSceneName(current);
|
||
}
|
||
|
||
if (string.IsNullOrEmpty(target))
|
||
{
|
||
target = string.IsNullOrEmpty(backFallbackSceneName) ? "Main_main" : backFallbackSceneName;
|
||
}
|
||
|
||
if (!string.IsNullOrEmpty(target) &&
|
||
!string.Equals(target, current, System.StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
StartCoroutine(LoadNavSceneAsync(target));
|
||
}
|
||
}
|
||
|
||
private void OnHomeNavClicked()
|
||
{
|
||
if (navSceneLoading) return;
|
||
string target = string.IsNullOrEmpty(homeSceneName) ? "UI_UI" : homeSceneName;
|
||
StartCoroutine(LoadNavSceneAsync(target));
|
||
}
|
||
|
||
private void OnSettingsNavClicked()
|
||
{
|
||
ToggleSettingsPrefab();
|
||
}
|
||
|
||
private System.Collections.IEnumerator LoadNavSceneAsync(string sceneName)
|
||
{
|
||
if (string.IsNullOrEmpty(sceneName)) yield break;
|
||
if (navSceneLoading) yield break;
|
||
|
||
navSceneLoading = true;
|
||
Time.timeScale = 1f;
|
||
|
||
if (gTransition.LoadScene(sceneName, LoadSceneMode.Single))
|
||
{
|
||
while (gTransition.IsBusy)
|
||
{
|
||
yield return null;
|
||
}
|
||
navSceneLoading = false;
|
||
yield break;
|
||
}
|
||
|
||
AsyncOperation op = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single);
|
||
while (op != null && !op.isDone)
|
||
{
|
||
yield return null;
|
||
}
|
||
|
||
navSceneLoading = false;
|
||
}
|
||
|
||
public void UpdateSteamUserInfo()
|
||
{
|
||
#if !(UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || UNITY_STANDALONE_OSX || STEAMWORKS_WIN || STEAMWORKS_LIN_OSX)
|
||
// 非 Steam 平台(如 Android):无 Steamworks,显示未知来源即可。
|
||
if (steamStatusText != null)
|
||
steamStatusText.text = LocalizationService.Get("steam.status.unknown", "Unknown Server");
|
||
return;
|
||
#else
|
||
if (!SteamManager.Initialized)
|
||
{
|
||
Debug.LogWarning("[Steam] SteamManager not initialized.");
|
||
if (steamStatusText != null)
|
||
steamStatusText.text = LocalizationService.Get("steam.status.unknown", "Unknown Server");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
CSteamID steamID = SteamUser.GetSteamID();
|
||
|
||
if (steamUserNameText != null)
|
||
steamUserNameText.text = SteamFriends.GetPersonaName();
|
||
|
||
if (steamUserID64Text != null)
|
||
steamUserID64Text.text = LocalizationService.GetFormat("steam.uid_format", steamID.m_SteamID.ToString());
|
||
|
||
// Online status check
|
||
EPersonaState state = SteamFriends.GetPersonaState();
|
||
bool isOffline = (state == EPersonaState.k_EPersonaStateOffline);
|
||
|
||
if (steamStatusText != null)
|
||
{
|
||
steamStatusText.text = isOffline
|
||
? LocalizationService.Get("steam.status.offline", "Steam Offline")
|
||
: LocalizationService.Get("steam.status.online", "Steam Online");
|
||
}
|
||
|
||
if (steamUserAvatarImage != null)
|
||
{
|
||
// Assign material if offline, otherwise set to null (default)
|
||
steamUserAvatarImage.material = isOffline ? steamMaterial : null;
|
||
|
||
int iImage = SteamFriends.GetMediumFriendAvatar(steamID);
|
||
if (iImage != -1)
|
||
{
|
||
uint width, height;
|
||
if (SteamUtils.GetImageSize(iImage, out width, out height))
|
||
{
|
||
byte[] imageBuffer = new byte[width * height * 4];
|
||
if (SteamUtils.GetImageRGBA(iImage, imageBuffer, (int)(width * height * 4)))
|
||
{
|
||
Texture2D texture = new Texture2D((int)width, (int)height, TextureFormat.RGBA32, false);
|
||
texture.LoadRawTextureData(imageBuffer);
|
||
texture.Apply();
|
||
|
||
// Flip the texture vertically because Steam returns it upside down
|
||
Texture2D flipped = new Texture2D((int)width, (int)height, TextureFormat.RGBA32, false);
|
||
for (int y = 0; y < (int)height; y++)
|
||
{
|
||
Color[] pixels = texture.GetPixels(0, y, (int)width, 1);
|
||
flipped.SetPixels(0, (int)height - 1 - y, (int)width, 1, pixels);
|
||
}
|
||
flipped.Apply();
|
||
|
||
steamUserAvatarImage.sprite = Sprite.Create(flipped, new Rect(0, 0, width, height), new Vector2(0.5f, 0.5f));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (System.Exception e)
|
||
{
|
||
Debug.LogError("[Steam] Error updating steam info: " + e);
|
||
if (steamStatusText != null)
|
||
steamStatusText.text = LocalizationService.Get("steam.status.unknown", "Unknown Server");
|
||
}
|
||
#endif
|
||
}
|
||
}
|
||
|
||
internal sealed class btmandtopPanelVisibilityRelay : MonoBehaviour
|
||
{
|
||
private btmandtopController owner;
|
||
private bool includesSettingsPanel;
|
||
|
||
public void Initialize(btmandtopController controller, bool isSettingsPanel)
|
||
{
|
||
owner = controller;
|
||
includesSettingsPanel = isSettingsPanel;
|
||
}
|
||
|
||
private void OnEnable()
|
||
{
|
||
if (owner != null)
|
||
{
|
||
owner.NotifyManagedPanelVisibilityChanged(includesSettingsPanel);
|
||
}
|
||
}
|
||
|
||
private void OnDisable()
|
||
{
|
||
if (owner != null)
|
||
{
|
||
owner.NotifyManagedPanelVisibilityChanged(includesSettingsPanel);
|
||
}
|
||
}
|
||
}
|
||
|
||
internal sealed class btmandtopCurrencyHoverRelay : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerClickHandler
|
||
{
|
||
private btmandtopController owner;
|
||
private bool isFragmentGroup;
|
||
|
||
public void Initialize(btmandtopController controller, bool fragmentGroup)
|
||
{
|
||
owner = controller;
|
||
isFragmentGroup = fragmentGroup;
|
||
}
|
||
|
||
public void OnPointerEnter(PointerEventData eventData)
|
||
{
|
||
if (owner != null)
|
||
{
|
||
owner.SetCurrencyHoverVisible(isFragmentGroup, true);
|
||
}
|
||
}
|
||
|
||
public void OnPointerExit(PointerEventData eventData)
|
||
{
|
||
if (owner != null)
|
||
{
|
||
owner.SetCurrencyHoverVisible(isFragmentGroup, false);
|
||
}
|
||
}
|
||
|
||
public void OnPointerClick(PointerEventData eventData)
|
||
{
|
||
if (owner != null)
|
||
{
|
||
owner.SetCurrencyHoverVisible(isFragmentGroup, true);
|
||
}
|
||
}
|
||
}
|