Files
Gen_Hack-and-Slash-Roguelite/Client/Assets/Scripts/Map/Landform.cs

364 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
/// <summary>
/// 定义瓦片状态结构体
/// </summary>
public struct TileState
{
// 瓦片的世界坐标
public Vector3Int position;
// 碰撞体类型
public Tile.ColliderType colliderType;
// 通行成本
public float travelCost;
/// <summary>
/// 构造函数,初始化瓦片状态。
/// </summary>
/// <param name="pos">瓦片的世界坐标。</param>
/// <param name="collider">碰撞体类型。</param>
/// <param name="cost">通行成本。</param>
public TileState(Vector3Int pos, Tile.ColliderType collider, float cost)
{
position = pos;
colliderType = collider;
travelCost = cost;
}
}
/// <summary>
/// 初始化函数,目前为空。
/// </summary>
public void Init()
{
}
/// <summary>
/// 获取当前地形所属的维度ID。
/// </summary>
/// <returns>维度ID字符串。</returns>
public string GetDimensionID()
{
if (dimension) return dimension.DimensionId;
Debug.LogWarning("Landform未分配Dimension对象返回默认ID。");
return "dimension_not_set";
}
/// <summary>
/// 获取基础瓦片地图的尺寸。
/// </summary>
/// <returns>瓦片地图的尺寸 (Vector3Int)。</returns>
public Vector3Int GetSize()
{
return baseTilemap.size;
}
public Vector3Int GetOrigin()
{
return baseTilemap.origin;
}
/// <summary>
/// 将格子坐标转换为世界坐标。
/// </summary>
/// <param name="coord">格子坐标。</param>
/// <returns>对应的世界坐标。</returns>
public Vector3 GetWorldCoordinates(Vector3Int coord)
{
return baseTilemap.CellToWorld(coord);
}
/// <summary>
/// 将世界坐标转换为格子坐标。
/// </summary>
/// <param name="worldPosition">世界坐标。</param>
/// <returns>对应的格子坐标。</returns>
public Vector3Int GetCellCoordinates(Vector3 worldPosition)
{
return baseTilemap.WorldToCell(worldPosition);
}
/// <summary>
/// 清除所有瓦片地图上的所有瓦片。
/// </summary>
public void Clear()
{
baseTilemap.ClearAllTiles();
buildingTilemap.ClearAllTiles();
plantTilemap.ClearAllTiles();
}
/// <summary>
/// 私有辅助方法安全地从指定Tilemap层获取TileDef。
/// </summary>
/// <param name="tilemap">要查询的Tilemap。</param>
/// <param name="cellPosition">格子坐标。</param>
/// <returns>找到的TileDef如果没有瓦片或定义则为null。</returns>
private TileDef GetTileDef(Tilemap tilemap, Vector3Int cellPosition)
{
var tile = tilemap.GetTile(cellPosition);
return tile ? DefineManager.Instance.FindDefine<TileDef>(tile.name) : null;
}
/// <summary>
/// 私有辅助方法从指定的Tilemap层获取瓦片的通行信息。
/// 可以确定瓦片是否导致不可通行,并返回其通行成本。
/// </summary>
/// <param name="tilemap">要查询的Tilemap。</param>
/// <param name="cellPosition">格子坐标。</param>
/// <returns>一个元组,包含可空成本 (null表示无特定成本) 和是否不可通行。</returns>
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<TileDef>(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); // 可通行,并返回其成本
}
/// <summary>
/// 获取指定格子坐标上的瓦片状态,包括碰撞体类型和通行成本。
/// 优先级buildingTilemap &gt; plantTilemap &gt; baseTilemap。
/// 此函数经优化以减少GC。
/// </summary>
/// <param name="cellPosition">要查询的格子坐标。</param>
/// <returns>包含瓦片碰撞体类型和通行成本的TileState结构。</returns>
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);
}
/// <summary>
/// 获取指定世界坐标上的瓦片状态,包括碰撞体类型和通行成本。
/// </summary>
/// <param name="worldPosition">要查询的世界坐标。</param>
/// <returns>包含瓦片碰撞体类型和通行成本的TileState结构。</returns>
public TileState GetTileState(Vector3 worldPosition)
{
var cellPosition = baseTilemap.WorldToCell(worldPosition);
return GetTileState(cellPosition);
}
/// <summary>
/// 获取指定格子坐标的通行成本。
/// 优先级buildingTilemap &gt; plantTilemap &gt; baseTilemap。
/// 区域外、存在碰撞体或通行成本达到1f的区域均视为不可通行 (返回1f)。
/// 此函数为路径查找优化避免GC。
/// </summary>
/// <param name="cellPosition">要查询的格子坐标。</param>
/// <returns>通行成本速度降低率1f表示不可通行。</returns>
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));
}
/// <summary>
/// 获取实体所占区域的通行成本。
/// 如果区域内任何一个格子不可通行 (GetTravelCost返回1f),则整个区域不可通行 (返回1f)。
/// 否则,取实体中心格子的通行成本。
/// 此函数适用于具有体积的实体进行路径查找。
/// </summary>
/// <param name="worldPosition">实体的瓦片坐标。</param>
/// <param name="size">实体的尺寸,以格子为单位 (例如Vector3Int(2, 2, 1) 表示一个 2x2 的实体)。</param>
/// <returns>区域的通行成本速度降低率1f表示不可通行。</returns>
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);
}
/// <summary>
/// 获取实体所占区域的通行成本。
/// 如果区域内任何一个格子不可通行 (GetTravelCost返回1f),则整个区域不可通行 (返回1f)。
/// 否则,取实体中心格子的通行成本。
/// 此函数适用于具有体积的实体进行路径查找。
/// </summary>
/// <param name="worldPosition">实体的世界坐标 (假定为中心点)。</param>
/// <param name="size">实体的尺寸,以格子为单位 (例如Vector3Int(2, 2, 1) 表示一个 2x2 的实体)。</param>
/// <returns>区域的通行成本速度降低率1f表示不可通行。</returns>
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;
}
}
}