Files
bansonic_beta_main/Assets/scripts/UI/Panel/UI_CharacterDialogController.cs
T

442 lines
14 KiB
C#

using System.Collections;
using DG.Tweening;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class UI_CharacterDialogController : MonoBehaviour
{
[SerializeField] private string requiredSceneName = "UI_UI";
[SerializeField] private string characterObjectName = "Image_Character";
[SerializeField] private string dialogObjectName = "UI_DIALOG";
[Header("Enter")]
[SerializeField] private float enterDuration = 0.3f;
[SerializeField] private float enterFromOffsetY = 60f;
[SerializeField] private Ease enterEase = Ease.OutCubic;
[Header("Blink Out")]
[SerializeField] private int blinkCount = 2;
[SerializeField] private float blinkHalfTime = 0.05f;
[Header("Auto Hide")]
[SerializeField] private float idleHideDelay = 5f;
[SerializeField] private bool useUnscaledTime = true;
private RectTransform dialogRect;
private CanvasGroup dialogGroup;
private Vector2 dialogBasePos;
private bool basePosCached;
private RectTransform boundCharacterRect;
private int bindAttemptToken;
private bool isVisible;
private Coroutine idleRoutine;
private Coroutine bindRoutine;
private Coroutine keepAliveRoutine;
private Sequence dialogSequence;
private UI_CharacterDialogClickProxy clickProxy;
private Button characterButton;
private bool initialHideDone;
private void OnEnable()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
SceneManager.sceneLoaded += OnSceneLoaded;
SceneManager.activeSceneChanged -= OnActiveSceneChanged;
SceneManager.activeSceneChanged += OnActiveSceneChanged;
RebindSoon();
if (keepAliveRoutine == null)
keepAliveRoutine = StartCoroutine(KeepBindingAliveRoutine());
}
private void OnDisable()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
SceneManager.activeSceneChanged -= OnActiveSceneChanged;
KillTweens();
if (characterButton != null)
{
characterButton.onClick.RemoveListener(OnCharacterClicked);
characterButton = null;
}
if (idleRoutine != null)
{
StopCoroutine(idleRoutine);
idleRoutine = null;
}
if (bindRoutine != null)
{
StopCoroutine(bindRoutine);
bindRoutine = null;
}
if (keepAliveRoutine != null)
{
StopCoroutine(keepAliveRoutine);
keepAliveRoutine = null;
}
clickProxy = null;
dialogRect = null;
dialogGroup = null;
boundCharacterRect = null;
basePosCached = false;
initialHideDone = false;
}
public void RebindSoon()
{
bindAttemptToken++;
if (bindRoutine != null)
StopCoroutine(bindRoutine);
bindRoutine = StartCoroutine(BindRoutine(bindAttemptToken));
}
private IEnumerator BindRoutine(int token)
{
yield return null;
TryBind();
yield return null;
if (token != bindAttemptToken)
yield break;
yield return null;
TryBind();
if (token != bindAttemptToken)
yield break;
yield return null;
TryBind();
if (token != bindAttemptToken)
yield break;
yield return new WaitForSecondsRealtime(0.2f);
TryBind();
if (token != bindAttemptToken)
yield break;
yield return new WaitForSecondsRealtime(0.6f);
TryBind();
bindRoutine = null;
}
private void TryBind()
{
if (!IsAllowedScene())
return;
Scene scene = gameObject.scene;
if (!scene.IsValid() || !scene.isLoaded)
scene = SceneManager.GetActiveScene();
Transform dialogTr = FindInSceneByName(scene, dialogObjectName);
if (dialogTr == null)
dialogTr = FindInSceneByName(SceneManager.GetActiveScene(), dialogObjectName);
if (dialogTr != null)
{
RectTransform nextDialogRect = dialogTr as RectTransform;
bool dialogChanged = dialogRect != nextDialogRect;
dialogRect = nextDialogRect;
if (dialogRect != null)
{
dialogGroup = dialogRect.GetComponent<CanvasGroup>();
if (dialogGroup == null)
dialogGroup = dialogRect.gameObject.AddComponent<CanvasGroup>();
if (dialogChanged || !basePosCached)
{
dialogBasePos = dialogRect.anchoredPosition;
basePosCached = true;
}
}
if (!initialHideDone)
{
HideImmediate();
initialHideDone = true;
}
}
Transform characterTr = FindInSceneByName(scene, characterObjectName);
if (characterTr == null)
characterTr = FindInSceneByName(SceneManager.GetActiveScene(), characterObjectName);
if (characterTr != null)
{
boundCharacterRect = characterTr as RectTransform;
clickProxy = characterTr.GetComponent<UI_CharacterDialogClickProxy>();
if (clickProxy == null)
clickProxy = characterTr.gameObject.AddComponent<UI_CharacterDialogClickProxy>();
clickProxy.Bind(this);
// Ensure the image can receive click events.
Graphic graphic = characterTr.GetComponent<Graphic>();
if (graphic != null)
graphic.raycastTarget = true;
// Also bind through Button click to avoid missing pointer callbacks.
Button btn = characterTr.GetComponent<Button>();
if (btn == null)
{
btn = characterTr.gameObject.AddComponent<Button>();
btn.transition = Selectable.Transition.None;
if (graphic != null)
btn.targetGraphic = graphic;
}
if (characterButton != null && characterButton != btn)
characterButton.onClick.RemoveListener(OnCharacterClicked);
characterButton = btn;
characterButton.onClick.RemoveListener(OnCharacterClicked);
characterButton.onClick.AddListener(OnCharacterClicked);
}
else
{
boundCharacterRect = null;
}
}
public void OnCharacterClicked()
{
if (!IsBindingValid())
TryBind();
if (dialogRect == null || dialogGroup == null || !basePosCached)
return;
if (isVisible)
{
BlinkOut(() => PlayEnter(), deactivateAtEnd: true);
}
else
{
PlayEnter();
}
}
private void PlayEnter()
{
if (dialogRect == null || dialogGroup == null)
return;
KillTweens();
dialogRect.gameObject.SetActive(true);
dialogRect.anchoredPosition = dialogBasePos - new Vector2(0f, Mathf.Abs(enterFromOffsetY));
dialogGroup.alpha = 0f;
dialogSequence = DOTween.Sequence().SetUpdate(useUnscaledTime);
dialogSequence.Join(dialogRect.DOAnchorPos(dialogBasePos, Mathf.Max(0.01f, enterDuration)).SetEase(enterEase));
dialogSequence.Join(dialogGroup.DOFade(1f, Mathf.Max(0.01f, enterDuration)).SetEase(Ease.OutCubic));
dialogSequence.OnComplete(() => dialogSequence = null);
isVisible = true;
RestartIdleTimer();
}
private void BlinkOut(System.Action onComplete, bool deactivateAtEnd)
{
if (dialogRect == null || dialogGroup == null)
{
onComplete?.Invoke();
return;
}
KillTweens();
dialogRect.gameObject.SetActive(true);
dialogSequence = DOTween.Sequence().SetUpdate(useUnscaledTime);
int blinks = Mathf.Max(1, blinkCount);
float half = Mathf.Max(0.01f, blinkHalfTime);
for (int i = 0; i < blinks; i++)
{
dialogSequence.Append(dialogGroup.DOFade(0f, half).SetEase(Ease.OutQuad));
dialogSequence.Append(dialogGroup.DOFade(1f, half).SetEase(Ease.OutQuad));
}
dialogSequence.Append(dialogGroup.DOFade(0f, half).SetEase(Ease.OutQuad));
dialogSequence.OnComplete(() =>
{
dialogSequence = null;
isVisible = false;
if (deactivateAtEnd && dialogRect != null)
dialogRect.gameObject.SetActive(false);
onComplete?.Invoke();
});
}
private void RestartIdleTimer()
{
if (idleRoutine != null)
StopCoroutine(idleRoutine);
idleRoutine = StartCoroutine(IdleHideRoutine());
}
private IEnumerator IdleHideRoutine()
{
float delay = Mathf.Max(0.2f, idleHideDelay);
float t = 0f;
while (t < delay)
{
t += useUnscaledTime ? Time.unscaledDeltaTime : Time.deltaTime;
yield return null;
}
if (isVisible)
{
BlinkOut(onComplete: null, deactivateAtEnd: true);
}
idleRoutine = null;
}
private void HideImmediate()
{
if (dialogRect == null || dialogGroup == null)
return;
KillTweens();
dialogGroup.alpha = 0f;
dialogRect.anchoredPosition = dialogBasePos;
dialogRect.gameObject.SetActive(false);
isVisible = false;
}
private void KillTweens()
{
if (dialogSequence != null)
{
dialogSequence.Kill();
dialogSequence = null;
}
if (dialogRect != null)
dialogRect.DOKill();
if (dialogGroup != null)
dialogGroup.DOKill();
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
if (string.Equals(scene.name, requiredSceneName, System.StringComparison.OrdinalIgnoreCase))
RebindSoon();
}
private void OnActiveSceneChanged(Scene from, Scene to)
{
if (string.Equals(to.name, requiredSceneName, System.StringComparison.OrdinalIgnoreCase))
RebindSoon();
}
private IEnumerator KeepBindingAliveRoutine()
{
while (enabled)
{
if (IsAllowedScene() && !IsBindingValid())
RebindSoon();
yield return new WaitForSecondsRealtime(0.5f);
}
}
private bool IsBindingValid()
{
if (!IsAllowedScene())
return false;
if (dialogRect == null || dialogGroup == null || !basePosCached)
return false;
if (boundCharacterRect == null)
return false;
if (characterButton == null)
return false;
return true;
}
private bool IsAllowedScene()
{
if (string.IsNullOrEmpty(requiredSceneName))
return true;
Scene own = gameObject.scene;
if (own.IsValid() && string.Equals(own.name, requiredSceneName, System.StringComparison.OrdinalIgnoreCase))
return true;
Scene active = SceneManager.GetActiveScene();
return active.IsValid() && string.Equals(active.name, requiredSceneName, System.StringComparison.OrdinalIgnoreCase);
}
private Transform FindInSceneByName(Scene scene, string objectName)
{
if (!scene.IsValid() || !scene.isLoaded || string.IsNullOrEmpty(objectName))
return null;
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;
}
return null;
}
}
public class UI_CharacterDialogClickProxy : MonoBehaviour, IPointerClickHandler
{
private UI_CharacterDialogController controller;
public void Bind(UI_CharacterDialogController owner)
{
controller = owner;
}
public void OnPointerClick(PointerEventData eventData)
{
controller?.OnCharacterClicked();
}
}
static class UI_CharacterDialogControllerBootstrap
{
private const string SceneName = "UI_UI";
private const string HostName = "__UICharacterDialogControllerHost";
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void Init()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
SceneManager.sceneLoaded += OnSceneLoaded;
}
private static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
if (!string.Equals(scene.name, SceneName, System.StringComparison.OrdinalIgnoreCase))
return;
GameObject host = FindHostInScene(scene, HostName);
if (host == null)
{
host = new GameObject(HostName);
SceneManager.MoveGameObjectToScene(host, scene);
}
UI_CharacterDialogController controller = host.GetComponent<UI_CharacterDialogController>();
if (controller == null)
controller = host.AddComponent<UI_CharacterDialogController>();
controller.RebindSoon();
}
private static GameObject FindHostInScene(Scene scene, string hostName)
{
if (!scene.IsValid() || !scene.isLoaded)
return null;
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 == hostName)
return t.gameObject;
}
return null;
}
}