Unity 5:如何将网格布局卡片缩放平移至屏幕中心并置于顶层
问题:滚动网格视图点击单元格后无法平移到中心并置于顶层,且层级调整后点击异常
我正在尝试实现一个可点击的滚动网格视图:点击单元格时,需要将它缩放并平移到屏幕中心,同时渲染在所有其他单元格上方。目前已经实现了点击和原地缩放,但平移到中心、置于顶层这两个效果一直没做好,而且调整渲染层级后还出现了触摸事件检测异常的问题——只有点击底层卡片原来的区域才能触发事件,哪怕卡片已经移到中心了。
相关信息
- 层级结构:
- 整体网格层级:[网格层级结构]
- 正常状态的网格:[网格正常状态]
- 点击后的网格状态:[点击后状态]
- 单元格的层级结构:
CardContainer:空对象,给子对象Card挂载AnimatorCard:挂载了脚本、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; }
遇到的具体问题
- 无法将点击的单元格平移到屏幕中心
- 调整
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; } } }
三、关闭卡片时的还原操作
建议用动画事件在关闭动画结束后还原卡片的父物体和位置,避免动画过程中位置跳动:
- 在Animator的
Close动画最后一帧添加一个事件,调用MoveCardBack方法 - 实现还原方法:
void MoveCardBack() { if(openedCardObject != null) { var rectTransform = openedCardObject.GetComponent<RectTransform>(); rectTransform.SetParent(originalCardParent, worldPositionStays: false); rectTransform.localPosition = originalCardLocalPos; } }
内容的提问来源于stack exchange,提问作者Androidian
相关产品推荐
相关产品推荐

