(client) feat:添加基地界面到游玩界面的过程,添加存档管理,技能树变得可用 (#58)

Co-authored-by: m0_75251201 <m0_75251201@noreply.gitcode.com>
Reviewed-on: http://47.107.252.169:3000/Roguelite-Game-Developing-Team/Gen_Hack-and-Slash-Roguelite/pulls/58
This commit is contained in:
2025-10-03 00:31:34 +08:00
parent aff747be17
commit dd9d90439d
134 changed files with 10322 additions and 4872 deletions

View File

@@ -1,26 +1,41 @@
using UnityEngine;
using UnityEngine.Tilemaps;
using Data; // For TileDef
using System.Collections.Generic;
using Managers; // For Dictionary
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 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;
@@ -29,26 +44,63 @@ namespace Map
}
}
/// <summary>
/// 初始化函数,目前为空。
/// </summary>
public void Init()
{
}
/// <summary>
/// 获取当前地形所属的维度ID。
/// </summary>
/// <returns>维度ID字符串。</returns>
public string GetDimensionID()
{
return dimension.DimensionId;
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();
@@ -56,104 +108,256 @@ namespace Map
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 > plantTilemap > baseTilemap。
/// 优先级buildingTilemap &gt; plantTilemap &gt; baseTilemap。
/// 此函数经优化以减少GC。
/// </summary>
/// <param name="cellPosition">要查询的格子坐标。</param>
/// <returns>包含瓦片碰撞体类型和通行成本的TileState结构。</returns>
public TileState GetTileState(Vector3Int cellPosition)
{
Tile.ColliderType finalColliderType = Tile.ColliderType.None;
float finalTravelCost = 0f; // 默认通行成本
var buildingTileDef = GetTileDef(buildingTilemap, cellPosition);
var plantTileDef = GetTileDef(plantTilemap, cellPosition);
var baseTileDef = GetTileDef(baseTilemap, cellPosition);
// 瓦片层级优先级Building > Plant > Base
// 存储查询到的瓦片定义及其所属Tilemap用于后续决定通行成本
Dictionary<Tilemap, (TileBase tile, TileDef def)> activeTiles = new Dictionary<Tilemap, (TileBase, TileDef)>();
var finalColliderType = Tile.ColliderType.None;
var finalTravelCost = 0f;
// 1. 尝试从 Building 层获取瓦片信息
TileBase buildingTile = buildingTilemap.GetTile(cellPosition);
if (buildingTile != null && DefineManager.Instance != null) // 确保DefineManager已实例化
// 确定最终的碰撞体类型 (优先级 Building > Plant > Base)
if (buildingTileDef != null && buildingTileDef.collider != Tile.ColliderType.None)
{
TileDef buildingTileDef = DefineManager.Instance.FindDefine<TileDef>(buildingTile.name);
if (buildingTileDef != null)
{
activeTiles[buildingTilemap] = (buildingTile, buildingTileDef);
if (buildingTileDef.collider != Tile.ColliderType.None)
{
finalColliderType = buildingTileDef.collider; // Building层的碰撞体优先级最高
}
}
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;
}
// 2. 尝试从 Plant 层获取瓦片信息 (只有当 Building 层没有提供碰撞体时才考虑)
if (finalColliderType == Tile.ColliderType.None)
{
TileBase plantTile = plantTilemap.GetTile(cellPosition);
if (plantTile != null && DefineManager.Instance != null)
{
TileDef plantTileDef = DefineManager.Instance.FindDefine<TileDef>(plantTile.name);
if (plantTileDef != null)
{
activeTiles[plantTilemap] = (plantTile, plantTileDef);
if (plantTileDef.collider != Tile.ColliderType.None)
{
finalColliderType = plantTileDef.collider; // Plant层的碰撞体优先级次之
}
}
}
}
// 3. 尝试从 Base 层获取瓦片信息 (只有当 Building 和 Plant 层都没有提供碰撞体时才考虑)
if (finalColliderType == Tile.ColliderType.None)
{
TileBase baseTile = baseTilemap.GetTile(cellPosition);
if (baseTile != null && DefineManager.Instance != null)
{
TileDef baseTileDef = DefineManager.Instance.FindDefine<TileDef>(baseTile.name);
if (baseTileDef != null)
{
activeTiles[baseTilemap] = (baseTile, baseTileDef);
if (baseTileDef.collider != Tile.ColliderType.None)
{
finalColliderType = baseTileDef.collider; // Base层的碰撞体优先级最低
}
}
}
}
// 根据最终碰撞体类型确定通行成本
// 确定最终的通行成本
if (finalColliderType != Tile.ColliderType.None)
{
finalTravelCost = 1f; // 有碰撞体通行成本为1不可通过
finalTravelCost = 1f; // 有碰撞体,通行成本为1 (不可通过)
}
else
{
// 如果没有碰撞体则取优先级最高的瓦片的tileCost作为最终通行成本。
// 优先级 Building > Plant > Base
TileDef highestPriorityDefWithCost = null;
if (activeTiles.ContainsKey(buildingTilemap))
if (buildingTileDef != null)
{
highestPriorityDefWithCost = activeTiles[buildingTilemap].def;
finalTravelCost = buildingTileDef.tileCost;
}
else if (activeTiles.ContainsKey(plantTilemap))
else if (plantTileDef != null)
{
highestPriorityDefWithCost = activeTiles[plantTilemap].def;
finalTravelCost = plantTileDef.tileCost;
}
else if (activeTiles.ContainsKey(baseTilemap))
else if (baseTileDef != null)
{
highestPriorityDefWithCost = activeTiles[baseTilemap].def;
finalTravelCost = baseTileDef.tileCost;
}
if (highestPriorityDefWithCost != null)
{
finalTravelCost = highestPriorityDefWithCost.tileCost;
}
// 如果任何层都没有有效瓦片或DefineManager未实例化则保持 finalTravelCost 为默认值 0f
// 如果没有任何有效的瓦片定义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;
}
}
}