Files
Gen_Hack-and-Slash-Roguelite/Client/Assets/Scripts/EventWorkClass/Event_EntityGenerater.cs

427 lines
21 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 System;
using System.Collections.Generic; // 新增用于List<T>
using Data;
using Managers;
using UnityEngine;
using Random = UnityEngine.Random;
using Newtonsoft.Json; // 新增Newtonsoft.Json的引用
namespace EventWorkClass
{
/// <summary>
/// Event_EntityGenerater 事件的配置数据结构。
/// </summary>
[Serializable]
public class EntityGenerateConfig
{
/// <summary>
/// 要生成的实体定义列表,将从中随机选择。
/// </summary>
public List<EntityDefinitionEntry> DefinitionsToChooseFrom;
/// <summary>
/// 要生成的实体总数。
/// </summary>
public int Count = 1; // 默认生成一个
/// <summary>
/// 生成位置类型。
/// </summary>
public EntitySpawnLocationType LocationType;
// 以下是各种LocationType可能需要的配置参数
/// <summary>
/// 用于 AroundSpecificCoordinates 类型:生成中心坐标。
/// </summary>
public Vector3 CenterCoordinates;
/// <summary>
/// 用于 AroundSpecificCoordinates 或 AroundTargetEntity 类型:生成半径。
/// </summary>
public float Radius = 10f; // 默认半径
/// <summary>
/// 用于 InsideSpecificBuildingType 类型目标建筑的类型ID。
/// </summary>
public string BuildingTypeDefName;
/// <summary>
/// 用于 AroundTargetEntity 类型:目标派系的定义名 (factionDefName)。
/// </summary>
public string TargetFactionDefName;
/// <summary>
/// 用于 AtPredefinedSpawnPoint 类型预定义生成点的ID。
/// </summary>
public string SpawnPointTileMapDefName;
/// <summary>
/// 用于 OffMap 类型:距离地图边界的额外偏移量。
/// </summary>
public float OffMapOffset = 5f;
}
/// <summary>
/// 单个实体定义的配置条目。
/// </summary>
[Serializable] // 保持可序列化以便在Unity Inspector中显示
public class EntityDefinitionEntry
{
/// <summary>
/// 实体定义名 (defName)。
/// </summary>
public string DefName;
/// <summary>
/// 实体定义的具体类型名。
/// </summary>
public string DefTypeName;
}
/// <summary>
/// 定义生成器生成地图实体的可能位置类型。
/// </summary>
public enum EntitySpawnLocationType
{
/// <summary>
/// 未指定或无效的生成位置类型。
/// </summary>
None = 0,
/// <summary>
/// 在地图的可玩区域边界之外生成实体。
/// 例如:用于在地图边缘或不可见区域生成物体,然后开始移动进入地图。
/// </summary>
OffMap,
/// <summary>
/// 在指定类型建筑的内部或附近生成实体。
/// 例如:在 "工厂"、"商店"、"居住区" 等建筑内或其出入口。
/// 需要一个标识符来指定建筑类型。
/// </summary>
InsideSpecificBuildingType,
/// <summary>
/// 在指定坐标点的周围区域内生成实体。
/// 需要额外的坐标参数和半径/范围参数。
/// </summary>
AroundSpecificCoordinates,
/// <summary>
/// 在特定派系或其他指定目标实体例如NPC或特定对象的周围区域内生成实体。
/// 需要一个标识符来指定目标派系的定义名,以及一个半径/范围参数。
/// </summary>
AroundTargetEntity,
/// <summary>
/// 在地图上的随机可生成点生成实体。
/// </summary>
RandomlyOnMap,
/// <summary>
/// 在预定义的生成点Spawn Point生成实体。
/// 这些点通常由地图设计者放置并带有唯一的ID或名称。
/// </summary>
AtPredefinedSpawnPoint
}
public class Event_EntityGenerater : EventWorkClassBase
{
private EntityGenerateConfig _config;
private List<EntityDef> _validatedEntityDefs; // 用于存储所有已验证的实体定义
/// <summary>
/// 初始化实体生成器事件。
/// </summary>
/// <param name="value">包含事件配置的JSON字符串。</param>
public override void Init(string value)
{
if (string.IsNullOrEmpty(value))
{
Debug.LogError("初始化值为空或null。请提供一个JSON配置字符串。");
return;
}
try
{
_config = JsonConvert.DeserializeObject<EntityGenerateConfig>(value); // 使用Newtonsoft.Json
}
catch (Exception ex)
{
Debug.LogError($"解析配置JSON时出错: {value}。异常信息: {ex.Message}");
return; // 解析失败,直接返回
}
if (_config == null)
{
Debug.LogError($"无法解析配置JSON: {value}");
return;
}
if (_config.DefinitionsToChooseFrom == null || _config.DefinitionsToChooseFrom.Count == 0)
{
Debug.LogError($"实体生成配置中没有定义任何要生成的实体。请检查 'DefinitionsToChooseFrom' 列表。");
return;
}
_validatedEntityDefs = new List<EntityDef>();
foreach (var entry in _config.DefinitionsToChooseFrom)
{
if (string.IsNullOrEmpty(entry.DefTypeName))
{
Debug.LogWarning($"实体定义类型名为空或null (实体定义名: '{entry.DefName}')。跳过此定义。");
continue;
}
// 遵循“单例总是有效”的指示,不检查 DefineManager.Instance 是否为 null
var entityDef = (EntityDef)DefineManager.Instance.FindDefine(entry.DefTypeName, entry.DefName);
if (entityDef == null)
{
Debug.LogWarning($"未找到实体定义 (名称: '{entry.DefName}', 类型: '{entry.DefTypeName}')。请检查配置。跳过此定义。");
}
else
{
_validatedEntityDefs.Add(entityDef);
}
}
if (_validatedEntityDefs.Count == 0)
{
Debug.LogError($"所有配置的实体定义都无效或未找到。事件初始化失败。");
}
}
/// <summary>
/// 运行实体生成器事件,在指定维度生成实体。
/// </summary>
/// <param name="dimensionID">要生成实体的维度ID。</param>
/// <param name="initiator">发起者</param>
public override void Run(string dimensionID,Entity.Entity initiator = null)
{
if (_config == null)
{
Debug.LogError("事件配置(_config)为空。Init()可能失败了。");
return;
}
if (_validatedEntityDefs == null || _validatedEntityDefs.Count == 0)
{
Debug.LogError("没有有效实体定义可供生成。请检查Init()方法和配置。");
return;
}
// 遵循“单例总是有效”的指示,不检查 EntityManager.Instance 是否为 null
// 如果 EntityManager.Instance 为 null则表明单例系统存在问题
// 此时应由单例系统自身处理其初始化错误,而不是每次使用时都进行防御性检查。
// 例如,如果 EntityManager.Instance 本身就是 null下面的 GenerateCharacterEntity 将抛出 NullReferenceException。
// 根据“单例总是有效”的指示,我们将此视为单例系统层面的设计或实现缺陷,而非 Event_EntityGenerater 的问题。
for (var i = 0; i < _config.Count; i++)
{
// 随机选择一个实体定义
var selectedEntityDef = _validatedEntityDefs[Random.Range(0, _validatedEntityDefs.Count)];
var position = GetPosition(dimensionID);
// 逻辑修改点1检查 GetPosition 是否返回了有效的非零位置,或者其零点是明确配置的。
// 如果 position 为 Vector3.zero 且不是由 AroundSpecificCoordinates 且中心点为 Vector3.zero 引起的,则跳过本次生成。
var isZeroDueToError = (position == Vector3.zero && (_config.LocationType != EntitySpawnLocationType.AroundSpecificCoordinates
|| _config.CenterCoordinates != Vector3.zero));
if (isZeroDueToError)
{
Debug.LogWarning($"未能为类型 {_config.LocationType} 获取有效的生成位置。跳过本次实体生成。");
continue; // 跳过本次循环,不生成实体
}
if (selectedEntityDef is CharacterDef characterDef)
{
EntityManager.Instance.GenerateCharacterEntity(dimensionID, characterDef, position);
continue;
}
if (selectedEntityDef is MonsterDef monsterDef)
{
EntityManager.Instance.GenerateMonsterEntity(dimensionID, monsterDef, position);
continue;
}
Debug.LogWarning($"目标实体 '{selectedEntityDef.defName}' (类型: {selectedEntityDef.GetType().Name}) 既不是 CharacterDef 也不是 MonsterDef。" +
$"如果你想生成其他类型EntityManage需要一个通用的生成实体方法。没有生成此实体。");
}
}
/// <summary>
/// 根据配置获取实体生成位置。
/// </summary>
/// <param name="dimensionID">要获取位置的维度ID。</param>
/// <returns>计算出的生成位置,如果失败则返回 Vector3.zero。</returns>
private Vector3 GetPosition(string dimensionID)
{
if (_config == null)
{
Debug.LogError("获取位置时配置为空。返回 Vector3.zero。");
return Vector3.zero;
}
// 遵循“单例总是有效”的指示,不检查 Program.Instance 是否为 null
var dimension = Program.Instance.GetDimension(dimensionID);
if (dimension == null)
{
Debug.LogError($"未找到维度 '{dimensionID}'。无法确定生成位置。");
return Vector3.zero;
}
var mapGenerator = dimension.landform;
// 逻辑修改点2更准确的错误信息
if (mapGenerator == null)
{
Debug.LogError($"维度 '{dimensionID}' 的地形 (landform) 为空。无法确定生成位置。"); // 错误信息更清晰
return Vector3.zero;
}
switch (_config.LocationType)
{
case EntitySpawnLocationType.OffMap:
{
var size = mapGenerator.GetSize();
var dir = Random.Range(0, 4); // 0:上, 1:下, 2:左, 3:右
Vector3Int gridCoord; // 地图网格坐标
Vector2 worldCoord2D; // GetWorldCoordinates 返回的 Vector2Int
Vector3 worldPos3D; // 最终的 Vector3 世界坐标
var offset = _config.OffMapOffset;
switch (dir)
{
case 0: // Top border (max Y)
gridCoord = new Vector3Int(Random.Range(0, size.x), size.y);
worldCoord2D = mapGenerator.GetWorldCoordinates(gridCoord);
worldPos3D = new Vector3(worldCoord2D.x, worldCoord2D.y, 0f); // 转换为 Vector3
worldPos3D.y += offset;
break;
case 1: // Bottom border (min Y)
gridCoord = new Vector3Int(Random.Range(0, size.x), 0);
worldCoord2D = mapGenerator.GetWorldCoordinates(gridCoord);
worldPos3D = new Vector3(worldCoord2D.x, worldCoord2D.y, 0f); // 转换为 Vector3
worldPos3D.y -= offset;
break;
case 2: // Left border (min X)
gridCoord = new Vector3Int(0, Random.Range(0, size.y));
worldCoord2D = mapGenerator.GetWorldCoordinates(gridCoord);
worldPos3D = new Vector3(worldCoord2D.x, worldCoord2D.y, 0f); // 转换为 Vector3
worldPos3D.x -= offset;
break;
case 3: // Right border (max X)
gridCoord = new Vector3Int(size.x, Random.Range(0, size.y));
worldCoord2D = mapGenerator.GetWorldCoordinates(gridCoord);
worldPos3D = new Vector3(worldCoord2D.x, worldCoord2D.y, 0f); // 转换为 Vector3
worldPos3D.x += offset;
break;
default:
Debug.LogError("OffMap 生成方向无效。不应该发生此情况。");
return Vector3.zero;
}
worldPos3D.x += Random.Range(-offset / 2f, offset / 2f);
worldPos3D.y += Random.Range(-offset / 2f, offset / 2f);
return worldPos3D;
}
case EntitySpawnLocationType.AroundSpecificCoordinates:
{
var center = _config.CenterCoordinates;
var radius = _config.Radius;
var randomOffset = Random.insideUnitCircle * radius;
var rawPosition = new Vector3(center.x + randomOffset.x, center.y + randomOffset.y, center.z);
// 逻辑修改点4应用位置钳制确保在地图范围内
return GetClampedWorldPosition(rawPosition, dimensionID);
}
case EntitySpawnLocationType.AroundTargetEntity:
{
if (string.IsNullOrEmpty(_config.TargetFactionDefName))
{
Debug.LogWarning($"配置了 'AroundTargetEntity',但 'TargetFactionDefName' 为空或null。无法找到派系实体。将在原点生成。");
return Vector3.zero;
}
// 遵循“单例总是有效”的指示,不检查 EntityManager.Instance 是否为 null
var factionEntities = EntityManager.Instance.FindEntitiesByFaction(dimensionID, _config.TargetFactionDefName);
if (factionEntities == null || factionEntities.Length == 0)
{
Debug.LogWarning($"在维度 '{dimensionID}' 中未找到派系 '{_config.TargetFactionDefName}' 的任何实体。将在原点生成。");
return Vector3.zero;
}
var randomIndex = Random.Range(0, factionEntities.Length);
var targetEntityPrefab = factionEntities[randomIndex];
// Unity对象隐式空比较
if (targetEntityPrefab == null || targetEntityPrefab.transform == null)
{
Debug.LogWarning($"从派系 '{_config.TargetFactionDefName}' 中随机选择的实体为空或没有Transform组件。将在原点生成。");
return Vector3.zero;
}
var center = targetEntityPrefab.transform.position;
var radius = _config.Radius;
var randomOffset = Random.insideUnitCircle * radius;
var rawPosition = new Vector3(center.x + randomOffset.x, center.y + randomOffset.y, center.z);
Debug.Log($"围绕派系 '{_config.TargetFactionDefName}' 的实体 (世界坐标: {center}) 生成。生成的偏移量: {randomOffset}。");
// 逻辑修改点4应用位置钳制确保在地图范围内
return GetClampedWorldPosition(rawPosition, dimensionID);
}
case EntitySpawnLocationType.RandomlyOnMap:
{
var size = mapGenerator.GetSize();
var randomGridPos = new Vector3Int(Random.Range(0, size.x), Random.Range(0, size.y));
var worldCoord2D = mapGenerator.GetWorldCoordinates(randomGridPos);
var worldPos3D = new Vector3(worldCoord2D.x, worldCoord2D.y, 0f);
Debug.Log($"随机地图生成位置: {worldPos3D} (网格坐标: {randomGridPos})。");
return worldPos3D;
}
case EntitySpawnLocationType.InsideSpecificBuildingType:
{
Debug.LogWarning($"类型为 'InsideSpecificBuildingType' ({_config.BuildingTypeDefName}) 的生成逻辑尚未实现。返回 Vector3.zero。");
return Vector3.zero;
}
case EntitySpawnLocationType.AtPredefinedSpawnPoint:
{
Debug.LogWarning($"类型为 'AtPredefinedSpawnPoint' ({_config.SpawnPointTileMapDefName}) 的生成逻辑尚未实现。返回 Vector3.zero。");
return Vector3.zero;
}
case EntitySpawnLocationType.None:
default:
Debug.LogWarning($"未知或不支持的生成位置类型: {_config.LocationType}。返回 Vector3.zero。");
return Vector3.zero;
}
}
// 逻辑修改点3新增 GetClampedWorldPosition 辅助方法
/// <summary>
/// 将给定的世界坐标限制在指定维度的地图可玩区域边界内。
/// 假设 mapGenerator.GetWorldCoordinates(gridPos) 返回瓦片中心,且瓦片世界大小为 1x1 单位。
/// </summary>
/// <param name="worldPosition">待限制的世界坐标。</param>
/// <param name="dimensionID">目标维度ID。</param>
/// <returns>限制在地图边界内的世界坐标。</returns>
private Vector3 GetClampedWorldPosition(Vector3 worldPosition, string dimensionID)
{
// 遵循“单例总是有效”的指示,不检查 Program.Instance 是否为 null
var dimension = Program.Instance.GetDimension(dimensionID);
if (dimension == null)
{
Debug.LogError($"无法获取维度 '{dimensionID}' 来限制位置。返回原始位置。");
return worldPosition;
}
var mapGenerator = dimension.landform;
if (mapGenerator == null)
{
Debug.LogError($"维度 '{dimensionID}' 的地形 (landform) 为空。无法限制位置。返回原始位置。");
return worldPosition;
}
var mapGridSize = mapGenerator.GetSize(); // 获取地图的网格尺寸 (宽度, 高度)
// 假设 GetWorldCoordinates(gridPos) 返回瓦片中心,且世界坐标中的单瓦片尺寸为 1x1。
// 所以,瓦片的半个世界尺寸为 0.5f。
var tileWorldHalfSize = 0.5f; // 理想情况下,这应从 mapGenerator 或某个全局配置获取。
// 获取最左下角 (0,0) 瓦片的中心世界坐标
Vector2 worldMinTileCenter = mapGenerator.GetWorldCoordinates(Vector3Int.zero);
// 获取最右上角 (width-1, height-1) 瓦片的中心世界坐标
Vector2 worldMaxTileCenter = mapGenerator.GetWorldCoordinates(new Vector3Int(mapGridSize.x - 1, mapGridSize.y - 1));
// 计算地图在世界坐标系中的绝对边界
// 我们取 x,y 轴的最小值和最大值,确保正确的边界范围
var worldMinX = worldMinTileCenter.x - tileWorldHalfSize;
var worldMaxX = worldMaxTileCenter.x + tileWorldHalfSize;
var worldMinY = worldMinTileCenter.y - tileWorldHalfSize;
var worldMaxY = worldMaxTileCenter.y + tileWorldHalfSize;
// 限制位置在计算出的世界地图边界内
var clampedPosition = worldPosition;
clampedPosition.x = Mathf.Clamp(worldPosition.x, worldMinX, worldMaxX);
clampedPosition.y = Mathf.Clamp(worldPosition.y, worldMinY, worldMaxY);
// Z轴通常用于深度此处保持不变
if (clampedPosition != worldPosition)
{
Debug.LogWarning($"生成位置 {worldPosition} (类型: {_config.LocationType}) 超出地图可玩区域边界,已限制为 {clampedPosition}。");
}
return clampedPosition;
}
}
}