同预制体卡牌点击异常:点击牌堆后首个Tableau卡牌失效问题排查
类纸牌游戏交互异常问题
我正在开发一款类纸牌游戏,所有卡牌均基于同一Prefab创建。Tableau区域的所有卡牌运行完全相同的脚本,但首个Tableau的首张卡牌仅在特定场景下可被点击。
我曾排查过Z轴位置相关问题,已使用Sorting Layers,但添加Z轴设置后问题仍未解决,故已移除该配置。
核心问题:点击牌堆卡牌后,首个Tableau的首张卡牌会被判定为“不存在”,被当作空Tableau处理,任何King(或本游戏中的Ace)都能放到它的位置;但未点击牌堆按钮时,该卡牌可正常交互。无法理解点击牌堆为何会触发此问题。
用户输入交互代码
public class UserInput : MonoBehaviour { public GameObject slot1;//Already selected card private Reset reset; private Game game; public float zOffSet = 0.5f; // Start is called once before the first execution of Update after the MonoBehaviour is created void Start() { reset = FindAnyObjectByType<Reset>(); game = FindAnyObjectByType<Game>(); slot1 = this.gameObject; } // Update is called once per frame void Update() { GetMouseClick(); } void GetMouseClick() { if (Input.GetMouseButtonDown(0)) { Vector3 mousePosition = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, Input.mousePosition.z)); //See if it hits anything Collider2D hit = Physics2D.OverlapPoint(Camera.main.ScreenToWorldPoint(Input.mousePosition)); if (hit) { if (hit.CompareTag("Card")) { CardHit(hit.gameObject); } else if (hit.CompareTag("Tableaus")) { TableauHit(hit.gameObject); } } } } void CardHit(GameObject selected) { //If face down & not blocked, flip it if (!selected.GetComponent<Selectable>().faceUp) { if (!Blocked(selected)) { selected.GetComponent<Selectable>().faceUp = true; slot1 = this.gameObject; } } //If face up and no card selected already, select it if(selected.GetComponent<Selectable>().faceUp) { if (slot1 == this.gameObject) { slot1 = selected; } //If card selected already (and it's not the same) & new card can go on it, move it. Simply select new card only if not. else if (slot1 != selected) { if (IsValid(selected)) { //Gain 2 points for valid card placement game.score += 2; PlaceCard(selected); } else { slot1 = selected; } } } } void TableauHit(GameObject selected) { if (slot1.CompareTag("Card")) { if (slot1.GetComponent<Selectable>().value == 13 || slot1.GetComponent<Selectable>().value == 1)//In this game, can place either Aces or Kings as the top value { PlaceKingOrAce(selected); } } } bool IsValid(GameObject selected) { bool cardValid = false; Selectable s1 = slot1.GetComponent<Selectable>();//Selectable script from slot 1 Selectable s2 = selected.GetComponent<Selectable>();//Selectable script from the recently selected game obj if (!s2.inDeck) { if(s1.suit==s2.suit) { if (s1.value == (s2.value - 1))//the number of card in play is one less than and the same suit as the card they want to place it on { cardValid = true; } else if (s1.value == 13 && s2.value == 1)//Kings can go on top of Aces { cardValid = true; } } } return cardValid; } void PlaceCard(GameObject selected) { Selectable s1 = slot1.GetComponent<Selectable>(); Selectable s2 = selected.GetComponent<Selectable>(); float yOffSet = 0.5f; slot1.transform.position = new Vector3(selected.transform.position.x, selected.transform.position.y - yOffSet);//, selected.transform.position.z - zOffSet); slot1.transform.parent = selected.transform; //Makes them move with parent cards slot1.GetComponent<Renderer>().sortingOrder = (slot1.transform.parent.GetComponent<Renderer>().sortingOrder) + 1;//One higher than the prior card's sorting order //Remove card from the deck, if it was in there if (s1.inDeck) { game.deck.Remove(slot1.name); } //Remove from the tableau it was in, if it was in one else { game.tableaus[s1.row].Remove(slot1.name); } s1.inDeck = false;//Can't add a card into deck s1.row = s2.row; //"Reset" slot1 slot1 = this.gameObject; //Increase zOffset zOffSet += 0.5f; } void PlaceKingOrAce(GameObject selected) { Selectable s1 = slot1.GetComponent<Selectable>(); Selectable s2 = selected.GetComponent<Selectable>(); slot1.transform.position = new Vector3(selected.transform.position.x, selected.transform.position.y);//, selected.transform.position.z); slot1.transform.parent = selected.transform; //Makes them move with parent cards //Remove card from the deck, if it was in there if (s1.inDeck) { game.deck.Remove(slot1.name); } //Remove from the tableau it was in, if it was in one else { game.tableaus[s1.row].Remove(slot1.name); } s1.inDeck = false;//Can't add a card into deck //"Reset" slot1 slot1 = this.gameObject; } bool Blocked(GameObject selected) { Selectable s2 = selected.GetComponent<Selectable>(); if (s2.name == game.tableaus[s2.row].Last())//Check it's the bottom card { return false; } else { return true; } } }
发牌逻辑代码
public void DealFromDeck() { float xOffset = 1.7f; string card; UpdateSprite[] allCards = FindObjectsOfType<UpdateSprite>(); if (deckLocation < (deck.Count))//can't increment it if at end of deck { card = deck[deckLocation]; } else//Reset when at end of deck { deckLocation = 0; card = deck[deckLocation]; //Erase deck button children foreach(UpdateSprite allCard in allCards) { if(allCard.transform.parent != null) { if (allCard.transform.parent.CompareTag("DeckButton")) { Destroy(allCard.gameObject); } } } } GameObject newCard = Instantiate(cardPrefab, new Vector3(deckButton.transform.position.x + xOffset, deckButton.transform.position.y), Quaternion.identity, deckButton.transform); newCard.GetComponent<Renderer>().sortingOrder = deckLocation+1; newCard.transform.localScale = new Vector3(15, 15, 0); newCard.name = card; newCard.GetComponent<Selectable>().faceUp = true; deckLocation++; deckZOffset += 0.3f; }
问题排查与修复方案
1. 修复点击检测的Z轴偏移问题
2D物理检测中,屏幕坐标转世界坐标时需固定Z轴为相机近平面,否则检测点可能不在正确的交互平面上。修改GetMouseClick方法:
void GetMouseClick() { if (Input.GetMouseButtonDown(0)) { // 固定Z轴为相机近平面,确保检测点在2D交互层 Vector3 mouseWorldPos = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, Camera.main.nearClipPlane)); Collider2D hit = Physics2D.OverlapPoint(mouseWorldPos); if (hit) { if (hit.CompareTag("Card")) { CardHit(hit.gameObject); } else if (hit.CompareTag("Tableaus")) { TableauHit(hit.gameObject); } } } }
2. 修正Blocked方法的逻辑漏洞
原方法依赖game.tableaus的字符串匹配判断可点击性,发牌后可能因数据同步问题出错。改为直接通过层级结构判断是否为Tableau最顶层卡牌:
bool Blocked(GameObject selected) { Transform parentTableau = selected.transform.parent; // 直接判断当前卡牌是否是Tableau的最后一个子物体(最顶层可交互卡牌) return selected.transform != parentTableau.GetChild(parentTableau.childCount - 1); }
3. 解决发牌时的Sorting Order冲突
发牌时确保新牌的Sorting Order高于所有已有卡牌,避免被Tableau的Collider覆盖点击:
// 替换发牌代码中的sorting order设置逻辑 int maxSortingOrder = 0; Renderer[] allRenderers = FindObjectsOfType<Renderer>(); foreach(Renderer r in allRenderers) { if(r.sortingOrder > maxSortingOrder) { maxSortingOrder = r.sortingOrder; } } newCard.GetComponent<Renderer>().sortingOrder = maxSortingOrder + 1;
4. 补全TableauHit的空Tableau判断
添加逻辑判断Tableau是否已有卡牌,避免将King/Ace放到已有卡牌的Tableau上:
void TableauHit(GameObject selected) { if (slot1.CompareTag("Card")) { Selectable slot1Sel = slot1.GetComponent<Selectable>(); // 检查Tableau是否为空 bool isTableauEmpty = selected.transform.childCount == 0; if ((slot1Sel.value == 13 || slot1Sel.value == 1) && isTableauEmpty) { PlaceKingOrAce(selected); } } }
内容的提问来源于stack exchange,提问作者shannon
相关产品推荐
相关产品推荐

