mirror of
http://47.107.252.169:3000/Roguelite-Game-Developing-Team/Gen_Hack-and-Slash-Roguelite.git
synced 2025-11-20 05:27:13 +08:00
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
427 lines
21 KiB
C#
427 lines
21 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|