Files
aibis-dream/Assets/Scripts/MiniGame/Tarot/TarotCard.cs
T
2026-04-08 18:28:27 +08:00

238 lines
8.2 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;
}
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 Y-scale.
/// </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);
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();
}
/// <summary>
/// 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.
/// </summary>
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<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
}
}