You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity卡牌游戏:如何识别卡牌所属区域并实现跨区域位置调整

卡牌区域识别与拖拽移区实现方案

现有代码的核心问题

  • 拖拽过程中临时将卡牌父节点设为Canvas,导致无法通过transform.parent稳定识别卡牌所属区域
  • 仅写死了BottomArea的碰撞检测,无通用全区域支持逻辑
  • 针对UGUI元素使用2D物理碰撞检测的方案兼容性差,需要额外配置碰撞体、刚体,维护成本高

实现思路

1. 区域识别核心逻辑

为每个游戏区域配置专属Tag,卡牌上维护独立的currentArea变量缓存所属区域,仅在卡牌最终放置时更新该值,不受拖拽过程中临时父节点变化的影响。
所有区域识别、拖拽交互全部基于UGUI原生事件系统实现,不需要额外物理组件。

2. 代码实现

替换原卡牌拖拽脚本(原Click.cs)

using UnityEngine;
using UnityEngine.EventSystems;

public class CardDrag : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    [Header("可允许移动的区域Tag列表")]
    public string[] validAreaTags = { "MyArea", "BottomArea" };
    
    private GameObject currentArea; // 缓存卡牌当前实际所属区域
    private GameObject hoverArea; // 缓存鼠标当前悬停的目标区域
    private Transform originalParent; // 拖拽前的父节点
    private Vector2 originalAnchoredPos; // 拖拽前的本地坐标
    private Canvas rootCanvas;
    private RectTransform cardRect;

    private void Awake()
    {
        cardRect = GetComponent<RectTransform>();
        rootCanvas = GetComponentInParent<Canvas>();
    }

    public void OnBeginDrag(PointerEventData eventData)
    {
        currentArea = transform.parent.gameObject;
        // 校验当前卡牌是否在可拖拽的源区域内
        bool canDrag = false;
        foreach (var tag in validAreaTags)
        {
            if (currentArea.CompareTag(tag))
            {
                canDrag = true;
                break;
            }
        }
        if (!canDrag) return;

        // 缓存拖拽前的状态
        originalParent = transform.parent;
        originalAnchoredPos = cardRect.anchoredPosition;
        // 临时置顶到Canvas顶层,避免被其他区域遮挡
        transform.SetParent(rootCanvas.transform, false);
        GetComponent<CanvasGroup>().blocksRaycasts = false;
    }

    public void OnDrag(PointerEventData eventData)
    {
        cardRect.anchoredPosition += eventData.delta;
        // 实时检测鼠标当前悬停的区域
        if (EventSystem.current.IsPointerOverGameObject())
        {
            var hoverObj = EventSystem.current.currentSelectedGameObject;
            if (hoverObj != null && hoverObj.GetComponent<GameArea>() != null)
            {
                hoverArea = hoverObj;
            }
        }
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        GetComponent<CanvasGroup>().blocksRaycasts = true;
        // 校验目标区域是否有效
        bool validTarget = false;
        if (hoverArea != null)
        {
            foreach (var tag in validAreaTags)
            {
                if (hoverArea.CompareTag(tag))
                {
                    validTarget = true;
                    break;
                }
            }
        }

        if (validTarget)
        {
            // 成功移动到目标区域,更新所属区域缓存
            currentArea = hoverArea;
            transform.SetParent(hoverArea.transform, false);
            // 触发区域布局自动重算,比如使用HorizontalLayoutGroup排序卡牌
            LayoutRebuilder.ForceRebuildLayoutImmediate(hoverArea.GetComponent<RectTransform>());
        }
        else
        {
            // 未放置到有效区域,回到原状态
            transform.SetParent(originalParent, false);
            cardRect.anchoredPosition = originalAnchoredPos;
        }
        hoverArea = null;
    }

    // 对外暴露获取卡牌所属区域的接口
    public GameObject GetCurrentArea()
    {
        return currentArea;
    }
}

新增游戏区域脚本(挂载在MyArea/LeftArea/RightArea/BottomArea四个物体上)

using UnityEngine;
using UnityEngine.EventSystems;

// 仅用于标识该物体是可放置卡牌的游戏区域
public class GameArea : MonoBehaviour { }

原有DrawCards脚本适配

原有实例化逻辑不需要大改,仅需做两个配置:

  • 在Unity编辑器中给4个游戏区域设置对应Tag:MyArea、LeftArea、RightArea、BottomArea
  • 给所有卡牌预设体添加CanvasGroup组件,保持blocksRaycasts默认勾选即可

补充说明

  • 任意卡牌的所属区域可以通过cardObject.GetComponent<CardDrag>().GetCurrentArea()直接获取,不需要遍历父节点
  • 要调整允许的拖拽转移规则,只需要修改validAreaTags数组的值,不需要改动核心拖拽逻辑

注意:要确保所有区域和卡牌都在同一Canvas下,且场景中存在EventSystem物体,否则事件接口不会触发。

内容的提问来源于stack exchange,提问作者Jemima Boodhun

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.24 01:54:03