修复已知功能
1. 添加章节开始 UI 介绍 2. 清理不使用的资源 3. 补充 AIChat 入口 UI 4. 接上所有的正常对话流程
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DataTable;
|
||||
using Definition;
|
||||
@@ -14,22 +15,45 @@ 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;
|
||||
@@ -46,6 +70,31 @@ namespace CustomComponent
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -54,6 +103,7 @@ namespace CustomComponent
|
||||
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()
|
||||
@@ -61,6 +111,7 @@ namespace CustomComponent
|
||||
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)
|
||||
@@ -159,10 +210,14 @@ namespace CustomComponent
|
||||
return false;
|
||||
}
|
||||
|
||||
PreloadChapterPortraitSprites();
|
||||
EnsureFormController();
|
||||
_formContext = null;
|
||||
_currentChapterId = chapterId;
|
||||
_currentLineIndex = -1;
|
||||
_pendingStartDialogId = 0;
|
||||
_activeChapterTitleRequestId = 0;
|
||||
_chapterTitleShownAfterInit = false;
|
||||
_isInitialized = true;
|
||||
_isPlaying = false;
|
||||
return true;
|
||||
@@ -196,6 +251,24 @@ namespace CustomComponent
|
||||
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();
|
||||
@@ -267,6 +340,21 @@ namespace CustomComponent
|
||||
|
||||
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;
|
||||
@@ -286,6 +374,9 @@ namespace CustomComponent
|
||||
_formContext = null;
|
||||
_currentChapterId = 0;
|
||||
_currentLineIndex = -1;
|
||||
_pendingStartDialogId = 0;
|
||||
_activeChapterTitleRequestId = 0;
|
||||
_chapterTitleShownAfterInit = false;
|
||||
_isInitialized = false;
|
||||
_isPlaying = false;
|
||||
}
|
||||
@@ -368,6 +459,138 @@ namespace CustomComponent
|
||||
_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;
|
||||
@@ -415,6 +638,29 @@ namespace CustomComponent
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,59 +14,192 @@ namespace CustomComponent
|
||||
[SerializeField] private Vector2 _defaultPivot = new(0.5f, 0.5f);
|
||||
|
||||
private Dictionary<string, Sprite> _spriteCache;
|
||||
private Dictionary<string, List<Action<Sprite>>> _pendingCallbacks;
|
||||
private HashSet<string> _loadingSpriteAssets;
|
||||
private ResourceComponent _resource;
|
||||
|
||||
void Start()
|
||||
{
|
||||
_spriteCache = new Dictionary<string, Sprite>();
|
||||
_resource = GameEntry.Resource;
|
||||
EnsureRuntimeCaches();
|
||||
}
|
||||
|
||||
private void EnsureRuntimeCaches()
|
||||
{
|
||||
_spriteCache ??= new Dictionary<string, Sprite>();
|
||||
_pendingCallbacks ??= new Dictionary<string, List<Action<Sprite>>>();
|
||||
_loadingSpriteAssets ??= new HashSet<string>();
|
||||
_resource ??= GameEntry.Resource;
|
||||
}
|
||||
|
||||
public bool TryGetCachedSprite(string assetName, out Sprite sprite)
|
||||
{
|
||||
EnsureRuntimeCaches();
|
||||
if (string.IsNullOrWhiteSpace(assetName))
|
||||
{
|
||||
sprite = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return _spriteCache.TryGetValue(assetName, out sprite);
|
||||
}
|
||||
|
||||
public void GetSprite(string assetName, Action<Sprite> callback)
|
||||
{
|
||||
EnsureRuntimeCaches();
|
||||
if (string.IsNullOrWhiteSpace(assetName))
|
||||
{
|
||||
callback?.Invoke(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_spriteCache.TryGetValue(assetName, out var sprite))
|
||||
{
|
||||
callback?.Invoke(sprite);
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
if (callback != null)
|
||||
{
|
||||
_resource.LoadAsset
|
||||
(
|
||||
AssetUtility.GetUISpriteAsset(assetName),
|
||||
Constant.AssetPriority.UIFormAsset,
|
||||
new LoadAssetCallbacks(
|
||||
(resourcePath, asset, duration, userData) =>
|
||||
AddPendingCallback(assetName, callback);
|
||||
}
|
||||
|
||||
if (_loadingSpriteAssets.Contains(assetName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_loadingSpriteAssets.Add(assetName);
|
||||
StartLoadSprite(assetName);
|
||||
}
|
||||
|
||||
public void PreloadSprite(string assetName)
|
||||
{
|
||||
EnsureRuntimeCaches();
|
||||
if (string.IsNullOrWhiteSpace(assetName) || _spriteCache.ContainsKey(assetName) ||
|
||||
_loadingSpriteAssets.Contains(assetName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_loadingSpriteAssets.Add(assetName);
|
||||
StartLoadSprite(assetName);
|
||||
}
|
||||
|
||||
public void PreloadSprites(IEnumerable<string> assetNames)
|
||||
{
|
||||
EnsureRuntimeCaches();
|
||||
if (assetNames == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (string assetName in assetNames)
|
||||
{
|
||||
PreloadSprite(assetName);
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterSprite(string assetName, Sprite sprite, bool overwrite = true)
|
||||
{
|
||||
EnsureRuntimeCaches();
|
||||
if (string.IsNullOrWhiteSpace(assetName) || sprite == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!overwrite && _spriteCache.ContainsKey(assetName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_spriteCache[assetName] = sprite;
|
||||
InvokeAndClearPendingCallbacks(assetName, sprite);
|
||||
_loadingSpriteAssets.Remove(assetName);
|
||||
}
|
||||
|
||||
private void StartLoadSprite(string assetName)
|
||||
{
|
||||
EnsureRuntimeCaches();
|
||||
if (_resource == null)
|
||||
{
|
||||
InvokeAndClearPendingCallbacks(assetName, null);
|
||||
_loadingSpriteAssets.Remove(assetName);
|
||||
return;
|
||||
}
|
||||
|
||||
_resource.LoadAsset
|
||||
(
|
||||
AssetUtility.GetUISpriteAsset(assetName),
|
||||
Constant.AssetPriority.UIFormAsset,
|
||||
new LoadAssetCallbacks(
|
||||
(resourcePath, asset, duration, userData) =>
|
||||
{
|
||||
Texture2D texture = asset as Texture2D;
|
||||
Sprite loadedSprite = null;
|
||||
if (texture != null)
|
||||
{
|
||||
Log.Debug(resourcePath);
|
||||
Texture2D texture = asset as Texture2D;
|
||||
if (texture != null)
|
||||
{
|
||||
Sprite newSprite = Sprite.Create(
|
||||
texture,
|
||||
new Rect(0, 0, texture.width, texture.height),
|
||||
_defaultPivot,
|
||||
_pixelsPerUnit);
|
||||
_spriteCache.Add(assetName, newSprite);
|
||||
callback?.Invoke(newSprite);
|
||||
}
|
||||
},
|
||||
(resourcePath, status, errorMessage, userData) =>
|
||||
{
|
||||
Log.Error("Can not load icon '{0}' from '{1}' with error message '{2}'.",
|
||||
assetName,
|
||||
resourcePath,
|
||||
errorMessage);
|
||||
loadedSprite = Sprite.Create(
|
||||
texture,
|
||||
new Rect(0, 0, texture.width, texture.height),
|
||||
_defaultPivot,
|
||||
_pixelsPerUnit);
|
||||
_spriteCache[assetName] = loadedSprite;
|
||||
}
|
||||
)
|
||||
);
|
||||
else
|
||||
{
|
||||
Log.Warning("Load sprite succeeded but asset is not Texture2D. AssetName='{0}', Path='{1}'.",
|
||||
assetName, resourcePath);
|
||||
}
|
||||
|
||||
InvokeAndClearPendingCallbacks(assetName, loadedSprite);
|
||||
_loadingSpriteAssets.Remove(assetName);
|
||||
},
|
||||
(resourcePath, status, errorMessage, userData) =>
|
||||
{
|
||||
Log.Error("Can not load icon '{0}' from '{1}' with error message '{2}'.",
|
||||
assetName,
|
||||
resourcePath,
|
||||
errorMessage);
|
||||
InvokeAndClearPendingCallbacks(assetName, null);
|
||||
_loadingSpriteAssets.Remove(assetName);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private void AddPendingCallback(string assetName, Action<Sprite> callback)
|
||||
{
|
||||
EnsureRuntimeCaches();
|
||||
if (!_pendingCallbacks.TryGetValue(assetName, out List<Action<Sprite>> callbackList))
|
||||
{
|
||||
callbackList = new List<Action<Sprite>>();
|
||||
_pendingCallbacks.Add(assetName, callbackList);
|
||||
}
|
||||
|
||||
callbackList.Add(callback);
|
||||
}
|
||||
|
||||
private void InvokeAndClearPendingCallbacks(string assetName, Sprite sprite)
|
||||
{
|
||||
EnsureRuntimeCaches();
|
||||
if (!_pendingCallbacks.TryGetValue(assetName, out List<Action<Sprite>> callbackList))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_pendingCallbacks.Remove(assetName);
|
||||
for (int i = 0; i < callbackList.Count; i++)
|
||||
{
|
||||
callbackList[i]?.Invoke(sprite);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
_spriteCache.Clear();
|
||||
_spriteCache?.Clear();
|
||||
_pendingCallbacks?.Clear();
|
||||
_loadingSpriteAssets?.Clear();
|
||||
_resource = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user