biography-of-lijie/Assets/GameMain/Scripts/CustomComponent/DialogComponent.cs

667 lines
21 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using DataTable;
using Definition;
using Definition.Enum;
using Event;
using GameFramework.DataTable;
using GameFramework.Event;
using UI;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace CustomComponent
{
[DisallowMultipleComponent]
public class DialogComponent : GameFrameworkComponent
{
[Serializable]
private sealed class SpeakerPortraitBinding
{
[SerializeField] private string _speakerId = string.Empty;
[SerializeField] private List<Sprite> _portraits = new List<Sprite>();
public string SpeakerId => _speakerId;
public List<Sprite> Portraits => _portraits;
}
#region Property
private const int DialogChapterDivisor = 1000;
private const int LineChapterDivisor = 100000000;
private const int LineDialogDivisor = 100000;
[Header("角色立绘配置(拖拽 Sprite")]
[SerializeField] private List<SpeakerPortraitBinding> _speakerPortraitBindings = new List<SpeakerPortraitBinding>();
private readonly Dictionary<int, DRDialog> _dialogMap = new();
private readonly Dictionary<int, List<DRDialogLine>> _dialogLinesMap = new();
private readonly Dictionary<int, int> _dialogFirstLineIdMap = new();
private static readonly HashSet<string> NonPortraitSpeakerIds = new(System.StringComparer.OrdinalIgnoreCase)
{
"Other",
"Subtitle",
"Time"
};
private IDataTable<DRDialog> _dtDialog;
private IDataTable<DRDialogLine> _dtDialogLine;
private DialogFormController _formController;
private DialogFormContext _formContext;
private int _pendingStartDialogId;
private int _activeChapterTitleRequestId;
private int _nextChapterTitleRequestId = 1;
private bool _chapterTitleShownAfterInit;
private int _currentChapterId;
private int _currentLineIndex = -1;
private bool _isInitialized;
private bool _isPlaying;
public bool IsInitialized => _isInitialized;
public bool IsPlaying => _isPlaying;
public int CurrentChapterId => _currentChapterId;
public int CurrentDialogId => _formContext != null ? _formContext.DialogId : 0;
public int CurrentLineId => _formContext != null ? _formContext.CurrentLineId : 0;
public int GetSpeakerPortraitCount(string speakerId)
{
if (string.IsNullOrWhiteSpace(speakerId))
{
return 0;
}
SpeakerPortraitBinding binding = FindSpeakerPortraitBinding(speakerId);
if (binding == null || binding.Portraits == null)
{
return 0;
}
int validCount = 0;
for (int i = 0; i < binding.Portraits.Count; i++)
{
if (binding.Portraits[i] != null)
{
validCount++;
}
}
return validCount;
}
#endregion
private void Start()
{
GameEntry.Event.Subscribe(DialogNextLineRequestEventArgs.EventId, OnDialogNextLineRequest);
GameEntry.Event.Subscribe(DialogSkipRequestEventArgs.EventId, OnDialogSkipRequest);
GameEntry.Event.Subscribe(DialogStopRequestEventArgs.EventId, OnDialogStopRequest);
GameEntry.Event.Subscribe(ChapterTitleCompletedEventArgs.EventId, OnChapterTitleCompleted);
}
private void OnDestroy()
{
GameEntry.Event.Unsubscribe(DialogNextLineRequestEventArgs.EventId, OnDialogNextLineRequest);
GameEntry.Event.Unsubscribe(DialogSkipRequestEventArgs.EventId, OnDialogSkipRequest);
GameEntry.Event.Unsubscribe(DialogStopRequestEventArgs.EventId, OnDialogStopRequest);
GameEntry.Event.Unsubscribe(ChapterTitleCompletedEventArgs.EventId, OnChapterTitleCompleted);
}
public bool Init(int chapterId)
{
if (chapterId <= 0)
{
Log.Warning("Dialog init failed. chapterId must be positive.");
return false;
}
if (!EnsureDataTables())
{
return false;
}
StopDialog();
_dialogMap.Clear();
_dialogLinesMap.Clear();
_dialogFirstLineIdMap.Clear();
DRDialog[] dialogRows = _dtDialog.GetDataRows((a, b) => a.Id.CompareTo(b.Id));
for (int i = 0; i < dialogRows.Length; i++)
{
DRDialog dialogRow = dialogRows[i];
if (dialogRow == null)
{
continue;
}
if (ParseChapterIdFromDialogId(dialogRow.Id) != chapterId)
{
continue;
}
_dialogMap[dialogRow.Id] = dialogRow;
}
if (_dialogMap.Count == 0)
{
Log.Warning("Dialog init failed. No dialog rows found for chapter '{0}'.", chapterId.ToString());
return false;
}
DRDialogLine[] lineRows = _dtDialogLine.GetDataRows((a, b) => a.Id.CompareTo(b.Id));
foreach (var lineRow in lineRows)
{
if (lineRow == null)
{
continue;
}
if (ParseChapterIdFromLineId(lineRow.Id) != chapterId)
{
continue;
}
int dialogId = ParseDialogIdFromLineId(lineRow.Id);
if (!_dialogMap.ContainsKey(dialogId))
{
continue;
}
if (!_dialogLinesMap.TryGetValue(dialogId, out List<DRDialogLine> dialogLines))
{
dialogLines = new List<DRDialogLine>();
_dialogLinesMap.Add(dialogId, dialogLines);
}
dialogLines.Add(lineRow);
}
List<int> invalidDialogIds = new List<int>();
foreach (var dialogPair in _dialogMap)
{
if (!_dialogLinesMap.TryGetValue(dialogPair.Key, out List<DRDialogLine> dialogLines) ||
dialogLines.Count == 0)
{
Log.Warning("Dialog init warning. Dialog '{0}' has no lines and will be ignored.",
dialogPair.Key.ToString());
invalidDialogIds.Add(dialogPair.Key);
continue;
}
dialogLines.Sort((a, b) => a.Id.CompareTo(b.Id));
_dialogFirstLineIdMap[dialogPair.Key] = dialogLines[0].Id;
}
foreach (var id in invalidDialogIds)
{
_dialogMap.Remove(id);
}
if (_dialogMap.Count == 0)
{
Log.Warning("Dialog init failed. No valid dialog remains for chapter '{0}'.", chapterId.ToString());
return false;
}
PreloadChapterPortraitSprites();
EnsureFormController();
_formContext = null;
_currentChapterId = chapterId;
_currentLineIndex = -1;
_pendingStartDialogId = 0;
_activeChapterTitleRequestId = 0;
_chapterTitleShownAfterInit = false;
_isInitialized = true;
_isPlaying = false;
return true;
}
public bool StartDialog(int dialogId)
{
if (!_isInitialized)
{
Log.Warning("Start dialog failed. Dialog component is not initialized.");
return false;
}
if (ParseChapterIdFromDialogId(dialogId) != _currentChapterId)
{
Log.Warning("Start dialog failed. Dialog '{0}' does not belong to chapter '{1}'.", dialogId.ToString(),
_currentChapterId.ToString());
return false;
}
if (!_dialogMap.TryGetValue(dialogId, out DRDialog dialogRow))
{
Log.Warning("Start dialog failed. Dialog '{0}' was not found in current chapter cache.",
dialogId.ToString());
return false;
}
if (!_dialogLinesMap.TryGetValue(dialogId, out List<DRDialogLine> dialogLines) || dialogLines.Count == 0)
{
Log.Warning("Start dialog failed. Dialog '{0}' has no playable lines.", dialogId.ToString());
return false;
}
if (_activeChapterTitleRequestId > 0)
{
_pendingStartDialogId = dialogId;
return true;
}
if (!_chapterTitleShownAfterInit)
{
_chapterTitleShownAfterInit = true;
_pendingStartDialogId = dialogId;
if (TryOpenChapterTitle(dialogRow))
{
return true;
}
_pendingStartDialogId = 0;
}
if (_isPlaying)
{
StopDialog();
}
EnsureFormController();
if (_formContext == null)
{
_formContext = new DialogFormContext();
}
_formContext.ChapterId = _currentChapterId;
_formContext.DialogId = dialogRow.Id;
_formContext.DialogTitle = dialogRow.Title;
_formContext.DialogUIMode = dialogRow.UIMode;
_formContext.PlayingSpeed =
(DialogPlayingSpeed)GameEntry.Setting.GetInt(Constant.Setting.DialogPlayingSpeed);
_formContext.DialogWindowAlpha =
(DialogWindowAlpha)GameEntry.Setting.GetInt(Constant.Setting.DialogWindowAlpha);
_currentLineIndex = 0;
ApplyLineToContext(dialogLines[_currentLineIndex], _currentLineIndex, dialogLines.Count);
_isPlaying = true;
_formController.OpenUI(_formContext);
_formController.OnDialogStarted(_formContext);
_formController.OnDialogLineChanged(_formContext);
return true;
}
public bool NextLine()
{
if (!_isPlaying)
{
Log.Warning("Next line failed. No dialog is playing.");
return false;
}
if (!TryGetCurrentDialogLines(out List<DRDialogLine> dialogLines))
{
StopDialog();
return false;
}
int nextLineIndex = _currentLineIndex + 1;
if (nextLineIndex >= dialogLines.Count)
{
EndDialogInternal();
return true;
}
_currentLineIndex = nextLineIndex;
ApplyLineToContext(dialogLines[_currentLineIndex], _currentLineIndex, dialogLines.Count);
_formController.OnDialogLineChanged(_formContext);
return true;
}
public bool SkipDialog()
{
if (!_isPlaying)
{
Log.Warning("Skip dialog failed. No dialog is playing.");
return false;
}
EndDialogInternal();
return true;
}
public void StopDialog()
{
_pendingStartDialogId = 0;
if (_activeChapterTitleRequestId > 0)
{
_activeChapterTitleRequestId = 0;
if (GameEntry.UI != null)
{
UGuiForm chapterTitleForm = GameEntry.UI.GetUIForm(UIFormId.ChapterTitleForm);
if (chapterTitleForm != null)
{
GameEntry.UI.CloseUIForm(chapterTitleForm.UIForm);
}
}
}
if (!_isPlaying)
{
return;
}
EndDialogInternal();
}
public void ClearRuntimeContext()
{
StopDialog();
_dialogMap.Clear();
_dialogLinesMap.Clear();
_dialogFirstLineIdMap.Clear();
_formContext = null;
_currentChapterId = 0;
_currentLineIndex = -1;
_pendingStartDialogId = 0;
_activeChapterTitleRequestId = 0;
_chapterTitleShownAfterInit = false;
_isInitialized = false;
_isPlaying = false;
}
private bool EnsureDataTables()
{
_dtDialog = GameEntry.DataTable.GetDataTable<DRDialog>();
if (_dtDialog == null)
{
Log.Warning("Dialog init failed. Data table DRDialog is missing.");
return false;
}
_dtDialogLine = GameEntry.DataTable.GetDataTable<DRDialogLine>();
if (_dtDialogLine == null)
{
Log.Warning("Dialog init failed. Data table DRDialogLine is missing.");
return false;
}
return true;
}
private void EnsureFormController()
{
if (_formController == null)
{
_formController = new DialogFormController();
}
}
private bool TryGetCurrentDialogLines(out List<DRDialogLine> dialogLines)
{
dialogLines = null;
if (_formContext == null)
{
Log.Warning("Dialog state invalid. Form context is null.");
return false;
}
if (!_dialogLinesMap.TryGetValue(_formContext.DialogId, out dialogLines))
{
Log.Warning("Dialog state invalid. Dialog lines are missing for dialog '{0}'.",
_formContext.DialogId.ToString());
return false;
}
return true;
}
private void EndDialogInternal()
{
int chapterId = _formContext != null ? _formContext.ChapterId : _currentChapterId;
int dialogId = _formContext != null ? _formContext.DialogId : 0;
int lineId = _formContext != null ? _formContext.CurrentLineId : 0;
_isPlaying = false;
_currentLineIndex = -1;
_formController.OnDialogEnded(_formContext);
_formController.CloseUI(() =>
{
if (dialogId > 0)
{
GameEntry.Event.Fire(this, DialogCompletedEventArgs.Create(chapterId, dialogId, lineId));
}
});
}
private void ApplyLineToContext(DRDialogLine lineRow, int lineIndex, int totalLines)
{
_formContext.CurrentLineId = lineRow.Id;
_formContext.SpeakerId = lineRow.SpeakerId;
_formContext.SpeakerName = lineRow.SpeakerName;
_formContext.Direction = lineRow.Direction;
_formContext.Text = lineRow.Text;
_formContext.Emphasis = lineRow.Emphasis;
_formContext.LineIndex = lineIndex;
_formContext.TotalLines = totalLines;
_formContext.IsLastLine = lineIndex >= totalLines - 1;
}
private void PreloadChapterPortraitSprites()
{
if (GameEntry.SpriteCache == null)
{
return;
}
HashSet<string> speakerIds = new HashSet<string>(System.StringComparer.OrdinalIgnoreCase);
foreach (KeyValuePair<int, List<DRDialogLine>> pair in _dialogLinesMap)
{
List<DRDialogLine> lines = pair.Value;
if (lines == null)
{
continue;
}
for (int i = 0; i < lines.Count; i++)
{
DRDialogLine line = lines[i];
if (line == null || !IsHumanSpeakerId(line.SpeakerId))
{
continue;
}
speakerIds.Add(line.SpeakerId);
}
}
if (speakerIds.Count == 0)
{
return;
}
foreach (string speakerId in speakerIds)
{
RegisterSpeakerPortraitsToCache(speakerId);
}
}
private void RegisterSpeakerPortraitsToCache(string speakerId)
{
SpeakerPortraitBinding binding = FindSpeakerPortraitBinding(speakerId);
if (binding == null || binding.Portraits == null || binding.Portraits.Count == 0)
{
Log.Warning("Portrait preload skipped. Missing speaker portrait binding. speakerId='{0}'.", speakerId);
return;
}
int portraitIndex = 1;
for (int i = 0; i < binding.Portraits.Count; i++)
{
Sprite portrait = binding.Portraits[i];
if (portrait == null)
{
continue;
}
string cacheKey = $"Characters/{speakerId}-{portraitIndex}.png";
GameEntry.SpriteCache.RegisterSprite(cacheKey, portrait, true);
portraitIndex++;
}
}
private SpeakerPortraitBinding FindSpeakerPortraitBinding(string speakerId)
{
if (string.IsNullOrWhiteSpace(speakerId) || _speakerPortraitBindings == null)
{
return null;
}
for (int i = 0; i < _speakerPortraitBindings.Count; i++)
{
SpeakerPortraitBinding binding = _speakerPortraitBindings[i];
if (binding == null || string.IsNullOrWhiteSpace(binding.SpeakerId))
{
continue;
}
if (string.Equals(binding.SpeakerId.Trim(), speakerId.Trim(), StringComparison.OrdinalIgnoreCase))
{
return binding;
}
}
return null;
}
private static bool IsHumanSpeakerId(string speakerId)
{
if (string.IsNullOrWhiteSpace(speakerId))
{
return false;
}
return !NonPortraitSpeakerIds.Contains(speakerId);
}
private bool TryOpenChapterTitle(DRDialog dialogRow)
{
if (GameEntry.UI == null)
{
return false;
}
int requestId = _nextChapterTitleRequestId++;
string subtitle = dialogRow == null || string.IsNullOrWhiteSpace(dialogRow.Title)
? string.Empty
: dialogRow.Title.Trim();
ChapterTitleFormContext context = new ChapterTitleFormContext
{
ChapterId = _currentChapterId,
RequestId = requestId,
Title = BuildChapterTitle(_currentChapterId),
Subtitle = subtitle
};
int? formSerialId = GameEntry.UI.OpenUIForm(UIFormId.ChapterTitleForm, context);
if (!formSerialId.HasValue)
{
Log.Warning("Open chapter title form failed. chapterId='{0}'.", _currentChapterId.ToString());
return false;
}
_activeChapterTitleRequestId = requestId;
return true;
}
private static string BuildChapterTitle(int chapterId)
{
return chapterId > 0 ? $"第{chapterId}章" : string.Empty;
}
private static int ParseChapterIdFromDialogId(int dialogId)
{
return dialogId / DialogChapterDivisor;
}
private static int ParseChapterIdFromLineId(int lineId)
{
return lineId / LineChapterDivisor;
}
private static int ParseDialogIdFromLineId(int lineId)
{
return lineId / LineDialogDivisor;
}
#region Event Handlers
private void OnDialogNextLineRequest(object sender, GameEventArgs e)
{
if (!(e is DialogNextLineRequestEventArgs))
{
return;
}
NextLine();
}
private void OnDialogSkipRequest(object sender, GameEventArgs e)
{
if (!(e is DialogSkipRequestEventArgs))
{
return;
}
SkipDialog();
}
private void OnDialogStopRequest(object sender, GameEventArgs e)
{
if (!(e is DialogStopRequestEventArgs))
{
return;
}
StopDialog();
}
private void OnChapterTitleCompleted(object sender, GameEventArgs e)
{
if (!(e is ChapterTitleCompletedEventArgs args))
{
return;
}
if (args.RequestId <= 0 || args.RequestId != _activeChapterTitleRequestId)
{
return;
}
_activeChapterTitleRequestId = 0;
int pendingDialogId = _pendingStartDialogId;
_pendingStartDialogId = 0;
if (pendingDialogId <= 0 || !_isInitialized)
{
return;
}
StartDialog(pendingDialogId);
}
#endregion
}
}