biography-of-lijie/Assets/GameMain/Scripts/UI/View/BottomDialogForm.cs

493 lines
16 KiB
C#

using System.Collections.Generic;
using System.Linq;
using DG.Tweening;
using Definition.Enum;
using TMPro;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UI;
using UnityGameFramework.Runtime;
namespace UI
{
public class BottomDialogForm : DialogFormBase
{
public override DialogFormMode UIMode => DialogFormMode.BottomBox;
[SerializeField] private GameObject _speakerArea;
[SerializeField] private TMP_Text _speakerNameText;
[SerializeField] private TMP_Text _contentText;
[FormerlySerializedAs("_leftSprite")] [SerializeField] private Image _leftImage;
[FormerlySerializedAs("_rightSprite")] [SerializeField] private Image _rightImage;
[SerializeField] private int _leftSpritePosition = 450;
[SerializeField] private int _rightSpritePosition = -450;
[SerializeField] private float _moveDuration = 0.25f;
[SerializeField] private Ease _moveEase = Ease.OutCubic;
[SerializeField] private Image[] _dialogBgImages;
[SerializeField] private GameObject _speakerNameArea;
private string _leftSpeakerToken = string.Empty;
private string _rightSpeakerToken = string.Empty;
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)
{
if (context == null)
{
Log.Warning("BottomDialogForm start failed. context is null.");
return;
}
_context = context;
if (_context.DialogWindowAlpha != this._currentWindowAlpha)
{
_currentWindowAlpha = _context.DialogWindowAlpha;
float targetAlpha = _currentWindowAlpha switch
{
DialogWindowAlpha.None => 1,
DialogWindowAlpha.Low => 0.95f,
DialogWindowAlpha.Medium => 0.8f,
DialogWindowAlpha.High => 0.65f,
_ => 0.9f
};
if (_dialogBgImages.Length == 0)
{
//TODO:一个很奇怪的问题,在 prefab 里赋好的值实例化出来就没了,只能先这样赋值
_dialogBgImages = GetComponentsInChildren<Image>().Where(image => image.name == "bg").ToArray();
}
foreach (Image image in _dialogBgImages)
{
Color color = image.color;
color.a = targetAlpha;
image.color = color;
}
}
if ((int)(_context.PlayingSpeed + 1) * 5 != (int)_currentPlayingSpeed)
{
_currentPlayingSpeed = 5f * (int)(_context.PlayingSpeed + 1);
}
string speakerName = _context.SpeakerName;
bool isHumanSpeaker = IsHumanSpeaker(_context.SpeakerId);
if (_speakerArea != null)
{
_speakerArea.SetActive(!string.IsNullOrEmpty(speakerName));
}
if (_speakerNameArea != null)
{
_speakerNameArea.SetActive(!string.IsNullOrEmpty(speakerName) && isHumanSpeaker);
}
if (_speakerNameText != null)
{
_speakerNameText.text = speakerName;
}
PlayTypewriter(_contentText, _context.Text, _currentPlayingSpeed);
MainOverlayForm.DispatchCue(_context.Emphasis);
if (string.IsNullOrEmpty(speakerName))
{
ClearSpeakerState();
ApplySpeakerLayout(false, false, true);
return;
}
if (!isHumanSpeaker)
{
ClearSpeakerState();
ApplySpeakerLayout(false, false, true);
return;
}
bool isRightSpeaker = context.Direction > 0;
if (isRightSpeaker)
{
_rightSpeakerToken = speakerName;
}
else
{
_leftSpeakerToken = speakerName;
}
bool hasLeftSpeaker = !string.IsNullOrEmpty(_leftSpeakerToken);
bool hasRightSpeaker = !string.IsNullOrEmpty(_rightSpeakerToken);
UpdateSpeakerPortrait(isRightSpeaker ? _rightImage : _leftImage, _context.SpeakerId,
_context.CurrentLineId, isRightSpeaker);
ApplySpeakerLayout(hasLeftSpeaker, hasRightSpeaker, false);
}
protected override void OnOpen(object userData)
{
ResetSpeakerVisualState();
base.OnOpen(userData);
}
protected override void OnClose(bool isShutdown, object userData)
{
ResetSpeakerVisualState();
base.OnClose(isShutdown, userData);
}
private void ClearSpeakerState()
{
if (_leftImage != null)
{
_leftImage.sprite = null;
}
if (_rightImage != null)
{
_rightImage.sprite = null;
}
_leftSpeakerToken = string.Empty;
_rightSpeakerToken = string.Empty;
_leftPortraitRequestVersion++;
_rightPortraitRequestVersion++;
}
private void KillLayoutTween()
{
if (_layoutSequence != null)
{
_layoutSequence.Kill();
_layoutSequence = null;
}
if (_leftImage != null && _leftImage.rectTransform != null)
{
_leftImage.rectTransform.DOKill();
}
if (_rightImage != null && _rightImage.rectTransform != null)
{
_rightImage.rectTransform.DOKill();
}
}
private void ApplySpeakerLayout(bool hasLeftSpeaker, bool hasRightSpeaker, bool instant)
{
if (_leftImage == null || _rightImage == null)
{
return;
}
bool leftCurrentlyVisible = _leftImage.gameObject.activeSelf;
bool rightCurrentlyVisible = _rightImage.gameObject.activeSelf;
bool leftTargetVisible = hasLeftSpeaker;
bool rightTargetVisible = hasRightSpeaker;
float leftTargetX = GetTargetX(true, hasLeftSpeaker, hasRightSpeaker);
float rightTargetX = GetTargetX(false, hasLeftSpeaker, hasRightSpeaker);
KillLayoutTween();
PrepareStartState(
leftCurrentlyVisible,
rightCurrentlyVisible,
leftTargetVisible,
rightTargetVisible,
hasLeftSpeaker,
hasRightSpeaker);
if (instant || _moveDuration <= 0f)
{
SetSpritePosition(_leftImage.rectTransform, leftTargetX);
SetSpritePosition(_rightImage.rectTransform, rightTargetX);
SetSpriteVisible(_leftImage, leftTargetVisible);
SetSpriteVisible(_rightImage, rightTargetVisible);
return;
}
_layoutSequence = DOTween.Sequence();
Tween leftTween = CreateMoveTween(_leftImage.rectTransform, leftTargetX);
if (leftTween != null)
{
_layoutSequence.Join(leftTween);
}
Tween rightTween = CreateMoveTween(_rightImage.rectTransform, rightTargetX);
if (rightTween != null)
{
_layoutSequence.Join(rightTween);
}
if (_layoutSequence.active && _layoutSequence.Duration(false) > 0f)
{
_layoutSequence.OnComplete(() =>
{
SetSpriteVisible(_leftImage, leftTargetVisible);
SetSpriteVisible(_rightImage, rightTargetVisible);
_layoutSequence = null;
});
}
else
{
SetSpritePosition(_leftImage.rectTransform, leftTargetX);
SetSpritePosition(_rightImage.rectTransform, rightTargetX);
SetSpriteVisible(_leftImage, leftTargetVisible);
SetSpriteVisible(_rightImage, rightTargetVisible);
_layoutSequence.Kill();
_layoutSequence = null;
}
}
private void PrepareStartState(
bool leftCurrentlyVisible,
bool rightCurrentlyVisible,
bool leftTargetVisible,
bool rightTargetVisible,
bool hasLeftSpeaker,
bool hasRightSpeaker)
{
if (leftTargetVisible && !leftCurrentlyVisible)
{
float leftStartX = GetAppearStartX(true, rightCurrentlyVisible, hasLeftSpeaker, hasRightSpeaker);
SetSpritePosition(_leftImage.rectTransform, leftStartX);
SetSpriteVisible(_leftImage, true);
}
if (rightTargetVisible && !rightCurrentlyVisible)
{
float rightStartX = GetAppearStartX(false, leftCurrentlyVisible, hasLeftSpeaker, hasRightSpeaker);
SetSpritePosition(_rightImage.rectTransform, rightStartX);
SetSpriteVisible(_rightImage, true);
}
if (leftCurrentlyVisible && !leftTargetVisible)
{
SetSpriteVisible(_leftImage, true);
}
if (rightCurrentlyVisible && !rightTargetVisible)
{
SetSpriteVisible(_rightImage, true);
}
}
private float GetAppearStartX(bool isLeft, bool otherCurrentlyVisible, bool hasLeftSpeaker,
bool hasRightSpeaker)
{
if (hasLeftSpeaker && hasRightSpeaker)
{
// single -> multi: hidden side starts from center, then both move to side positions.
if (otherCurrentlyVisible)
{
return GetSingleSpeakerCenterPosition(isLeft);
}
return isLeft ? _leftSpritePosition : _rightSpritePosition;
}
// single appears: active side starts from its side and moves to center.
return isLeft ? _leftSpritePosition : _rightSpritePosition;
}
private float GetTargetX(bool isLeft, bool hasLeftSpeaker, bool hasRightSpeaker)
{
if (hasLeftSpeaker && hasRightSpeaker)
{
return isLeft ? _leftSpritePosition : _rightSpritePosition;
}
if (hasLeftSpeaker || hasRightSpeaker)
{
// multi -> single: both move to center first, then inactive side hides.
return GetSingleSpeakerCenterPosition(isLeft);
}
return isLeft ? _leftSpritePosition : _rightSpritePosition;
}
private Tween CreateMoveTween(RectTransform rectTransform, float targetX)
{
if (rectTransform == null)
{
return null;
}
if (Mathf.Abs(rectTransform.anchoredPosition.x - targetX) < 0.01f)
{
return null;
}
return rectTransform.DOAnchorPosX(targetX, _moveDuration).SetEase(_moveEase);
}
private static void SetSpriteVisible(Image spriteImage, bool visible)
{
if (spriteImage == null)
{
return;
}
spriteImage.gameObject.SetActive(visible);
}
private static void SetSpritePosition(RectTransform rectTransform, float xPosition)
{
if (rectTransform == null)
{
return;
}
Vector2 anchoredPosition = rectTransform.anchoredPosition;
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;
}
int portraitCount = ResolveAvailablePortraitCount(speakerId);
string portraitAssetName = $"Characters/{speakerId}-{ResolvePortraitIndex(speakerId, lineId, portraitCount)}.png";
if (GameEntry.SpriteCache == null)
{
return;
}
GameEntry.SpriteCache.GetSprite(portraitAssetName, sprite =>
{
if (isRightSpeaker)
{
if (requestVersion != _rightPortraitRequestVersion || _rightImage == null)
{
return;
}
if (sprite != null)
{
_rightImage.sprite = sprite;
}
return;
}
if (requestVersion != _leftPortraitRequestVersion || _leftImage == null)
{
return;
}
if (sprite != null)
{
_leftImage.sprite = sprite;
}
});
}
private static int ResolvePortraitIndex(string speakerId, int lineId, int portraitCount)
{
int safePortraitCount = portraitCount > 0 ? portraitCount : 3;
unchecked
{
int hash = 17;
hash = hash * 31 + lineId;
hash = hash * 31 + (string.IsNullOrEmpty(speakerId)
? 0
: System.StringComparer.OrdinalIgnoreCase.GetHashCode(speakerId));
int mod = hash % safePortraitCount;
if (mod < 0)
{
mod += safePortraitCount;
}
return mod + 1;
}
}
private static int ResolveAvailablePortraitCount(string speakerId)
{
if (GameEntry.Dialog == null)
{
return 0;
}
return GameEntry.Dialog.GetSpeakerPortraitCount(speakerId);
}
private static bool IsHumanSpeaker(string speakerId)
{
if (string.IsNullOrWhiteSpace(speakerId))
{
return false;
}
return !NonPortraitSpeakerIds.Contains(speakerId);
}
private void ResetSpeakerVisualState()
{
KillLayoutTween();
ClearSpeakerState();
ApplySpeakerLayout(false, false, true);
}
private float GetSingleSpeakerCenterPosition(bool isLeft)
{
RectTransform rectTransform = isLeft
? _leftImage != null ? _leftImage.rectTransform : null
: _rightImage != null ? _rightImage.rectTransform : null;
return GetCenterAnchoredX(rectTransform);
}
private static float GetCenterAnchoredX(RectTransform rectTransform)
{
if (rectTransform == null)
{
return 0f;
}
if (!(rectTransform.parent is RectTransform parent))
{
return 0f;
}
float anchorX = rectTransform.anchorMin.x;
return (0.5f - anchorX) * parent.rect.width;
}
}
}