Files
2026-06-30 21:21:30 +08:00

297 lines
10 KiB
C#

using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using DG.Tweening;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class CharacterCardView : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler,
IPointerEnterHandler, IPointerExitHandler, IPointerMoveHandler
{
public int characterID;
public string currentBoundaryLevel;
public GameObject charactCard;
public Image characterImage;
public Image levelIcon;
public Image occupationIcon;
public bool isSelected=false;
// drag
private GameObject dragGhost;
private Canvas rootCanvas;
private CanvasGroup canvasGroup;
private LayoutElement layoutElement;
[Header("Hover Anim")]
[SerializeField] bool enableHoverAnim = true;
[SerializeField] float hoverScale = 1.06f;
[SerializeField] float hoverTweenTime = 0.12f;
[SerializeField] float hoverExpand = 0.18f;
[SerializeField] float hoverCompress = 0.08f;
[SerializeField] float gyroMaxAngle = 6f;
[SerializeField] float gyroFollow = 8f;
private bool isHovering;
private bool isDragging;
private Camera lastEventCamera;
class LayoutInfo
{
public CharacterCardView card;
public LayoutElement layout;
public float baseSize;
}
static readonly Dictionary<Transform, List<LayoutInfo>> layoutCache = new();
static readonly Dictionary<Transform, bool> layoutAxisIsHorizontal = new();
void Start()
{
charactCard = gameObject;
rootCanvas = GetComponentInParent<Canvas>();
canvasGroup = GetComponent<CanvasGroup>() ?? gameObject.AddComponent<CanvasGroup>();
layoutElement = GetComponent<LayoutElement>() ?? gameObject.AddComponent<LayoutElement>();
if (characterImage != null) characterImage.raycastTarget = true;
}
/// <summary>
/// Documentation text normalized.
public void beSelected()
{
if (!IsCharacterUnlocked(characterID))
{
return;
}
if (!isSelected)
{
isSelected = true;
TeamManager.Instance.currentSelectedCharacterCard = new CharacterView(characterID, currentBoundaryLevel);
characterImage.color = new Color(0.5f, 0.5f, 0.5f,1);
}
else beUnSelected();
}
public void beUnSelected()
{
// Documentation text normalized.
isSelected = false;
TeamManager.Instance.removeTeamCharacter(characterID);
characterImage.color = new Color(1f, 1f, 1f,1);
}
// Drag handlers: create ghost based on characterImage.sprite
public void OnBeginDrag(PointerEventData eventData)
{
Debug.Log($"CharacterCardView OnBeginDrag: {gameObject.name}");
if (!IsCharacterUnlocked(characterID)) return;
if (characterImage == null || characterImage.sprite == null) return;
if (rootCanvas == null) rootCanvas = GetComponentInParent<Canvas>();
if (rootCanvas == null) return;
// prevent the original from blocking raycasts so drop targets can receive events
if (canvasGroup == null) canvasGroup = GetComponent<CanvasGroup>() ?? gameObject.AddComponent<CanvasGroup>();
canvasGroup.blocksRaycasts = false;
canvasGroup.alpha = 0f;
isDragging = true;
ApplyHoverLayout(false);
StopHoverEffects();
if (layoutElement == null) layoutElement = GetComponent<LayoutElement>() ?? gameObject.AddComponent<LayoutElement>();
layoutElement.ignoreLayout = true;
ForceRebuildParentLayout();
// ensure pointerDrag references this object for drop handlers
eventData.pointerDrag = gameObject;
dragGhost = new GameObject("_RoleDragGhost");
dragGhost.transform.SetParent(rootCanvas.transform, false);
var img = dragGhost.AddComponent<Image>();
img.raycastTarget = false;
img.sprite = characterImage.sprite;
img.SetNativeSize();
var cg = dragGhost.AddComponent<CanvasGroup>();
cg.blocksRaycasts = false;
cg.alpha = 0.5f; // Set transparency to half
UpdateGhostPosition(eventData);
}
public void OnDrag(PointerEventData eventData)
{
if (dragGhost == null) return;
UpdateGhostPosition(eventData);
}
public void OnEndDrag(PointerEventData eventData)
{
Debug.Log($"CharacterCardView OnEndDrag: {gameObject.name}");
if (dragGhost != null)
{
Destroy(dragGhost);
dragGhost = null;
}
// restore raycast blocking on the original
if (canvasGroup != null)
canvasGroup.blocksRaycasts = true;
if (canvasGroup != null)
canvasGroup.alpha = 1f;
if (layoutElement != null)
layoutElement.ignoreLayout = false;
isDragging = false;
ForceRebuildParentLayout();
}
private void UpdateGhostPosition(PointerEventData eventData)
{
if (rootCanvas == null) return;
RectTransformUtility.ScreenPointToLocalPointInRectangle(
rootCanvas.transform as RectTransform,
eventData.position,
eventData.pressEventCamera,
out Vector2 localPoint);
if (dragGhost != null)
dragGhost.transform.localPosition = localPoint;
}
private bool IsCharacterUnlocked(int id)
{
if (id <= 0)
{
return false;
}
AllyHero_SO[] heroes = RuntimeResourcesCache.LoadAllAllyHeroes();
for (int i = 0; i < heroes.Length; i++)
{
AllyHero_SO hero = heroes[i];
if (hero != null && hero.ally_heroID == id)
{
return hero.isUnlocked;
}
}
return false;
}
public void OnPointerEnter(PointerEventData eventData)
{
if (!enableHoverAnim || isDragging) return;
isHovering = true;
lastEventCamera = eventData.enterEventCamera;
ApplyHoverLayout(true);
transform.DOKill();
transform.DOScale(hoverScale, hoverTweenTime).SetEase(Ease.OutCubic);
}
public void OnPointerExit(PointerEventData eventData)
{
if (!enableHoverAnim) return;
isHovering = false;
ApplyHoverLayout(false);
StopHoverEffects();
}
public void OnPointerMove(PointerEventData eventData)
{
if (!enableHoverAnim) return;
lastEventCamera = eventData.enterEventCamera;
}
void LateUpdate()
{
if (!enableHoverAnim || !isHovering || isDragging) return;
RectTransform rt = transform as RectTransform;
if (rt == null) return;
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(rt, Input.mousePosition, lastEventCamera, out Vector2 localPoint))
{
return;
}
Vector2 size = rt.rect.size;
if (size.x <= 0f || size.y <= 0f) return;
Vector2 norm = new Vector2(localPoint.x / size.x, localPoint.y / size.y);
norm = Vector2.ClampMagnitude(norm * 2f, 1f);
float xRot = -norm.y * gyroMaxAngle;
float yRot = norm.x * gyroMaxAngle;
Quaternion target = Quaternion.Euler(xRot, yRot, 0f);
transform.localRotation = Quaternion.Slerp(transform.localRotation, target, Time.unscaledDeltaTime * gyroFollow);
}
void StopHoverEffects()
{
transform.DOKill();
transform.DOScale(1f, hoverTweenTime).SetEase(Ease.OutCubic);
transform.localRotation = Quaternion.identity;
}
void ApplyHoverLayout(bool active)
{
Transform parent = transform.parent;
if (parent == null) return;
List<LayoutInfo> group = GetOrCacheGroup(parent);
if (group == null || group.Count == 0) return;
bool horizontal = layoutAxisIsHorizontal.TryGetValue(parent, out bool h) ? h : true;
for (int i = 0; i < group.Count; i++)
{
LayoutInfo info = group[i];
if (info == null || info.layout == null) continue;
float baseSize = info.baseSize;
float target = baseSize;
if (active)
{
target = info.card == this
? baseSize * (1f + hoverExpand)
: baseSize * Mathf.Max(0.1f, 1f - hoverCompress);
}
if (horizontal)
info.layout.preferredWidth = target;
else
info.layout.preferredHeight = target;
}
ForceRebuildParentLayout();
}
List<LayoutInfo> GetOrCacheGroup(Transform parent)
{
if (layoutCache.TryGetValue(parent, out var cached) && cached != null && cached.Count > 0)
{
return cached;
}
bool horizontal = parent.GetComponent<HorizontalLayoutGroup>() != null;
bool vertical = parent.GetComponent<VerticalLayoutGroup>() != null;
layoutAxisIsHorizontal[parent] = !vertical || horizontal;
var list = new List<LayoutInfo>();
CharacterCardView[] cards = parent.GetComponentsInChildren<CharacterCardView>(true);
for (int i = 0; i < cards.Length; i++)
{
var card = cards[i];
if (card == null) continue;
var le = card.layoutElement ?? card.GetComponent<LayoutElement>() ?? card.gameObject.AddComponent<LayoutElement>();
RectTransform rt = card.transform as RectTransform;
float baseSize = 100f;
if (rt != null)
{
baseSize = horizontal ? rt.rect.width : rt.rect.height;
}
if (horizontal)
le.preferredWidth = baseSize;
else
le.preferredHeight = baseSize;
list.Add(new LayoutInfo { card = card, layout = le, baseSize = baseSize });
}
layoutCache[parent] = list;
return list;
}
void ForceRebuildParentLayout()
{
if (transform.parent == null) return;
RectTransform prt = transform.parent as RectTransform;
if (prt != null)
LayoutRebuilder.ForceRebuildLayoutImmediate(prt);
}
}