using UnityEngine; using UnityEngine.Tilemaps; using Data; using Managers; namespace Map { public class Landform : MonoBehaviour { // 基础地形瓦片地图 public Tilemap baseTilemap; // 建筑瓦片地图 public Tilemap buildingTilemap; // 植物瓦片地图 public Tilemap plantTilemap; // 维度信息 public Dimension dimension; /// /// 定义瓦片状态结构体 /// public struct TileState { // 瓦片的世界坐标 public Vector3Int position; // 碰撞体类型 public Tile.ColliderType colliderType; // 通行成本 public float travelCost; /// /// 构造函数,初始化瓦片状态。 /// /// 瓦片的世界坐标。 /// 碰撞体类型。 /// 通行成本。 public TileState(Vector3Int pos, Tile.ColliderType collider, float cost) { position = pos; colliderType = collider; travelCost = cost; } } /// /// 初始化函数,目前为空。 /// public void Init() { } /// /// 获取当前地形所属的维度ID。 /// /// 维度ID字符串。 public string GetDimensionID() { if (dimension) return dimension.DimensionId; Debug.LogWarning("Landform未分配Dimension对象,返回默认ID。"); return "dimension_not_set"; } /// /// 获取基础瓦片地图的尺寸。 /// /// 瓦片地图的尺寸 (Vector3Int)。 public Vector3Int GetSize() { return baseTilemap.size; } public Vector3Int GetOrigin() { return baseTilemap.origin; } /// /// 将格子坐标转换为世界坐标。 /// /// 格子坐标。 /// 对应的世界坐标。 public Vector3 GetWorldCoordinates(Vector3Int coord) { return baseTilemap.CellToWorld(coord); } /// /// 将世界坐标转换为格子坐标。 /// /// 世界坐标。 /// 对应的格子坐标。 public Vector3Int GetCellCoordinates(Vector3 worldPosition) { return baseTilemap.WorldToCell(worldPosition); } /// /// 清除所有瓦片地图上的所有瓦片。 /// public void Clear() { baseTilemap.ClearAllTiles(); buildingTilemap.ClearAllTiles(); plantTilemap.ClearAllTiles(); } /// /// 私有辅助方法:安全地从指定Tilemap层获取TileDef。 /// /// 要查询的Tilemap。 /// 格子坐标。 /// 找到的TileDef,如果没有瓦片或定义则为null。 private TileDef GetTileDef(Tilemap tilemap, Vector3Int cellPosition) { var tile = tilemap.GetTile(cellPosition); return tile ? DefineManager.Instance.FindDefine(tile.name) : null; } /// /// 私有辅助方法:从指定的Tilemap层获取瓦片的通行信息。 /// 可以确定瓦片是否导致不可通行,并返回其通行成本。 /// /// 要查询的Tilemap。 /// 格子坐标。 /// 一个元组,包含可空成本 (null表示无特定成本) 和是否不可通行。 private (float? cost, bool isImpassable) _GetTileCostInfo(Tilemap tilemap, Vector3Int cellPosition) { var tile = tilemap.GetTile(cellPosition); if (!tile) { return (null, false); // 该层无瓦片 } var tileDef = DefineManager.Instance.FindDefine(tile.name); if (tileDef == null) { // 如果瓦片存在但无定义,视为其不影响通行成本,也不导致路径阻塞。 return (null, false); } // 如果瓦片有碰撞体或者其定义通行成本已经达到或超过 1f,则该瓦片使得格子不可通行。 if (tileDef.collider != Tile.ColliderType.None || tileDef.tileCost >= 1f) { return (1f, true); // 不可通行 } return (tileDef.tileCost, false); // 可通行,并返回其成本 } /// /// 获取指定格子坐标上的瓦片状态,包括碰撞体类型和通行成本。 /// 优先级:buildingTilemap > plantTilemap > baseTilemap。 /// 此函数经优化以减少GC。 /// /// 要查询的格子坐标。 /// 包含瓦片碰撞体类型和通行成本的TileState结构。 public TileState GetTileState(Vector3Int cellPosition) { var buildingTileDef = GetTileDef(buildingTilemap, cellPosition); var plantTileDef = GetTileDef(plantTilemap, cellPosition); var baseTileDef = GetTileDef(baseTilemap, cellPosition); var finalColliderType = Tile.ColliderType.None; var finalTravelCost = 0f; // 确定最终的碰撞体类型 (优先级 Building > Plant > Base) if (buildingTileDef != null && buildingTileDef.collider != Tile.ColliderType.None) { finalColliderType = buildingTileDef.collider; } else if (plantTileDef != null && plantTileDef.collider != Tile.ColliderType.None) { finalColliderType = plantTileDef.collider; } else if (baseTileDef != null && baseTileDef.collider != Tile.ColliderType.None) { finalColliderType = baseTileDef.collider; } // 确定最终的通行成本 if (finalColliderType != Tile.ColliderType.None) { finalTravelCost = 1f; // 有碰撞体,则通行成本为1 (不可通过) } else { // 如果没有碰撞体,则取优先级最高的瓦片的tileCost作为最终通行成本。 // 优先级 Building > Plant > Base if (buildingTileDef != null) { finalTravelCost = buildingTileDef.tileCost; } else if (plantTileDef != null) { finalTravelCost = plantTileDef.tileCost; } else if (baseTileDef != null) { finalTravelCost = baseTileDef.tileCost; } // 如果没有任何有效的瓦片定义,finalTravelCost 保持默认值 0f } return new TileState(cellPosition, finalColliderType, finalTravelCost); } /// /// 获取指定世界坐标上的瓦片状态,包括碰撞体类型和通行成本。 /// /// 要查询的世界坐标。 /// 包含瓦片碰撞体类型和通行成本的TileState结构。 public TileState GetTileState(Vector3 worldPosition) { var cellPosition = baseTilemap.WorldToCell(worldPosition); return GetTileState(cellPosition); } /// /// 获取指定格子坐标的通行成本。 /// 优先级:buildingTilemap > plantTilemap > baseTilemap。 /// 区域外、存在碰撞体或通行成本达到1f的区域均视为不可通行 (返回1f)。 /// 此函数为路径查找优化,避免GC。 /// /// 要查询的格子坐标。 /// 通行成本(速度降低率),1f表示不可通行。 public float GetTravelCost(Vector3Int cellPosition) { // 0. 检查格子是否在基本地图区域(baseTilemap.cellBounds)之外。 // 假设baseTilemap.cellBounds定义了陆地的总体逻辑边界。 if (!baseTilemap.cellBounds.Contains(cellPosition)) { return 1f; // 区域外始终为1 (不可通行) } float? highestPriorityTraversableCost = null; // 用于存储最高优先级的可通行成本 // 1. 检查 Building 层 var buildingResult = _GetTileCostInfo(buildingTilemap, cellPosition); if (buildingResult.isImpassable) { return 1f; // Building 层导致不可通行 } if (buildingResult.cost.HasValue) { highestPriorityTraversableCost = buildingResult.cost.Value; } // 2. 检查 Plant 层 (仅当 Building 层未导致不可通行且未提供通行成本时) if (!highestPriorityTraversableCost.HasValue) // Building层未提供有效成本时,才考虑Plant层 { var plantResult = _GetTileCostInfo(plantTilemap, cellPosition); if (plantResult.isImpassable) { return 1f; // Plant 层导致不可通行 } if (plantResult.cost.HasValue) { highestPriorityTraversableCost = plantResult.cost.Value; } } // 3. 检查 Base 层 (仅当 Building 和 Plant 层均未导致不可通行且未提供通行成本时) if (!highestPriorityTraversableCost.HasValue) // Building和Plant层都未提供有效成本时,才考虑Base层 { var baseResult = _GetTileCostInfo(baseTilemap, cellPosition); if (baseResult.isImpassable) { return 1f; // Base 层导致不可通行 } if (baseResult.cost.HasValue) { highestPriorityTraversableCost = baseResult.cost.Value; } } // 如果没有发现不可通行的条件,返回最高优先级的可通行成本,如果都没有则为 0f (完全可通行)。 var finalCost = highestPriorityTraversableCost ?? 0f; // 再次确认最终成本,如果达到或超过1f,依然视为不可通行。 if (finalCost >= 1f) { return 1f; } return finalCost; } public float GetTravelCost(Vector3 cellPosition) { return GetTravelCost(GetCellCoordinates(cellPosition)); } /// /// 获取实体所占区域的通行成本。 /// 如果区域内任何一个格子不可通行 (GetTravelCost返回1f),则整个区域不可通行 (返回1f)。 /// 否则,取实体中心格子的通行成本。 /// 此函数适用于具有体积的实体进行路径查找。 /// /// 实体的瓦片坐标。 /// 实体的尺寸,以格子为单位 (例如:Vector3Int(2, 2, 1) 表示一个 2x2 的实体)。 /// 区域的通行成本(速度降低率),1f表示不可通行。 public float GetTravelCostForArea(Vector3Int worldPosition, Vector3Int size) { // 将传入的格子坐标视为实体的中心格子 var entityCenterCell = worldPosition; var minX = entityCenterCell.x - size.x / 2; var minY = entityCenterCell.y - size.y / 2; var maxX = minX + size.x; var maxY = minY + size.y; // 遍历实体覆盖的所有格子 for (var x = minX; x < maxX; x++) { for (var y = minY; y < maxY; y++) { // 对于当前遍历的每个格子,其Z坐标使用实体中心格子的Z坐标 var currentCell = new Vector3Int(x, y, entityCenterCell.z); // 使用优化过的 GetTravelCost 函数判断每个格子的通行成本 // 假设 GetTravelCost(Vector3Int cellPosition) 是最底层的、直接获取格子通行成本的函数 var cellCost = GetTravelCost(currentCell); if (cellCost >= 1f) { return 1f; // 区域内任何一个格子不可通行,则整个区域不可通行 (快速失败原则) } } } // 如果所有覆盖的格子都可通行,则返回实体中心格子的通行成本 return GetTravelCost(entityCenterCell); } /// /// 获取实体所占区域的通行成本。 /// 如果区域内任何一个格子不可通行 (GetTravelCost返回1f),则整个区域不可通行 (返回1f)。 /// 否则,取实体中心格子的通行成本。 /// 此函数适用于具有体积的实体进行路径查找。 /// /// 实体的世界坐标 (假定为中心点)。 /// 实体的尺寸,以格子为单位 (例如:Vector3Int(2, 2, 1) 表示一个 2x2 的实体)。 /// 区域的通行成本(速度降低率),1f表示不可通行。 public float GetTravelCostForArea(Vector3 worldPosition, Vector3Int size) { // 将实体的世界坐标转换为其所在的格子坐标,然后调用基于格子坐标的重载函数 var entityCenterCell = baseTilemap.WorldToCell(worldPosition); return GetTravelCostForArea(entityCenterCell, size); } public TileBase SetBaseTile(TileBase tile, Vector3Int position) { var current=baseTilemap.GetTile(position); baseTilemap.SetTile(position, tile); return current; } } }