(client) feat:支持定义实体的碰撞体大小和偏移;建筑支持定义实体建筑和瓦片建筑,建筑支持指定按钮回调;添加存档管理器;Dev支持设置是否暂停;实体允许定义事件组;添加基地界面 (#57)

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/57
This commit is contained in:
2025-09-28 15:02:57 +08:00
parent 87a8abe86c
commit aff747be17
232 changed files with 39203 additions and 4161 deletions

View File

@@ -1,12 +1,9 @@
using Base;
using UnityEngine;
namespace EventWorkClass
{
public abstract class EventWorkClassBase
{
public abstract void Init(string value);
public abstract void Run(string dimensionID);
public abstract void Run(string dimensionID, Entity.Entity initiator = null);
}
}

View File

@@ -177,7 +177,8 @@ namespace EventWorkClass
/// 运行实体生成器事件,在指定维度生成实体。
/// </summary>
/// <param name="dimensionID">要生成实体的维度ID。</param>
public override void Run(string dimensionID)
/// <param name="initiator">发起者</param>
public override void Run(string dimensionID,Entity.Entity initiator = null)
{
if (_config == null)
{
@@ -190,6 +191,7 @@ namespace EventWorkClass
return;
}
for (int i = 0; i < _config.Count; i++)
{
// 随机选择一个实体定义
@@ -205,13 +207,13 @@ namespace EventWorkClass
if (selectedEntityDef is CharacterDef characterDef)
{
EntityManage.Instance.GenerateEntity(dimensionID, characterDef, position);
EntityManager.Instance.GenerateEntity(dimensionID, characterDef, position);
continue;
}
if (selectedEntityDef is MonsterDef monsterDef)
{
EntityManage.Instance.GenerateMonsterEntity(dimensionID, monsterDef, position);
EntityManager.Instance.GenerateMonsterEntity(dimensionID, monsterDef, position);
continue;
}
Debug.LogWarning($"目标实体 '{selectedEntityDef.defName}' (类型: {selectedEntityDef.GetType().Name}) 既不是 CharacterDef 也不是 MonsterDef。" +
@@ -237,7 +239,7 @@ namespace EventWorkClass
Debug.LogError($"未找到维度 '{dimensionID}'。无法确定生成位置。");
return Vector3.zero;
}
var mapGenerator = dimension.mapGenerator;
var mapGenerator = dimension.landform;
if (mapGenerator == null)
{
Debug.LogError($"维度 '{dimensionID}' 的地图生成器为空。无法确定生成位置。");
@@ -249,32 +251,32 @@ namespace EventWorkClass
{
var size = mapGenerator.GetSize();
var dir = Random.Range(0, 4); // 0:上, 1:下, 2:左, 3:右
Vector2Int gridCoord; // 地图网格坐标
Vector3Int gridCoord; // 地图网格坐标
Vector2 worldCoord2D; // GetWorldCoordinates 返回的 Vector2Int
Vector3 worldPos3D; // 最终的 Vector3 世界坐标
var offset = _config.OffMapOffset;
switch (dir)
{
case 0: // Top border (max Y)
gridCoord = new Vector2Int(Random.Range(0, size.x), size.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 Vector2Int(Random.Range(0, size.x), 0);
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 Vector2Int(0, Random.Range(0, size.y));
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 Vector2Int(size.x, Random.Range(0, size.y));
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;
@@ -301,7 +303,7 @@ namespace EventWorkClass
Debug.LogWarning($"配置了 'AroundTargetEntity',但 'TargetFactionDefName' 为空或null。无法找到派系实体。将在原点生成。");
return Vector3.zero;
}
var factionEntities = EntityManage.Instance.FindEntitiesByFaction(dimensionID, _config.TargetFactionDefName);
var factionEntities = EntityManager.Instance.FindEntitiesByFaction(dimensionID, _config.TargetFactionDefName);
if (factionEntities == null || factionEntities.Length == 0)
{
Debug.LogWarning($"在维度 '{dimensionID}' 中未找到派系 '{_config.TargetFactionDefName}' 的任何实体。将在原点生成。");
@@ -324,7 +326,7 @@ namespace EventWorkClass
case EntitySpawnLocationType.RandomlyOnMap:
{
var size = mapGenerator.GetSize();
var randomGridPos = new Vector2Int(Random.Range(0, size.x), Random.Range(0, size.y));
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);

View File

@@ -1,273 +0,0 @@
using System;
using System.Collections.Generic;
using Data;
using Managers;
using UnityEngine;
using System.Linq;
using Entity;
using Map; // 用于 LINQ
namespace EventWorkClass
{
/// <summary>
/// Event_GiveHediff 事件的配置数据结构。
/// </summary>
[Serializable]
public class GiveHediffConfig
{
/// <summary>
/// 要添加或移除的 Hediff 的定义名 (defName)。
/// </summary>
public string HediffDefName;
/// <summary>
/// 要添加或移除的 Hediff 定义的具体类型名,例如 "HediffDef" 或其子类名。
/// 用于 DefineManager 的严格类型查找。
/// </summary>
public string HediffDefTypeName;
/// <summary>
/// 要执行的操作类型:添加或移除 Hediff。
/// </summary>
public HediffActionType ActionType;
/// <summary>
/// 目标实体选择类型。
/// </summary>
public TargetEntitySelectionType TargetSelectionType;
// 以下是各种 TargetSelectionType 可能需要的配置参数
/// <summary>
/// 用于 SpecificEntityById 类型目标实体的唯一ID。
/// </summary>
public string TargetEntityId;
/// <summary>
/// 用于 AllEntitiesInFaction 或 TargetSpecificFactionLeader 类型:目标派系的定义名 (factionDefName)。
/// </summary>
public string TargetFactionDefName;
/// <summary>
/// 用于 AroundSpecificCoordinates 类型:生成中心坐标。
/// </summary>
public Vector3 CenterCoordinates;
/// <summary>
/// 用于 AroundSpecificCoordinates 类型:搜索半径。
/// </summary>
public float Radius = 10f; // 默认半径
}
/// <summary>
/// 定义针对 Hediff 的操作类型。
/// </summary>
public enum HediffActionType
{
/// <summary>
/// 添加 Hediff。
/// </summary>
Add,
/// <summary>
/// 移除 Hediff。
/// </summary>
Remove
}
/// <summary>
/// 定义目标实体选择的类型。
/// </summary>
public enum TargetEntitySelectionType
{
/// <summary>
/// 未指定或无效的目标选择类型。
/// </summary>
None = 0,
/// <summary>
/// 目标是当前玩家(如果存在)。
/// </summary>
Player,
/// <summary>
/// 目标是指定派系的所有活动实体。
/// </summary>
AllEntitiesInFaction,
/// <summary>
/// 目标是指定坐标周围半径内的所有生物。
/// </summary>
AroundSpecificCoordinates
}
/// <summary>
/// Event_GiveHediff 事件类:用于给予或移除健康状态 (Hediff)。
/// </summary>
public class Event_GiveHediff : EventWorkClassBase
{
private GiveHediffConfig _config;
private HediffDef _aimHediffDef;
/// <summary>
/// 初始化 Hediff 给予/移除事件。
/// </summary>
/// <param name="value">包含事件配置的JSON字符串。</param>
public override void Init(string value)
{
if (string.IsNullOrEmpty(value))
{
Debug.LogError("Event_GiveHediff: 初始化值为空或null。请提供一个JSON配置字符串。");
return;
}
try
{
_config = JsonUtility.FromJson<GiveHediffConfig>(value);
if (_config == null)
{
Debug.LogError($"Event_GiveHediff: 无法解析配置JSON: {value}");
return;
}
if (string.IsNullOrEmpty(_config.HediffDefTypeName) || string.IsNullOrEmpty(_config.HediffDefName))
{
Debug.LogError($"Event_GiveHediff: Hediff定义类型名或名称为空或null (名称: '{_config.HediffDefName}', 类型: '{_config.HediffDefTypeName}')。无法查找Hediff定义。");
return;
}
_aimHediffDef = (HediffDef)DefineManager.Instance.FindDefine(_config.HediffDefTypeName, _config.HediffDefName);
if (_aimHediffDef == null)
{
Debug.LogError($"Event_GiveHediff: 未找到Hediff定义 (名称: '{_config.HediffDefName}', 类型: '{_config.HediffDefTypeName}')。请检查配置。");
}
}
catch (Exception ex)
{
Debug.LogError($"Event_GiveHediff: 解析配置JSON时出错: {value}。异常信息: {ex.Message}");
}
}
/// <summary>
/// 运行 Hediff 给予/移除事件。
/// </summary>
/// <param name="dimensionID">目标实体所在的维度ID。</param>
public override void Run(string dimensionID)
{
if (_config == null)
{
Debug.LogError("Event_GiveHediff: 事件配置(_config)为空。Init()可能失败了。");
return;
}
if (_aimHediffDef == null)
{
Debug.LogError($"Event_GiveHediff: 目标Hediff定义为空 (名称: {_config.HediffDefName}, 类型: {_config.HediffDefTypeName})。无法执行操作。");
return;
}
var targetEntities = GetTargetEntities(dimensionID);
if (targetEntities == null || targetEntities.Count == 0)
{
Debug.LogWarning($"Event_GiveHediff: 在维度 '{dimensionID}' 中未找到符合条件的目标实体 (选择类型: {_config.TargetSelectionType})。");
return;
}
var successCount = 0;
var failedCount = 0;
var hediffInstance = new Hediff(_aimHediffDef); // 创建 Hediff 实例
foreach (var entity in targetEntities)
{
if (entity is LivingEntity livingEntity)
{
try
{
if (_config.ActionType == HediffActionType.Add)
{
livingEntity.AddHediff(hediffInstance);
Debug.Log($"Event_GiveHediff: 已向实体 '{livingEntity.name}' 添加 Hediff '{_aimHediffDef.defName}'。");
successCount++;
}
else if (_config.ActionType == HediffActionType.Remove)
{
livingEntity.RemoveHediff(hediffInstance);
Debug.Log($"Event_GiveHediff: 已从实体 '{livingEntity.name}' 移除 Hediff '{_aimHediffDef.defName}'。");
successCount++;
}
}
catch (Exception ex)
{
Debug.LogError($"Event_GiveHediff: 对实体 '{entity.name}' 执行 Hediff 操作时出错: {ex.Message}");
failedCount++;
}
}
else
{
Debug.LogWarning($"Event_GiveHediff: 实体 '{entity.name}' (类型: {entity.GetType().Name}) 不是 LivingEntity无法添加/移除 Hediff。");
failedCount++;
}
}
Debug.Log($"Event_GiveHediff: 完成操作。成功: {successCount},失败: {failedCount} (Hediff: {_aimHediffDef.defName}, 操作: {_config.ActionType})。");
}
/// <summary>
/// 根据配置获取目标实体列表。
/// </summary>
/// <param name="dimensionID">维度ID。</param>
/// <returns>符合条件的目标实体列表。</returns>
private List<Entity.Entity> GetTargetEntities(string dimensionID)
{
var entities = new List<Entity.Entity>();
var dimension = Program.Instance.GetDimension(dimensionID);
if (dimension == null)
{
Debug.LogError($"Event_GiveHediff: 未找到维度 '{dimensionID}'。无法获取目标实体。");
return entities;
}
switch (_config.TargetSelectionType)
{
case TargetEntitySelectionType.Player:
// 假设 PlayerManager.Instance.GetPlayerEntity(dimensionID) 返回 LivingEntity
// 或者你的玩家实体本身就是 LivingEntity
var player = dimension.focusEntity as LivingEntity; // 假设玩家是维度的一部分且是LivingEntity
if (player != null)
{
entities.Add(player);
}
else
{
Debug.LogWarning($"Event_GiveHediff: 未能找到维度 '{dimensionID}' 中的玩家实体或玩家不是 LivingEntity。");
}
break;
case TargetEntitySelectionType.AllEntitiesInFaction:
if (string.IsNullOrEmpty(_config.TargetFactionDefName))
{
Debug.LogWarning("Event_GiveHediff: 配置了 'AllEntitiesInFaction',但 'TargetFactionDefName' 为空。");
return entities;
}
entities.AddRange(EntityManage.Instance
.FindEntitiesByFaction(dimensionID, _config.TargetFactionDefName)
.Select(item => item.entity));
break;
case TargetEntitySelectionType.AroundSpecificCoordinates:
// 假设 EntityManage 提供了根据位置和半径查找实体的方法
// 或者可以直接通过场景查询(例如 Physics.OverlapSphere 或者自定义管理系统)
// 为了简化,我将假设 EntityManage 提供 FindEntitiesAroundRadius 方法
var allEntitiesInDimension = EntityManage.Instance.GetAllEntities(dimensionID)
.Select(item => item.entity);
var center = _config.CenterCoordinates;
var radius = _config.Radius;
foreach (var ent in allEntitiesInDimension)
{
// 确保实体有 Transform 且在半径内
if (ent != null && ent.transform != null && Vector3.Distance(ent.transform.position, center) <= radius)
{
entities.Add(ent);
}
}
break;
case TargetEntitySelectionType.None:
default:
Debug.LogWarning($"Event_GiveHediff: 未知或不支持的目标实体选择类型: {_config.TargetSelectionType}。");
break;
}
return entities;
}
}
}

View File

@@ -0,0 +1,74 @@
using System;
using Base;
using Newtonsoft.Json;
namespace EventWorkClass
{
public class Event_OpenUI : EventWorkClassBase
{
// 新增私有数据类用于JSON反序列化
private class OpenUIEventData
{
public string uiName { get; set; } // 对应JSON中的 "uiName" 字段
}
// 新增私有字段用于存储解析出的UI名称
private string _uiName;
/// <summary>
/// 初始化事件解析JSON字符串获取UI名称。
/// </summary>
/// <param name="value">包含UI名称的JSON字符串例如: {"uiName": "MainMenuUI"}</param>
/// <exception cref="System.ArgumentException">当value为null或空时抛出。</exception>
/// <exception cref="System.FormatException">当JSON格式无效或缺少'uiName'属性时抛出。</exception>
/// <exception cref="System.InvalidOperationException">当发生其他意外错误时抛出。</exception>
public override void Init(string value)
{
if (string.IsNullOrEmpty(value))
{
throw new System.ArgumentException(
"Event_OpenUI Init: value cannot be null or empty. It must contain the UI configuration JSON.",
nameof(value));
}
try
{
// 反序列化JSON字符串为OpenUIEventData对象
OpenUIEventData eventData = JsonConvert.DeserializeObject<OpenUIEventData>(value);
// 检查反序列化结果是否有效
if (eventData == null || string.IsNullOrEmpty(eventData.uiName))
{
throw new System.FormatException(
$"Event_OpenUI Init: Invalid JSON format or missing 'uiName' property in value: '{value}'");
}
// 存储UI名称
_uiName = eventData.uiName;
}
catch (Newtonsoft.Json.JsonSerializationException ex)
{
// JSON反序列化失败的特定处理
throw new System.FormatException(
$"Event_OpenUI Init: Failed to deserialize JSON for value: '{value}'. Please ensure it is a valid JSON string with a 'uiName' property. Error: {ex.Message}",
ex);
}
catch (Exception ex)
{
// 捕获其他所有潜在异常,提供更通用的错误信息
throw new System.InvalidOperationException(
$"Event_OpenUI Init: An unexpected error occurred during initialization for value: '{value}'. Error: {ex.Message}",
ex);
}
}
/// <summary>
/// 运行事件打开UI界面。
/// </summary>
/// <param name="dimensionID">维度ID (当前未被使用,但保留接口)。</param>
/// <param name="initiator">触发此事件的实体 (当前未被使用,但保留接口)。</param>
public override void Run(string dimensionID, Entity.Entity initiator = null)
{
UIInputControl.Instance.Show(_uiName);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 83d4eabcba674ad6b8120b84cb71de7a
timeCreated: 1758949174

View File

@@ -0,0 +1,161 @@
using Managers;
using Newtonsoft.Json;
using UnityEngine;
namespace EventWorkClass
{
/// <summary>
/// 音频播放事件的配置类。
/// 通过JSON字符串进行初始化。
/// </summary>
public class AudioEventConfig
{
[Tooltip("要播放的音频定义名,通过 AudioManager 获取 AudioClip。")]
public string audioClipName;
[Tooltip("播放音量。0.0 到 1.0。")]
[Range(0f, 1f)]
public float volume = 1.0f;
[Tooltip("是否循环播放。")]
public bool loop = false;
[Tooltip("空间混合。0.0 为完全2D1.0 为完全3D。")]
[Range(0f, 1f)]
public float spatialBlend = 0.0f; // 默认为2D音效
[Tooltip("如果存在发起者是否将音频播放到发起者initiator的 AudioSource 上。")]
public bool attachToInitiator = true;
[Tooltip("播放前的延迟秒数。")]
public float delay = 0.0f;
[Tooltip("如果是非循环播放且是临时创建的 AudioSource是否在播放结束后自动销毁。")]
public bool destroyAfterPlay = true;
/// <summary>
/// 验证配置是否有效。
/// </summary>
public bool IsValid()
{
return !string.IsNullOrEmpty(audioClipName);
}
}
/// <summary>
/// 实现一个音频播放事件。
/// 配置通过JSON字符串传入可指定音频名、音量、循环、空间混合、是否依附于发起者等。
/// </summary>
public class Event_PlayAudio : EventWorkClassBase
{
private AudioEventConfig _config;
private bool _isInitialized = false;
/// <summary>
/// 初始化音频播放事件解析JSON配置。
/// </summary>
/// <param name="value">包含音频配置的JSON字符串。</param>
public override void Init(string value)
{
_isInitialized = false;
if (string.IsNullOrEmpty(value))
{
Debug.LogError("EventPlayAudio 初始化错误:配置 JSON 值为 null 或空。跳过初始化。");
return;
}
try
{
// 使用 Newtonsoft.Json 进行反序列化
_config = JsonConvert.DeserializeObject<AudioEventConfig>(value);
if (_config == null || !_config.IsValid())
{
Debug.LogError("EventPlayAudio 初始化错误:反序列化的配置无效或为空。音频剪辑名称可能缺失。跳过初始化。");
}
else
{
_isInitialized = true;
}
}
catch (System.Exception ex) // 可以更具体地捕获 Newtonsoft.Json.JsonException
{
Debug.LogError($"EventPlayAudio 初始化错误:使用 Newtonsoft.Json 解析 JSON 配置失败:{ex.Message}\nJSON: {value}");
}
}
/// <summary>
/// 运行音频播放事件。
/// </summary>
/// <param name="dimensionID">维度ID此事件中暂未使用保留扩展性。</param>
/// <param name="initiator">事件发起者实体可能包含一个AudioSource。</param>
public override void Run(string dimensionID, Entity.Entity initiator = null)
{
if (!_isInitialized)
{
Debug.LogError("EventPlayAudio 运行错误:事件未初始化或配置无效。跳过音频播放。");
return;
}
// 获取音频片段
var audioClip = AudioManager.Instance.GetAudioClip(_config.audioClipName);
if (!audioClip)
{
Debug.LogError($"EventPlayAudio 运行错误AudioManager 中未找到音频剪辑 '{_config.audioClipName}'。跳过播放。");
return;
}
AudioSource audioSourceToUse = null;
var isTemporaryAudioSource = false;
// 尝试使用发起者的AudioSource
if (_config.attachToInitiator && initiator != null && initiator.Audio != null)
{
audioSourceToUse = initiator.Audio;
}
else
{
// 如果没有发起者AudioSource或不依附于发起者则创建临时的GameObject和AudioSource
var tempAudioGO = new GameObject($"TempAudio__{_config.audioClipName}");
audioSourceToUse = tempAudioGO.AddComponent<AudioSource>();
isTemporaryAudioSource = true;
if (_config.spatialBlend > 0f)
{
if (initiator != null)
{
tempAudioGO.transform.position = initiator.transform.position;
}
else
{
tempAudioGO.transform.position = Vector3.zero; // 默认位置
}
}
// 如果 spatialBlend 为 0则无需设置位置它将保持默认的 Vector3.zero。
}
// 配置AudioSource参数
audioSourceToUse.clip = audioClip;
audioSourceToUse.volume = _config.volume;
audioSourceToUse.loop = _config.loop;
audioSourceToUse.spatialBlend = _config.spatialBlend;
audioSourceToUse.playOnAwake = false; // 由Run方法显式控制播放
// 播放音频
if (_config.delay > 0)
{
audioSourceToUse.PlayDelayed(_config.delay);
}
else
{
audioSourceToUse.Play();
}
// 如果是临时AudioSource且不循环播放且配置为播放后销毁则在播放结束后自动销毁
if (isTemporaryAudioSource && !_config.loop && _config.destroyAfterPlay)
{
// 计算销毁时间:延迟 + 音频长度
var destroyTime = _config.delay + audioClip.length;
Object.Destroy(audioSourceToUse.gameObject, destroyTime);
}
// 如果是循环播放的临时AudioSource不自动销毁需要外部机制停止/销毁
else if (isTemporaryAudioSource && _config.loop)
{
Debug.LogWarning($"EventPlayAudio为 '{_config.audioClipName}' 创建了循环临时 AudioSource。它将不会被自动销毁。需要外部管理来停止和销毁它。");
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c4babb896d7a4ce3a3fc68604379bc58
timeCreated: 1758536607