494 lines
17 KiB
C#
494 lines
17 KiB
C#
using System.Collections;
|
|
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
using UnityEngine.UI;
|
|
|
|
public class UnimplementedFeaturePrompt : MonoBehaviour
|
|
{
|
|
public static bool RuntimeEnabled = false;
|
|
private static UnimplementedFeaturePrompt instance;
|
|
|
|
[Header("Flash")]
|
|
[SerializeField] private int flashCount = 2;
|
|
[SerializeField] private float flashOnTime = 0.04f;
|
|
[SerializeField] private float flashOffTime = 0.03f;
|
|
|
|
private GameObject root;
|
|
private GameObject pressText;
|
|
private GameObject noChoice;
|
|
private GameObject choiceLessThanFive;
|
|
private GameObject image3;
|
|
private Button continueButton;
|
|
private Button considerButton;
|
|
private Button doNotShowAgainButton;
|
|
private Button okayButton;
|
|
private Coroutine flashRoutine;
|
|
|
|
public static void ShowPrompt()
|
|
{
|
|
if (!RuntimeEnabled)
|
|
return;
|
|
EnsureInstance();
|
|
if (instance != null)
|
|
instance.ShowInternal();
|
|
}
|
|
|
|
private static void EnsureInstance()
|
|
{
|
|
if (instance != null)
|
|
return;
|
|
|
|
GameObject host = SceneObjectLookupCache.Find("__UnimplementedFeaturePromptHost");
|
|
if (host == null)
|
|
{
|
|
host = new GameObject("__UnimplementedFeaturePromptHost");
|
|
DontDestroyOnLoad(host);
|
|
}
|
|
|
|
instance = host.GetComponent<UnimplementedFeaturePrompt>();
|
|
if (instance == null)
|
|
instance = host.AddComponent<UnimplementedFeaturePrompt>();
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
SceneManager.sceneLoaded -= OnSceneLoaded;
|
|
SceneManager.sceneLoaded += OnSceneLoaded;
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
SceneManager.sceneLoaded -= OnSceneLoaded;
|
|
}
|
|
|
|
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
|
|
{
|
|
// Re-resolve references every scene so we can reuse existing UI if present.
|
|
root = null;
|
|
pressText = null;
|
|
noChoice = null;
|
|
choiceLessThanFive = null;
|
|
image3 = null;
|
|
continueButton = null;
|
|
considerButton = null;
|
|
doNotShowAgainButton = null;
|
|
okayButton = null;
|
|
}
|
|
|
|
private void ShowInternal()
|
|
{
|
|
EnsurePromptRoot();
|
|
if (root == null)
|
|
{
|
|
Debug.LogWarning("[UnimplementedFeaturePrompt] Prompt root was not found and fallback creation failed.");
|
|
return;
|
|
}
|
|
|
|
EnsureMinimumPromptElements();
|
|
BringRootToFront();
|
|
BindOkayButton();
|
|
|
|
root.SetActive(true);
|
|
CanvasGroup rootGroup = root.GetComponent<CanvasGroup>();
|
|
if (rootGroup != null)
|
|
rootGroup.alpha = 1f;
|
|
|
|
SetActiveSafe(noChoice, false);
|
|
SetActiveSafe(choiceLessThanFive, false);
|
|
SetActiveSafe(image3, false);
|
|
SetButtonVisible(continueButton, false);
|
|
SetButtonVisible(considerButton, false);
|
|
SetButtonVisible(doNotShowAgainButton, false);
|
|
SetButtonVisible(okayButton, true);
|
|
SetActiveSafe(pressText, true);
|
|
|
|
if (flashRoutine != null)
|
|
StopCoroutine(flashRoutine);
|
|
flashRoutine = StartCoroutine(FlashMessage());
|
|
}
|
|
|
|
private IEnumerator FlashMessage()
|
|
{
|
|
GameObject target = pressText != null ? pressText : root;
|
|
if (target == null)
|
|
yield break;
|
|
|
|
CanvasGroup cg = target.GetComponent<CanvasGroup>();
|
|
if (cg == null)
|
|
cg = target.AddComponent<CanvasGroup>();
|
|
|
|
int count = Mathf.Max(1, flashCount);
|
|
float onTime = Mathf.Clamp(flashOnTime, 0.01f, 0.05f);
|
|
float offTime = Mathf.Clamp(flashOffTime, 0.01f, 0.05f);
|
|
|
|
cg.alpha = 0f;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
cg.alpha = 1f;
|
|
yield return new WaitForSecondsRealtime(onTime);
|
|
if (i < count - 1)
|
|
{
|
|
cg.alpha = 0f;
|
|
yield return new WaitForSecondsRealtime(offTime);
|
|
}
|
|
}
|
|
cg.alpha = 1f;
|
|
flashRoutine = null;
|
|
}
|
|
|
|
private void OnOkayClicked()
|
|
{
|
|
if (flashRoutine != null)
|
|
{
|
|
StopCoroutine(flashRoutine);
|
|
flashRoutine = null;
|
|
}
|
|
if (root != null)
|
|
{
|
|
UI_PanelExitUtility.PlayExitThen(root, () =>
|
|
{
|
|
if (root != null)
|
|
root.SetActive(false);
|
|
});
|
|
}
|
|
}
|
|
|
|
private void BindOkayButton()
|
|
{
|
|
if (okayButton == null)
|
|
return;
|
|
|
|
okayButton.onClick.RemoveListener(OnOkayClicked);
|
|
okayButton.onClick.AddListener(OnOkayClicked);
|
|
}
|
|
|
|
private void EnsurePromptRoot()
|
|
{
|
|
Scene activeScene = SceneManager.GetActiveScene();
|
|
if (root != null && root.scene.IsValid() && root.scene == activeScene)
|
|
return;
|
|
|
|
root = FindSceneObjectByExactName("\uFF01mustselectyouridol");
|
|
if (root == null)
|
|
root = FindSceneObjectByContainsName("mustselectyouridol");
|
|
|
|
if (root == null)
|
|
root = CreateFallbackPrompt();
|
|
|
|
if (root == null)
|
|
return;
|
|
|
|
pressText = FindChildByName(root.transform, "Press an unimplemented feature");
|
|
if (pressText == null)
|
|
pressText = FindChildByName(root.transform, "pressanunimplementedfeature");
|
|
|
|
noChoice = FindChildByName(root.transform, "No choice");
|
|
choiceLessThanFive = FindChildByName(root.transform, "choice < 5");
|
|
image3 = FindChildByName(root.transform, "Image (3)");
|
|
continueButton = FindButtonByName(root.transform, "Continue");
|
|
considerButton = FindButtonByName(root.transform, "Consider");
|
|
doNotShowAgainButton = FindButtonByName(root.transform, "Do not show again");
|
|
okayButton = FindButtonByName(root.transform, "okay");
|
|
if (okayButton == null)
|
|
okayButton = FindButtonByContainsName(root.transform, "ok");
|
|
}
|
|
|
|
private GameObject CreateFallbackPrompt()
|
|
{
|
|
Canvas canvas = FindTopCanvasInActiveScene();
|
|
if (canvas == null)
|
|
canvas = CreateFallbackCanvas();
|
|
if (canvas == null)
|
|
return null;
|
|
|
|
GameObject rootGo = new GameObject("\uFF01mustselectyouridol", typeof(RectTransform), typeof(CanvasGroup), typeof(Image));
|
|
rootGo.transform.SetParent(canvas.transform, false);
|
|
RectTransform rootRt = rootGo.GetComponent<RectTransform>();
|
|
rootRt.anchorMin = Vector2.zero;
|
|
rootRt.anchorMax = Vector2.one;
|
|
rootRt.offsetMin = Vector2.zero;
|
|
rootRt.offsetMax = Vector2.zero;
|
|
Image bg = rootGo.GetComponent<Image>();
|
|
bg.color = new Color(0f, 0f, 0f, 0.35f);
|
|
|
|
GameObject textGo = new GameObject("Press an unimplemented feature", typeof(RectTransform), typeof(Text));
|
|
textGo.transform.SetParent(rootGo.transform, false);
|
|
RectTransform textRt = textGo.GetComponent<RectTransform>();
|
|
textRt.anchorMin = new Vector2(0.5f, 0.5f);
|
|
textRt.anchorMax = new Vector2(0.5f, 0.5f);
|
|
textRt.sizeDelta = new Vector2(820f, 80f);
|
|
textRt.anchoredPosition = new Vector2(0f, 40f);
|
|
Text t = textGo.GetComponent<Text>();
|
|
t.font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
|
|
t.fontSize = 40;
|
|
t.alignment = TextAnchor.MiddleCenter;
|
|
t.color = Color.white;
|
|
t.text = "Press an unimplemented feature";
|
|
|
|
GameObject okayGo = new GameObject("okay", typeof(RectTransform), typeof(Image), typeof(Button));
|
|
okayGo.transform.SetParent(rootGo.transform, false);
|
|
RectTransform okayRt = okayGo.GetComponent<RectTransform>();
|
|
okayRt.anchorMin = new Vector2(0.5f, 0.5f);
|
|
okayRt.anchorMax = new Vector2(0.5f, 0.5f);
|
|
okayRt.sizeDelta = new Vector2(200f, 60f);
|
|
okayRt.anchoredPosition = new Vector2(0f, -40f);
|
|
Image okBg = okayGo.GetComponent<Image>();
|
|
okBg.color = Color.white;
|
|
|
|
GameObject okayTextGo = new GameObject("Text (Legacy)", typeof(RectTransform), typeof(Text));
|
|
okayTextGo.transform.SetParent(okayGo.transform, false);
|
|
RectTransform okayTextRt = okayTextGo.GetComponent<RectTransform>();
|
|
okayTextRt.anchorMin = Vector2.zero;
|
|
okayTextRt.anchorMax = Vector2.one;
|
|
okayTextRt.offsetMin = Vector2.zero;
|
|
okayTextRt.offsetMax = Vector2.zero;
|
|
Text okText = okayTextGo.GetComponent<Text>();
|
|
okText.font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
|
|
okText.fontSize = 30;
|
|
okText.alignment = TextAnchor.MiddleCenter;
|
|
okText.color = new Color(0.23f, 0.23f, 0.23f, 1f);
|
|
okText.text = "okay";
|
|
|
|
return rootGo;
|
|
}
|
|
|
|
private static void SetButtonVisible(Button button, bool visible)
|
|
{
|
|
if (button != null)
|
|
button.gameObject.SetActive(visible);
|
|
}
|
|
|
|
private static void SetActiveSafe(GameObject go, bool active)
|
|
{
|
|
if (go != null)
|
|
go.SetActive(active);
|
|
}
|
|
|
|
private static GameObject FindSceneObjectByExactName(string objectName)
|
|
{
|
|
if (string.IsNullOrEmpty(objectName))
|
|
return null;
|
|
|
|
Scene scene = SceneManager.GetActiveScene();
|
|
Transform[] all = UnityEngine.Object.FindObjectsByType<Transform>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
|
for (int i = 0; i < all.Length; i++)
|
|
{
|
|
Transform t = all[i];
|
|
if (t == null || t.gameObject.scene != scene)
|
|
continue;
|
|
if (string.Equals(t.name, objectName, System.StringComparison.OrdinalIgnoreCase))
|
|
return t.gameObject;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static GameObject FindSceneObjectByContainsName(string token)
|
|
{
|
|
if (string.IsNullOrEmpty(token))
|
|
return null;
|
|
|
|
Scene scene = SceneManager.GetActiveScene();
|
|
Transform[] all = UnityEngine.Object.FindObjectsByType<Transform>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
|
for (int i = 0; i < all.Length; i++)
|
|
{
|
|
Transform t = all[i];
|
|
if (t == null || t.gameObject.scene != scene)
|
|
continue;
|
|
if (t.name.IndexOf(token, System.StringComparison.OrdinalIgnoreCase) >= 0)
|
|
return t.gameObject;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static GameObject FindChildByName(Transform root, string childName)
|
|
{
|
|
if (root == null || string.IsNullOrEmpty(childName))
|
|
return null;
|
|
|
|
Transform[] children = root.GetComponentsInChildren<Transform>(true);
|
|
for (int i = 0; i < children.Length; i++)
|
|
{
|
|
Transform c = children[i];
|
|
if (c != null && string.Equals(c.name, childName, System.StringComparison.OrdinalIgnoreCase))
|
|
return c.gameObject;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static Button FindButtonByName(Transform root, string buttonName)
|
|
{
|
|
if (root == null || string.IsNullOrEmpty(buttonName))
|
|
return null;
|
|
|
|
Button[] buttons = root.GetComponentsInChildren<Button>(true);
|
|
for (int i = 0; i < buttons.Length; i++)
|
|
{
|
|
Button b = buttons[i];
|
|
if (b != null && string.Equals(b.name, buttonName, System.StringComparison.OrdinalIgnoreCase))
|
|
return b;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static Button FindButtonByContainsName(Transform root, string token)
|
|
{
|
|
if (root == null || string.IsNullOrEmpty(token))
|
|
return null;
|
|
|
|
string normalizedToken = token.Replace(" ", "").ToLowerInvariant();
|
|
Button[] buttons = root.GetComponentsInChildren<Button>(true);
|
|
for (int i = 0; i < buttons.Length; i++)
|
|
{
|
|
Button b = buttons[i];
|
|
if (b == null)
|
|
continue;
|
|
|
|
string current = b.name.Replace(" ", "").ToLowerInvariant();
|
|
if (current.Contains(normalizedToken))
|
|
return b;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static Canvas FindTopCanvasInActiveScene()
|
|
{
|
|
Scene scene = SceneManager.GetActiveScene();
|
|
Canvas[] canvases = UnityEngine.Object.FindObjectsByType<Canvas>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
|
Canvas best = null;
|
|
int bestOrder = int.MinValue;
|
|
|
|
for (int i = 0; i < canvases.Length; i++)
|
|
{
|
|
Canvas c = canvases[i];
|
|
if (c == null || c.gameObject.scene != scene)
|
|
continue;
|
|
|
|
int order = c.sortingOrder;
|
|
if (c.renderMode == RenderMode.ScreenSpaceOverlay)
|
|
order += 10000;
|
|
|
|
if (best == null || order > bestOrder)
|
|
{
|
|
best = c;
|
|
bestOrder = order;
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
|
|
private static Canvas CreateFallbackCanvas()
|
|
{
|
|
Scene scene = SceneManager.GetActiveScene();
|
|
if (!scene.IsValid() || !scene.isLoaded)
|
|
return null;
|
|
|
|
GameObject go = new GameObject("__UnimplementedPromptCanvas",
|
|
typeof(RectTransform),
|
|
typeof(Canvas),
|
|
typeof(CanvasScaler),
|
|
typeof(GraphicRaycaster));
|
|
try
|
|
{
|
|
SceneManager.MoveGameObjectToScene(go, scene);
|
|
}
|
|
catch
|
|
{
|
|
// keep default scene placement if move fails
|
|
}
|
|
|
|
Canvas canvas = go.GetComponent<Canvas>();
|
|
if (canvas != null)
|
|
{
|
|
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
|
canvas.sortingOrder = 32700;
|
|
}
|
|
|
|
CanvasScaler scaler = go.GetComponent<CanvasScaler>();
|
|
if (scaler != null)
|
|
{
|
|
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
|
scaler.referenceResolution = new Vector2(1920f, 1080f);
|
|
// 锁高度缩放:分辨率跟随屏幕后,非 16:9 屏也能保证各 UI 层一致缩放、不错位。
|
|
scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
|
|
scaler.matchWidthOrHeight = 1f;
|
|
}
|
|
|
|
return canvas;
|
|
}
|
|
|
|
private void EnsureMinimumPromptElements()
|
|
{
|
|
if (root == null)
|
|
return;
|
|
|
|
if (pressText == null)
|
|
{
|
|
GameObject textGo = new GameObject("Press an unimplemented feature", typeof(RectTransform), typeof(Text));
|
|
textGo.transform.SetParent(root.transform, false);
|
|
RectTransform textRt = textGo.GetComponent<RectTransform>();
|
|
textRt.anchorMin = new Vector2(0.5f, 0.5f);
|
|
textRt.anchorMax = new Vector2(0.5f, 0.5f);
|
|
textRt.sizeDelta = new Vector2(820f, 80f);
|
|
textRt.anchoredPosition = new Vector2(0f, 40f);
|
|
|
|
Text t = textGo.GetComponent<Text>();
|
|
t.font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
|
|
t.fontSize = 40;
|
|
t.alignment = TextAnchor.MiddleCenter;
|
|
t.color = Color.white;
|
|
t.text = "Press an unimplemented feature";
|
|
pressText = textGo;
|
|
}
|
|
|
|
if (okayButton == null)
|
|
{
|
|
GameObject okayGo = new GameObject("okay", typeof(RectTransform), typeof(Image), typeof(Button));
|
|
okayGo.transform.SetParent(root.transform, false);
|
|
RectTransform okayRt = okayGo.GetComponent<RectTransform>();
|
|
okayRt.anchorMin = new Vector2(0.5f, 0.5f);
|
|
okayRt.anchorMax = new Vector2(0.5f, 0.5f);
|
|
okayRt.sizeDelta = new Vector2(200f, 60f);
|
|
okayRt.anchoredPosition = new Vector2(0f, -40f);
|
|
|
|
Image okBg = okayGo.GetComponent<Image>();
|
|
okBg.color = Color.white;
|
|
|
|
GameObject textGo = new GameObject("Text (Legacy)", typeof(RectTransform), typeof(Text));
|
|
textGo.transform.SetParent(okayGo.transform, false);
|
|
RectTransform textRt = textGo.GetComponent<RectTransform>();
|
|
textRt.anchorMin = Vector2.zero;
|
|
textRt.anchorMax = Vector2.one;
|
|
textRt.offsetMin = Vector2.zero;
|
|
textRt.offsetMax = Vector2.zero;
|
|
|
|
Text t = textGo.GetComponent<Text>();
|
|
t.font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
|
|
t.fontSize = 30;
|
|
t.alignment = TextAnchor.MiddleCenter;
|
|
t.color = new Color(0.23f, 0.23f, 0.23f, 1f);
|
|
t.text = "okay";
|
|
|
|
okayButton = okayGo.GetComponent<Button>();
|
|
}
|
|
}
|
|
|
|
private void BringRootToFront()
|
|
{
|
|
if (root == null)
|
|
return;
|
|
|
|
Canvas canvas = root.GetComponent<Canvas>();
|
|
if (canvas == null)
|
|
canvas = root.AddComponent<Canvas>();
|
|
if (root.GetComponent<GraphicRaycaster>() == null)
|
|
root.AddComponent<GraphicRaycaster>();
|
|
|
|
canvas.overrideSorting = true;
|
|
canvas.sortingOrder = 32700;
|
|
|
|
if (root.transform.parent != null)
|
|
root.transform.SetAsLastSibling();
|
|
}
|
|
}
|