#nullable enable using System.Collections; using ReplacedPerson.Core; using TMPro; using UnityEngine; using UnityEngine.UI; namespace ReplacedPerson.Runtime { [DisallowMultipleComponent] public sealed class ReplacedSlotResolutionMotion : MonoBehaviour { private Image? _background; private TMP_Text? _mainText; private RectTransform? _leftToken; private RectTransform? _rightToken; private CanvasGroup? _leftGroup; private CanvasGroup? _rightGroup; private TMP_Text? _leftLabel; private TMP_Text? _rightLabel; private Coroutine? _animation; private Color _baseColor; private float _baseFontSize; public bool IsPlaying => _animation != null; private void Awake() { _background = GetComponent(); _mainText = transform.Find("Text")?.GetComponent(); if (_background != null) _baseColor = _background.color; if (_mainText != null) _baseFontSize = _mainText.fontSize; CreateToken("PlayerToken", out _leftToken, out _leftGroup, out _leftLabel); CreateToken("EnemyToken", out _rightToken, out _rightGroup, out _rightLabel); SetTokensActive(false); } public void Play(ReplacedSlotResolution resolution, int localPlayer) { if (_animation != null) StopCoroutine(_animation); _animation = StartCoroutine(Animate(resolution, localPlayer)); } private IEnumerator Animate(ReplacedSlotResolution resolution, int localPlayer) { if (_background == null || _mainText == null || _leftToken == null || _rightToken == null || _leftGroup == null || _rightGroup == null || _leftLabel == null || _rightLabel == null) yield break; var localIsOne = localPlayer == 0; var localType = localIsOne ? resolution.PlayerOneType : resolution.PlayerTwoType; var enemyType = localIsOne ? resolution.PlayerTwoType : resolution.PlayerOneType; var localRoll = localIsOne ? resolution.PlayerOneRoll : resolution.PlayerTwoRoll; var enemyRoll = localIsOne ? resolution.PlayerTwoRoll : resolution.PlayerOneRoll; var localValue = localIsOne ? resolution.PlayerOneValue : resolution.PlayerTwoValue; var enemyValue = localIsOne ? resolution.PlayerTwoValue : resolution.PlayerOneValue; var localRerolled = localIsOne ? resolution.PlayerOneRerolled : resolution.PlayerTwoRerolled; var enemyRerolled = localIsOne ? resolution.PlayerTwoRerolled : resolution.PlayerOneRerolled; var localDamage = localIsOne ? resolution.DamageToPlayerOne : resolution.DamageToPlayerTwo; var enemyDamage = localIsOne ? resolution.DamageToPlayerTwo : resolution.DamageToPlayerOne; SetTokensActive(true); _leftGroup.alpha = 1f; _rightGroup.alpha = 1f; _leftLabel.text = "我方\n" + TypeName(localType); _rightLabel.text = "敌方\n" + TypeName(enemyType); _leftToken.GetComponent().color = TypeColor(localType); _rightToken.GetComponent().color = TypeColor(enemyType); _mainText.text = $"槽 {resolution.SlotIndex + 1}\n对位"; _leftToken.anchoredPosition = new Vector2(-280, 0); _rightToken.anchoredPosition = new Vector2(280, 0); transform.localScale = Vector3.one; _mainText.fontSize = Mathf.Max(_baseFontSize, 23f); const float approachDuration = .28f; var elapsed = 0f; while (elapsed < approachDuration) { elapsed += Time.unscaledDeltaTime; var t = Mathf.SmoothStep(0, 1, Mathf.Clamp01(elapsed / approachDuration)); _leftToken.anchoredPosition = Vector2.Lerp(new Vector2(-280, 0), new Vector2(-82, 0), t); _rightToken.anchoredPosition = Vector2.Lerp(new Vector2(280, 0), new Vector2(82, 0), t); yield return null; } transform.localScale = Vector3.one * 1.045f; _mainText.text = $"槽 {resolution.SlotIndex + 1}\n投掷中"; _leftLabel.fontSize = 21; _rightLabel.fontSize = 21; yield return new WaitForSecondsRealtime(.04f); transform.localScale = Vector3.one; for (var frame = 0; frame < 8; frame++) { var leftFace = (frame * 5 + 1) % 6 + 1; var rightFace = (frame * 3 + 4) % 6 + 1; _leftLabel.text = $"我方\nD6 {leftFace}"; _rightLabel.text = $"敌方\nD6 {rightFace}"; _leftToken.localRotation = Quaternion.Euler(0, 0, frame % 2 == 0 ? -2f : 2f); _rightToken.localRotation = Quaternion.Euler(0, 0, frame % 2 == 0 ? 2f : -2f); yield return new WaitForSecondsRealtime(.075f); } _leftToken.localRotation = Quaternion.identity; _rightToken.localRotation = Quaternion.identity; _leftLabel.text = $"我方\nD6 {Roll(localRoll, localRerolled)}"; _rightLabel.text = $"敌方\nD6 {Roll(enemyRoll, enemyRerolled)}"; _mainText.fontSize = Mathf.Max(_baseFontSize, 27f); _mainText.text = $"槽 {resolution.SlotIndex + 1}\n骰点 {localRoll} : {enemyRoll}"; yield return new WaitForSecondsRealtime(.75f); var flash = localDamage > 0 ? new Color(.55f, .10f, .09f, 1f) : enemyDamage > 0 ? new Color(.45f, .32f, .10f, 1f) : new Color(.24f, .29f, .27f, 1f); _background.color = flash; _leftLabel.text = $"我方\n{TypeName(localType)} {localValue}"; _rightLabel.text = $"敌方\n{TypeName(enemyType)} {enemyValue}"; _mainText.fontSize = Mathf.Max(_baseFontSize, 21f); _mainText.text = $"槽 {resolution.SlotIndex + 1}\n{TypeName(localType)} {localValue} : {enemyValue} {TypeName(enemyType)}\n{resolution.Summary}"; yield return new WaitForSecondsRealtime(.55f); elapsed = 0f; const float fadeDuration = .22f; while (elapsed < fadeDuration) { elapsed += Time.unscaledDeltaTime; var t = Mathf.Clamp01(elapsed / fadeDuration); _leftGroup.alpha = 1f - t; _rightGroup.alpha = 1f - t; _background.color = Color.Lerp(flash, _baseColor, t); yield return null; } _background.color = _baseColor; _mainText.fontSize = _baseFontSize; transform.localScale = Vector3.one; SetTokensActive(false); _animation = null; } private void CreateToken(string tokenName, out RectTransform token, out CanvasGroup group, out TMP_Text label) { var tokenObject = new GameObject(tokenName, typeof(RectTransform), typeof(CanvasRenderer), typeof(Image), typeof(CanvasGroup)); token = tokenObject.GetComponent(); token.SetParent(transform, false); token.anchorMin = token.anchorMax = new Vector2(.5f, .5f); token.sizeDelta = new Vector2(142, 86); tokenObject.GetComponent().raycastTarget = false; group = tokenObject.GetComponent(); group.blocksRaycasts = false; var labelObject = new GameObject("Label", typeof(RectTransform), typeof(CanvasRenderer), typeof(TextMeshProUGUI)); var labelRect = labelObject.GetComponent(); labelRect.SetParent(token, false); labelRect.anchorMin = Vector2.zero; labelRect.anchorMax = Vector2.one; labelRect.offsetMin = new Vector2(8, 5); labelRect.offsetMax = new Vector2(-8, -5); label = labelObject.GetComponent(); label.font = _mainText != null ? _mainText.font : null; label.fontSize = 20; label.alignment = TextAlignmentOptions.Center; label.color = new Color(.96f, .94f, .88f, 1f); label.raycastTarget = false; } private void SetTokensActive(bool active) { if (_leftToken != null) _leftToken.gameObject.SetActive(active); if (_rightToken != null) _rightToken.gameObject.SetActive(active); } private void OnDisable() { if (_animation != null) StopCoroutine(_animation); _animation = null; if (_background != null) _background.color = _baseColor; if (_mainText != null) _mainText.fontSize = _baseFontSize; transform.localScale = Vector3.one; if (_leftToken != null) _leftToken.localRotation = Quaternion.identity; if (_rightToken != null) _rightToken.localRotation = Quaternion.identity; SetTokensActive(false); } private static string Roll(int value, bool rerolled) => rerolled ? value + "(重掷)" : value.ToString(); private static string TypeName(ReplacedEffectType type) => type switch { ReplacedEffectType.Attack => "攻击", ReplacedEffectType.Dodge => "闪避", ReplacedEffectType.Counter => "反击", _ => "未知" }; private static Color TypeColor(ReplacedEffectType type) => type switch { ReplacedEffectType.Attack => new Color(.63f, .13f, .11f, .98f), ReplacedEffectType.Dodge => new Color(.18f, .31f, .29f, .98f), ReplacedEffectType.Counter => new Color(.42f, .31f, .16f, .98f), _ => new Color(.22f, .23f, .23f, .98f) }; } }