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;
}
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 Y-scale.
///
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);
Orientation = targetOrientation;
var s = transform.localScale;
s.y = Mathf.Abs(s.y) * (targetOrientation == TarotOrientation.Inverted ? -1f : 1f);
transform.localScale = s;
});
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).
/// After animation completes, resets rotation to identity and applies Y-scale for the new state.
///
public IEnumerator SetOrientationAnimated(TarotOrientation targetOrientation, float duration = 0.4f)
{
if (Orientation == targetOrientation) yield break;
_flipTween?.Kill();
_orientationTween?.Kill();
// 使用本地旋转,避免 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;
Orientation = targetOrientation;
transform.localRotation = Quaternion.identity;
var s = transform.localScale;
s.y = Mathf.Abs(s.y) * (targetOrientation == TarotOrientation.Inverted ? -1f : 1f);
transform.localScale = s;
}
public void SetOrientationImmediate(TarotOrientation orientation)
{
Orientation = orientation;
var s = transform.localScale;
s.y = Mathf.Abs(s.y) * (orientation == TarotOrientation.Inverted ? -1f : 1f);
transform.localScale = s;
}
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
}
}