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

Unity 5:如何将网格布局卡片缩放平移至屏幕中心并置于顶层

问题:滚动网格视图点击单元格后无法平移到中心并置于顶层,且层级调整后点击异常

我正在尝试实现一个可点击的滚动网格视图:点击单元格时,需要将它缩放并平移到屏幕中心,同时渲染在所有其他单元格上方。目前已经实现了点击和原地缩放,但平移到中心、置于顶层这两个效果一直没做好,而且调整渲染层级后还出现了触摸事件检测异常的问题——只有点击底层卡片原来的区域才能触发事件,哪怕卡片已经移到中心了。

相关信息

  • 层级结构:
    • 整体网格层级:[网格层级结构]
    • 正常状态的网格:[网格正常状态]
    • 点击后的网格状态:[点击后状态]
  • 单元格的层级结构:
    • CardContainer:空对象,给子对象Card挂载Animator
    • Card:挂载了脚本、Canvas Renderer和Animator的对象
    • StatsImage:点击后滑出的子对象
    • Image:带有Image、Shadow脚本和Canvas Renderer的UI图片
    • 其他:简单文本组件

我目前的C#代码

void Populate() 
{ 
    GameObject cardContainerInstance, cardInstance; 
    foreach (var c in cardsCollection.GetAll()) 
    { 
        if (c.IsOwned) 
        { 
            cardContainerInstance = Instantiate(cardContainer, transform); 
            cardInstance = cardContainerInstance.transform.Find("Card").gameObject; 
            var cardManager = cardInstance.GetComponent<CardManager>(); 
            cardManager.card = c; 
            cardManager.AddListener(this); 
        } 
        else 
        { 
            Instantiate(cardSlot, transform); 
        } 
    } 
} 

public void OnCardClick(GameObject cardObject, Card card) 
{ 
    Debug.Log("OnCardClick " + card.name); 
    if (openedCard != null) 
    { 
        if (openedCard.Number == card.Number) 
        { 
            CloseCard(openedCardObject); 
        } 
        else 
        { 
            CloseCard(openedCardObject); 
            OpenCard(cardObject, card); 
        } 
    } 
    else 
    { 
        OpenCard(cardObject, card); 
    } 
} 

void OpenCard(GameObject cardObject, Card card) 
{ 
    //cardObject.GetComponent<Canvas>().sortingOrder = 1; 
    var animator = cardObject.GetComponent<Animator>(); 
    animator.SetTrigger("Open"); 
    openedCard = card; 
    openedCardObject = cardObject; 
} 

void CloseCard(GameObject cardObject) 
{ 
    var animator = cardObject.GetComponent<Animator>(); 
    animator.SetTrigger("Close"); 
    openedCard = null; 
    openedCardObject = null; 
} 

遇到的具体问题

  1. 无法将点击的单元格平移到屏幕中心
  2. 调整Canvas.sortingOrder后,卡片能显示在顶层,但点击事件只能触发原来位置的区域,新位置点击无效(已经添加了GraphicsRaycaster)

解决方案

一、解决平移到屏幕中心的问题

如果你的网格基于UI的RectTransform,可以直接计算屏幕中心的UI坐标并设置卡片位置,或者用Animator实现位移动画:

方案1:代码直接设置位置

// 先在类里添加两个变量记录卡片原始状态
private Transform originalCardParent;
private Vector3 originalCardLocalPos;

void OpenCard(GameObject cardObject, Card card) 
{ 
    var rectTransform = cardObject.GetComponent<RectTransform>();
    // 记录原始父物体和位置,关闭时需要还原
    originalCardParent = rectTransform.parent;
    originalCardLocalPos = rectTransform.localPosition;

    // 获取Canvas根节点(确保是UI的顶层容器)
    Canvas rootCanvas = FindObjectOfType<Canvas>();
    // 将卡片移到Canvas根节点下,避免网格容器的布局限制
    rectTransform.SetParent(rootCanvas.transform, worldPositionStays: false);

    // 计算屏幕中心的UI本地坐标(适配Overlay模式的Canvas)
    Vector2 screenCenter = new Vector2(Screen.width / 2, Screen.height / 2);
    RectTransformUtility.ScreenPointToLocalPointInRectangle(
        rootCanvas.transform as RectTransform,
        screenCenter,
        null, // Overlay模式不需要传Camera
        out Vector2 localCenter
    );
    // 设置卡片到中心位置
    rectTransform.localPosition = localCenter;

    // 置为顶层
    rectTransform.SetAsLastSibling();

    var animator = cardObject.GetComponent<Animator>();
    animator.SetTrigger("Open");
    openedCard = card;
    openedCardObject = cardObject;
}

方案2:用Animator实现位移动画

在Animator的Open动画中添加位置关键帧,把卡片的目标位置设为屏幕中心的UI坐标(可以先在Scene视图中拖到中心,记录坐标值),这样动画会更流畅自然。

二、解决点击事件异常的问题

点击异常的核心是射线检测区域和卡片实际位置不匹配,试试这几个方案:

方案1:将卡片移到Canvas根节点

刚才的代码已经包含了这一步——把卡片从网格容器移到Canvas根节点下,这样它的射线检测区域会完全跟随自身位置,不会受原网格布局的影响。

方案2:确保GraphicsRaycaster配置正确

  • 检查Card对象上是否有GraphicsRaycaster组件,没有就添加一个
  • 确认GraphicsRaycaster的Ignore Reversed Graphics勾选,Blocking Objects设为None(如果不需要遮挡检测)

方案3:禁用其他卡片的交互

打开卡片时,临时禁用网格中其他所有卡片的交互组件(比如Button、EventTrigger),避免误触:

void OpenCard(GameObject cardObject, Card card) 
{
    // ... 其他代码 ...
    // 禁用网格中所有其他卡片的交互
    foreach(Transform child in transform)
    {
        var button = child.GetComponentInChildren<Button>();
        if(button != null && button.gameObject != cardObject)
        {
            button.interactable = false;
        }
    }
}

void CloseCard(GameObject cardObject) 
{
    // ... 其他代码 ...
    // 恢复所有卡片的交互
    foreach(Transform child in transform)
    {
        var button = child.GetComponentInChildren<Button>();
        if(button != null)
        {
            button.interactable = true;
        }
    }
}

三、关闭卡片时的还原操作

建议用动画事件在关闭动画结束后还原卡片的父物体和位置,避免动画过程中位置跳动:

  1. 在Animator的Close动画最后一帧添加一个事件,调用MoveCardBack方法
  2. 实现还原方法:
void MoveCardBack()
{
    if(openedCardObject != null)
    {
        var rectTransform = openedCardObject.GetComponent<RectTransform>();
        rectTransform.SetParent(originalCardParent, worldPositionStays: false);
        rectTransform.localPosition = originalCardLocalPos;
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:03:00