Files
Workspace/Assets/Demos/ReplacedPerson/Runtime/ReplacedSlotResolutionMotion.cs
T
2026-08-17 18:18:13 +08:00

200 lines
9.6 KiB
C#

#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<Image>();
_mainText = transform.Find("Text")?.GetComponent<TMP_Text>();
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<Image>().color = TypeColor(localType);
_rightToken.GetComponent<Image>().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<RectTransform>();
token.SetParent(transform, false);
token.anchorMin = token.anchorMax = new Vector2(.5f, .5f);
token.sizeDelta = new Vector2(142, 86);
tokenObject.GetComponent<Image>().raycastTarget = false;
group = tokenObject.GetComponent<CanvasGroup>();
group.blocksRaycasts = false;
var labelObject = new GameObject("Label", typeof(RectTransform), typeof(CanvasRenderer), typeof(TextMeshProUGUI));
var labelRect = labelObject.GetComponent<RectTransform>();
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<TextMeshProUGUI>();
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)
};
}
}