Files
Ajedrez_Purgatorio/Assets/Game/Scripts/Data/SpeakerDatabase.cs
T
jimmyabv 088d70db6f feat: La Muerte as speaker with custom art + data-driven quotes
- Portraits/muerte.png (256x256 flat style) wired into SpeakerDatabase.asset
  via deterministic GUID/fileID; SpeakerDatabaseSetup batch keeps it in sync
- PortraitRegistry now fills null portraits of existing speakers (was skip-only)
- MuerteQuotes loads pools from Resources Dialogues/muerte_quotes.json with
  embedded fallback per purgatorio-final.md
- Boss art: Resources/Pieces/la_muerte.png (1024px, PPU 100); SpawnBoss swaps
  the tinted king placeholder for the custom sprite when present
- Cleanup: stale TODO in CampaignState.StartChapter, /TestResults/ gitignored,
  duplicate Assets/Game/Audio removed (broken YAML metas, zero references)

Verified in Unity 6000.3.13f1 batch mode: clean import, 200/200 EditMode tests
2026-08-24 20:07:42 -03:00

137 lines
3.8 KiB
C#

using System;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// Base de datos de speakers con sus portraits.
/// ScriptableObject que mapea nombres de speakers a sus imágenes de portrait.
/// </summary>
[CreateAssetMenu(fileName = "SpeakerDatabase", menuName = "Ajedrez Purgatorio/Narrative/Speaker Database")]
public class SpeakerDatabase : ScriptableObject
{
[Tooltip("Lista de speakers con sus portraits")]
[SerializeField] private List<SpeakerEntry> _speakers = new List<SpeakerEntry>();
private Dictionary<string, Sprite> _speakerLookup;
void OnEnable()
{
BuildLookup();
}
/// <summary>
/// Construye el diccionario de búsqueda rápida.
/// </summary>
private void BuildLookup()
{
_speakerLookup = new Dictionary<string, Sprite>();
foreach (var entry in _speakers)
{
if (!string.IsNullOrEmpty(entry.speakerName))
{
_speakerLookup[entry.speakerName.ToLower()] = entry.portrait;
}
}
}
/// <summary>
/// Obtiene el portrait de un speaker por nombre.
/// </summary>
public Sprite GetPortrait(string speakerName)
{
if (_speakerLookup == null)
{
BuildLookup();
}
if (string.IsNullOrEmpty(speakerName))
{
return null;
}
string key = speakerName.ToLower();
if (_speakerLookup.TryGetValue(key, out Sprite portrait))
{
return portrait;
}
Debug.LogWarning($"[SpeakerDatabase] No se encontró portrait para: {speakerName}");
return null;
}
/// <summary>
/// Obtiene el portrait de un speaker sin loguear warning si falta.
/// Usado por sistemas runtime (PortraitRegistry) que comprueban existencia.
/// </summary>
public bool TryGetPortrait(string speakerName, out Sprite portrait)
{
portrait = null;
if (_speakerLookup == null)
{
BuildLookup();
}
if (string.IsNullOrEmpty(speakerName))
{
return false;
}
return _speakerLookup.TryGetValue(speakerName.ToLower(), out portrait) && portrait != null;
}
/// <summary>
/// Verifica si existe un speaker en la base de datos.
/// </summary>
public bool HasSpeaker(string speakerName)
{
if (_speakerLookup == null)
{
BuildLookup();
}
return !string.IsNullOrEmpty(speakerName) &&
_speakerLookup.ContainsKey(speakerName.ToLower());
}
/// <summary>
/// Registra (o actualiza) un portrait en runtime.
/// Usado por PortraitRegistry para speakers de campañas nuevas sin editar el asset.
/// </summary>
public void RegisterPortrait(string speakerName, Sprite portrait)
{
if (string.IsNullOrEmpty(speakerName) || portrait == null)
return;
string key = speakerName.ToLower();
// Mantener la lista serializada sincronizada para el Inspector.
var existing = _speakers.Find(s => !string.IsNullOrEmpty(s.speakerName) && s.speakerName.ToLower() == key);
if (existing != null)
{
existing.portrait = portrait;
}
else
{
_speakers.Add(new SpeakerEntry { speakerName = speakerName, portrait = portrait });
}
_speakerLookup[key] = portrait;
}
}
/// <summary>
/// Entrada individual de speaker con su portrait.
/// </summary>
[Serializable]
public class SpeakerEntry
{
[Tooltip("Nombre del speaker (debe coincidir con el campo 'speaker' en JSON)")]
public string speakerName;
[Tooltip("Imagen del portrait del speaker")]
public Sprite portrait;
}