规范对话数据
This commit is contained in:
@@ -346,12 +346,13 @@ namespace CustomComponent
|
||||
_currentLineIndex = -1;
|
||||
|
||||
_formController.OnDialogEnded(_formContext);
|
||||
_formController.CloseUI();
|
||||
|
||||
if (dialogId > 0)
|
||||
_formController.CloseUI(() =>
|
||||
{
|
||||
GameEntry.Event.Fire(this, DialogCompletedEventArgs.Create(chapterId, dialogId, lineId));
|
||||
}
|
||||
if (dialogId > 0)
|
||||
{
|
||||
GameEntry.Event.Fire(this, DialogCompletedEventArgs.Create(chapterId, dialogId, lineId));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void ApplyLineToContext(DRDialogLine lineRow, int lineIndex, int totalLines)
|
||||
@@ -359,7 +360,6 @@ namespace CustomComponent
|
||||
_formContext.CurrentLineId = lineRow.Id;
|
||||
_formContext.SpeakerId = lineRow.SpeakerId;
|
||||
_formContext.SpeakerName = lineRow.SpeakerName;
|
||||
_formContext.Expression = lineRow.Expression;
|
||||
_formContext.Direction = lineRow.Direction;
|
||||
_formContext.Text = lineRow.Text;
|
||||
_formContext.Emphasis = lineRow.Emphasis;
|
||||
|
||||
+2
@@ -9,6 +9,8 @@ namespace CustomComponent
|
||||
|
||||
public override string ActionName => "ChangeBackground";
|
||||
|
||||
public override bool RequiresCompletion => true;
|
||||
|
||||
public override void Execute(StoryDirectorComponent director)
|
||||
{
|
||||
if (director == null)
|
||||
|
||||
@@ -19,6 +19,8 @@ namespace CustomComponent
|
||||
|
||||
public virtual string ActionName => GetType().Name;
|
||||
|
||||
public virtual bool RequiresCompletion => false;
|
||||
|
||||
public bool IsMatch(StoryTriggerType triggerType, int triggerId)
|
||||
{
|
||||
if (!_enabled || _triggerType != triggerType)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Definition.Enum;
|
||||
using Event;
|
||||
@@ -20,21 +21,42 @@ namespace CustomComponent
|
||||
[SerializeField] private string _backgroundAssetNamePrefix = string.Empty;
|
||||
|
||||
private readonly HashSet<string> _consumedDirectiveTokens = new HashSet<string>();
|
||||
private readonly HashSet<string> _queuedDirectiveTokens = new HashSet<string>();
|
||||
private readonly Queue<PendingDirectiveInvocation> _pendingDirectives =
|
||||
new Queue<PendingDirectiveInvocation>();
|
||||
|
||||
private BgFormController _bgFormController;
|
||||
private int _bgRequestVersion;
|
||||
private bool _isExecuting;
|
||||
private Coroutine _directiveExecutionCoroutine;
|
||||
private int _nextBackgroundRequestId = 1;
|
||||
private int _lastBackgroundRequestId;
|
||||
private int _completedBackgroundRequestId;
|
||||
|
||||
private struct PendingDirectiveInvocation
|
||||
{
|
||||
public StoryDirectiveAsset Directive;
|
||||
public StoryTriggerType TriggerType;
|
||||
public int TriggerId;
|
||||
public string DirectiveToken;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
GameEntry.Event.Subscribe(DialogCompletedEventArgs.EventId, OnDialogCompleted);
|
||||
GameEntry.Event.Subscribe(CombineCompletedEventArgs.EventId, OnCombineCompleted);
|
||||
GameEntry.Event.Subscribe(BgTransitionCompletedEventArgs.EventId, OnBgTransitionCompleted);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
GameEntry.Event.Unsubscribe(DialogCompletedEventArgs.EventId, OnDialogCompleted);
|
||||
GameEntry.Event.Unsubscribe(CombineCompletedEventArgs.EventId, OnCombineCompleted);
|
||||
GameEntry.Event.Unsubscribe(BgTransitionCompletedEventArgs.EventId, OnBgTransitionCompleted);
|
||||
|
||||
if (_directiveExecutionCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_directiveExecutionCoroutine);
|
||||
_directiveExecutionCoroutine = null;
|
||||
}
|
||||
|
||||
_bgFormController?.CloseUI();
|
||||
_bgFormController = null;
|
||||
@@ -43,6 +65,8 @@ namespace CustomComponent
|
||||
public void ResetConsumedDirectives()
|
||||
{
|
||||
_consumedDirectiveTokens.Clear();
|
||||
_queuedDirectiveTokens.Clear();
|
||||
_pendingDirectives.Clear();
|
||||
}
|
||||
|
||||
private void OnDialogCompleted(object sender, GameFramework.Event.GameEventArgs e)
|
||||
@@ -67,40 +91,43 @@ namespace CustomComponent
|
||||
|
||||
private void ExecuteDirectives(StoryTriggerType triggerType, int triggerId)
|
||||
{
|
||||
if (_isExecuting)
|
||||
bool hasEnqueuedDirective = false;
|
||||
for (int i = 0; i < _directives.Count; i++)
|
||||
{
|
||||
StoryDirectiveAsset directive = _directives[i];
|
||||
if (directive == null || !directive.IsMatch(triggerType, triggerId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string directiveToken = BuildDirectiveToken(directive, i);
|
||||
if (!_allowRepeatTrigger &&
|
||||
(_consumedDirectiveTokens.Contains(directiveToken) || _queuedDirectiveTokens.Contains(directiveToken)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_pendingDirectives.Enqueue(new PendingDirectiveInvocation
|
||||
{
|
||||
Directive = directive,
|
||||
TriggerType = triggerType,
|
||||
TriggerId = triggerId,
|
||||
DirectiveToken = directiveToken
|
||||
});
|
||||
hasEnqueuedDirective = true;
|
||||
|
||||
if (!_allowRepeatTrigger)
|
||||
{
|
||||
_queuedDirectiveTokens.Add(directiveToken);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasEnqueuedDirective || _directiveExecutionCoroutine != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isExecuting = true;
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < _directives.Count; i++)
|
||||
{
|
||||
StoryDirectiveAsset directive = _directives[i];
|
||||
if (directive == null || !directive.IsMatch(triggerType, triggerId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string directiveToken = BuildDirectiveToken(directive, i);
|
||||
if (!_allowRepeatTrigger && _consumedDirectiveTokens.Contains(directiveToken))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ExecuteDirective(directive, triggerType, triggerId);
|
||||
|
||||
if (!_allowRepeatTrigger)
|
||||
{
|
||||
_consumedDirectiveTokens.Add(directiveToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isExecuting = false;
|
||||
}
|
||||
_directiveExecutionCoroutine = StartCoroutine(ProcessDirectiveQueue());
|
||||
}
|
||||
|
||||
private static string BuildDirectiveToken(StoryDirectiveAsset directive, int directiveIndex)
|
||||
@@ -119,6 +146,60 @@ namespace CustomComponent
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator ProcessDirectiveQueue()
|
||||
{
|
||||
while (_pendingDirectives.Count > 0)
|
||||
{
|
||||
PendingDirectiveInvocation invocation = _pendingDirectives.Dequeue();
|
||||
if (!_allowRepeatTrigger)
|
||||
{
|
||||
_queuedDirectiveTokens.Remove(invocation.DirectiveToken);
|
||||
}
|
||||
|
||||
ExecuteDirective(invocation.Directive, invocation.TriggerType, invocation.TriggerId);
|
||||
|
||||
if (invocation.Directive.RequiresCompletion)
|
||||
{
|
||||
yield return WaitForDirectiveCompletion(invocation.Directive);
|
||||
}
|
||||
|
||||
if (!_allowRepeatTrigger)
|
||||
{
|
||||
_consumedDirectiveTokens.Add(invocation.DirectiveToken);
|
||||
}
|
||||
}
|
||||
|
||||
_directiveExecutionCoroutine = null;
|
||||
}
|
||||
|
||||
private IEnumerator WaitForDirectiveCompletion(StoryDirectiveAsset directive)
|
||||
{
|
||||
if (!(directive is StoryChangeBackgroundDirectiveAsset))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
int requestId = _lastBackgroundRequestId;
|
||||
if (requestId <= 0)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
const float timeoutSeconds = 10f;
|
||||
float elapsed = 0f;
|
||||
while (_completedBackgroundRequestId < requestId)
|
||||
{
|
||||
elapsed += Time.unscaledDeltaTime;
|
||||
if (elapsed >= timeoutSeconds)
|
||||
{
|
||||
Log.Warning("StoryDirector wait background transition timeout. requestId={0}", requestId.ToString());
|
||||
break;
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void ExecuteStartDialog(int dialogId)
|
||||
{
|
||||
if (dialogId <= 0)
|
||||
@@ -155,6 +236,8 @@ namespace CustomComponent
|
||||
|
||||
public void ExecuteChangeBackground(string backgroundAssetName)
|
||||
{
|
||||
_lastBackgroundRequestId = 0;
|
||||
|
||||
string assetName = string.IsNullOrWhiteSpace(backgroundAssetName)
|
||||
? string.Empty
|
||||
: backgroundAssetName.Trim();
|
||||
@@ -164,39 +247,24 @@ namespace CustomComponent
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameEntry.SpriteCache == null)
|
||||
{
|
||||
Log.Warning("StoryDirector change background failed. SpriteCache component is missing.");
|
||||
return;
|
||||
}
|
||||
|
||||
int requestVersion = ++_bgRequestVersion;
|
||||
GameEntry.SpriteCache.GetSprite(_backgroundAssetNamePrefix + assetName,
|
||||
sprite => { OnBackgroundLoaded(requestVersion, assetName, sprite); });
|
||||
}
|
||||
|
||||
private void OnBackgroundLoaded(int requestVersion, string assetName, Sprite sprite)
|
||||
{
|
||||
if (requestVersion != _bgRequestVersion)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (sprite == null)
|
||||
{
|
||||
Log.Warning("StoryDirector change background failed. Loaded sprite is null for '{0}'.", assetName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_bgFormController == null)
|
||||
{
|
||||
_bgFormController = new BgFormController();
|
||||
}
|
||||
|
||||
_bgFormController.OpenUI(new BgFormContext
|
||||
int requestId = _nextBackgroundRequestId++;
|
||||
_lastBackgroundRequestId = requestId;
|
||||
|
||||
int? formSerialId = _bgFormController.OpenUI(new BgFormContext
|
||||
{
|
||||
Sprite = sprite
|
||||
BackgroundAssetName = _backgroundAssetNamePrefix + assetName,
|
||||
TransitionRequestId = requestId
|
||||
});
|
||||
if (!formSerialId.HasValue)
|
||||
{
|
||||
Log.Warning("StoryDirector change background failed. BgForm open returned null.");
|
||||
_completedBackgroundRequestId = Mathf.Max(_completedBackgroundRequestId, requestId);
|
||||
}
|
||||
}
|
||||
|
||||
public void ExecuteEndChapter(int chapterId)
|
||||
@@ -208,6 +276,21 @@ namespace CustomComponent
|
||||
GameEntry.Event.Fire(this, StoryChapterEndedEventArgs.Create(finalChapterId));
|
||||
}
|
||||
|
||||
private void OnBgTransitionCompleted(object sender, GameFramework.Event.GameEventArgs e)
|
||||
{
|
||||
if (!(e is BgTransitionCompletedEventArgs args))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.RequestId <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_completedBackgroundRequestId = Mathf.Max(_completedBackgroundRequestId, args.RequestId);
|
||||
}
|
||||
|
||||
private static CombineFormContext BuildCombineFormContext(StoryCombineConfig config)
|
||||
{
|
||||
if (config != null && config.Slots != null && config.Slots.Count > 0)
|
||||
@@ -355,4 +438,4 @@ namespace CustomComponent
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,11 +18,6 @@ namespace DataTable
|
||||
/// </summary>
|
||||
public string SpeakerId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取说话人表情。
|
||||
/// </summary>
|
||||
public ExpressionType Expression { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取说话人显示名。
|
||||
/// </summary>
|
||||
@@ -52,7 +47,6 @@ namespace DataTable
|
||||
m_Id = int.Parse(fields[index++]);
|
||||
index++;
|
||||
SpeakerId = fields[index++];
|
||||
Expression = EnumUtility<ExpressionType>.Get(fields[index++]);
|
||||
SpeakerName = fields[index++];
|
||||
Direction = int.Parse(fields[index++]);
|
||||
Text = fields[index++];
|
||||
@@ -61,4 +55,4 @@ namespace DataTable
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,6 @@ namespace Definition.Enum
|
||||
None,
|
||||
Normal,
|
||||
Shock,
|
||||
Happy,
|
||||
Angry,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b9a16ac60e72fd3429a6704ac84f277b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,31 @@
|
||||
using GameFramework;
|
||||
using GameFramework.Event;
|
||||
|
||||
namespace Event
|
||||
{
|
||||
public class BgTransitionCompletedEventArgs : GameEventArgs
|
||||
{
|
||||
public static readonly int EventId = typeof(BgTransitionCompletedEventArgs).GetHashCode();
|
||||
|
||||
public override int Id => EventId;
|
||||
|
||||
public int RequestId { get; private set; }
|
||||
|
||||
public BgTransitionCompletedEventArgs()
|
||||
{
|
||||
RequestId = 0;
|
||||
}
|
||||
|
||||
public static BgTransitionCompletedEventArgs Create(int requestId)
|
||||
{
|
||||
var args = ReferencePool.Acquire<BgTransitionCompletedEventArgs>();
|
||||
args.RequestId = requestId;
|
||||
return args;
|
||||
}
|
||||
|
||||
public override void Clear()
|
||||
{
|
||||
RequestId = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 93ab6695ce06a7046b0cae37b46a813f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -7,5 +7,9 @@ namespace UI
|
||||
public class BgFormContext : UIContext
|
||||
{
|
||||
public Sprite Sprite;
|
||||
|
||||
public string BackgroundAssetName = string.Empty;
|
||||
|
||||
public int TransitionRequestId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ namespace UI
|
||||
public int CurrentLineId = 0;
|
||||
public string SpeakerId = string.Empty;
|
||||
public string SpeakerName = string.Empty;
|
||||
public ExpressionType Expression = ExpressionType.None;
|
||||
public int Direction = 0;
|
||||
public string Text = string.Empty;
|
||||
public EmphasisType Emphasis = EmphasisType.None;
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace UI
|
||||
private DialogFormBase _dialogForm;
|
||||
private int? _formSerialId;
|
||||
private bool _pendingRefresh;
|
||||
private Action _pendingCloseCompleted;
|
||||
|
||||
public DialogFormController()
|
||||
{
|
||||
@@ -31,7 +32,8 @@ namespace UI
|
||||
UIFormId targetFormId = MapDialogFormId(context.DialogUIMode);
|
||||
if (targetFormId == UIFormId.Undefined)
|
||||
{
|
||||
Log.Warning("DialogFormController open failed. Unsupported mode '{0}'.", context.DialogUIMode.ToString());
|
||||
Log.Warning("DialogFormController open failed. Unsupported mode '{0}'.",
|
||||
context.DialogUIMode.ToString());
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -48,8 +50,14 @@ namespace UI
|
||||
}
|
||||
|
||||
public void CloseUI()
|
||||
{
|
||||
CloseUI(onClosed: null);
|
||||
}
|
||||
|
||||
public void CloseUI(Action onClosed = null)
|
||||
{
|
||||
_pendingRefresh = false;
|
||||
_pendingCloseCompleted = onClosed;
|
||||
|
||||
if (_formSerialId.HasValue)
|
||||
{
|
||||
@@ -61,6 +69,8 @@ namespace UI
|
||||
{
|
||||
_dialogForm.Close();
|
||||
}
|
||||
|
||||
InvokeCloseCompletedIfNeeded();
|
||||
}
|
||||
|
||||
public void OnDialogStarted(DialogFormContext context)
|
||||
@@ -163,6 +173,19 @@ namespace UI
|
||||
_dialogForm = null;
|
||||
_formSerialId = null;
|
||||
_pendingRefresh = false;
|
||||
InvokeCloseCompletedIfNeeded();
|
||||
}
|
||||
|
||||
private void InvokeCloseCompletedIfNeeded()
|
||||
{
|
||||
if (_pendingCloseCompleted == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Action callback = _pendingCloseCompleted;
|
||||
_pendingCloseCompleted = null;
|
||||
callback.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
using DG.Tweening;
|
||||
using Event;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityGameFramework.Runtime;
|
||||
@@ -7,8 +9,15 @@ namespace UI
|
||||
public class BgForm : UGuiForm
|
||||
{
|
||||
[SerializeField] private Image _bgImage;
|
||||
[SerializeField] private Image _fadeMaskImage;
|
||||
[SerializeField] private float _fadeToBlackDuration = 0.2f;
|
||||
[SerializeField] private float _fadeFromBlackDuration = 0.25f;
|
||||
[SerializeField] private Ease _fadeEase = Ease.Linear;
|
||||
|
||||
private Sprite _currentSprite;
|
||||
private string _currentAssetName = string.Empty;
|
||||
private Tween _fadeTween;
|
||||
private int _refreshVersion;
|
||||
|
||||
protected override void OnOpen(object userData)
|
||||
{
|
||||
@@ -25,18 +34,243 @@ namespace UI
|
||||
|
||||
protected override void OnClose(bool isShutdown, object userData)
|
||||
{
|
||||
_refreshVersion++;
|
||||
KillFadeTween();
|
||||
SetFadeMaskAlpha(0f);
|
||||
|
||||
_currentSprite = null;
|
||||
_bgImage.sprite = null;
|
||||
_currentAssetName = string.Empty;
|
||||
if (_bgImage != null)
|
||||
{
|
||||
_bgImage.sprite = null;
|
||||
}
|
||||
|
||||
base.OnClose(isShutdown, userData);
|
||||
}
|
||||
|
||||
public void RefreshUI(BgFormContext context)
|
||||
{
|
||||
if (this._currentSprite == context.Sprite) return;
|
||||
if (context == null)
|
||||
{
|
||||
Log.Warning("BgForm refresh failed. context is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
this._currentSprite = context.Sprite;
|
||||
_bgImage.sprite = context.Sprite;
|
||||
int requestId = context.TransitionRequestId;
|
||||
|
||||
if (context.Sprite != null)
|
||||
{
|
||||
RefreshWithSprite(context.Sprite, context.BackgroundAssetName, requestId);
|
||||
return;
|
||||
}
|
||||
|
||||
string targetAssetName = string.IsNullOrWhiteSpace(context.BackgroundAssetName)
|
||||
? string.Empty
|
||||
: context.BackgroundAssetName.Trim();
|
||||
if (string.IsNullOrEmpty(targetAssetName))
|
||||
{
|
||||
PublishTransitionCompleted(requestId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_currentSprite != null && string.Equals(_currentAssetName, targetAssetName))
|
||||
{
|
||||
PublishTransitionCompleted(requestId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameEntry.SpriteCache == null)
|
||||
{
|
||||
Log.Warning("BgForm refresh failed. SpriteCache is missing.");
|
||||
PublishTransitionCompleted(requestId);
|
||||
return;
|
||||
}
|
||||
|
||||
int version = ++_refreshVersion;
|
||||
FadeToBlack(() =>
|
||||
{
|
||||
if (version != _refreshVersion)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameEntry.SpriteCache.GetSprite(targetAssetName, sprite =>
|
||||
{
|
||||
if (version != _refreshVersion)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (sprite != null)
|
||||
{
|
||||
_currentSprite = sprite;
|
||||
_currentAssetName = targetAssetName;
|
||||
if (_bgImage != null)
|
||||
{
|
||||
_bgImage.sprite = sprite;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning("BgForm refresh failed. Can not load sprite '{0}'.", targetAssetName);
|
||||
}
|
||||
|
||||
FadeFromBlack(() => { PublishTransitionCompleted(requestId); });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void RefreshWithSprite(Sprite sprite, string backgroundAssetName, int requestId)
|
||||
{
|
||||
if (sprite == null)
|
||||
{
|
||||
PublishTransitionCompleted(requestId);
|
||||
return;
|
||||
}
|
||||
|
||||
bool isSameSprite = _currentSprite == sprite;
|
||||
bool hasSameAssetName = !string.IsNullOrEmpty(backgroundAssetName) &&
|
||||
string.Equals(_currentAssetName, backgroundAssetName);
|
||||
if (isSameSprite && (string.IsNullOrEmpty(backgroundAssetName) || hasSameAssetName))
|
||||
{
|
||||
PublishTransitionCompleted(requestId);
|
||||
return;
|
||||
}
|
||||
|
||||
int version = ++_refreshVersion;
|
||||
FadeToBlack(() =>
|
||||
{
|
||||
if (version != _refreshVersion)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_currentSprite = sprite;
|
||||
_currentAssetName = string.IsNullOrWhiteSpace(backgroundAssetName)
|
||||
? _currentAssetName
|
||||
: backgroundAssetName.Trim();
|
||||
if (_bgImage != null)
|
||||
{
|
||||
_bgImage.sprite = sprite;
|
||||
}
|
||||
FadeFromBlack(() => { PublishTransitionCompleted(requestId); });
|
||||
});
|
||||
}
|
||||
|
||||
private void FadeToBlack(TweenCallback onComplete)
|
||||
{
|
||||
EnsureFadeMask();
|
||||
if (_fadeMaskImage == null)
|
||||
{
|
||||
onComplete?.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
_fadeMaskImage.transform.SetAsLastSibling();
|
||||
KillFadeTween();
|
||||
|
||||
if (_fadeToBlackDuration <= 0f)
|
||||
{
|
||||
SetFadeMaskAlpha(1f);
|
||||
onComplete?.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
_fadeTween = _fadeMaskImage.DOFade(1f, _fadeToBlackDuration)
|
||||
.SetEase(_fadeEase)
|
||||
.OnComplete(onComplete);
|
||||
}
|
||||
|
||||
private void FadeFromBlack(TweenCallback onComplete = null)
|
||||
{
|
||||
EnsureFadeMask();
|
||||
if (_fadeMaskImage == null)
|
||||
{
|
||||
onComplete?.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
_fadeMaskImage.transform.SetAsLastSibling();
|
||||
KillFadeTween();
|
||||
|
||||
if (_fadeFromBlackDuration <= 0f)
|
||||
{
|
||||
SetFadeMaskAlpha(0f);
|
||||
onComplete?.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
_fadeTween = _fadeMaskImage.DOFade(0f, _fadeFromBlackDuration)
|
||||
.SetEase(_fadeEase)
|
||||
.OnComplete(onComplete);
|
||||
}
|
||||
|
||||
private void EnsureFadeMask()
|
||||
{
|
||||
if (_fadeMaskImage != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RectTransform parentRect = transform as RectTransform;
|
||||
if (parentRect == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject maskObject = new GameObject("BgFadeMask", typeof(RectTransform), typeof(Image));
|
||||
maskObject.transform.SetParent(parentRect, false);
|
||||
RectTransform maskRect = maskObject.GetComponent<RectTransform>();
|
||||
if (maskRect != null)
|
||||
{
|
||||
maskRect.anchorMin = Vector2.zero;
|
||||
maskRect.anchorMax = Vector2.one;
|
||||
maskRect.offsetMin = Vector2.zero;
|
||||
maskRect.offsetMax = Vector2.zero;
|
||||
}
|
||||
|
||||
_fadeMaskImage = maskObject.GetComponent<Image>();
|
||||
if (_fadeMaskImage != null)
|
||||
{
|
||||
_fadeMaskImage.raycastTarget = false;
|
||||
_fadeMaskImage.color = Color.clear;
|
||||
}
|
||||
}
|
||||
|
||||
private void KillFadeTween()
|
||||
{
|
||||
if (_fadeTween == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_fadeTween.Kill();
|
||||
_fadeTween = null;
|
||||
}
|
||||
|
||||
private void SetFadeMaskAlpha(float alpha)
|
||||
{
|
||||
if (_fadeMaskImage == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Color color = _fadeMaskImage.color;
|
||||
color.r = 0f;
|
||||
color.g = 0f;
|
||||
color.b = 0f;
|
||||
color.a = Mathf.Clamp01(alpha);
|
||||
_fadeMaskImage.color = color;
|
||||
}
|
||||
|
||||
private void PublishTransitionCompleted(int requestId)
|
||||
{
|
||||
if (requestId <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameEntry.Event.Fire(this, BgTransitionCompletedEventArgs.Create(requestId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ namespace UI
|
||||
|
||||
[SerializeField] private Image[] _dialogBgImages;
|
||||
|
||||
[SerializeField] private GameObject _speakerNameArea;
|
||||
|
||||
private readonly int _singleSpeakerCenterPosition = Screen.width / 2;
|
||||
|
||||
private string _leftSpeakerToken = string.Empty;
|
||||
@@ -40,6 +42,16 @@ namespace UI
|
||||
private Sequence _layoutSequence;
|
||||
private DialogWindowAlpha _currentWindowAlpha = DialogWindowAlpha.Medium;
|
||||
private float _currentPlayingSpeed = 10f;
|
||||
private int _leftPortraitRequestVersion;
|
||||
private int _rightPortraitRequestVersion;
|
||||
|
||||
private static readonly HashSet<string> NonPortraitSpeakerIds =
|
||||
new HashSet<string>(System.StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"Other",
|
||||
"Subtitle",
|
||||
"Time"
|
||||
};
|
||||
|
||||
public override void StartDialog(DialogFormContext context)
|
||||
{
|
||||
@@ -89,6 +101,11 @@ namespace UI
|
||||
_speakerArea.SetActive(!string.IsNullOrEmpty(speakerName));
|
||||
}
|
||||
|
||||
if (_speakerNameArea != null)
|
||||
{
|
||||
_speakerNameArea.SetActive(!string.IsNullOrEmpty(speakerName) && IsHumanSpeaker(_context.SpeakerId));
|
||||
}
|
||||
|
||||
if (_speakerNameText != null)
|
||||
{
|
||||
_speakerNameText.text = speakerName;
|
||||
@@ -115,6 +132,9 @@ namespace UI
|
||||
|
||||
bool hasLeftSpeaker = !string.IsNullOrEmpty(_leftSpeakerToken);
|
||||
bool hasRightSpeaker = !string.IsNullOrEmpty(_rightSpeakerToken);
|
||||
|
||||
UpdateSpeakerPortrait(isRightSpeaker ? _rightSprite : _leftSprite, _context.SpeakerId,
|
||||
_context.CurrentLineId, isRightSpeaker);
|
||||
ApplySpeakerLayout(hasLeftSpeaker, hasRightSpeaker, false);
|
||||
}
|
||||
|
||||
@@ -130,6 +150,8 @@ namespace UI
|
||||
{
|
||||
_leftSpeakerToken = string.Empty;
|
||||
_rightSpeakerToken = string.Empty;
|
||||
_leftPortraitRequestVersion++;
|
||||
_rightPortraitRequestVersion++;
|
||||
}
|
||||
|
||||
private void KillLayoutTween()
|
||||
@@ -313,5 +335,82 @@ namespace UI
|
||||
anchoredPosition.x = xPosition;
|
||||
rectTransform.anchoredPosition = anchoredPosition;
|
||||
}
|
||||
|
||||
private void UpdateSpeakerPortrait(Image portraitImage, string speakerId, int lineId,
|
||||
bool isRightSpeaker)
|
||||
{
|
||||
if (portraitImage == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int requestVersion = isRightSpeaker ? ++_rightPortraitRequestVersion : ++_leftPortraitRequestVersion;
|
||||
if (string.IsNullOrWhiteSpace(speakerId) || NonPortraitSpeakerIds.Contains(speakerId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string portraitAssetName = $"Characters/{speakerId}-{ResolvePortraitIndex(speakerId, lineId)}.png";
|
||||
if (GameEntry.SpriteCache == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameEntry.SpriteCache.GetSprite(portraitAssetName, sprite =>
|
||||
{
|
||||
if (isRightSpeaker)
|
||||
{
|
||||
if (requestVersion != _rightPortraitRequestVersion || _rightSprite == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (sprite != null)
|
||||
{
|
||||
_rightSprite.sprite = sprite;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (requestVersion != _leftPortraitRequestVersion || _leftSprite == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (sprite != null)
|
||||
{
|
||||
_leftSprite.sprite = sprite;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static int ResolvePortraitIndex(string speakerId, int lineId)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hash = 17;
|
||||
hash = hash * 31 + lineId;
|
||||
hash = hash * 31 + (string.IsNullOrEmpty(speakerId)
|
||||
? 0
|
||||
: System.StringComparer.OrdinalIgnoreCase.GetHashCode(speakerId));
|
||||
int mod = hash % 3;
|
||||
if (mod < 0)
|
||||
{
|
||||
mod += 3;
|
||||
}
|
||||
|
||||
return mod + 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsHumanSpeaker(string speakerId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(speakerId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return !NonPortraitSpeakerIds.Contains(speakerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user