Files
aibis-dream/Assets/Scripts/MiniGame/Tarot/TarotCard.cs
T

252 lines
8.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections;
using System.Linq;
using AibisDream.Framework;
using DG.Tweening;
using UnityEngine;
using UnityEngine.EventSystems;
namespace AibisDream.MiniGame.Tarot
{
public enum TarotOrientation
{
Normal,
Inverted
}
[RequireComponent(typeof(SpriteRenderer))]
[RequireComponent(typeof(BoxCollider2D))]
public class TarotCard : MonoBehaviour, IInteraction
{
#region IInteraction
public bool IsActive => _isInteractable;
public bool IsAvailable => _isInteractable;
public GameObject GetGameObject() => gameObject;
#endregion
#region Public State
public bool IsFaceUp { get; private set; }
public TarotOrientation Orientation { get; private set; } = TarotOrientation.Normal;
public int Index { get; private set; }
#endregion
#region Events
public Action OnHoverEnter;
public Action OnHoverExit;
/// <summary>PointerDown(按下时触发;点击确认由 TarotDeck 在 MouseButtonUp 时统一处理,避免 hover 位移动导致 PointerExit 阻断 PointerClick</summary>
public Action OnPointerDown;
#endregion
private SpriteRenderer _spriteRenderer;
private EventTriggerEx _eventTrigger;
private Sprite _frontSprite;
private Sprite _backSprite;
private bool _isInteractable;
private Tweener _flipTween;
private Tweener _orientationTween;
public void Init(int index, Sprite frontSprite, Sprite backSprite)
{
Index = index;
_spriteRenderer = GetComponent<SpriteRenderer>();
// 运行时若 TarotManager 未拖入 Sprite,不要用 null 覆盖预制体上的图,否则牌会完全消失
_frontSprite = frontSprite;
_backSprite = backSprite != null ? backSprite : _spriteRenderer.sprite;
if (backSprite != null)
_spriteRenderer.sprite = backSprite;
else if (_spriteRenderer.sprite == null)
Debug.LogWarning("[TarotCard] 背面与预制体均无 Sprite,牌将不可见。请在 TarotManager 上指定 Back / Front Sprite。", this);
IsFaceUp = false;
Orientation = TarotOrientation.Normal;
transform.localScale = Vector3.one;
transform.localRotation = Quaternion.identity;
}
private void Awake()
{
_spriteRenderer = GetComponent<SpriteRenderer>();
SetupEventTrigger();
}
private void OnDestroy()
{
if (_eventTrigger != null)
{
_eventTrigger.UnRegister(EventTriggerType.PointerEnter, HandlePointerEnter);
_eventTrigger.UnRegister(EventTriggerType.PointerExit, HandlePointerExit);
_eventTrigger.UnRegister(EventTriggerType.PointerDown, HandlePointerDown);
}
_flipTween?.Kill();
_orientationTween?.Kill();
}
#region EventTriggerEx Setup
private void SetupEventTrigger()
{
_eventTrigger = GetComponent<EventTriggerEx>();
if (_eventTrigger == null)
{
_eventTrigger = gameObject.AddComponent<EventTriggerEx>();
}
EnsureEventTriggerEntries();
_eventTrigger.Register(EventTriggerType.PointerEnter, HandlePointerEnter);
_eventTrigger.Register(EventTriggerType.PointerExit, HandlePointerExit);
_eventTrigger.Register(EventTriggerType.PointerDown, HandlePointerDown);
}
private void EnsureEventTriggerEntries()
{
if (_eventTrigger == null) return;
var triggers = _eventTrigger.triggers;
var needed = new[]
{
EventTriggerType.PointerEnter,
EventTriggerType.PointerExit,
EventTriggerType.PointerDown
};
foreach (var t in needed)
{
if (triggers.Any(e => e.eventID == t)) continue;
triggers.Add(new EventTrigger.Entry { eventID = t });
}
}
private void HandlePointerEnter(BaseEventData data)
{
if (!_isInteractable) return;
OnHoverEnter?.Invoke();
}
private void HandlePointerExit(BaseEventData data)
{
if (!_isInteractable) return;
OnHoverExit?.Invoke();
}
private void HandlePointerDown(BaseEventData data)
{
if (!_isInteractable) return;
OnPointerDown?.Invoke();
}
#endregion
#region Public API
public void SetInteractable(bool interactable)
{
_isInteractable = interactable;
}
/// <summary>
/// Flip the card to reveal front side with a target orientation.
/// X-scale: 1 -> 0 (swap sprite) -> 1, then apply orientation via local Z rotation (0° / 180°).
/// </summary>
public IEnumerator Flip(TarotOrientation targetOrientation, float duration = 0.5f)
{
_flipTween?.Kill();
_orientationTween?.Kill();
float half = duration * 0.5f;
var scale = transform.localScale;
var seq = DOTween.Sequence();
seq.Append(transform.DOScaleX(0f, half).SetEase(Ease.InQuad));
seq.AppendCallback(() =>
{
IsFaceUp = !IsFaceUp;
var next = IsFaceUp ? _frontSprite : _backSprite;
if (next != null)
_spriteRenderer.sprite = next;
else if (IsFaceUp)
Debug.LogWarning("[TarotCard] Front Sprite 未设置,翻牌后无法显示正面。", this);
ApplyOrientationToTransform(targetOrientation);
});
seq.Append(transform.DOScaleX(scale.x, half).SetEase(Ease.OutQuad));
yield return seq.WaitForCompletion();
}
/// <summary>
/// Change orientation of an already face-up card (rotate 180 degrees around local Z).
/// 结束后与翻牌一致:用本地 Z 旋转表示逆位,避免动画末尾用负 scale.y 与 Rz(180°) 不等价造成镜像闪变。
/// </summary>
public IEnumerator SetOrientationAnimated(TarotOrientation targetOrientation, float duration = 0.4f)
{
if (Orientation == targetOrientation) yield break;
_flipTween?.Kill();
_orientationTween?.Kill();
// 旧版用负 scale.y 表示逆位时,收束为绕本地 Z 再转 180°,避免与后续 Rz 动画叠加错乱
EnsureOrientationUsesRotationNotNegativeScale();
// 使用本地旋转,避免 DORotate(世界欧拉)与 localEulerAngles 混用导致几乎不转或乱跳
var endLocal = transform.localRotation * Quaternion.Euler(0f, 0f, 180f);
_orientationTween = transform.DOLocalRotateQuaternion(endLocal, duration)
.SetEase(Ease.InOutCubic);
yield return _orientationTween.WaitForCompletion();
_orientationTween = null;
ApplyOrientationToTransform(targetOrientation);
}
public void SetOrientationImmediate(TarotOrientation orientation)
{
ApplyOrientationToTransform(orientation);
}
/// <summary>正立/逆位统一用本地 Z0° / 180°),scale.y 恒为正。</summary>
private void ApplyOrientationToTransform(TarotOrientation orientation)
{
Orientation = orientation;
transform.localRotation = Quaternion.Euler(0f, 0f, orientation == TarotOrientation.Inverted ? 180f : 0f);
var s = transform.localScale;
s.y = Mathf.Abs(s.y);
transform.localScale = s;
}
/// <summary>仅处理旧版负 scale.y:改为正 scale 并在本地绕 Z 补 180°,不覆盖扇形等已有 localRotation。</summary>
private void EnsureOrientationUsesRotationNotNegativeScale()
{
var s = transform.localScale;
if (s.y >= 0f) return;
s.y = Mathf.Abs(s.y);
transform.localScale = s;
transform.localRotation = transform.localRotation * Quaternion.Euler(0f, 0f, 180f);
}
public void SetAlpha(float alpha)
{
if (_spriteRenderer == null) _spriteRenderer = GetComponent<SpriteRenderer>();
var c = _spriteRenderer.color;
c.a = alpha;
_spriteRenderer.color = c;
}
public Tweener DOFade(float target, float duration)
{
return _spriteRenderer.DOFade(target, duration);
}
public void SetSortingOrder(int order)
{
if (_spriteRenderer == null) _spriteRenderer = GetComponent<SpriteRenderer>();
_spriteRenderer.sortingOrder = order;
}
#endregion
}
}