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> layoutCache = new(); static readonly Dictionary layoutAxisIsHorizontal = new(); void Start() { charactCard = gameObject; rootCanvas = GetComponentInParent(); canvasGroup = GetComponent() ?? gameObject.AddComponent(); layoutElement = GetComponent() ?? gameObject.AddComponent(); if (characterImage != null) characterImage.raycastTarget = true; } /// /// 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(); if (rootCanvas == null) return; // prevent the original from blocking raycasts so drop targets can receive events if (canvasGroup == null) canvasGroup = GetComponent() ?? gameObject.AddComponent(); canvasGroup.blocksRaycasts = false; canvasGroup.alpha = 0f; isDragging = true; ApplyHoverLayout(false); StopHoverEffects(); if (layoutElement == null) layoutElement = GetComponent() ?? gameObject.AddComponent(); 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(); img.raycastTarget = false; img.sprite = characterImage.sprite; img.SetNativeSize(); var cg = dragGhost.AddComponent(); 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 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 GetOrCacheGroup(Transform parent) { if (layoutCache.TryGetValue(parent, out var cached) && cached != null && cached.Count > 0) { return cached; } bool horizontal = parent.GetComponent() != null; bool vertical = parent.GetComponent() != null; layoutAxisIsHorizontal[parent] = !vertical || horizontal; var list = new List(); CharacterCardView[] cards = parent.GetComponentsInChildren(true); for (int i = 0; i < cards.Length; i++) { var card = cards[i]; if (card == null) continue; var le = card.layoutElement ?? card.GetComponent() ?? card.gameObject.AddComponent(); 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); } }