2025-09-03 19:59:22 +08:00
|
|
|
|
using System;
|
2025-10-10 14:08:23 +08:00
|
|
|
|
using System.Collections.Generic;
|
2025-09-03 19:59:22 +08:00
|
|
|
|
using Data;
|
|
|
|
|
|
using Managers;
|
2025-10-10 14:08:23 +08:00
|
|
|
|
using Newtonsoft.Json;
|
2025-09-03 19:59:22 +08:00
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
using Random = UnityEngine.Random;
|
|
|
|
|
|
|
|
|
|
|
|
namespace EventWorkClass
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
2025-10-10 14:08:23 +08:00
|
|
|
|
/// Event_EntityGenerater 事件的配置数据结构。
|
2025-09-03 19:59:22 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
[Serializable]
|
|
|
|
|
|
public class EntityGenerateConfig
|
|
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
// 要生成的实体定义列表,将从中随机选择。
|
2025-09-19 08:26:54 +08:00
|
|
|
|
public List<EntityDefinitionEntry> DefinitionsToChooseFrom;
|
|
|
|
|
|
|
2025-10-10 14:08:23 +08:00
|
|
|
|
// 要生成的实体总数。
|
|
|
|
|
|
public int Count = 1;
|
2025-09-19 08:26:54 +08:00
|
|
|
|
|
2025-10-10 14:08:23 +08:00
|
|
|
|
// 生成位置类型。
|
2025-09-03 19:59:22 +08:00
|
|
|
|
public EntitySpawnLocationType LocationType;
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
|
|
|
|
|
// 用于 AroundSpecificCoordinates 类型:生成中心坐标。
|
2025-09-03 19:59:22 +08:00
|
|
|
|
public Vector3 CenterCoordinates;
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
|
|
|
|
|
// 用于 AroundSpecificCoordinates 或 AroundTargetEntity 类型:生成半径。
|
|
|
|
|
|
public float Radius = 10f;
|
|
|
|
|
|
|
|
|
|
|
|
// 用于 InsideSpecificBuildingType 类型:目标建筑的类型ID。
|
2025-09-19 08:26:54 +08:00
|
|
|
|
public string BuildingTypeDefName;
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
|
|
|
|
|
// 用于 AroundTargetEntity 类型:目标派系的定义名 (factionDefName)。
|
2025-09-03 19:59:22 +08:00
|
|
|
|
public string TargetFactionDefName;
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
|
|
|
|
|
// 用于 AtPredefinedSpawnPoint 类型:预定义生成点的ID。
|
2025-09-19 08:26:54 +08:00
|
|
|
|
public string SpawnPointTileMapDefName;
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
|
|
|
|
|
// 用于 OffMap 类型:距离地图边界的额外偏移量。
|
|
|
|
|
|
// (新的解释:从地图边界向内偏移的距离,确保实体生成在地图内部可放置区域)
|
2025-09-03 19:59:22 +08:00
|
|
|
|
public float OffMapOffset = 5f;
|
|
|
|
|
|
}
|
2025-09-19 08:26:54 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2025-10-10 14:08:23 +08:00
|
|
|
|
/// 单个实体定义的配置条目。
|
2025-09-19 08:26:54 +08:00
|
|
|
|
/// </summary>
|
2025-10-10 14:08:23 +08:00
|
|
|
|
[Serializable]
|
2025-09-19 08:26:54 +08:00
|
|
|
|
public class EntityDefinitionEntry
|
|
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
// 实体定义名 (defName)。
|
2025-09-19 08:26:54 +08:00
|
|
|
|
public string DefName;
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
|
|
|
|
|
// 实体定义的具体类型标识符。
|
|
|
|
|
|
// 已从 string 改为枚举,以增加类型安全。
|
|
|
|
|
|
public EntityDefTypeIdentifier DefTypeName;
|
2025-09-19 08:26:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-03 19:59:22 +08:00
|
|
|
|
/// <summary>
|
2025-10-10 14:08:23 +08:00
|
|
|
|
/// 定义生成器生成地图实体的可能位置类型。
|
2025-09-03 19:59:22 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
public enum EntitySpawnLocationType
|
2025-10-10 14:08:23 +08:00
|
|
|
|
{
|
|
|
|
|
|
// 未指定或无效的生成位置类型。
|
|
|
|
|
|
None = 0,
|
|
|
|
|
|
|
|
|
|
|
|
// 在地图的可玩区域边界生成实体(实体将生成在地图内部,靠近边界)。
|
|
|
|
|
|
OffMap,
|
|
|
|
|
|
|
|
|
|
|
|
// 在指定类型建筑的内部或附近生成实体。
|
|
|
|
|
|
InsideSpecificBuildingType,
|
|
|
|
|
|
|
|
|
|
|
|
// 在指定坐标点的周围区域内生成实体。
|
|
|
|
|
|
AroundSpecificCoordinates,
|
|
|
|
|
|
|
|
|
|
|
|
// 在特定派系(或其他指定目标实体,例如NPC或特定对象)的周围区域内生成实体。
|
|
|
|
|
|
AroundTargetEntity,
|
|
|
|
|
|
|
|
|
|
|
|
// 在地图上的随机可生成点生成实体。
|
|
|
|
|
|
RandomlyOnMap,
|
|
|
|
|
|
|
|
|
|
|
|
// 在预定义的生成点(Spawn Point)生成实体。
|
|
|
|
|
|
AtPredefinedSpawnPoint,
|
|
|
|
|
|
|
|
|
|
|
|
//调用者位置生成
|
|
|
|
|
|
AtCallerPosition
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 支持的实体定义类型标识符。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public enum EntityDefTypeIdentifier
|
2025-09-03 19:59:22 +08:00
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
2025-10-10 14:08:23 +08:00
|
|
|
|
/// 未指定或无效的实体定义类型。
|
2025-09-03 19:59:22 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
None = 0,
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
2025-09-03 19:59:22 +08:00
|
|
|
|
/// <summary>
|
2025-10-10 14:08:23 +08:00
|
|
|
|
/// 角色定义类型。
|
2025-09-03 19:59:22 +08:00
|
|
|
|
/// </summary>
|
2025-10-10 14:08:23 +08:00
|
|
|
|
CharacterDef,
|
|
|
|
|
|
|
2025-09-03 19:59:22 +08:00
|
|
|
|
/// <summary>
|
2025-10-10 14:08:23 +08:00
|
|
|
|
/// 怪物定义类型。
|
2025-09-03 19:59:22 +08:00
|
|
|
|
/// </summary>
|
2025-10-10 14:08:23 +08:00
|
|
|
|
MonsterDef,
|
|
|
|
|
|
|
2025-09-03 19:59:22 +08:00
|
|
|
|
/// <summary>
|
2025-10-10 14:08:23 +08:00
|
|
|
|
/// 建筑定义类型。
|
2025-09-03 19:59:22 +08:00
|
|
|
|
/// </summary>
|
2025-10-10 14:08:23 +08:00
|
|
|
|
BuildingDef,
|
|
|
|
|
|
|
2025-09-03 19:59:22 +08:00
|
|
|
|
/// <summary>
|
2025-10-10 14:08:23 +08:00
|
|
|
|
/// 子弹定义类型。
|
2025-09-03 19:59:22 +08:00
|
|
|
|
/// </summary>
|
2025-10-10 14:08:23 +08:00
|
|
|
|
BulletDef,
|
|
|
|
|
|
|
2025-09-03 19:59:22 +08:00
|
|
|
|
/// <summary>
|
2025-10-10 14:08:23 +08:00
|
|
|
|
/// 物品定义类型(用于可拾取物)。
|
2025-09-03 19:59:22 +08:00
|
|
|
|
/// </summary>
|
2025-10-10 14:08:23 +08:00
|
|
|
|
ItemDef,
|
|
|
|
|
|
|
2025-09-03 19:59:22 +08:00
|
|
|
|
/// <summary>
|
2025-10-10 14:08:23 +08:00
|
|
|
|
/// 武器定义类型。
|
2025-09-03 19:59:22 +08:00
|
|
|
|
/// </summary>
|
2025-10-10 14:08:23 +08:00
|
|
|
|
WeaponDef // 新增 WeaponDef 类型
|
|
|
|
|
|
// 根据您的Def命名约定和需要添加其他Def类型。
|
2025-09-03 19:59:22 +08:00
|
|
|
|
}
|
2025-09-19 08:26:54 +08:00
|
|
|
|
|
2025-09-03 19:59:22 +08:00
|
|
|
|
public class Event_EntityGenerater : EventWorkClassBase
|
|
|
|
|
|
{
|
|
|
|
|
|
private EntityGenerateConfig _config;
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
|
|
|
|
|
// _validatedEntityDefs 类型从 List<EntityDef> 变为 List<Define>
|
|
|
|
|
|
private List<Define> _validatedEntityDefs; // 用于存储所有已验证的实体定义
|
2025-09-03 19:59:22 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2025-10-10 14:08:23 +08:00
|
|
|
|
/// 初始化实体生成器事件。
|
2025-09-03 19:59:22 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="value">包含事件配置的JSON字符串。</param>
|
|
|
|
|
|
public override void Init(string value)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrEmpty(value))
|
|
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
Debug.LogError("Event_EntityGenerater.Init:初始化值为空或null。请提供一个JSON配置字符串。");
|
2025-09-03 19:59:22 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
2025-09-03 19:59:22 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
_config = JsonConvert.DeserializeObject<EntityGenerateConfig>(value);
|
2025-09-19 08:26:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
Debug.LogError($"Event_EntityGenerater.Init:解析配置JSON时出错: {value}。异常信息: {ex.Message}");
|
|
|
|
|
|
return;
|
2025-09-19 08:26:54 +08:00
|
|
|
|
}
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
2025-09-19 08:26:54 +08:00
|
|
|
|
if (_config == null)
|
|
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
Debug.LogError($"Event_EntityGenerater.Init:无法解析配置JSON: {value}");
|
2025-09-19 08:26:54 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
2025-09-19 08:26:54 +08:00
|
|
|
|
if (_config.DefinitionsToChooseFrom == null || _config.DefinitionsToChooseFrom.Count == 0)
|
|
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
// 更具体地说明为何有问题
|
|
|
|
|
|
Debug.LogError(
|
|
|
|
|
|
"Event_EntityGenerater.Init:实体生成配置中没有定义任何要生成的实体。请检查 'DefinitionsToChooseFrom' 列表。事件初始化失败。");
|
|
|
|
|
|
return; // 如果没有配置,直接返回,避免后续NPE
|
2025-09-19 08:26:54 +08:00
|
|
|
|
}
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
|
|
|
|
|
// _validatedEntityDefs 初始化类型从 List<EntityDef> 变为 List<Define>
|
|
|
|
|
|
_validatedEntityDefs = new List<Define>();
|
2025-09-19 08:26:54 +08:00
|
|
|
|
foreach (var entry in _config.DefinitionsToChooseFrom)
|
|
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
// 确保 DefName 不为空
|
|
|
|
|
|
if (string.IsNullOrEmpty(entry.DefName))
|
2025-09-03 19:59:22 +08:00
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
// 细化日志,指明来源方法
|
|
|
|
|
|
Debug.LogWarning($"Event_EntityGenerater.Init:实体定义名为空或null (类型标识符: '{entry.DefTypeName}')。跳过此定义。");
|
2025-09-19 08:26:54 +08:00
|
|
|
|
continue;
|
2025-09-03 19:59:22 +08:00
|
|
|
|
}
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
|
|
|
|
|
// 确保枚举类型不是 None,否则无法确定查找哪种Def
|
|
|
|
|
|
if (entry.DefTypeName == EntityDefTypeIdentifier.None)
|
2025-09-03 19:59:22 +08:00
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
// 细化日志,指明来源方法
|
|
|
|
|
|
Debug.LogWarning($"Event_EntityGenerater.Init:实体定义类型标识符为 None (实体定义名: '{entry.DefName}')。跳过此定义。");
|
|
|
|
|
|
continue;
|
2025-09-03 19:59:22 +08:00
|
|
|
|
}
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
|
|
|
|
|
Define entityDef = null;
|
|
|
|
|
|
// 使用泛型 FindDefine<T>(defName) 获取定义
|
|
|
|
|
|
switch (entry.DefTypeName)
|
2025-09-03 19:59:22 +08:00
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
case EntityDefTypeIdentifier.CharacterDef:
|
|
|
|
|
|
entityDef = DefineManager.Instance.FindDefine<CharacterDef>(entry.DefName);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case EntityDefTypeIdentifier.MonsterDef:
|
|
|
|
|
|
entityDef = DefineManager.Instance.FindDefine<MonsterDef>(entry.DefName);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case EntityDefTypeIdentifier.BuildingDef:
|
|
|
|
|
|
entityDef = DefineManager.Instance.FindDefine<BuildingDef>(entry.DefName);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case EntityDefTypeIdentifier.BulletDef:
|
|
|
|
|
|
entityDef = DefineManager.Instance.FindDefine<BulletDef>(entry.DefName);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case EntityDefTypeIdentifier.ItemDef:
|
|
|
|
|
|
entityDef = DefineManager.Instance.FindDefine<ItemDef>(entry.DefName);
|
|
|
|
|
|
break;
|
|
|
|
|
|
// 新增:处理 WeaponDef 类型
|
|
|
|
|
|
case EntityDefTypeIdentifier.WeaponDef:
|
|
|
|
|
|
entityDef = DefineManager.Instance.FindDefine<WeaponDef>(entry.DefName);
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
// 细化日志,指明来源方法并提供更多上下文
|
|
|
|
|
|
Debug.LogWarning(
|
|
|
|
|
|
$"Event_EntityGenerater.Init:不支持的实体定义类型标识符 '{entry.DefTypeName}' (实体定义名: '{entry.DefName}')。跳过此定义。");
|
|
|
|
|
|
continue; // 跳过此定义,因为它无法被匹配到已知类型
|
2025-09-03 19:59:22 +08:00
|
|
|
|
}
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
|
|
|
|
|
if (entityDef == null)
|
|
|
|
|
|
// 细化日志,指明来源方法
|
|
|
|
|
|
Debug.LogWarning(
|
|
|
|
|
|
$"Event_EntityGenerater.Init:未找到实体定义 (名称: '{entry.DefName}', 类型标识符: '{entry.DefTypeName}')。请检查配置或类型。跳过此定义。");
|
|
|
|
|
|
else
|
|
|
|
|
|
// 修改:直接添加 Define 对象到 _validatedEntityDefs,无需强制转换
|
|
|
|
|
|
_validatedEntityDefs.Add(entityDef);
|
2025-09-03 19:59:22 +08:00
|
|
|
|
}
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
2025-09-19 08:26:54 +08:00
|
|
|
|
if (_validatedEntityDefs.Count == 0)
|
2025-10-10 14:08:23 +08:00
|
|
|
|
// 提高错误级别并提供清晰的后果说明
|
|
|
|
|
|
Debug.LogError("Event_EntityGenerater.Init:所有配置的实体定义都无效或未找到。事件初始化失败。事件将无法在Run时生成任何实体。");
|
2025-09-03 19:59:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2025-10-10 14:08:23 +08:00
|
|
|
|
/// 运行实体生成器事件,在指定维度生成实体。
|
2025-09-03 19:59:22 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="dimensionID">要生成实体的维度ID。</param>
|
2025-09-28 15:02:57 +08:00
|
|
|
|
/// <param name="initiator">发起者</param>
|
2025-10-10 14:08:23 +08:00
|
|
|
|
public override void Run(string dimensionID, Entity.Entity initiator = null)
|
2025-09-03 19:59:22 +08:00
|
|
|
|
{
|
|
|
|
|
|
if (_config == null)
|
|
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
Debug.LogError("Event_EntityGenerater.Run:事件配置(_config)为空。Init()可能失败了。");
|
2025-09-03 19:59:22 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
2025-09-19 08:26:54 +08:00
|
|
|
|
if (_validatedEntityDefs == null || _validatedEntityDefs.Count == 0)
|
2025-09-03 19:59:22 +08:00
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
Debug.LogError("Event_EntityGenerater.Run:没有有效实体定义可供生成。请检查Init()方法和配置。");
|
2025-09-03 19:59:22 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-09-19 08:26:54 +08:00
|
|
|
|
|
2025-10-10 14:08:23 +08:00
|
|
|
|
const int MAX_RETRY_ATTEMPTS = 10;
|
|
|
|
|
|
|
2025-10-03 00:31:34 +08:00
|
|
|
|
for (var i = 0; i < _config.Count; i++)
|
2025-09-03 19:59:22 +08:00
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
var retryCount = 0;
|
|
|
|
|
|
var generated = false;
|
2025-09-19 08:26:54 +08:00
|
|
|
|
// 随机选择一个实体定义
|
|
|
|
|
|
var selectedEntityDef = _validatedEntityDefs[Random.Range(0, _validatedEntityDefs.Count)];
|
2025-10-10 14:08:23 +08:00
|
|
|
|
// 尝试生成,最多重试 MAX_RETRY_ATTEMPTS 次
|
|
|
|
|
|
while (retryCount < MAX_RETRY_ATTEMPTS)
|
2025-09-19 08:26:54 +08:00
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
// 获取生成位置
|
|
|
|
|
|
var position = GetPosition(dimensionID,initiator);
|
|
|
|
|
|
if (position == Vector3.zero) // 如果 GetPosition 失败,直接跳过本次生成
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogWarning($"Event_EntityGenerater.Run: 无法获取有效的生成位置。尝试下一次生成。");
|
|
|
|
|
|
retryCount++;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查移动成本是否允许生成(成本 < 1 才允许)
|
|
|
|
|
|
// 对于需要放置在地图内的实体,此检查是必要的。
|
|
|
|
|
|
var cost = Program.Instance.GetDimension(dimensionID).landform.GetTravelCost(position);
|
|
|
|
|
|
if (cost >= 1) // 假设 cost >= 1 表示不可通行或不可放置
|
|
|
|
|
|
{
|
|
|
|
|
|
retryCount++;
|
|
|
|
|
|
continue; // 重试
|
|
|
|
|
|
}
|
|
|
|
|
|
position.z = 0;
|
|
|
|
|
|
|
|
|
|
|
|
switch (selectedEntityDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
case CharacterDef characterDef:
|
|
|
|
|
|
EntityManager.Instance.GenerateCharacterEntity(dimensionID, characterDef, position);
|
|
|
|
|
|
generated = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case MonsterDef monsterDef:
|
|
|
|
|
|
EntityManager.Instance.GenerateMonsterEntity(dimensionID, monsterDef, position);
|
|
|
|
|
|
generated = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case BuildingDef buildingDef:
|
|
|
|
|
|
EntityManager.Instance.GenerateBuildingEntity(dimensionID, buildingDef, position);
|
|
|
|
|
|
generated = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case BulletDef bulletDef:
|
|
|
|
|
|
EntityManager.Instance.GenerateBulletEntity(dimensionID, bulletDef, position,
|
|
|
|
|
|
Vector3.right); // 子弹生成可能不需要严格的 walkable 检查,但为了保持一致性。
|
|
|
|
|
|
generated = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case ItemDef itemDef:
|
|
|
|
|
|
EntityManager.Instance.GeneratePickupEntity(dimensionID, itemDef, position);
|
|
|
|
|
|
generated = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
Debug.LogWarning(
|
|
|
|
|
|
$"Event_EntityGenerater.Run:目标实体 '{selectedEntityDef.defName}' (实际类型: {selectedEntityDef.GetType().Name}) 未匹配到任何已知的实体生成逻辑。未生成此实体。");
|
|
|
|
|
|
generated = false; // 未成功生成(类型不支持)
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (generated) break;
|
|
|
|
|
|
|
|
|
|
|
|
retryCount++;
|
2025-09-19 08:26:54 +08:00
|
|
|
|
}
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
|
|
|
|
|
if (!generated)
|
2025-09-19 08:26:54 +08:00
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
Debug.LogWarning($"Event_EntityGenerater.Run: 尝试 {MAX_RETRY_ATTEMPTS} 次后仍无法成功生成实体 (预期生成 {selectedEntityDef?.defName ?? "未知定义"})。本次生成失败。");
|
2025-09-19 08:26:54 +08:00
|
|
|
|
}
|
2025-09-03 19:59:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2025-10-10 14:08:23 +08:00
|
|
|
|
/// 根据配置获取实体生成位置。
|
2025-09-03 19:59:22 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="dimensionID">要获取位置的维度ID。</param>
|
|
|
|
|
|
/// <returns>计算出的生成位置,如果失败则返回 Vector3.zero。</returns>
|
2025-10-10 14:08:23 +08:00
|
|
|
|
private Vector3 GetPosition(string dimensionID,Entity.Entity initiator = null)
|
2025-09-03 19:59:22 +08:00
|
|
|
|
{
|
|
|
|
|
|
if (_config == null)
|
|
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
// 细化日志,指明来源方法
|
|
|
|
|
|
Debug.LogError("Event_EntityGenerater.GetPosition:获取位置时配置(_config)为空。返回 Vector3.zero。");
|
2025-09-03 19:59:22 +08:00
|
|
|
|
return Vector3.zero;
|
|
|
|
|
|
}
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
2025-09-03 19:59:22 +08:00
|
|
|
|
var dimension = Program.Instance.GetDimension(dimensionID);
|
2025-10-10 14:08:23 +08:00
|
|
|
|
if (!dimension)
|
2025-09-03 19:59:22 +08:00
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
// 细化日志,指明来源方法
|
|
|
|
|
|
Debug.LogError($"Event_EntityGenerater.GetPosition:未找到维度 '{dimensionID}'。无法确定生成位置。返回 Vector3.zero。");
|
2025-09-03 19:59:22 +08:00
|
|
|
|
return Vector3.zero;
|
|
|
|
|
|
}
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
2025-09-28 15:02:57 +08:00
|
|
|
|
var mapGenerator = dimension.landform;
|
2025-10-10 14:08:23 +08:00
|
|
|
|
if (!mapGenerator)
|
2025-09-03 19:59:22 +08:00
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
// 细化日志,指明来源方法
|
|
|
|
|
|
Debug.LogError(
|
|
|
|
|
|
$"Event_EntityGenerater.GetPosition:维度 '{dimensionID}' 的地形 (landform) 为空。无法确定生成位置。返回 Vector3.zero。");
|
2025-09-03 19:59:22 +08:00
|
|
|
|
return Vector3.zero;
|
|
|
|
|
|
}
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
2025-09-03 19:59:22 +08:00
|
|
|
|
switch (_config.LocationType)
|
|
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
// 逻辑修改点 1: 优化 OffMap 位置生成逻辑
|
2025-09-03 19:59:22 +08:00
|
|
|
|
case EntitySpawnLocationType.OffMap:
|
|
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
var mapGridSize = mapGenerator.GetSize();
|
|
|
|
|
|
const float tileWorldHalfSize = 0.5f; // 假设每个瓦片在世界空间中的大小是1x1,GetWorldCoordinates返回瓦片中心
|
|
|
|
|
|
|
|
|
|
|
|
// 计算地图的实际可玩世界边界(包含边界瓦片)
|
|
|
|
|
|
Vector2 worldMinTileCenter = mapGenerator.GetWorldCoordinates(Vector3Int.zero);
|
|
|
|
|
|
Vector2 worldMaxTileCenter = mapGenerator.GetWorldCoordinates(new Vector3Int(mapGridSize.x - 1, mapGridSize.y - 1));
|
|
|
|
|
|
|
|
|
|
|
|
var mapWorldMinX = worldMinTileCenter.x - tileWorldHalfSize; // 地图的实际左边界
|
|
|
|
|
|
var mapWorldMaxX = worldMaxTileCenter.x + tileWorldHalfSize; // 地图的实际右边界
|
|
|
|
|
|
var mapWorldMinY = worldMinTileCenter.y - tileWorldHalfSize; // 地图的实际下边界
|
|
|
|
|
|
var mapWorldMaxY = worldMaxTileCenter.y + tileWorldHalfSize; // 地图的实际上边界
|
|
|
|
|
|
|
|
|
|
|
|
var dir = (OffMapDirection)Random.Range(0, 4); // 随机选择一个方向
|
|
|
|
|
|
Vector3 worldPos3D;
|
|
|
|
|
|
|
|
|
|
|
|
// 确保_config.OffMapOffset至少有一个小值,以便实体能进入地图内部
|
|
|
|
|
|
var safeOffset = Mathf.Max(0.1f, _config.OffMapOffset);
|
|
|
|
|
|
|
|
|
|
|
|
// 定义在另一个维度上的随机范围,考虑到偏移量
|
|
|
|
|
|
var minSpanX = mapWorldMinX + safeOffset;
|
|
|
|
|
|
var maxSpanX = mapWorldMaxX - safeOffset;
|
|
|
|
|
|
var minSpanY = mapWorldMinY + safeOffset;
|
|
|
|
|
|
var maxSpanY = mapWorldMaxY - safeOffset;
|
|
|
|
|
|
|
|
|
|
|
|
// 检查偏移量是否过大导致有效生成范围无效
|
|
|
|
|
|
if (minSpanX > maxSpanX || minSpanY > maxSpanY)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogWarning(
|
|
|
|
|
|
$"Event_EntityGenerater.GetPosition: OffMapOffset ({_config.OffMapOffset}) 对于地图尺寸来说太大了。尝试使用最小偏移量。");
|
|
|
|
|
|
safeOffset = 0.1f; // 回退到最小偏移量
|
|
|
|
|
|
minSpanX = mapWorldMinX + safeOffset;
|
|
|
|
|
|
maxSpanX = mapWorldMaxX - safeOffset;
|
|
|
|
|
|
minSpanY = mapWorldMinY + safeOffset;
|
|
|
|
|
|
maxSpanY = mapWorldMaxY - safeOffset;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果即使使用最小偏移量,地图也无法提供有效范围,则返回地图中心
|
|
|
|
|
|
if (minSpanX > maxSpanX || minSpanY > maxSpanY) {
|
|
|
|
|
|
Debug.LogError("Event_EntityGenerater.GetPosition: 地图太小,即使使用最小 OffMapOffset 也无法正确生成。返回地图中心。");
|
|
|
|
|
|
return new Vector3(mapWorldMinX + (mapWorldMaxX - mapWorldMinX) / 2, mapWorldMinY + (mapWorldMaxY - mapWorldMinY) / 2, 0f);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 根据选择的方向,在地图边界内部偏移量处生成随机点
|
2025-09-03 19:59:22 +08:00
|
|
|
|
switch (dir)
|
|
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
case OffMapDirection.Up: // 上边界 (Y 靠近 mapWorldMaxY)
|
|
|
|
|
|
worldPos3D = new Vector3(Random.Range(minSpanX, maxSpanX), mapWorldMaxY - safeOffset, 0f);
|
2025-09-03 19:59:22 +08:00
|
|
|
|
break;
|
2025-10-10 14:08:23 +08:00
|
|
|
|
case OffMapDirection.Down: // 下边界 (Y 靠近 mapWorldMinY)
|
|
|
|
|
|
worldPos3D = new Vector3(Random.Range(minSpanX, maxSpanX), mapWorldMinY + safeOffset, 0f);
|
2025-09-03 19:59:22 +08:00
|
|
|
|
break;
|
2025-10-10 14:08:23 +08:00
|
|
|
|
case OffMapDirection.Left: // 左边界 (X 靠近 mapWorldMinX)
|
|
|
|
|
|
worldPos3D = new Vector3(mapWorldMinX + safeOffset, Random.Range(minSpanY, maxSpanY), 0f);
|
2025-09-03 19:59:22 +08:00
|
|
|
|
break;
|
2025-10-10 14:08:23 +08:00
|
|
|
|
case OffMapDirection.Right: // 右边界 (X 靠近 mapWorldMaxX)
|
|
|
|
|
|
worldPos3D = new Vector3(mapWorldMaxX - safeOffset, Random.Range(minSpanY, maxSpanY), 0f);
|
2025-09-03 19:59:22 +08:00
|
|
|
|
break;
|
|
|
|
|
|
default:
|
2025-10-10 14:08:23 +08:00
|
|
|
|
Debug.LogError("Event_EntityGenerater.GetPosition:OffMap 生成方向无效。不应该发生此情况。返回 Vector3.zero。");
|
2025-09-03 19:59:22 +08:00
|
|
|
|
return Vector3.zero;
|
|
|
|
|
|
}
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
|
|
|
|
|
// 在指定偏移量附近再增加一些随机性,使生成更自然,但要确保仍在 clamped 范围内,
|
|
|
|
|
|
// 这里不做,因为上面的随机范围已经考虑了,再加可能推出去。
|
|
|
|
|
|
// 如果需要,可以在这里增加一个小的随机扰动,然后调用 GetClampedWorldPosition 确保不越界。
|
|
|
|
|
|
return worldPos3D; // 返回在地图边界内部的有效世界坐标
|
2025-09-03 19:59:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
case EntitySpawnLocationType.AroundSpecificCoordinates:
|
|
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
var worldPos = Program.Instance.GetDimension(dimensionID)?.landform
|
|
|
|
|
|
.GetWorldCoordinates(new Vector3Int((int)_config.CenterCoordinates.x,(int) _config.CenterCoordinates.y));
|
|
|
|
|
|
if (worldPos != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var rawPosition = GetRandomPositionInCircle(worldPos.Value, _config.Radius);
|
|
|
|
|
|
return GetClampedWorldPosition(rawPosition, dimensionID);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return GetClampedWorldPosition(Vector3.one, dimensionID);
|
2025-09-03 19:59:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
case EntitySpawnLocationType.AroundTargetEntity:
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrEmpty(_config.TargetFactionDefName))
|
|
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
// 细化日志,指明来源方法
|
|
|
|
|
|
Debug.LogWarning(
|
|
|
|
|
|
"Event_EntityGenerater.GetPosition:配置了 'AroundTargetEntity',但 'TargetFactionDefName' 为空或null。无法找到派系实体。返回 Vector3.zero。");
|
2025-09-03 19:59:22 +08:00
|
|
|
|
return Vector3.zero;
|
|
|
|
|
|
}
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
|
|
|
|
|
var factionEntities =
|
|
|
|
|
|
EntityManager.Instance.FindEntitiesByFaction(dimensionID, _config.TargetFactionDefName);
|
2025-09-03 19:59:22 +08:00
|
|
|
|
if (factionEntities == null || factionEntities.Length == 0)
|
|
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
// 细化日志,指明来源方法
|
|
|
|
|
|
Debug.LogWarning(
|
|
|
|
|
|
$"Event_EntityGenerater.GetPosition:在维度 '{dimensionID}' 中未找到派系 '{_config.TargetFactionDefName}' 的任何实体。返回 Vector3.zero。");
|
2025-09-03 19:59:22 +08:00
|
|
|
|
return Vector3.zero;
|
|
|
|
|
|
}
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
|
|
|
|
|
var targetEntity = factionEntities[Random.Range(0, factionEntities.Length)];
|
|
|
|
|
|
// 检查随机选择的实体是否有效且有Transform组件
|
|
|
|
|
|
if (!targetEntity || !targetEntity.transform)
|
2025-09-03 19:59:22 +08:00
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
// 细化日志,指明来源方法
|
|
|
|
|
|
Debug.LogWarning(
|
|
|
|
|
|
$"Event_EntityGenerater.GetPosition:从派系 '{_config.TargetFactionDefName}' 中随机选择的实体为空或没有Transform组件。返回 Vector3.zero。");
|
2025-09-03 19:59:22 +08:00
|
|
|
|
return Vector3.zero;
|
|
|
|
|
|
}
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
|
|
|
|
|
// 调用辅助方法 GetRandomPositionInCircle
|
|
|
|
|
|
var rawPosition = GetRandomPositionInCircle(targetEntity.transform.position, _config.Radius);
|
2025-10-03 00:31:34 +08:00
|
|
|
|
return GetClampedWorldPosition(rawPosition, dimensionID);
|
2025-09-03 19:59:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
case EntitySpawnLocationType.RandomlyOnMap:
|
|
|
|
|
|
{
|
|
|
|
|
|
var size = mapGenerator.GetSize();
|
2025-09-28 15:02:57 +08:00
|
|
|
|
var randomGridPos = new Vector3Int(Random.Range(0, size.x), Random.Range(0, size.y));
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
2025-09-03 19:59:22 +08:00
|
|
|
|
var worldCoord2D = mapGenerator.GetWorldCoordinates(randomGridPos);
|
|
|
|
|
|
var worldPos3D = new Vector3(worldCoord2D.x, worldCoord2D.y, 0f);
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
2025-09-03 19:59:22 +08:00
|
|
|
|
return worldPos3D;
|
|
|
|
|
|
}
|
|
|
|
|
|
case EntitySpawnLocationType.InsideSpecificBuildingType:
|
|
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
// 细化日志,指明来源方法
|
|
|
|
|
|
Debug.LogWarning(
|
|
|
|
|
|
$"Event_EntityGenerater.GetPosition:类型为 'InsideSpecificBuildingType' ({_config.BuildingTypeDefName ?? "未指定"}) 的生成逻辑尚未实现。返回 Vector3.zero。");
|
2025-09-03 19:59:22 +08:00
|
|
|
|
return Vector3.zero;
|
|
|
|
|
|
}
|
|
|
|
|
|
case EntitySpawnLocationType.AtPredefinedSpawnPoint:
|
2025-10-10 14:08:23 +08:00
|
|
|
|
{
|
|
|
|
|
|
// 细化日志,指明来源方法
|
|
|
|
|
|
Debug.LogWarning(
|
|
|
|
|
|
$"Event_EntityGenerater.GetPosition:类型为 'AtPredefinedSpawnPoint' ({_config.SpawnPointTileMapDefName ?? "未指定"}) 的生成逻辑尚未实现。返回 Vector3.zero。");
|
|
|
|
|
|
return Vector3.zero;
|
|
|
|
|
|
}
|
|
|
|
|
|
case EntitySpawnLocationType.AtCallerPosition:
|
|
|
|
|
|
{
|
|
|
|
|
|
if (initiator == null || initiator.transform == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogWarning(
|
|
|
|
|
|
"Event_EntityGenerater.GetPosition:配置了 'AtCallerPosition',但发起者实体或其Transform为空。返回 Vector3.zero。");
|
|
|
|
|
|
return Vector3.zero;
|
|
|
|
|
|
}
|
|
|
|
|
|
return initiator.Position;
|
|
|
|
|
|
}
|
2025-09-03 19:59:22 +08:00
|
|
|
|
case EntitySpawnLocationType.None:
|
|
|
|
|
|
default:
|
2025-10-10 14:08:23 +08:00
|
|
|
|
// 细化日志,指明来源方法
|
|
|
|
|
|
Debug.LogWarning(
|
|
|
|
|
|
$"Event_EntityGenerater.GetPosition:未知或不支持的生成位置类型: {_config.LocationType}。返回 Vector3.zero。");
|
2025-09-03 19:59:22 +08:00
|
|
|
|
return Vector3.zero;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-03 00:31:34 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2025-10-10 14:08:23 +08:00
|
|
|
|
/// 在指定中心点周围半径内获取一个随机世界坐标。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="center">中心坐标。</param>
|
|
|
|
|
|
/// <param name="radius">半径。</param>
|
|
|
|
|
|
/// <returns>随机生成的坐标。</returns>
|
|
|
|
|
|
private Vector3 GetRandomPositionInCircle(Vector3 center, float radius)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (radius <= 0f)
|
|
|
|
|
|
// 如果半径无效,直接返回中心点。可以根据需求选择是否打印警告。
|
|
|
|
|
|
// Debug.LogWarning($"Event_EntityGenerater.GetRandomPositionInCircle:半径为 {radius},将直接返回中心点 {center}。");
|
|
|
|
|
|
return center;
|
|
|
|
|
|
|
|
|
|
|
|
var randomOffset = Random.insideUnitCircle * radius; // 在圆形区域内随机偏移
|
|
|
|
|
|
return new Vector3(center.x + randomOffset.x, center.y + randomOffset.y, center.z);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 将给定的世界坐标限制在指定维度的地图可玩区域边界内。
|
|
|
|
|
|
/// 假设 mapGenerator.GetWorldCoordinates(gridPos) 返回瓦片中心,且瓦片世界大小为 1x1 单位。
|
2025-10-03 00:31:34 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="worldPosition">待限制的世界坐标。</param>
|
|
|
|
|
|
/// <param name="dimensionID">目标维度ID。</param>
|
|
|
|
|
|
/// <returns>限制在地图边界内的世界坐标。</returns>
|
|
|
|
|
|
private Vector3 GetClampedWorldPosition(Vector3 worldPosition, string dimensionID)
|
|
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
// Program.Instance 是一个单例,根据约定不进行空检查
|
2025-10-03 00:31:34 +08:00
|
|
|
|
var dimension = Program.Instance.GetDimension(dimensionID);
|
2025-10-10 14:08:23 +08:00
|
|
|
|
if (!dimension)
|
2025-10-03 00:31:34 +08:00
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
// 细化日志,指明来源方法
|
|
|
|
|
|
Debug.LogError($"Event_EntityGenerater.GetClampedWorldPosition:无法获取维度 '{dimensionID}' 来限制位置。返回原始位置。");
|
2025-10-03 00:31:34 +08:00
|
|
|
|
return worldPosition;
|
|
|
|
|
|
}
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
2025-10-03 00:31:34 +08:00
|
|
|
|
var mapGenerator = dimension.landform;
|
2025-10-10 14:08:23 +08:00
|
|
|
|
if (!mapGenerator)
|
2025-10-03 00:31:34 +08:00
|
|
|
|
{
|
2025-10-10 14:08:23 +08:00
|
|
|
|
// 细化日志,指明来源方法
|
|
|
|
|
|
Debug.LogError(
|
|
|
|
|
|
$"Event_EntityGenerater.GetClampedWorldPosition:维度 '{dimensionID}' 的地形 (landform) 为空。无法限制位置。返回原始位置。");
|
2025-10-03 00:31:34 +08:00
|
|
|
|
return worldPosition;
|
|
|
|
|
|
}
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
|
|
|
|
|
var mapGridSize = mapGenerator.GetSize();
|
|
|
|
|
|
|
|
|
|
|
|
// 假设每个瓦片在世界空间中的大小是1x1,GetWorldCoordinates返回瓦片中心
|
|
|
|
|
|
var tileWorldHalfSize = 0.5f;
|
|
|
|
|
|
|
|
|
|
|
|
// 计算地图的最小和最大世界坐标(包含边界瓦片)
|
2025-10-03 00:31:34 +08:00
|
|
|
|
Vector2 worldMinTileCenter = mapGenerator.GetWorldCoordinates(Vector3Int.zero);
|
2025-10-10 14:08:23 +08:00
|
|
|
|
Vector2 worldMaxTileCenter =
|
|
|
|
|
|
mapGenerator.GetWorldCoordinates(new Vector3Int(mapGridSize.x - 1, mapGridSize.y - 1));
|
|
|
|
|
|
|
|
|
|
|
|
var worldMinX = worldMinTileCenter.x - tileWorldHalfSize; // 地图的实际左边界
|
|
|
|
|
|
var worldMaxX = worldMaxTileCenter.x + tileWorldHalfSize; // 地图的实际右边界
|
|
|
|
|
|
var worldMinY = worldMinTileCenter.y - tileWorldHalfSize; // 地图的实际下边界
|
|
|
|
|
|
var worldMaxY = worldMaxTileCenter.y + tileWorldHalfSize; // 地图的实际上边界
|
|
|
|
|
|
|
2025-10-03 00:31:34 +08:00
|
|
|
|
var clampedPosition = worldPosition;
|
|
|
|
|
|
clampedPosition.x = Mathf.Clamp(worldPosition.x, worldMinX, worldMaxX);
|
|
|
|
|
|
clampedPosition.y = Mathf.Clamp(worldPosition.y, worldMinY, worldMaxY);
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
|
|
|
|
|
|
2025-10-03 00:31:34 +08:00
|
|
|
|
if (clampedPosition != worldPosition)
|
2025-10-10 14:08:23 +08:00
|
|
|
|
// 细化日志,指明来源方法,并增加_config为null的健壮性检查
|
|
|
|
|
|
Debug.LogWarning(
|
|
|
|
|
|
$"Event_EntityGenerater.GetClampedWorldPosition:原始生成位置 {worldPosition} (LocationType: {_config?.LocationType.ToString() ?? "未知"}) 超出地图可玩区域边界,已限制为 {clampedPosition}。");
|
2025-10-03 00:31:34 +08:00
|
|
|
|
return clampedPosition;
|
|
|
|
|
|
}
|
2025-10-10 14:08:23 +08:00
|
|
|
|
|
|
|
|
|
|
// 辅助枚举:OffMap 生成方向
|
|
|
|
|
|
private enum OffMapDirection
|
|
|
|
|
|
{
|
|
|
|
|
|
Up,
|
|
|
|
|
|
Down,
|
|
|
|
|
|
Left,
|
|
|
|
|
|
Right
|
|
|
|
|
|
}
|
2025-09-03 19:59:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|