Carcassonne卡牌游戏道路计分函数随机重复调用问题求助
《卡卡颂》桌游开发问题:道路检测函数重复触发排查
我正在开发桌游《卡卡颂》(Carcassonne),使用预制卡牌,每张卡牌用s、v、j、z四个变量对应本国语言的方位体系。我编写了函数识别卡牌上标记为"R"(代表道路)的边,并查找相邻卡牌——比如当s=="R"时,会调用第二个函数查找上方卡牌,同时设置lastSide为"j"避免回溯。由于道路只会出现在卡牌的两个边上,所以设置了变量nicovani。
目前遇到的问题:有时放置卡牌后,函数会先从刚放置的卡牌触发一次调用,接着从之前放置的卡牌触发一次,随后又从刚放置的卡牌再次触发,无法定位问题根源,这是完成游戏前最后要解决的问题。
相关代码
public string s; public string v; public string j; public string z; private int cross = 0; public bool Layed; public bool IsRoadEnding; private string lastSide; private int nicovani = 0; private bool isScored = false; public void OnMouseDown() { if(Layed == false) { if(r == 1) { r = 2; IsHere = false; StartCoroutine(Follow()); } else { if(IsHere == true) { TheWholeThing(); } else { r = 1; transform.position = spawner.transform.position; } } } } void TheWholeThing() { setPos = new Vector2 (Mathf.RoundToInt(transform.position.x), Mathf.RoundToInt(transform.position.y)); r = 1; transform.position = setPos; FindTile(); CheckTile(asociatedTile); if(r == 1) { drawer.SpawnCard(); SetTile(asociatedTile); gmg.GenerateGrid(transform.position.x, transform.position.y+1, "j" , s); gmg.GenerateGrid(transform.position.x, transform.position.y-1, "s", j); gmg.GenerateGrid(transform.position.x +1, transform.position.y, "z", v); gmg.GenerateGrid(transform.position.x -1, transform.position.y, "v", z); Layed = true; startingTile = gameObject.transform; if(nicovani < 2) { FindScoringRoad(transform.position.x, transform.position.y, "", s, v, j, z); return; } } else { return; } IsHere = false; } void FindScoringRoad(float x, float y, string side, string s, string v, string j, string z) { if(isScored == false) { lastSide = side; if(lastSide != "s") { if(s == "R") { cross = 0; Debug.Log("s"); FindNextCard("j", x, y + 1); } } if(lastSide != "v") { if(v == "R") { cross = 0; Debug.Log("v"); FindNextCard("z", x + 1, y); } } if(lastSide != "j") { if(j == "R") { if(nicovani == 2) { nicovani = 0; return; } else { cross = 0; Debug.Log("j"); FindNextCard("s", x , y - 1); } } } if(lastSide != "z") { if(z == "R") { if(nicovani == 2) { nicovani = 0; return; } else { cross = 0; Debug.Log("z"); Debug.Log(x + " " + y); FindNextCard("v", x - 1, y); } } } cross = 0; return; } } void FindNextCard(string side, float x, float y) { if(x == startingTile.position.x && y == startingTile.position.y) { Debug.Log("Road Closed"); isScored = true; return; } foreach(GameObject card in drawer.spawnedCards) { if(card.transform.position.x == x && card.transform.position.y == y) { var cardS = card.GetComponent<Card>(); if(cross < 1) { FindScoringRoad(card.transform.position.x, card.transform.position.y, side, cardS.s, cardS.v, cardS.j, cardS.z); cross++; } return; } } Debug.Log("Ends here"); cross = 0; nicovani++; return; }
问题根源分析
- 实例变量状态冲突:
nicovani、cross、isScored、lastSide都是Card类的私有成员,每个卡牌实例都有独立的这些变量。递归调用相邻卡牌的FindScoringRoad时,会切换到相邻卡牌的实例变量,导致不同卡牌的状态互相干扰,触发重复调用。 - 起始位置覆盖问题:
startingTile在新卡牌放置时会被赋值为当前卡牌的transform,旧卡牌的递归调用会使用这个新值,导致道路闭合判断逻辑混乱,引发重复检测。 - 回溯状态管理失效:
lastSide是当前卡牌实例的变量,递归到相邻卡牌后无法保留原卡牌的lastSide状态,返回时可能重复检查同一方向,造成循环调用。
修复方案
1. 将递归状态参数化
把所有影响递归逻辑的状态变量(lastSide、nicovani、cross、isScored、起始位置)改成FindScoringRoad和FindNextCard的参数,让每次递归调用都携带独立的状态,避免不同卡牌实例的变量互相干扰。
修改后的核心函数示例:
// 修改FindScoringRoad,传递独立状态参数 void FindScoringRoad(float x, float y, string lastSide, string s, string v, string j, string z, int nicovani, bool isScored, Vector2 startPos) { if(isScored) return; // 检查s方向道路 if(lastSide != "s" && s == "R") { FindNextCard("j", x, y + 1, nicovani, isScored, startPos); } // 检查v方向道路 if(lastSide != "v" && v == "R") { FindNextCard("z", x + 1, y, nicovani, isScored, startPos); } // 检查j方向道路 if(lastSide != "j" && j == "R") { if(nicovani == 2) return; FindNextCard("s", x, y - 1, nicovani, isScored, startPos); } // 检查z方向道路 if(lastSide != "z" && z == "R") { if(nicovani == 2) return; FindNextCard("v", x - 1, y, nicovani, isScored, startPos); } } void FindNextCard(string side, float x, float y, int nicovani, bool isScored, Vector2 startPos) { // 判断是否回到起始位置(道路闭合) if(x == startPos.x && y == startPos.y) { Debug.Log("Road Closed"); isScored = true; return; } foreach(GameObject card in drawer.spawnedCards) { if(card.transform.position.x == x && card.transform.position.y == y) { var cardS = card.GetComponent<Card>(); // 传递当前状态到下一次递归 FindScoringRoad(card.transform.position.x, card.transform.position.y, side, cardS.s, cardS.v, cardS.j, cardS.z, nicovani, isScored, startPos); return; } } Debug.Log("Ends here"); nicovani++; return; } // 在TheWholeThing中调用时传入初始状态 void TheWholeThing() { // ... 原有代码 ... Layed = true; Vector2 startPos = transform.position; if(nicovani < 2) { FindScoringRoad(transform.position.x, transform.position.y, "", s, v, j, z, 0, false, startPos); return; } // ... 原有代码 ... }
2. 移除Card类中的状态变量
删除Card类中不再需要的私有成员:cross、lastSide、nicovani、isScored、startingTile,避免这些变量残留引发其他问题。
3. 优化道路检测逻辑
可以考虑使用迭代代替递归,或者创建一个独立的RoadDetectionManager单例类,统一管理所有道路检测的状态,进一步避免实例变量冲突。
内容的提问来源于stack exchange,提问作者Stephan
相关产品推荐
相关产品推荐

