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;
/// PointerDown(按下时触发;点击确认由 TarotDeck 在 MouseButtonUp 时统一处理,避免 hover 位移动导致 PointerExit 阻断 PointerClick)
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();
// 运行时若 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();
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();
if (_eventTrigger == null)
{
_eventTrigger = gameObject.AddComponent();
}
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;
}
///
/// 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°).
///
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();
}
///
/// Change orientation of an already face-up card (rotate 180 degrees around local Z).
/// 结束后与翻牌一致:用本地 Z 旋转表示逆位,避免动画末尾用负 scale.y 与 Rz(180°) 不等价造成镜像闪变。
///
public IEnumerator SetOrientationAnimated(TarotOrientation targetOrientation, float duration = 0.7f)
{
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.InOutSine);
yield return _orientationTween.WaitForCompletion();
_orientationTween = null;
ApplyOrientationToTransform(targetOrientation);
}
public void SetOrientationImmediate(TarotOrientation orientation)
{
ApplyOrientationToTransform(orientation);
}
/// 正立/逆位统一用本地 Z(0° / 180°),scale.y 恒为正。
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;
}
/// 仅处理旧版负 scale.y:改为正 scale 并在本地绕 Z 补 180°,不覆盖扇形等已有 localRotation。
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();
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.sortingOrder = order;
}
#endregion
}
}