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

Unity 2D非网格不规则地块移动点范围检测实现问题

不规则地块回合制战棋的移动范围检测实现方案

我正在开发一款回合制战棋游戏,玩家部队可在类似Paradox风格的不规则形状省份地块上移动,默认每支部队每回合拥有2点移动点,可移动对应数量的省份。但作为新手,我无法实现准确的移动范围检测——要么判定所有地块都在范围内,要么无法精准计算。由于地块为不规则形状,未采用网格系统,目前我通过Collider重叠来检测相邻地块,以下是我尝试的代码(重点查看GetTilesInRange方法):

{ 
    
    private void Awake()
    {
        Army = GameObject.FindGameObjectsWithTag("Division");   
        Map = GameObject.FindGameObjectsWithTag("Tile");
        DivisionHeightOffset = new Vector3(0f, -ñDivisionHeightOffsetY, 0f);
        
        
    }
    
    private void Update()
    {
       mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
       CheckforSelectedDivisions();       
    }


    private void CheckforSelectedDivisions()    
    {             
      foreach ( GameObject Division in Army)
      {          
        if (Division.GetComponent<DivisionBehaviour>().Selected && !Checked) 
        {                        
          Checked = true;
          SelectedDivision = Division;
          SelectedDivisionPosition = Division.transform.position;
          NameOfSelectedDivision = Division.name;
          DivisionOverlapPoint = Division.transform.position + DivisionHeightOffset; 
          
          DivisionMovementPoints = (Division.GetComponent<DivisionBehaviour>().MovementPoints);
          GetCurrentTile();                                     
        }          
      }
      if(SelectedDivision != null) { if(!SelectedDivision.GetComponent<DivisionBehaviour>().Selected) {Checked = false;}}
    }

    private void GetCurrentTile()
    {
        
        foreach (GameObject Tile in Map) if (Tile.GetComponent<Collider2D>())
        {           
           if(Tile.GetComponent<Collider2D>().OverlapPoint(DivisionOverlapPoint))
           {              
              CurrentTile = Tile;              
              Debug.Log(NameOfSelectedDivision + " is in " + CurrentTile.name);
              CurrentTilePosition = Tile.transform.position;
              CurrentTileBounds = Tile.GetComponent<Collider2D>().bounds;
              GetTilesInRange();
           }      
        }
    }

    private void GetTilesInRange()
    {                                       
       i = DivisionMovementPoints;

        
        foreach (GameObject TileB in Map) if (TileB.GetComponent<Collider2D>() && TileB != CurrentTile && !TileB.GetComponent<TileInfo>().Movable && !TileB.GetComponent<TileInfo>().Movable2)
        {
        
              
             if(TileB.GetComponent<Collider2D>().bounds.Intersects(CurrentTileBounds))
             {
                  
               MovableTile = TileB;
               Debug.Log(MovableTile.name + " is in range!");

                    
                            
             }

             else if(i > 0 && TileB.GetComponent<Collider2D>().bounds.Intersects(MovableTile.GetComponent<Collider2D>().bounds))
             {
                  
                    i = i - 1;
                  MovableTile = TileB;
                  Debug.Log(MovableTile.name + " is in range!");
             }

        }


       
    }
}

核心问题分析

你的GetTilesInRange方法存在逻辑缺陷:

  • 单次遍历试图处理多层移动,逻辑混乱,无法正确累加移动步数
  • MovableTile变量被反复覆盖,无法追踪已遍历的节点
  • 未区分不同移动步数的层级,导致范围判定误差

正确实现方案(基于BFS广度优先搜索)

不规则地块的移动范围检测本质是图的遍历问题:每个地块是节点,相邻地块是边,移动点数是遍历的最大深度。用BFS可以精准计算所有可达地块:

步骤1:预先生成地块邻接表

避免每次检测都遍历所有Collider,初始化时为每个地块生成邻接列表:

public class TileInfo : MonoBehaviour
{
    public List<TileInfo> Neighbors = new List<TileInfo>();
    public bool IsMovable; // 标记地块是否可移动

    void Awake()
    {
        // 初始化时计算所有相邻地块
        foreach (GameObject tileObj in GameObject.FindGameObjectsWithTag("Tile"))
        {
            TileInfo otherTile = tileObj.GetComponent<TileInfo>();
            if (otherTile != this && GetComponent<Collider2D>().bounds.Intersects(otherTile.GetComponent<Collider2D>().bounds))
            {
                Neighbors.Add(otherTile);
            }
        }
    }
}

步骤2:实现BFS移动范围检测

替换原GetTilesInRange方法,用BFS遍历所有可达地块:

private void GetTilesInRange()
{
    // 重置所有地块的可移动标记
    foreach (GameObject tileObj in Map)
    {
        TileInfo tile = tileObj.GetComponent<TileInfo>();
        tile.IsMovable = false;
    }

    TileInfo currentTileInfo = CurrentTile.GetComponent<TileInfo>();
    // 队列存储(当前地块,剩余移动点数)
    Queue<(TileInfo tile, int remainingMovePoints)> queue = new Queue<(TileInfo, int)>();
    HashSet<TileInfo> visited = new HashSet<TileInfo>();

    // 起点入队(当前地块作为起点,不算可移动目标)
    queue.Enqueue((currentTileInfo, DivisionMovementPoints));
    visited.Add(currentTileInfo);

    while (queue.Count > 0)
    {
        var (current, remaining) = queue.Dequeue();

        // 剩余移动点数>0时,遍历相邻地块
        if (remaining > 0)
        {
            foreach (TileInfo neighbor in current.Neighbors)
            {
                if (!visited.Contains(neighbor))
                {
                    neighbor.IsMovable = true;
                    visited.Add(neighbor);
                    queue.Enqueue((neighbor, remaining - 1));
                    Debug.Log(neighbor.gameObject.name + " is in range!");
                }
            }
        }
    }
}

步骤3:优化现有代码

  • 避免在Update中反复调用GetComponent,提前缓存组件引用
  • 用事件监听替代CheckforSelectedDivisions的每帧遍历,提升性能
  • 修正DivisionHeightOffset里的笔误ñDivisionHeightOffsetY,改为合法变量名(如_divisionHeightOffsetY)

额外优化建议

  • 地块数量较多时,用空间划分(如QuadTree)减少邻接表预计算的碰撞检测次数
  • 支持不同地块消耗不同移动点数(如山地耗2点、平原耗1点),只需在BFS入队时调整剩余点数
  • 视觉上高亮可移动地块,提升玩家操作体验

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 20:30:55