mirror of
https://github.com/Earth-Genesis-Games/Ajedrez_Purgatorio.git
synced 2026-09-11 09:47:35 +00:00
fix: defeat-to-Memorial chain, first-run dialogues, memorial typing, dialogue layout
This commit is contained in:
@@ -7,6 +7,7 @@ using UnityEngine;
|
||||
/// Sistema central de diálogos.
|
||||
/// Maneja la carga de nodos, navegación, typewriter effect y branching condicional.
|
||||
/// </summary>
|
||||
[DefaultExecutionOrder(-100)]
|
||||
public class DialogueSystem : MonoBehaviour
|
||||
{
|
||||
public static DialogueSystem Instance { get; private set; }
|
||||
|
||||
@@ -56,10 +56,15 @@ public class CampaignConfig : ScriptableObject
|
||||
|
||||
/// <summary>
|
||||
/// Carga los datos desde el JSON al habilitar el ScriptableObject.
|
||||
/// Silencioso si aún no hay fuente: CreateInstance dispara OnEnable antes
|
||||
/// de que CampaignManager pueda asignar el JSON en runtime.
|
||||
/// </summary>
|
||||
void OnEnable()
|
||||
{
|
||||
LoadFromJson();
|
||||
if (_campaignDataJson != null || _runtimeJsonSource != null)
|
||||
LoadFromJson();
|
||||
else
|
||||
_isLoaded = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -51,6 +51,10 @@ namespace AjedrezPurgatorio.UI
|
||||
private int _currentChapterIndex;
|
||||
private bool _isSubmitting = false;
|
||||
|
||||
private UnityEngine.EventSystems.BaseInputModule _disabledModule;
|
||||
private UnityEngine.EventSystems.StandaloneInputModule _enabledLegacyModule;
|
||||
private bool _legacyModuleWasAdded;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity Lifecycle
|
||||
@@ -92,6 +96,66 @@ namespace AjedrezPurgatorio.UI
|
||||
{
|
||||
_submitButton.onClick.RemoveListener(OnSubmitClick);
|
||||
}
|
||||
|
||||
RestoreInputModule();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TMP_InputField edita texto por el pipeline clásico (Input.inputString).
|
||||
/// El EventSystem de las escenas usa InputSystemUIInputModule, que procesa
|
||||
/// clics pero no alimenta la escritura legacy: mientras el Memorial esté
|
||||
/// abierto se usa StandaloneInputModule y al cerrarse se restaura el módulo.
|
||||
/// </summary>
|
||||
private void EnsureTypableEventSystem()
|
||||
{
|
||||
var es = FindObjectOfType<UnityEngine.EventSystems.EventSystem>();
|
||||
if (es == null)
|
||||
{
|
||||
var go = new GameObject("EventSystem");
|
||||
es = go.AddComponent<UnityEngine.EventSystems.EventSystem>();
|
||||
_enabledLegacyModule = go.AddComponent<UnityEngine.EventSystems.StandaloneInputModule>();
|
||||
_legacyModuleWasAdded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var newModule = es.GetComponent<UnityEngine.InputSystem.UI.InputSystemUIInputModule>();
|
||||
if (newModule == null || !newModule.enabled)
|
||||
return;
|
||||
|
||||
newModule.enabled = false;
|
||||
_disabledModule = newModule;
|
||||
|
||||
var legacy = es.GetComponent<UnityEngine.EventSystems.StandaloneInputModule>();
|
||||
if (legacy != null)
|
||||
{
|
||||
legacy.enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_enabledLegacyModule = es.gameObject.AddComponent<UnityEngine.EventSystems.StandaloneInputModule>();
|
||||
_legacyModuleWasAdded = true;
|
||||
}
|
||||
|
||||
Debug.Log("[DeadKingInputUI] Módulo de UI cambiado a StandaloneInputModule para permitir escritura.");
|
||||
}
|
||||
|
||||
private void RestoreInputModule()
|
||||
{
|
||||
if (_disabledModule != null)
|
||||
{
|
||||
_disabledModule.enabled = true;
|
||||
_disabledModule = null;
|
||||
}
|
||||
|
||||
if (_enabledLegacyModule != null)
|
||||
{
|
||||
if (_legacyModuleWasAdded && _enabledLegacyModule != null)
|
||||
Destroy(_enabledLegacyModule);
|
||||
else
|
||||
_enabledLegacyModule.enabled = false;
|
||||
_enabledLegacyModule = null;
|
||||
_legacyModuleWasAdded = false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -104,7 +168,16 @@ namespace AjedrezPurgatorio.UI
|
||||
/// </summary>
|
||||
public bool IsProperlyWired =>
|
||||
_panel != null && _nameInputField != null &&
|
||||
_messageInputField != null && _submitButton != null;
|
||||
_messageInputField != null && _submitButton != null &&
|
||||
InputFieldUsable(_nameInputField) && InputFieldUsable(_messageInputField);
|
||||
|
||||
/// <summary>
|
||||
/// Un InputField sin viewport o sin textComponent (cáscara vacía de la
|
||||
/// escena) no acepta escritura aunque exista: se considera sin cablear.
|
||||
/// </summary>
|
||||
private static bool InputFieldUsable(TMP_InputField field) =>
|
||||
field != null && field.textViewport != null &&
|
||||
field.textComponent != null && !field.readOnly;
|
||||
|
||||
/// <summary>
|
||||
/// Shows the Dead King input panel with campaign context.
|
||||
@@ -113,6 +186,8 @@ namespace AjedrezPurgatorio.UI
|
||||
/// <param name="chapterIndex">Chapter where defeat occurred</param>
|
||||
public void Show(CampaignState campaignState, int chapterIndex)
|
||||
{
|
||||
Debug.Log($"[DeadKingInputUI] Show() llamado (capítulo {chapterIndex}).");
|
||||
|
||||
if (campaignState == null)
|
||||
{
|
||||
Debug.LogError("[DeadKingInputUI] Cannot show with null campaign state.");
|
||||
@@ -120,12 +195,20 @@ namespace AjedrezPurgatorio.UI
|
||||
}
|
||||
|
||||
// Escenas con copias duplicadas o sin cablear: si faltan referencias
|
||||
// esenciales se construye un panel funcional en runtime para que el
|
||||
// jugador SIEMPRE pueda inscribir su nombre y mensaje.
|
||||
// esenciales (o los InputFields son cáscaras sin viewport/texto)
|
||||
// se oculta el panel roto y se construye uno funcional en runtime
|
||||
// para que el jugador SIEMPRE pueda inscribir su nombre y mensaje.
|
||||
if (!IsProperlyWired)
|
||||
{
|
||||
Debug.LogWarning($"[DeadKingInputUI] Referencias sin cablear (panel={(_panel != null)} nombre={(_nameInputField != null)} mensaje={(_messageInputField != null)} submit={(_submitButton != null)}). Construyendo panel en runtime...");
|
||||
Debug.LogWarning($"[DeadKingInputUI] Referencias inservibles (panel={(_panel != null)} nombre={(_nameInputField != null)} mensaje={(_messageInputField != null)} submit={(_submitButton != null)}). Construyendo panel en runtime...");
|
||||
if (_panel != null && _panel != gameObject)
|
||||
_panel.SetActive(false);
|
||||
BuildRuntimeFallback();
|
||||
|
||||
// El botón del panel runtime invoca handlers de ESTE componente:
|
||||
// su GameObject debe estar activo o StartCoroutine fallará.
|
||||
if (!gameObject.activeInHierarchy)
|
||||
gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
_campaignState = campaignState;
|
||||
@@ -188,6 +271,8 @@ namespace AjedrezPurgatorio.UI
|
||||
/// </summary>
|
||||
private void OnSubmitClick()
|
||||
{
|
||||
Debug.Log("[DeadKingInputUI] Inscribir clickeado.");
|
||||
|
||||
if (_isSubmitting)
|
||||
{
|
||||
if (_enableDebugLogging)
|
||||
@@ -248,8 +333,8 @@ namespace AjedrezPurgatorio.UI
|
||||
_feedbackText.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
// Wait for feedback display time
|
||||
yield return new WaitForSeconds(_feedbackDisplayTime);
|
||||
// Wait for feedback display time (unscaled: el juego puede estar pausado)
|
||||
yield return new WaitForSecondsRealtime(_feedbackDisplayTime);
|
||||
|
||||
// Hide feedback and input panel
|
||||
if (_feedbackText != null)
|
||||
@@ -472,7 +557,7 @@ namespace AjedrezPurgatorio.UI
|
||||
/// </summary>
|
||||
private void BuildRuntimeFallback()
|
||||
{
|
||||
EnsureEventSystem();
|
||||
EnsureTypableEventSystem();
|
||||
|
||||
TMP_FontAsset font = TMP_Settings.defaultFontAsset;
|
||||
|
||||
@@ -480,7 +565,10 @@ namespace AjedrezPurgatorio.UI
|
||||
var canvas = canvasGO.AddComponent<Canvas>();
|
||||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
canvas.sortingOrder = 460;
|
||||
canvasGO.AddComponent<CanvasScaler>().uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||||
var scaler = canvasGO.AddComponent<CanvasScaler>();
|
||||
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||||
scaler.referenceResolution = new Vector2(1920f, 1080f);
|
||||
scaler.matchWidthOrHeight = 0f;
|
||||
canvasGO.AddComponent<GraphicRaycaster>();
|
||||
_panel = canvasGO;
|
||||
|
||||
@@ -492,29 +580,34 @@ namespace AjedrezPurgatorio.UI
|
||||
|
||||
var title = CreateText("Title", "INSCRIBE TU NOMBRE EN EL MEMORIAL", 32, new Color(0.9f, 0.82f, 0.55f), font);
|
||||
title.transform.SetParent(canvasGO.transform, false);
|
||||
Anchor(title.rectTransform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0, -110), new Vector2(1200, 55));
|
||||
Anchor(title.rectTransform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0, -95), new Vector2(1200, 56));
|
||||
|
||||
var subtitle = CreateText("Subtitle", "El Purgatorio recuerda a sus reyes caídos.", 22, new Color(0.75f, 0.72f, 0.65f), font);
|
||||
subtitle.fontStyle = FontStyles.Italic;
|
||||
subtitle.transform.SetParent(canvasGO.transform, false);
|
||||
Anchor(subtitle.rectTransform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0, -160), new Vector2(900, 40));
|
||||
Anchor(subtitle.rectTransform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0, -145), new Vector2(900, 40));
|
||||
|
||||
_nameInputField = CreateInputField(
|
||||
"NameInput", "Tu nombre...", font, canvasGO.transform,
|
||||
new Vector2(0, -235), new Vector2(620, 58), multiline: false);
|
||||
new Vector2(0, 112), new Vector2(620, 58), multiline: false);
|
||||
|
||||
_messageInputField = CreateInputField(
|
||||
"MessageInput", "Un mensaje para los vivos (opcional)...", font, canvasGO.transform,
|
||||
new Vector2(0, -345), new Vector2(620, 110), multiline: true);
|
||||
new Vector2(0, 0), new Vector2(620, 110), multiline: true);
|
||||
|
||||
_submitButton = CreateButton("SubmitButton", "Inscribir en el Memorial", font, new Color(0.45f, 0.08f, 0.08f));
|
||||
_submitButton.transform.SetParent(canvasGO.transform, false);
|
||||
Anchor((RectTransform)_submitButton.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0, -250), new Vector2(420, 64));
|
||||
Anchor((RectTransform)_submitButton.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0, -105), new Vector2(420, 64));
|
||||
|
||||
// Awake corrió con referencias nulas: cablear límites y listener aquí.
|
||||
_nameInputField.characterLimit = _maxNameLength;
|
||||
_messageInputField.characterLimit = _maxMessageLength;
|
||||
_submitButton.onClick.AddListener(OnSubmitClick);
|
||||
|
||||
var module = UnityEngine.EventSystems.EventSystem.current != null
|
||||
? UnityEngine.EventSystems.EventSystem.current.currentInputModule
|
||||
: null;
|
||||
Debug.Log($"[DeadKingInputUI] Panel runtime construido. Módulo UI activo: {(module != null ? module.GetType().Name : "NINGUNO")}.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -577,16 +670,6 @@ namespace AjedrezPurgatorio.UI
|
||||
return inputField;
|
||||
}
|
||||
|
||||
private static void EnsureEventSystem()
|
||||
{
|
||||
if (FindObjectOfType<UnityEngine.EventSystems.EventSystem>() != null)
|
||||
return;
|
||||
|
||||
var es = new GameObject("EventSystem");
|
||||
es.AddComponent<UnityEngine.EventSystems.EventSystem>();
|
||||
es.AddComponent<UnityEngine.EventSystems.StandaloneInputModule>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fade Effects
|
||||
|
||||
@@ -62,6 +62,21 @@ namespace AjedrezPurgatorio.UI
|
||||
{
|
||||
_onContinueCallback = onContinue;
|
||||
|
||||
Canvas parentCanvas = GetComponentInParent<Canvas>();
|
||||
if (parentCanvas != null)
|
||||
{
|
||||
parentCanvas.sortingOrder = 300;
|
||||
Debug.Log($"[DefeatUI] Mostrando derrota '{chapterName}' ({reason}). Canvas '{parentCanvas.name}' orden forzado a {parentCanvas.sortingOrder}.");
|
||||
}
|
||||
else
|
||||
Debug.LogWarning("[DefeatUI] Mostrando derrota sin Canvas padre.");
|
||||
|
||||
if (_canvasGroup != null)
|
||||
{
|
||||
_canvasGroup.blocksRaycasts = true;
|
||||
_canvasGroup.interactable = true;
|
||||
}
|
||||
|
||||
if (_titleText != null)
|
||||
_titleText.text = "DERROTA";
|
||||
|
||||
@@ -84,6 +99,11 @@ namespace AjedrezPurgatorio.UI
|
||||
else
|
||||
gameObject.SetActive(true);
|
||||
|
||||
if (_continueButton != null)
|
||||
Debug.Log($"[DefeatUI] Botón Continuar: interactable={_continueButton.interactable} activoEnJerarquia={_continueButton.gameObject.activeInHierarchy}.");
|
||||
else
|
||||
Debug.LogWarning("[DefeatUI] Botón Continuar NO asignado.");
|
||||
|
||||
StartCoroutine(FadeIn());
|
||||
|
||||
if (_enableDebugLogging)
|
||||
@@ -97,17 +117,14 @@ namespace AjedrezPurgatorio.UI
|
||||
|
||||
private void OnContinueClick()
|
||||
{
|
||||
if (_enableDebugLogging)
|
||||
Debug.Log("[DefeatUI] Continue clicked.");
|
||||
Debug.Log("[DefeatUI] Continuar clickeado.");
|
||||
|
||||
System.Action callback = _onContinueCallback;
|
||||
_onContinueCallback = null;
|
||||
|
||||
Hide();
|
||||
StartCoroutine(InvokeContinueAfterFade());
|
||||
}
|
||||
|
||||
private IEnumerator InvokeContinueAfterFade()
|
||||
{
|
||||
yield return new WaitForSecondsRealtime(_fadeOutDuration);
|
||||
_onContinueCallback?.Invoke();
|
||||
callback?.Invoke();
|
||||
}
|
||||
|
||||
private IEnumerator FadeIn()
|
||||
|
||||
@@ -24,9 +24,11 @@ public class DialogueUI : MonoBehaviour
|
||||
[Header("Input System")]
|
||||
[SerializeField] private InputActionReference _advanceDialogueAction;
|
||||
|
||||
// Estado
|
||||
private Coroutine _blinkCoroutine;
|
||||
private bool _isInputEnabled = false;
|
||||
// Estado
|
||||
private Coroutine _blinkCoroutine;
|
||||
private bool _isInputEnabled = false;
|
||||
private bool _subscribed = false;
|
||||
private Coroutine _subscribeCoroutine;
|
||||
|
||||
void Update()
|
||||
{
|
||||
@@ -54,14 +56,16 @@ public class DialogueUI : MonoBehaviour
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
// Suscribirse a eventos de DialogueSystem
|
||||
// Suscribirse a eventos de DialogueSystem.
|
||||
// En la primera carga puede no existir aún (orden de Awake/OnEnable):
|
||||
// diferir la suscripción en vez de perderla para siempre.
|
||||
if (DialogueSystem.Instance != null)
|
||||
{
|
||||
DialogueSystem.Instance.OnNodeDisplayed += OnNodeDisplayed;
|
||||
DialogueSystem.Instance.OnNodeComplete += OnNodeComplete;
|
||||
DialogueSystem.Instance.OnDialogueComplete += OnDialogueComplete;
|
||||
DialogueSystem.Instance.OnSpeakerChanged += OnSpeakerChanged;
|
||||
DialogueSystem.Instance.OnTextUpdated += OnTextUpdated;
|
||||
SubscribeToDialogueSystem();
|
||||
}
|
||||
else if (_subscribeCoroutine == null)
|
||||
{
|
||||
_subscribeCoroutine = StartCoroutine(SubscribeWhenReady());
|
||||
}
|
||||
|
||||
// Habilitar input action
|
||||
@@ -76,11 +80,90 @@ public class DialogueUI : MonoBehaviour
|
||||
{
|
||||
_dialoguePanel.SetActive(false);
|
||||
}
|
||||
|
||||
RelayoutToAvoidPortraitOverlap();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reubica retrato (izquierda), nombre y texto para que el texto nunca
|
||||
/// quede encima de la imagen del personaje. Idempotente.
|
||||
/// </summary>
|
||||
private void RelayoutToAvoidPortraitOverlap()
|
||||
{
|
||||
if (_speakerPortrait != null)
|
||||
{
|
||||
RectTransform portraitRt = _speakerPortrait.rectTransform;
|
||||
portraitRt.anchorMin = new Vector2(0f, 0.5f);
|
||||
portraitRt.anchorMax = new Vector2(0f, 0.5f);
|
||||
portraitRt.pivot = new Vector2(0.5f, 0.5f);
|
||||
portraitRt.sizeDelta = new Vector2(240f, 300f);
|
||||
portraitRt.anchoredPosition = new Vector2(150f, 0f);
|
||||
}
|
||||
|
||||
if (_dialogueText != null)
|
||||
{
|
||||
RectTransform textRt = _dialogueText.rectTransform;
|
||||
textRt.anchorMin = new Vector2(0f, 0f);
|
||||
textRt.anchorMax = new Vector2(1f, 1f);
|
||||
textRt.pivot = new Vector2(0.5f, 0.5f);
|
||||
textRt.offsetMin = new Vector2(315f, 24f);
|
||||
textRt.offsetMax = new Vector2(-28f, -102f);
|
||||
_dialogueText.alignment = TextAlignmentOptions.TopLeft;
|
||||
}
|
||||
|
||||
if (_speakerNameText != null)
|
||||
{
|
||||
RectTransform nameRt = _speakerNameText.rectTransform;
|
||||
nameRt.anchorMin = new Vector2(0f, 1f);
|
||||
nameRt.anchorMax = new Vector2(0f, 1f);
|
||||
nameRt.pivot = new Vector2(0f, 1f);
|
||||
nameRt.sizeDelta = new Vector2(520f, 46f);
|
||||
nameRt.anchoredPosition = new Vector2(315f, -16f);
|
||||
}
|
||||
}
|
||||
|
||||
private void SubscribeToDialogueSystem()
|
||||
{
|
||||
if (_subscribed || DialogueSystem.Instance == null)
|
||||
return;
|
||||
|
||||
DialogueSystem.Instance.OnNodeDisplayed += OnNodeDisplayed;
|
||||
DialogueSystem.Instance.OnNodeComplete += OnNodeComplete;
|
||||
DialogueSystem.Instance.OnDialogueComplete += OnDialogueComplete;
|
||||
DialogueSystem.Instance.OnSpeakerChanged += OnSpeakerChanged;
|
||||
DialogueSystem.Instance.OnTextUpdated += OnTextUpdated;
|
||||
_subscribed = true;
|
||||
}
|
||||
|
||||
private IEnumerator SubscribeWhenReady()
|
||||
{
|
||||
yield return new WaitUntil(() => DialogueSystem.Instance != null);
|
||||
|
||||
_subscribeCoroutine = null;
|
||||
SubscribeToDialogueSystem();
|
||||
|
||||
// Si un diálogo ya está activo al momento de suscribirse (arrancó
|
||||
// antes de que esta UI existiera), sincronizar su estado actual.
|
||||
if (_subscribed && DialogueSystem.Instance.IsActive && DialogueSystem.Instance.CurrentNode != null)
|
||||
{
|
||||
DialogueNode currentNode = DialogueSystem.Instance.CurrentNode;
|
||||
OnSpeakerChanged(currentNode.speaker);
|
||||
OnNodeDisplayed(currentNode);
|
||||
OnTextUpdated(currentNode.text);
|
||||
if (!DialogueSystem.Instance.IsTyping)
|
||||
OnNodeComplete(currentNode);
|
||||
}
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
// Desuscribirse para evitar memory leaks
|
||||
if (_subscribeCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_subscribeCoroutine);
|
||||
_subscribeCoroutine = null;
|
||||
}
|
||||
|
||||
if (DialogueSystem.Instance != null)
|
||||
{
|
||||
DialogueSystem.Instance.OnNodeDisplayed -= OnNodeDisplayed;
|
||||
@@ -89,6 +172,7 @@ public class DialogueUI : MonoBehaviour
|
||||
DialogueSystem.Instance.OnSpeakerChanged -= OnSpeakerChanged;
|
||||
DialogueSystem.Instance.OnTextUpdated -= OnTextUpdated;
|
||||
}
|
||||
_subscribed = false;
|
||||
|
||||
// Deshabilitar input action
|
||||
if (_advanceDialogueAction != null && _advanceDialogueAction.action != null)
|
||||
|
||||
Reference in New Issue
Block a user