(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,10 +0,0 @@
namespace Managers
{
public class ArchiveManager
{
public bool Save(string filename)
{
return false;
}
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: a6952f5a135a4809bdeb721b4761aa6f
timeCreated: 1752635779

View File

@@ -42,7 +42,7 @@ namespace Managers
// 如果已经有数据,则跳过初始化,防止重复加载。
return;
}
defaultAudioClip = Resources.Load<AudioClip>("Default/DefaultAudio"); // 假设存在一个默认音频路径
defaultAudioClip = Resources.Load<AudioClip>("Default/DefaultAudio");
if (defaultAudioClip == null)
{
Debug.LogWarning("AudioManager: 无法加载默认音频 'Resources/Default/DefaultAudio'。请确保文件存在。");

View File

@@ -14,7 +14,7 @@ namespace Managers
/// 管理游戏中所有实体(角色、建筑、子弹等)的生命周期、存储和分发。
/// 实现了维度感知,这意味着不同维度的实体数据是独立管理的。
/// </summary>
public class EntityManage : Utils.MonoSingleton<EntityManage>, ITick
public class EntityManager : Utils.MonoSingleton<EntityManager>, ITick
{
/// <summary>
/// 维度感知的实体存储结构。
@@ -147,6 +147,12 @@ namespace Managers
foreach (var entityToRemove in entitiesToRemove)
{
entities.Remove(entityToRemove);
var drawNode = entityToRemove.entity?.entityDef?.drawingOrder.GetDrawNodeDef(
entityToRemove.entity.CurrentState, entityToRemove.entity.CurrentOrientation,out _);
if (drawNode != null)
TemporaryAnimationManager.Instance.GenerateTemporaryAnimation(drawNode,
entityToRemove.Position, drawNode.GetMinAnimationDuration());
if (entityToRemove != null)
{
Destroy(entityToRemove.gameObject);
@@ -357,20 +363,18 @@ namespace Managers
/// <param name="dimensionId">实体所属的维度ID。</param>
/// <param name="entityDef">实体定义对象。</param>
/// <param name="pos">生成位置。</param>
public void GenerateEntity(string dimensionId, EntityDef entityDef, Vector3 pos)
public EntityPrefab GenerateEntity(string dimensionId, EntityDef entityDef, Vector3 pos)
{
if (!characterPrefab)
{
Debug.LogError("实体管理器characterPrefab 为空!请分配一个有效的预制体。");
GenerateDefaultEntity(dimensionId, pos);
return;
return GenerateDefaultEntity(dimensionId, pos);
}
if (entityDef == null)
{
Debug.LogError("实体管理器EntityDef 为空!无法生成实体。");
GenerateDefaultEntity(dimensionId, pos);
return;
return GenerateDefaultEntity(dimensionId, pos);
}
var result = GenerateEntityInternal(
@@ -379,7 +383,7 @@ namespace Managers
pos,
entityDef
);
if (result == null) GenerateDefaultEntity(dimensionId, pos);
return !result ? GenerateDefaultEntity(dimensionId, pos) : result;
}
public void GenerateMonsterEntity(string dimensionId, MonsterDef monsterDef, Vector3 pos)
{
@@ -411,7 +415,7 @@ namespace Managers
/// <param name="dimensionId">实体所属的维度ID。</param>
/// <param name="buildingDef">建筑定义对象。</param>
/// <param name="pos">网格位置。</param>
public void GenerateBuildingEntity(string dimensionId, BuildingDef buildingDef, Vector3Int pos)
public void GenerateBuildingEntity(string dimensionId, BuildingDef buildingDef, Vector3 pos)
{
if (!buildingPrefab)
{
@@ -427,14 +431,14 @@ namespace Managers
return;
}
var worldPos = new Vector3(pos.x, pos.y, pos.z);
var worldPos = new Vector3(pos.x, pos.y, 0.5f);
var result = GenerateEntityInternal(
dimensionId,
buildingPrefab.gameObject,
worldPos,
buildingDef
);
if (result == null) GenerateDefaultEntity(dimensionId, worldPos);
if (!result) GenerateDefaultEntity(dimensionId, worldPos);
}
/// <summary>
@@ -555,25 +559,25 @@ namespace Managers
/// </summary>
/// <param name="dimensionId">实体所属的维度ID。</param>
/// <param name="pos">生成位置。</param>
public void GenerateDefaultEntity(string dimensionId, Vector3 pos)
public EntityPrefab GenerateDefaultEntity(string dimensionId, Vector3 pos)
{
if (!Program.Instance.GetDimension(dimensionId))
{
Debug.LogError($"实体管理器:无法生成默认实体:维度 '{dimensionId}' 在程序Program中不活跃或未注册。");
return;
return null;
}
var parentLayer = EnsureLayerExists(dimensionId, "DefaultEntityLevel");
if (parentLayer == null)
{
Debug.LogError($"实体管理器:无法在维度 '{dimensionId}' 中获取默认实体的父 Transform。");
return;
return null;
}
if (defaultEntityPrefab == null || defaultEntityPrefab.gameObject == null)
{
Debug.LogError($"实体管理器defaultEntityPrefab 为空或没有GameObject。无法为维度 '{dimensionId}' 生成默认实体。");
return;
return null;
}
var entity = Instantiate(defaultEntityPrefab, pos, Quaternion.identity, parentLayer);
@@ -583,12 +587,13 @@ namespace Managers
{
Debug.LogError($"实体管理器:默认实体预制体 '{defaultEntityPrefab.name}' 缺少 EntityPrefab 组件。正在销毁已实例化的对象。");
Destroy(entity.gameObject);
return;
return null;
}
const string factionKey = "default";
_pendingAdditions.Add(Tuple.Create(dimensionId, factionKey, entityComponent));
entityComponent.DefaultInit();
return entityComponent;
}
/// <summary>
@@ -856,6 +861,7 @@ namespace Managers
protected override void OnStart()
{
SceneManager.sceneLoaded += OnSceneLoaded;
Clock.AddTick(this);
}
/// <summary>
@@ -864,6 +870,7 @@ namespace Managers
private void OnDestroy()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
Clock.RemoveTick(this);
}
/// <summary>
@@ -932,5 +939,7 @@ namespace Managers
}
}
}
}
}

View File

@@ -41,13 +41,13 @@ namespace Managers
public void Init()
{
// 如果事件和故事定义已经加载,则直接返回,避免重复初始化。
if (EventDefs != null && StoryDefs != null)
if (EventDefs != null && StoryDefs != null)
return;
// 从定义管理器查询并加载所有事件定义。
var eventDefs = DefineManager.Instance.QueryDefinesByType<EventDef>();
EventDefs = new Dictionary<string, EventWorkClassBase>();
if(eventDefs==null)
if (eventDefs == null)
return;
foreach (var def in eventDefs)
{
@@ -56,30 +56,35 @@ namespace Managers
Debug.LogWarning($"警告:事件名称重复,已跳过加载名称为 '{def.defName}' 的事件定义。");
continue;
}
var eventWorker = GetAndInstantiateEventWorker(def.workClass);
var eventWorker = Utils.StringUtils.GetAndInstantiateEventWorker(def.workClass);
if (eventWorker == null)
{
Debug.LogWarning($"警告:未能找到或实例化名称为 '{def.workClass}' 的事件工作类,已跳过加载名称为 '{def.defName}' 的事件定义。");
continue;
}
eventWorker.Init(def.parameter);
eventWorker.Init(def.parameter);
EventDefs.Add(def.defName, eventWorker);
}
// 从定义管理器查询并加载所有故事定义。
var storyDefs = DefineManager.Instance.QueryDefinesByType<StoryDef>();
StoryDefs = new Dictionary<string, StoryDef>();
foreach (var storyDef in storyDefs)
{
if (StoryDefs.ContainsKey(storyDef.defName))
if (storyDefs != null)
foreach (var storyDef in storyDefs)
{
Debug.LogWarning($"警告:故事名称重复,已跳过加载名称为 '{storyDef.defName}' 的故事定义。");
continue;
if (StoryDefs.ContainsKey(storyDef.defName))
{
Debug.LogWarning($"警告:故事名称重复,已跳过加载名称为 '{storyDef.defName}' 的故事定义。");
continue;
}
StoryDefs.Add(storyDef.defName, storyDef);
}
StoryDefs.Add(storyDef.defName, storyDef);
}
// 将自身注册到时钟系统,以便每帧更新故事播放。
Clock.AddTick(this);
Clock.AddTick(this);
}
/// <summary>
@@ -121,6 +126,22 @@ namespace Managers
eventWorker.Run(dimensionID);
}
public void Action(string eventName, Entity.Entity initiator)
{
if (!EventDefs.TryGetValue(eventName, out var eventWorker))
{
Debug.LogWarning($"警告:未能找到名称为 '{eventName}' 的事件定义,已跳过执行该事件。");
return;
}
eventWorker.Run(initiator.currentDimensionId, initiator);
}
public void Action(EventDef eventDef, Entity.Entity initiator)
{
Action(eventDef.defName, initiator);
}
/// <summary>
/// 播放指定名称的故事。
/// </summary>
@@ -201,69 +222,6 @@ namespace Managers
}
/// <summary>
/// 根据类名从指定命名空间和程序集下获取并实例化一个 <see cref="EventWorkClassBase"/> 的子类。
/// </summary>
/// <param name="className">要实例化的类的短名称(不包含命名空间)。</param>
/// <param name="targetNamespace">目标类所在的完整命名空间。</param>
/// <param name="assemblyToSearch">要搜索的程序集。如果为 null将搜索 <see cref="EventWorkClassBase"/> 所在的程序集。</param>
/// <returns>实例化后的 <see cref="EventWorkClassBase"/> 对象,如果找不到或不符合条件则返回 null。</returns>
public static EventWorkClassBase GetAndInstantiateEventWorker(
string className,
string targetNamespace = "EventWorkClass", // 默认命名空间
Assembly assemblyToSearch = null) // 默认程序集
{
// 1. 确定要搜索的程序集。
if (assemblyToSearch == null)
{
// 默认从 EventWorkClassBase 所在的程序集查找,通常其实现类也位于此程序集。
assemblyToSearch = typeof(EventWorkClassBase).Assembly;
}
// 2. 构造完整的类型名称。
var fullTypeName = $"{targetNamespace}.{className}";
Type targetType = null;
// 3. 尝试直接从程序集获取类型。
targetType = assemblyToSearch.GetType(fullTypeName);
// 4. 进行类型检查。
if (targetType == null)
{
Debug.LogError($"错误:在程序集 '{assemblyToSearch.FullName}' 的命名空间 '{targetNamespace}' 中未找到类 '{className}'。");
return null;
}
// 检查是否是 EventWorkClassBase 的子类。
if (!typeof(EventWorkClassBase).IsAssignableFrom(targetType))
{
Debug.LogError($"错误:类 '{fullTypeName}' 不是 '{typeof(EventWorkClassBase).FullName}' 的子类。");
return null;
}
// 检查是否可以实例化(非抽象类,非接口)。
if (targetType.IsAbstract || targetType.IsInterface)
{
Debug.LogError($"错误:类 '{fullTypeName}' 是抽象类或接口,不能直接实例化。");
return null;
}
// 5. 实例化对象。
try
{
// 使用 Activator.CreateInstance 实例化对象。它默认调用无参公共构造函数。
var instance = Activator.CreateInstance(targetType);
return instance as EventWorkClassBase;
}
catch (MissingMethodException ex)
{
Debug.LogError($"错误:类 '{fullTypeName}' 没有公共的无参构造函数。详细信息: {ex.Message}");
return null;
}
catch (Exception ex)
{
Debug.LogError($"实例化类 '{fullTypeName}' 时发生未知错误。详细信息: {ex.Message}");
return null;
}
}
/// <summary>
/// 表示一个正在播放的故事实例的状态机。

View File

@@ -0,0 +1,10 @@
using System;
using Entity;
namespace Managers
{
public class HediffManager:Utils.Singleton<HediffManager>
{
public Hediff[] BaseHediffs= Array.Empty<Hediff>();
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5b36ca3a691f4002a5c8360743670569
timeCreated: 1759024031

View File

@@ -39,13 +39,15 @@ namespace Managers
/// <summary>
/// 初始化图像管理器,加载默认精灵并处理所有 ImageDef 定义。
/// </summary>
/// <remarks>
/// <para>
/// **修正后的逻辑**:移除 <c>packagesImages.Count > 0</c> 的重复加载检查。
/// <c>Init</c> 方法现在将始终执行完整的初始化流程,确保所有组件都被正确加载。
/// 如果需要重新加载,应调用 <c>Reload()</c> 方法。
/// </para>
/// </remarks>
public void Init()
{
if (packagesImages.Count > 0)
{
// 如果已经有数据,则跳过初始化,防止重复加载。
return;
}
defaultSprite = Resources.Load<Sprite>("Default/DefaultImage");
if (defaultSprite == null)
{
@@ -59,6 +61,10 @@ namespace Managers
/// </summary>
public void InitImageDef()
{
// 在每次 InitImageDef 调用前,清空旧数据,以确保重新初始化是干净的。
packagesImages.Clear();
sprites.Clear();
var textureCache = new Dictionary<string, Texture2D>();
var imageDef = Managers.DefineManager.Instance.QueryDefinesByType<ImageDef>();
@@ -79,7 +85,7 @@ namespace Managers
try
{
string cacheKey;
Texture2D texture = null;
Texture2D texture;
if (ima.path.StartsWith("res:"))
{
@@ -146,27 +152,28 @@ namespace Managers
// 资源加载失败
if (!texture)
{
Debug.LogError($"未能加载图像定义关联的纹理: '{ima.defName}' (路径: '{ima.path}', 包ID: '{ima.packID}')。请验证路径和文件是否存在。");
Debug.LogError($"未能加载图像定义关联的纹理: '{ima.defName}' (路径: '{ima.path}', 包ID: '{ima.packID}')。" +
$"请验证文件路径、文件权限以及Unity Asset导入设置中的 'Read/Write Enabled' 选项。");
continue;
}
// --- 修改开始 ---
Texture2D processedTexture = texture;
var processedTexture = texture;
// 如果需要翻转,创建翻转后的纹理
if (ima.flipX || ima.flipY)
{
Texture2D flippedTex = FlipTexture(texture, ima.flipX, ima.flipY);
if (flippedTex != null)
{
processedTexture = flippedTex;
}
else
{
Debug.LogError($"未能翻转图像定义关联的纹理: '{ima.defName}' (路径: '{ima.path}', 包ID: '{ima.packID}'),将使用原始纹理。");
}
var flippedTex = FlipTexture(texture, ima.flipX, ima.flipY);
if (flippedTex != null)
{
processedTexture = flippedTex;
}
else
{
Debug.LogError($"未能翻转图像定义关联的纹理: '{ima.defName}' (路径: '{ima.path}', 包ID: '{ima.packID}'),将使用原始纹理。");
}
}
packagesImages[ima.defName] = processedTexture;
packagesImages[ima.defName] = processedTexture;
// 索引计算依赖这些参数来匹配游戏资源加载的预期。
SplitTextureIntoSprites(ima.defName, processedTexture, ima.hCount, ima.wCount, ima.pixelsPerUnit,
ima.flipX, ima.flipY);
}
@@ -187,14 +194,22 @@ namespace Managers
/// <param name="rows">水平分割的行数。</param>
/// <param name="cols">垂直分割的列数。</param>
/// <param name="pixelsPerUnit">每个单元的像素数用于Sprite.Create。</param>
/// <param name="flipX">是否沿X轴翻转此参数用于调整索引。</param>
/// <param name="flipY">是否沿Y轴翻转此参数用于调整索引。</param>
/// <remarks>
/// <para>
/// **修正后的逻辑**:恢复 <c>flipX</c> 和 <c>flipY</c> 参数及其在索引计算中的使用。
/// 这确保了精灵的编号逻辑与原始设计保持一致,避免了游戏资源加载错误。
/// </para>
/// </remarks>
private void SplitTextureIntoSprites(
string baseName,
Texture2D texture,
int rows,
int cols,
int pixelsPerUnit,
bool flipX = false,
bool flipY = false)
bool flipX = false,
bool flipY = false)
{
if (!texture)
{
@@ -202,30 +217,26 @@ namespace Managers
return;
}
// 如果行数或列数小于1则设为1不分割
rows = Mathf.Max(1, rows);
cols = Mathf.Max(1, cols);
var textureWidth = texture.width;
var textureHeight = texture.height;
// 创建未分割的完整精灵,使用原始名称 (baseName即 ImageDef.name)
var fullSpriteRect = new Rect(0, 0, textureWidth, textureHeight);
var fullSprite = Sprite.Create(texture, fullSpriteRect, new Vector2(0.5f, 0.5f), pixelsPerUnit);
fullSprite.name = baseName; // 确保 Sprite.name 被设置
fullSprite.name = baseName;
sprites[baseName] = fullSprite;
// 如果不分割rows和cols都为1提前返回
if (rows == 1 && cols == 1)
{
return;
}
// 检查纹理尺寸是否可被分割数整除
if (textureWidth % cols != 0 || textureHeight % rows != 0)
{
Debug.LogError($"'{baseName}' 的纹理尺寸 ({textureWidth}x{textureHeight}) 不能被指定的行数 ({rows}) 和列数 ({cols}) 完美整除。子精灵将不会生成或可能不正确。仅显示完整精灵。");
return; // 终止子精灵分割,只保留完整的精灵
Debug.LogWarning($"'{baseName}' 的纹理尺寸 ({textureWidth}x{textureHeight}) 不能被指定的行数 ({rows}) 和列数 ({cols}) 完美整除。子精灵将不会生成或可能不正确。仅显示完整精灵。");
return;
}
var tileWidth = textureWidth / cols;
@@ -237,7 +248,8 @@ namespace Managers
{
Rect spriteRect = new(col * tileWidth, row * tileHeight, tileWidth, tileHeight);
var sprite = Sprite.Create(texture, spriteRect, new Vector2(0.5f, 0.5f), pixelsPerUnit);
// 精灵索引计算方式
// 此处的 flipX/Y 参数用于调整索引,以符合游戏或美术的特定约定。
var index = (flipY ? row : (rows - row - 1)) * cols + (flipX ? cols - 1 - col : col);
var spriteName = $"{baseName}_{index}";
sprite.name = spriteName;
@@ -252,12 +264,12 @@ namespace Managers
/// <param name="originalTexture">原始纹理。</param>
/// <param name="flipX">是否沿X轴翻转。</param>
/// <param name="flipY">是否沿Y轴翻转。</param>
/// <returns>一个新的、翻转后的Texture2D实例如果不需要翻转则返回原纹理。</returns>
/// <returns>一个新的、翻转后的Texture2D实例如果不需要翻转则返回原纹理如果纹理不可读返回null。</returns>
private Texture2D FlipTexture(Texture2D originalTexture, bool flipX, bool flipY)
{
if (!flipX && !flipY)
{
return originalTexture; // 无需翻转,返回原始纹理
return originalTexture;
}
if (originalTexture == null)
@@ -265,38 +277,32 @@ namespace Managers
Debug.LogError("FlipTexture: 原始纹理为null无法翻转。");
return null;
}
// Unity Texture2D 的 GetPixels/SetPixels 操作要求纹理是可读写的。
// 确保纹理的可读写性,或创建一个可读写的副本。
// Configs.ConfigProcessor.LoadTextureByIO 和 Resources.Load的纹理
// 它们的"Read/Write Enabled"导入设置必须开启。
// 这里为了确保纹理处理成功,我们将像素复制到一个新的可读写纹理中。
int width = originalTexture.width;
int height = originalTexture.height;
// 创建一个全新的Texture2D来承载翻转后的像素。
// 使用Color32代替Color效率更高且通常直接对应原始图像数据。
Texture2D flippedTexture = new Texture2D(width, height, originalTexture.format, originalTexture.mipmapCount > 1);
// 给翻转后的纹理一个名字,方便调试
if (!originalTexture.isReadable)
{
Debug.LogError($"FlipTexture: 纹理 '{originalTexture.name}' (或其来源) 不可读。 " +
$"请确保在Unity导入设置中勾选 'Read/Write Enabled' 选项。无法进行翻转。");
return null;
}
var width = originalTexture.width;
var height = originalTexture.height;
var flippedTexture = new Texture2D(width, height, originalTexture.format, originalTexture.mipmapCount > 1);
flippedTexture.name = originalTexture.name + (flipX ? "_flippedX" : "") + (flipY ? "_flippedY" : "");
Color32[] originalPixels = originalTexture.GetPixels32();
Color32[] flippedPixels = new Color32[width * height];
var originalPixels = originalTexture.GetPixels32();
var flippedPixels = new Color32[width * height];
for (int y = 0; y < height; y++)
for (var y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
for (var x = 0; x < width; x++)
{
// 计算原始像素的索引
int originalIndex = y * width + x;
var originalIndex = y * width + x;
var targetX = flipX ? (width - 1 - x) : x;
var targetY = flipY ? (height - 1 - y) : y;
var flippedIndex = targetY * width + targetX;
// 计算翻转后像素的目标坐标
int targetX = flipX ? (width - 1 - x) : x;
int targetY = flipY ? (height - 1 - y) : y;
// 计算翻转后像素的目标索引
int flippedIndex = targetY * width + targetX;
if (originalIndex >= 0 && originalIndex < originalPixels.Length &&
flippedIndex >= 0 && flippedIndex < flippedPixels.Length)
{
@@ -304,14 +310,13 @@ namespace Managers
}
else
{
// 理论上不应该发生,但作为安全措施
Debug.LogWarning($"FlipTexture: 像素索引计算异常,原始: ({x},{y}) -> 索引 {originalIndex};翻转: ({targetX},{targetY}) -> 索引 {flippedIndex}. Texture: {originalTexture.name}");
}
}
}
flippedTexture.SetPixels32(flippedPixels);
flippedTexture.Apply(); // 应用像素更改到GPU
flippedTexture.Apply();
return flippedTexture;
}
@@ -319,10 +324,6 @@ namespace Managers
/// <summary>
/// 清理所有已加载的纹理和精灵数据。
/// </summary>
/// <remarks>
/// 此方法会清空 <see cref="packagesImages"/> 和 <see cref="sprites"/> 字典,
/// 但不会卸载 <see cref="defaultSprite"/>,因为它通常通过 Resources.Load 加载,由 Unity 管理其生命周期。
/// </remarks>
public void Clear()
{
packagesImages.Clear();
@@ -332,21 +333,17 @@ namespace Managers
/// <summary>
/// 重新加载所有图像数据。
/// </summary>
/// <remarks>
/// 此方法会首先调用 <see cref="Clear()"/> 清理所有数据,然后调用 <see cref="Init()"/> 重新初始化。
/// </remarks>
public void Reload()
{
Clear();
Init();
}
/// <summary>
/// 根据 <see cref="ImageDef"/> 对象获取对应的精灵。
/// 根据 <see cref="Data.ImageDef"/> 对象获取对应的精灵。
/// </summary>
/// <param name="ima">包含精灵名称的 <see cref="ImageDef"/> 对象。</param>
/// <returns>如果找到对应的精灵,则返回该精灵;否则返回 <see cref="defaultSprite"/>。</returns>
/// <param name="ima">图像定义对象。</param>
/// <returns>对应的精灵,如果 <paramref name="ima"/> 为空,则返回默认精灵。</returns>
public Sprite GetSprite(ImageDef ima)
{
return ima == null ? defaultSprite : GetSprite(ima.defName);
@@ -355,10 +352,15 @@ namespace Managers
/// <summary>
/// 根据精灵名称全局唯一的DefName获取对应的精灵。
/// </summary>
/// <param name="name">精灵名称。</param>
/// <returns>如果找到对应的精灵,则返回该精灵;否则返回 <see cref="defaultSprite"/>。</returns>
/// <param name="name">要获取的精灵名称。</param>
/// <returns>对应的精灵,如果名称为空或找不到,则返回默认精灵。</returns>
public Sprite GetSprite(string name)
{
if (string.IsNullOrEmpty(name))
{
Debug.LogWarning($"GetSprite: 尝试获取的精灵名称为空或null。返回默认精灵。");
return defaultSprite;
}
return sprites.GetValueOrDefault(name, defaultSprite);
}
@@ -367,13 +369,18 @@ namespace Managers
/// </summary>
/// <param name="name">精灵的基础名称。</param>
/// <param name="index">子精灵的索引。</param>
/// <returns>如果找到对应的子精灵,则返回该精灵;否则返回 <see cref="defaultSprite"/>。</returns>
/// <returns>对应的子精灵,如果找不到,则返回默认精灵。</returns>
public Sprite GetSprite(string name, int index)
{
var fullName = $"{name}_{index}";
return GetSprite(fullName);
}
/// <summary>
/// 根据精灵名称数组获取对应的精灵数组。
/// </summary>
/// <param name="names">精灵名称数组。</param>
/// <returns>对应的精灵数组,如果输入为空,则返回包含默认精灵的数组。</returns>
public Sprite[] GetSprites(string[] names)
{
if (names == null || names.Length == 0)

View File

@@ -1,10 +1,260 @@
using System;
using System.Collections.Generic;
using Entity;
using System.IO;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEngine;
namespace Managers
{
public class SaveManager:Utils.Singleton<SaveManager>
/// <summary>
/// 用于标记那些包含可存储数据的单例类。
/// 实现此接口的单例应在SaveManager初始化后注册自身。
/// </summary>
public interface ISavableSingleton
{
public List<Hediff> baseHediffs = new();
}
}
/// <summary>
/// 用于标记在单例类中需要被SaveManager存储的属性。
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class SavableAttribute : Attribute
{
/// <summary>
/// 获取或设置用于存储的唯一键。如果为空,则使用属性名称。
/// </summary>
public string Key { get; }
public SavableAttribute(string key = null)
{
Key = key;
}
}
public class SaveManager : Utils.Singleton<SaveManager>, ILaunchManager
{
// 存档文件名
private const string SaveFileName = "game_save.json";
// 存档路径
private static string SaveFilePath => Path.Combine(Application.persistentDataPath, SaveFileName);
public string StepDescription => "加载存档中";
// 存储所有需要保存状态的单例实例
private readonly Dictionary<Type, ISavableSingleton> _savableSingletons = new();
/// <summary>
/// 注册一个需要保存状态的单例实例。
/// </summary>
/// <param name="singleton">实现ISavableSingleton接口的单例实例。</param>
public void RegisterSavable(ISavableSingleton singleton)
{
var singletonType = singleton.GetType();
if (_savableSingletons.TryAdd(singletonType, singleton))
{
// DEBUG: Debug.Log($"SaveManager: 已注册可保存单例: {singletonType.Name}");
}
else
{
Debug.LogWarning($"SaveManager: 单例 {singletonType.Name} 已注册。");
}
}
/// <summary>
/// 移除一个已注册的单例实例。
/// </summary>
/// <param name="singleton">要移除的单例实例。</param>
public void UnregisterSavable(ISavableSingleton singleton)
{
var singletonType = singleton.GetType();
if (_savableSingletons.Remove(singletonType))
{
// DEBUG: Debug.Log($"SaveManager: 已移除可保存单例: {singletonType.Name}");
}
}
/// <summary>
/// 实现ILaunchManager接口的Init方法在启动时加载存档。
/// </summary>
public void Init()
{
LoadGame(); // 启动时加载存档
}
/// <summary>
/// 实现ILaunchManager接口的Clear方法清空存档数据。
/// </summary>
public void Clear()
{
try
{
if (File.Exists(SaveFilePath))
{
File.Delete(SaveFilePath);
Debug.Log($"SaveManager: 存档文件 '{SaveFileName}' 已删除。");
}
}
catch (Exception ex)
{
Debug.LogError($"SaveManager: 清空存档数据失败: {ex.Message}");
}
// 清空所有注册单例的Savable属性设置为默认值
foreach (var singletonEntry in _savableSingletons)
{
var singletonType = singletonEntry.Key;
var singletonInstance = singletonEntry.Value;
foreach (var prop in GetSavableProperties(singletonType))
{
prop.SetValue(singletonInstance, GetDefaultValue(prop.PropertyType));
}
}
}
/// <summary>
/// 保存游戏数据到文件。
/// </summary>
public void SaveGame()
{
var dataToSave = new Dictionary<string, JToken>();
foreach (var singletonEntry in _savableSingletons)
{
var singletonType = singletonEntry.Key;
var singletonInstance = singletonEntry.Value;
foreach (var prop in GetSavableProperties(singletonType))
{
var key = GetSavableKey(prop, singletonType);
var value = prop.GetValue(singletonInstance);
try
{
dataToSave[key] = JToken.FromObject(value);
}
catch (Exception ex)
{
Debug.LogError(
$"SaveManager: 无法将属性 '{key}' (类型: {prop.PropertyType.Name}) 转换为 JToken: {ex.Message}");
}
}
}
try
{
var settings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
TypeNameHandling = TypeNameHandling.Auto
};
var jsonString = JsonConvert.SerializeObject(dataToSave, settings);
File.WriteAllText(SaveFilePath, jsonString);
// DEBUG: Debug.Log($"SaveManager: 游戏数据已保存到 '{SaveFilePath}'。");
}
catch (Exception ex)
{
Debug.LogError($"SaveManager: 保存游戏数据到 '{SaveFilePath}' 失败: {ex.Message}");
}
}
/// <summary>
/// 从文件加载游戏数据。
/// </summary>
public void LoadGame()
{
if (!File.Exists(SaveFilePath))
{
// DEBUG: Debug.Log($"SaveManager: 未找到存档文件 '{SaveFilePath}'。将使用默认值启动。");
return;
}
try
{
var jsonString = File.ReadAllText(SaveFilePath);
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
};
var savedData = JsonConvert.DeserializeObject<Dictionary<string, JToken>>(jsonString, settings);
if (savedData == null)
{
Debug.LogError("SaveManager: 存档文件为空或已损坏。将使用默认值启动。");
return;
}
foreach (var singletonEntry in _savableSingletons)
{
var singletonType = singletonEntry.Key;
var singletonInstance = singletonEntry.Value;
foreach (var prop in GetSavableProperties(singletonType))
{
var key = GetSavableKey(prop, singletonType);
if (savedData.TryGetValue(key, out var jToken))
{
try
{
var value = jToken.ToObject(prop.PropertyType, JsonSerializer.Create(settings));
prop.SetValue(singletonInstance, value);
}
catch (Exception innerEx)
{
Debug.LogError(
$"SaveManager: 无法反序列化类型 '{singletonType.Name}' 的属性 '{key}' (类型: {prop.PropertyType.Name}): {innerEx.Message}");
}
}
else
{
Debug.LogWarning(
$"SaveManager: 存档文件中未找到类型 '{singletonType.Name}' 的属性 '{key}'。将保留当前值。");
}
}
}
}
catch (JsonException ex)
{
Debug.LogError($"SaveManager: 解析存档文件失败 (JSON 错误): {ex.Message}");
File.Delete(SaveFilePath); // 建议删除损坏的存档文件
Debug.LogWarning("SaveManager: 已删除损坏的存档文件以防止进一步问题。");
}
catch (Exception ex)
{
Debug.LogError(
$"SaveManager: 从 '{SaveFilePath}' 加载游戏数据失败 (文件 I/O 或其他错误): {ex.Message}");
}
}
/// <summary>
/// 获取一个类型中所有带有SavableAttribute的公共实例属性。
/// </summary>
private IEnumerable<PropertyInfo> GetSavableProperties(Type type)
{
return type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(prop => prop.CanRead && prop.CanWrite && prop.IsDefined(typeof(SavableAttribute), true));
}
/// <summary>
/// 根据属性和其所属类型生成一个唯一的存储键。
/// </summary>
private string GetSavableKey(PropertyInfo prop, Type singletonType)
{
var savableAttr = prop.GetCustomAttribute<SavableAttribute>();
return savableAttr?.Key ?? $"{singletonType.Name}.{prop.Name}";
}
/// <summary>
/// 获取给定类型的默认值。
/// </summary>
private object GetDefaultValue(Type type)
{
if (type.IsValueType)
{
return Activator.CreateInstance(type);
}
return null;
}
}
}

View File

@@ -1,10 +1,11 @@
using System;
using Data;
using System.Collections.Generic;
using System.Linq;
namespace Managers
{
public class SkillTreeManager : Utils.Singleton<SkillTreeManager>, ILaunchManager
public class SkillTreeManager : Utils.Singleton<SkillTreeManager>, ILaunchManager, ISavableSingleton
{
public string StepDescription => "正在加载技能树";
@@ -12,7 +13,19 @@ namespace Managers
private Dictionary<string, SkillTreeDef> _skillNodesByDefName;
private Dictionary<string, List<SkillTreeDef>> _skillNodesByTag;
private Dictionary<string, List<SkillTreeDef>> _skillNodesByFaction;
private Dictionary<string, List<SkillTreeDef>> _childrenOfNode; // key: parent defName, value: list of children nodes
private Dictionary<string, List<SkillTreeDef>> _childrenOfNode;
[Savable] public HashSet<string> UnlockedSkillTrees { get; set; } = new();
public SkillTreeManager()
{
SaveManager.Instance.RegisterSavable(this);
}
~SkillTreeManager()
{
SaveManager.Instance.UnregisterSavable(this);
}
public void Init()
{
@@ -51,6 +64,7 @@ namespace Managers
{
_skillNodesByTag[node.tag] = new List<SkillTreeDef>();
}
_skillNodesByTag[node.tag].Add(node);
}
@@ -62,6 +76,7 @@ namespace Managers
{
_skillNodesByFaction[node.faction.defName] = new List<SkillTreeDef>();
}
_skillNodesByFaction[node.faction.defName].Add(node);
}
}
@@ -75,12 +90,14 @@ namespace Managers
foreach (var prerequisite in childNode.prerequisites)
{
// 确保父节点在 _skillNodesByDefName 中存在,避免野指针或无效引用
if (prerequisite != null && _skillNodesByDefName.TryGetValue(prerequisite.defName, out var parentNode))
if (prerequisite != null &&
_skillNodesByDefName.TryGetValue(prerequisite.defName, out var parentNode))
{
if (!_childrenOfNode.ContainsKey(parentNode.defName))
{
_childrenOfNode[parentNode.defName] = new List<SkillTreeDef>();
}
_childrenOfNode[parentNode.defName].Add(childNode);
}
else if (prerequisite != null)
@@ -148,7 +165,7 @@ namespace Managers
public string[] GetAllTag()
{
return _skillNodesByTag.Keys.ToArray();
return _skillNodesByTag.Keys.ToArray();
}
// --- 父子关系查询 ---
@@ -162,7 +179,8 @@ namespace Managers
public bool IsDirectParentOf(SkillTreeDef baseNode, SkillTreeDef targetNode)
{
if (baseNode == null || targetNode == null || targetNode.prerequisites == null) return false;
return targetNode.prerequisites.Any(prerequisite => prerequisite != null && prerequisite.defName == baseNode.defName);
return targetNode.prerequisites.Any(prerequisite =>
prerequisite != null && prerequisite.defName == baseNode.defName);
}
/// <summary>
@@ -217,8 +235,8 @@ namespace Managers
if (node == null || node.prerequisites == null) return new List<SkillTreeDef>();
// 确保返回的是真实存在于管理器中的节点,而不是可能无效的引用
return node.prerequisites.Where(p => p != null && _skillNodesByDefName.ContainsKey(p.defName))
.Select(p => _skillNodesByDefName[p.defName])
.ToList();
.Select(p => _skillNodesByDefName[p.defName])
.ToList();
}
/// <summary>
@@ -250,7 +268,8 @@ namespace Managers
{
foreach (var directParentRef in node.prerequisites)
{
if (directParentRef != null && _skillNodesByDefName.TryGetValue(directParentRef.defName, out var parentNode))
if (directParentRef != null &&
_skillNodesByDefName.TryGetValue(directParentRef.defName, out var parentNode))
{
if (!ancestors.Contains(parentNode))
{
@@ -269,7 +288,8 @@ namespace Managers
{
foreach (var prerequisiteRef in current.prerequisites)
{
if (prerequisiteRef != null && _skillNodesByDefName.TryGetValue(prerequisiteRef.defName, out var actualParentNode))
if (prerequisiteRef != null &&
_skillNodesByDefName.TryGetValue(prerequisiteRef.defName, out var actualParentNode))
{
if (!ancestors.Contains(actualParentNode))
{
@@ -280,6 +300,7 @@ namespace Managers
}
}
}
return ancestors.ToList();
}
@@ -326,7 +347,54 @@ namespace Managers
}
}
}
return descendants.ToList();
}
/// <summary>
/// 确保技能树定义名称有效。
/// </summary>
/// <param name="skillTreeDefName">要验证的技能树定义名称。</param>
/// <exception cref="ArgumentException">如果技能树定义名称无效(不在 _skillNodesByDefName 中),则抛出。</exception>
private bool ValidateSkillTreeDefName(string skillTreeDefName)
{
return _skillNodesByDefName.ContainsKey(skillTreeDefName);
}
/// <summary>
/// 尝试解锁一个技能树。
/// 必须是有效的 skillTreeDefName否则会抛出 ArgumentException。
/// </summary>
/// <param name="skillTreeDefName">要解锁的技能树的定义名称。</param>
/// <returns>如果技能树成功解锁(之前未解锁),则为 true如果技能树已经解锁则为 false。</returns>
/// <exception cref="ArgumentException">如果 skillTreeDefName 无效。</exception>
public bool UnlockSkillTree(string skillTreeDefName)
{
return ValidateSkillTreeDefName(skillTreeDefName) && UnlockedSkillTrees.Add(skillTreeDefName);
}
/// <summary>
/// 尝试加锁(锁定)一个技能树。
/// 必须是有效的 skillTreeDefName否则会抛出 ArgumentException。
/// </summary>
/// <param name="skillTreeDefName">要加锁的技能树的定义名称。</param>
/// <returns>如果技能树成功加锁(之前已解锁),则为 true如果技能树已经加锁则为 false。</returns>
/// <exception cref="ArgumentException">如果 skillTreeDefName 无效。</exception>
public bool LockSkillTree(string skillTreeDefName)
{
return ValidateSkillTreeDefName(skillTreeDefName) && UnlockedSkillTrees.Remove(skillTreeDefName);
}
/// <summary>
/// 查询指定技能树是否已解锁。
/// 必须是有效的 skillTreeDefName否则会抛出 ArgumentException。
/// </summary>
/// <param name="skillTreeDefName">要查询的技能树的定义名称。</param>
/// <returns>如果技能树已解锁,则为 true否则为 false。</returns>
/// <exception cref="ArgumentException">如果 skillTreeDefName 无效。</exception>
public bool IsSkillTreeUnlocked(string skillTreeDefName)
{
return ValidateSkillTreeDefName(skillTreeDefName) && UnlockedSkillTrees.Contains(skillTreeDefName);
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using Data;
using Prefab;
using UnityEngine;
using UnityEngine.EventSystems;
@@ -10,52 +11,43 @@ namespace Managers
{
public class TemporaryAnimationManager : MonoSingleton<TemporaryAnimationManager>
{
// 将包装图层改为由代码动态创建和管理
private GameObject _temporaryAnimationLevel;
private GameObject _temporaryAnimationUILevel;
private GameObject _temporaryAnimationLevel; // 非UI临时动画的父级GameObject
private GameObject _temporaryAnimationUILevel; // UI临时动画的父级GameObject
[SerializeField] private TemporaryAnimatorImageUI temporaryAnimatorImageUIPrefab;
[SerializeField] private TemporaryAnimatorSprite temporaryAnimatorSpritePrefab;
[SerializeField] private TemporaryAnimatorText temporaryAnimatorTextPrefab;
[SerializeField] private TemporaryAnimatorText temporaryAnimatorUITextPrefab;
protected override void OnStart()
{
SceneManager.sceneLoaded += OnSceneLoaded;
// 首次启动时为当前场景创建动画层级,确保动画容器在首次使用前存在。
CreateAnimationLayersForCurrentScene(SceneManager.GetActiveScene(), LoadSceneMode.Single);
}
private void OnDestroy()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
// 在Manager销毁时,销毁创建的图层,防止残留
if (_temporaryAnimationLevel != null)
{
Destroy(_temporaryAnimationLevel);
}
if (_temporaryAnimationUILevel != null)
{
Destroy(_temporaryAnimationUILevel);
}
// 管理器销毁时,销毁所有创建的动画层级。
Destroy(_temporaryAnimationLevel);
Destroy(_temporaryAnimationUILevel);
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
// 在新场景加载时重新创建动画
// 在新场景加载时重新创建动画层级。
CreateAnimationLayersForCurrentScene(scene, mode);
}
/// <summary>
/// 为当前活动场景创建或重新创建临时动画的父级层。
/// 为当前活动场景创建或重新创建临时动画的父级层
/// </summary>
/// <param name="scene">当前加载的场景。</param>
/// <param name="mode">场景加载模式。</param>
private void CreateAnimationLayersForCurrentScene(Scene scene, LoadSceneMode mode)
{
// 销毁旧的图层(如果存在),确保每个场景都有独立的图层
// 注意如果TemporaryAnimationManager是DontDestroyOnLoad
// 那么旧的图层可能还在,需要显式销毁。
// 销毁场景切换时可能存在的旧动画层级。
if (_temporaryAnimationLevel != null)
{
Destroy(_temporaryAnimationLevel);
@@ -65,228 +57,368 @@ namespace Managers
Destroy(_temporaryAnimationUILevel);
}
// 创建非UI动画的包装图层
// 创建非UI临时动画的根GameObject。
_temporaryAnimationLevel = new GameObject("TemporaryAnimations");
// 将其移动到当前加载的场景的根目录,使其成为场景的一部分
// 将非UI动画根GameObject移动到当前场景。
SceneManager.MoveGameObjectToScene(_temporaryAnimationLevel, scene);
// 1. 获取或创建 Canvas
// 获取场景主Canvas如果不存在则创建。
var mainCanvas = GetOrCreateMainCanvas();
// 2. 创建 _temporaryAnimationUILevel GameObject
// 创建UI临时动画的根GameObject
_temporaryAnimationUILevel = new GameObject("TemporaryUIAnimations");
// 3. 将其设置为 Canvas 的子对象
// SetParent(parentTransform, worldPositionStay: false) 会将子对象的局部位置重置为 (0,0,0)
// 这对于新的UI元素通常是期望的行为
// 将UI动画根GameObject设为主Canvas的子对象
_temporaryAnimationUILevel.transform.SetParent(mainCanvas.transform, false);
// 4. 重置新创建的UI容器的局部变换
// 确保它相对于父级 Canvas 处于一个干净的初始状态
_temporaryAnimationUILevel.transform.localPosition = Vector3.zero;
_temporaryAnimationUILevel.transform.localScale = Vector3.one;
_temporaryAnimationUILevel.transform.localRotation = Quaternion.identity;
}
/// <summary>
/// 生成一个非UI文本临时动画。
/// </summary>
/// <param name="str">动画的文本内容。</param>
/// <param name="position">动画在世界坐标系中的初始位置。</param>
/// <param name="lifeTime">动画的生命周期(秒)。</param>
/// <param name="str">文本内容。</param>
/// <param name="position">世界坐标系中的初始位置。</param>
/// <param name="lifeTime">生命周期(秒)。</param>
/// <param name="fps">动画帧率。</param>
/// <param name="xShift">X轴位移函数。</param>
/// <param name="yShift">Y轴位移函数。</param>
public void GenerateTemporaryAnimation(string str, Vector3 position, float lifeTime = 3, float fps = 3,
/// <returns>创建的 TemporaryAnimatorText 对象。</returns>
public TemporaryAnimatorText GenerateTemporaryAnimation(string str, Vector3 position, float lifeTime = 3, float fps = 3,
Func<float, float> xShift = null,
Func<float, float> yShift = null)
{
if (!_temporaryAnimationLevel)
if (_temporaryAnimationLevel == null)
{
Debug.LogError("TemporaryAnimationLevel is not initialized. Cannot generate non-UI animation.");
return;
Debug.LogError("TemporaryAnimationManager: 非UI动画层未初始化。请确保管理器在生成非UI动画前已正确初始化。");
return null;
}
var textObj = Instantiate(temporaryAnimatorTextPrefab, _temporaryAnimationLevel.transform);
textObj.transform.position = new Vector3(position.x,position.y);
textObj.transform.position = new Vector3(position.x, position.y);
textObj.Init(str, fps);
textObj.SetAnimationFunctions(xShift, yShift);
textObj.lifeTime = lifeTime;
textObj.gameObject.SetActive(true);
return textObj;
}
/// <summary>
/// 生成一个非UI文本临时动画可指定父级Transform。
/// </summary>
/// <param name="str">动画的文本内容。</param>
/// <param name="position">动画在世界坐标系中的初始位置。</param>
/// <param name="parent">动画的父级Transform。如果为null则使用默认非UI动画层。</param>
/// <param name="lifeTime">动画的生命周期(秒)。</param>
/// <param name="str">文本内容。</param>
/// <param name="position">世界坐标系中的初始位置。</param>
/// <param name="parent">父级Transform。如果为null则使用默认非UI动画层。</param>
/// <param name="lifeTime">生命周期(秒)。</param>
/// <param name="fps">动画帧率。</param>
/// <param name="xShift">X轴位移函数。</param>
/// <param name="yShift">Y轴位移函数。</param>
public void GenerateTemporaryAnimation(string str, Vector3 position, Transform parent, float lifeTime = 3, float fps = 3,
/// <returns>创建的 TemporaryAnimatorText 对象。</returns>
public TemporaryAnimatorText GenerateTemporaryAnimation(string str, Vector3 position, Transform parent, float lifeTime = 3, float fps = 3,
Func<float, float> xShift = null,
Func<float, float> yShift = null)
{
Transform actualParent = parent ?? _temporaryAnimationLevel?.transform;
if (actualParent == null)
if (_temporaryAnimationLevel == null)
{
Debug.LogError("TemporaryAnimationLevel or specified parent is not initialized. Cannot generate non-UI animation.");
return;
Debug.LogError("TemporaryAnimationManager: 非UI动画层未初始化。请确保管理器在生成非UI动画前已正确初始化。");
return null;
}
// 优先使用传入的父级否则使用默认的非UI动画层。
var actualParent = parent ?? _temporaryAnimationLevel.transform;
var textObj = Instantiate(temporaryAnimatorTextPrefab, actualParent);
textObj.transform.position = position; // 外部传入的position应视为世界坐标
textObj.transform.position = position;
textObj.Init(str, fps);
textObj.SetAnimationFunctions(xShift, yShift);
textObj.lifeTime = lifeTime;
textObj.gameObject.SetActive(true);
return textObj;
}
/// <summary>
/// 生成一个非UI精灵临时动画。
/// </summary>
/// <param name="sprites">动画的精灵帧数组。</param>
/// <param name="position">动画在世界坐标系中的初始位置。</param>
/// <param name="lifeTime">动画的生命周期(秒)。</param>
/// <param name="sprites">精灵帧数组。</param>
/// <param name="position">世界坐标系中的初始位置。</param>
/// <param name="lifeTime">生命周期(秒)。</param>
/// <param name="fps">动画帧率。</param>
/// <param name="xShift">X轴位移函数。</param>
/// <param name="yShift">Y轴位移函数。</param>
public void GenerateTemporaryAnimation(Sprite[] sprites, Vector3 position,float lifeTime = 3, float fps = 3, Func<float, float> xShift = null,
/// <returns>创建的 TemporaryAnimatorSprite 对象。</returns>
public TemporaryAnimatorSprite GenerateTemporaryAnimation(Sprite[] sprites, Vector3 position,float lifeTime = 3, float fps = 3, Func<float, float> xShift = null,
Func<float, float> yShift = null)
{
if (!_temporaryAnimationLevel)
if (_temporaryAnimationLevel == null)
{
Debug.LogError("TemporaryAnimationLevel is not initialized. Cannot generate non-UI animation.");
return;
Debug.LogError("TemporaryAnimationManager: 非UI动画层未初始化。请确保管理器在生成非UI动画前已正确初始化。");
return null;
}
var obj = Instantiate(temporaryAnimatorSpritePrefab, _temporaryAnimationLevel.transform);
obj.transform.position = new Vector3(position.x,position.y);
obj.transform.position = new Vector3(position.x, position.y);
obj.Init(sprites, fps);
obj.SetAnimationFunctions(xShift, yShift);
obj.lifeTime = lifeTime;
obj.gameObject.SetActive(true);
return obj;
}
/// <summary>
/// 生成一个非UI精灵临时动画可指定父级Transform。
/// </summary>
/// <param name="sprites">动画的精灵帧数组。</param>
/// <param name="position">动画在世界坐标系中的初始位置。</param>
/// <param name="parent">动画的父级Transform。如果为null则使用默认非UI动画层。</param>
/// <param name="lifeTime">动画的生命周期(秒)。</param>
/// <param name="sprites">精灵帧数组。</param>
/// <param name="position">世界坐标系中的初始位置。</param>
/// <param name="parent">父级Transform。如果为null则使用默认非UI动画层。</param>
/// <param name="lifeTime">生命周期(秒)。</param>
/// <param name="fps">动画帧率。</param>
/// <param name="xShift">X轴位移函数。</param>
/// <param name="yShift">Y轴位移函数。</param>
public void GenerateTemporaryAnimation(Sprite[] sprites, Vector3 position, Transform parent, float lifeTime = 3, float fps = 3, Func<float, float> xShift = null,
/// <returns>创建的 TemporaryAnimatorSprite 对象。</returns>
public TemporaryAnimatorSprite GenerateTemporaryAnimation(Sprite[] sprites, Vector3 position, Transform parent, float lifeTime = 3, float fps = 3, Func<float, float> xShift = null,
Func<float, float> yShift = null)
{
Transform actualParent = parent ?? _temporaryAnimationLevel?.transform;
if (actualParent == null)
if (_temporaryAnimationLevel == null)
{
Debug.LogError("TemporaryAnimationLevel or specified parent is not initialized. Cannot generate non-UI animation.");
return;
Debug.LogError("TemporaryAnimationManager: 非UI动画层未初始化。请确保管理器在生成非UI动画前已正确初始化。");
return null;
}
// 优先使用传入的父级否则使用默认的非UI动画层。
var actualParent = parent ?? _temporaryAnimationLevel.transform;
var obj = Instantiate(temporaryAnimatorSpritePrefab, actualParent);
obj.transform.position = position; // 外部传入的position应视为世界坐标
obj.transform.position = position;
obj.Init(sprites, fps);
obj.SetAnimationFunctions(xShift, yShift);
obj.lifeTime = lifeTime;
obj.gameObject.SetActive(true);
return obj;
}
/// <summary>
/// 生成一个非UI DrawNodeDef 临时动画及其子动画。
/// </summary>
/// <param name="drawNode">绘制节点定义。</param>
/// <param name="position">世界坐标系中的初始位置。</param>
/// <param name="lifeTime">生命周期(秒)。</param>
/// <param name="fps">动画帧率。</param>
/// <param name="xShift">X轴位移函数。</param>
/// <param name="yShift">Y轴位移函数。</param>
/// <returns>创建的根 TemporaryAnimatorSprite 对象。</returns>
public TemporaryAnimatorSprite GenerateTemporaryAnimation(DrawNodeDef drawNode, Vector3 position,float lifeTime = 3, float fps = 3, Func<float, float> xShift = null,
Func<float, float> yShift = null)
{
if (_temporaryAnimationLevel == null)
{
Debug.LogError("TemporaryAnimationManager: 非UI动画层未初始化。请确保管理器在生成非UI动画前已正确初始化。");
return null;
}
var textureSprite=PackagesImageManager.Instance.GetSprites(drawNode.textures);
if (textureSprite == null)
{
Debug.LogWarning($"TemporaryAnimationManager: 无法找到DrawNodeDef纹理 '{drawNode.textures}' 的精灵。跳过动画。");
return null;
}
var obj = Instantiate(temporaryAnimatorSpritePrefab, _temporaryAnimationLevel.transform);
obj.transform.position = new Vector2(position.x, position.y) + drawNode.position;
obj.Init(textureSprite, drawNode.FPS);
obj.SetAnimationFunctions(xShift, yShift);
obj.lifeTime = lifeTime;
obj.gameObject.SetActive(true);
if (drawNode.nodes == null) return obj;
foreach (var childNode in drawNode.nodes)
{
// 递归生成子动画使用默认的非UI动画层。
GenerateTemporaryAnimation(childNode, position, _temporaryAnimationLevel.transform, lifeTime, fps, xShift, yShift);
}
return obj;
}
/// <summary>
/// 生成一个非UI DrawNodeDef 临时动画及其子动画可指定父级Transform。
/// </summary>
/// <param name="drawNode">绘制节点定义。</param>
/// <param name="position">世界坐标系中的初始位置。</param>
/// <param name="parent">父级Transform。如果为null则使用默认非UI动画层。</param>
/// <param name="lifeTime">生命周期(秒)。</param>
/// <param name="fps">动画帧率。</param>
/// <param name="xShift">X轴位移函数。</param>
/// <param name="yShift">Y轴位移函数。</param>
/// <returns>创建的根 TemporaryAnimatorSprite 对象。</returns>
public TemporaryAnimatorSprite GenerateTemporaryAnimation(DrawNodeDef drawNode, Vector3 position,Transform parent,float lifeTime = 3, float fps = 3, Func<float, float> xShift = null,
Func<float, float> yShift = null)
{
if (_temporaryAnimationLevel == null)
{
Debug.LogError("TemporaryAnimationManager: 非UI动画层未初始化。请确保管理器在生成非UI动画前已正确初始化。");
return null;
}
// 优先使用传入的父级否则使用默认的非UI动画层。
var actualParent = parent ?? _temporaryAnimationLevel.transform;
var textureSprite=PackagesImageManager.Instance.GetSprites(drawNode.textures);
if (textureSprite == null)
{
Debug.LogWarning($"TemporaryAnimationManager: 无法找到DrawNodeDef纹理 '{drawNode.textures}' 的精灵。跳过动画。");
return null;
}
var obj = Instantiate(temporaryAnimatorSpritePrefab, actualParent);
obj.transform.position = new Vector2(position.x, position.y) + drawNode.position;
obj.Init(textureSprite, drawNode.FPS);
obj.SetAnimationFunctions(xShift, yShift);
obj.lifeTime = lifeTime;
obj.gameObject.SetActive(true);
if (drawNode.nodes == null) return obj;
foreach (var childNode in drawNode.nodes)
{
// 递归生成子动画,子节点位置基于父节点位置加上自身偏移,父级为当前对象。
GenerateTemporaryAnimation(childNode, position + (Vector3)drawNode.position, obj.transform, lifeTime, fps, xShift, yShift);
}
return obj;
}
public TemporaryAnimatorImageUI GenerateTemporaryAnimationUI(DrawNodeDef drawNode, Vector3 worldPosition,Transform parent,float lifeTime = 3, float fps = 3, Func<float, float> xShift = null,
Func<float, float> yShift = null)
{
if (_temporaryAnimationLevel == null)
{
Debug.LogError("TemporaryAnimationManager: 非UI动画层未初始化。请确保管理器在生成非UI动画前已正确初始化。");
return null;
}
// 优先使用传入的父级否则使用默认的非UI动画层。
var actualParent = parent ?? _temporaryAnimationLevel.transform;
var textureSprite=PackagesImageManager.Instance.GetSprites(drawNode.textures);
if (textureSprite == null)
{
Debug.LogWarning($"TemporaryAnimationManager: 无法找到DrawNodeDef纹理 '{drawNode.textures}' 的精灵。跳过动画。");
return null;
}
var obj = Instantiate(temporaryAnimatorImageUIPrefab, actualParent);
obj.transform.position = new Vector2(worldPosition.x, worldPosition.y) + drawNode.position;
obj.Init(textureSprite, drawNode.FPS);
obj.SetAnimationFunctions(xShift, yShift);
obj.lifeTime = lifeTime;
obj.gameObject.SetActive(true);
if (drawNode.nodes == null) return obj;
foreach (var childNode in drawNode.nodes)
{
// 递归生成子动画,子节点位置基于父节点位置加上自身偏移,父级为当前对象。
GenerateTemporaryAnimation(childNode, worldPosition + (Vector3)drawNode.position, obj.transform, lifeTime, fps, xShift, yShift);
}
return obj;
}
/// <summary>
/// 生成一个UI文本临时动画。
/// 此重载将传入的 <paramref name="position"/> 视为屏幕坐标 (如鼠标位置)并将其自动转换为Canvas局部坐标。
/// </summary>
/// <param name="str">动画的文本内容。</param>
/// <param name="position">动画在屏幕坐标系中的初始位置(如鼠标位置)。</param>
/// <param name="lifeTime">动画的生命周期(秒)。</param>
/// <param name="str">文本内容。</param>
/// <param name="position">屏幕坐标系中的初始位置。</param>
/// <param name="lifeTime">生命周期(秒)。</param>
/// <param name="fps">动画帧率。</param>
/// <param name="xShift">X轴位移函数。</param>
/// <param name="yShift">Y轴位移函数。</param>
public void GenerateTemporaryAnimationUI(string str, Vector2 position,float lifeTime = 3, float fps = 3, Func<float, float> xShift = null,
/// <returns>创建的 TemporaryAnimatorText 对象。</returns>
public TemporaryAnimatorText GenerateTemporaryAnimationUI(string str, Vector2 position,float lifeTime = 3, float fps = 3, Func<float, float> xShift = null,
Func<float, float> yShift = null)
{
if (!_temporaryAnimationUILevel)
if (_temporaryAnimationUILevel == null)
{
Debug.LogError("TemporaryAnimationUILevel is not initialized. Cannot generate UI animation.");
return;
Debug.LogError("TemporaryAnimationManager: UI动画层未初始化。无法生成UI动画。");
return null;
}
var textObj = Instantiate(temporaryAnimatorUITextPrefab, _temporaryAnimationUILevel.transform);
RectTransform canvasRectTransform = _temporaryAnimationUILevel.transform as RectTransform;
var canvasRectTransform = _temporaryAnimationUILevel.transform as RectTransform;
if (canvasRectTransform != null)
{
// 获取Canvas组件特别是RenderMode为ScreenSpaceCamera时需要worldCamera
Canvas rootCanvas = canvasRectTransform.root.GetComponent<Canvas>();
var rootCanvas = canvasRectTransform.root.GetComponent<Canvas>();
Camera eventCamera = null;
if (rootCanvas.renderMode == RenderMode.ScreenSpaceCamera || rootCanvas.renderMode == RenderMode.WorldSpace)
// 根据Canvas的RenderMode确定用于屏幕坐标转换的相机。
if (rootCanvas.renderMode == RenderMode.ScreenSpaceOverlay)
{
eventCamera = rootCanvas.worldCamera; // 使用Canvas指定的相机
if (eventCamera == null) // 如果Canvas没有指定相机尝试主相机
eventCamera = null;
}
else if (rootCanvas.renderMode == RenderMode.ScreenSpaceCamera || rootCanvas.renderMode == RenderMode.WorldSpace)
{
eventCamera = rootCanvas.worldCamera;
if (eventCamera == null)
{
eventCamera = Camera.main;
eventCamera = Camera.main;
if (eventCamera == null)
{
Debug.LogWarning($"Canvas '{rootCanvas.name}' 处于 {rootCanvas.renderMode} 模式但世界相机为null且未找到主相机。UI动画位置可能不正确。");
}
}
}
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRectTransform, position, eventCamera, out var localPoint))
Vector2 localPoint;
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRectTransform, position, eventCamera, out localPoint))
{
textObj.rectTransform.anchoredPosition = localPoint;
}
else
{
textObj.rectTransform.anchoredPosition = position;
Debug.LogWarning("Failed to convert screen point to local point for UI text animation. Using raw position.");
Debug.LogWarning($"未能将屏幕点 {position} 转换为UI动画在Canvas '{rootCanvas.name}' 上的局部点。将使用默认锚点位置 (0,0)。请检查Canvas的RenderMode和世界相机设置。");
}
}
else
{
textObj.rectTransform.anchoredPosition = position;
Debug.LogWarning("TemporaryAnimationUILevel is not a RectTransform. Unable to convert screen point. Using raw position.");
Debug.LogError("TemporaryAnimationManager: UI动画层不是RectTransform。无法转换屏幕点。UI动画无法正确放置。");
}
textObj.Init(str, fps);
textObj.SetAnimationFunctions(xShift, yShift);
textObj.lifeTime = lifeTime;
textObj.gameObject.SetActive(true);
return textObj;
}
/// <summary>
/// 生成一个UI文本临时动画可指定父级RectTransform。
/// 此重载将传入的 <paramref name="position"/> 视为相对于父级RectTransform的 anchoredPosition。
/// </summary>
/// <param name="str">动画的文本内容。</param>
/// <param name="position">动画在UI坐标系(RectTransform.anchoredPosition)中的初始位置。</param>
/// <param name="parent">动画的父级RectTransform。如果为null则使用默认的UI动画层。</param>
/// <param name="lifeTime">动画的生命周期(秒)。</param>
/// <param name="str">文本内容。</param>
/// <param name="position">UI坐标系(RectTransform.anchoredPosition)中的初始位置。</param>
/// <param name="parent">父级RectTransform。如果为null则使用默认的UI动画层。</param>
/// <param name="lifeTime">生命周期(秒)。</param>
/// <param name="fps">动画帧率。</param>
/// <param name="xShift">X轴位移函数。</param>
/// <param name="yShift">Y轴位移函数。</param>
public void GenerateTemporaryAnimationUI(string str, Vector2 position, RectTransform parent, float lifeTime = 3, float fps = 3, Func<float, float> xShift = null,
/// <returns>创建的 TemporaryAnimatorText 对象。</returns>
public TemporaryAnimatorText GenerateTemporaryAnimationUI(string str, Vector2 position, RectTransform parent, float lifeTime = 3, float fps = 3, Func<float, float> xShift = null,
Func<float, float> yShift = null)
{
RectTransform actualParent = parent ?? (_temporaryAnimationUILevel?.transform as RectTransform);
if (actualParent == null)
if (_temporaryAnimationUILevel == null)
{
Debug.LogError("TemporaryAnimationUILevel or specified parent is not initialized or not a RectTransform. Cannot generate UI animation.");
return;
Debug.LogError("TemporaryAnimationManager: UI动画层未初始化。请确保管理器在生成UI动画前已正确初始化。");
return null;
}
var defaultUIParent = _temporaryAnimationUILevel.transform as RectTransform;
if (defaultUIParent == null)
{
Debug.LogError("TemporaryAnimationManager: 默认UI动画层不是RectTransform。无法生成UI动画。");
return null;
}
var actualParent = parent ?? defaultUIParent;
var textObj = Instantiate(temporaryAnimatorUITextPrefab);
textObj.rectTransform.SetParent(actualParent, false); // SetParent with worldPositionStay: false
textObj.rectTransform.SetParent(actualParent, false);
textObj.rectTransform.anchoredPosition = position;
textObj.Init(str, fps);
textObj.SetAnimationFunctions(xShift, yShift);
textObj.lifeTime = lifeTime;
textObj.gameObject.SetActive(true);
return textObj;
}
@@ -294,35 +426,45 @@ namespace Managers
/// 生成一个UI精灵临时动画。
/// 此重载将传入的 <paramref name="position"/> 视为屏幕坐标 (如鼠标位置)并将其自动转换为Canvas局部坐标。
/// </summary>
/// <param name="sprites">动画的精灵帧数组。</param>
/// <param name="position">动画在屏幕坐标系中的初始位置(如鼠标位置)。</param>
/// <param name="lifeTime">动画的生命周期(秒)。</param>
/// <param name="sprites">精灵帧数组。</param>
/// <param name="position">屏幕坐标系中的初始位置。</param>
/// <param name="lifeTime">生命周期(秒)。</param>
/// <param name="fps">动画帧率。</param>
/// <param name="xShift">X轴位移函数。</param>
/// <param name="yShift">Y轴位移函数。</param>
public void GenerateTemporaryAnimationUI(Sprite[] sprites, Vector2 position,float lifeTime = 3, float fps = 3, Func<float, float> xShift = null,
/// <returns>创建的 TemporaryAnimatorImageUI 对象。</returns>
public TemporaryAnimatorImageUI GenerateTemporaryAnimationUI(Sprite[] sprites, Vector2 position,float lifeTime = 3, float fps = 3, Func<float, float> xShift = null,
Func<float, float> yShift = null)
{
if (_temporaryAnimationUILevel == null)
{
Debug.LogError("TemporaryAnimationUILevel is not initialized. Cannot generate UI animation.");
return;
Debug.LogError("TemporaryAnimationManager: UI动画层未初始化。无法生成UI动画。");
return null;
}
var obj = Instantiate(temporaryAnimatorImageUIPrefab, _temporaryAnimationUILevel.transform);
RectTransform canvasRectTransform = _temporaryAnimationUILevel.transform as RectTransform;
var canvasRectTransform = _temporaryAnimationUILevel.transform as RectTransform;
if (canvasRectTransform != null)
{
// 获取Canvas组件特别是RenderMode为ScreenSpaceCamera时需要worldCamera
Canvas rootCanvas = canvasRectTransform.root.GetComponent<Canvas>();
var rootCanvas = canvasRectTransform.root.GetComponent<Canvas>();
Camera eventCamera = null;
if (rootCanvas.renderMode == RenderMode.ScreenSpaceCamera || rootCanvas.renderMode == RenderMode.WorldSpace)
// 根据Canvas的RenderMode确定用于屏幕坐标转换的相机。
if (rootCanvas.renderMode == RenderMode.ScreenSpaceOverlay)
{
eventCamera = rootCanvas.worldCamera; // 使用Canvas指定的相机
if (eventCamera == null) // 如果Canvas没有指定相机尝试主相机
eventCamera = null;
}
else if (rootCanvas.renderMode == RenderMode.ScreenSpaceCamera || rootCanvas.renderMode == RenderMode.WorldSpace)
{
eventCamera = rootCanvas.worldCamera;
if (eventCamera == null)
{
eventCamera = Camera.main;
eventCamera = Camera.main;
if (eventCamera == null)
{
Debug.LogWarning($"Canvas '{rootCanvas.name}' 处于 {rootCanvas.renderMode} 模式但世界相机为null且未找到主相机。UI动画位置可能不正确。");
}
}
}
@@ -333,55 +475,61 @@ namespace Managers
}
else
{
obj.rectTransform.anchoredPosition = position;
Debug.LogWarning("Failed to convert screen point to local point for UI sprite animation. Using raw position.");
Debug.LogWarning($"未能将屏幕点 {position} 转换为UI动画在Canvas '{rootCanvas.name}' 上的局部点。将使用默认锚点位置 (0,0)。请检查Canvas的RenderMode和世界相机设置。");
}
}
else
{
obj.rectTransform.anchoredPosition = position;
Debug.LogWarning("TemporaryAnimationUILevel is not a RectTransform. Unable to convert screen point. Using raw position.");
Debug.LogError("TemporaryAnimationManager: UI动画层不是RectTransform。无法转换屏幕点。UI动画无法正确放置。");
}
obj.Init(sprites, fps);
obj.SetAnimationFunctions(xShift, yShift);
obj.lifeTime = lifeTime;
obj.gameObject.SetActive(true);
return obj;
}
/// <summary>
/// 生成一个UI精灵临时动画可指定父级RectTransform。
/// 此重载将传入的 <paramref name="position"/> 视为相对于父级RectTransform的 anchoredPosition。
/// </summary>
/// <param name="sprites">动画的精灵帧数组。</param>
/// <param name="position">动画在UI坐标系(RectTransform.anchoredPosition)中的初始位置。</param>
/// <param name="parent">动画的父级RectTransform。如果为null则使用默认的UI动画层。</param>
/// <param name="lifeTime">动画的生命周期(秒)。</param>
/// <param name="sprites">精灵帧数组。</param>
/// <param name="position">UI坐标系(RectTransform.anchoredPosition)中的初始位置。</param>
/// <param name="parent">父级RectTransform。如果为null则使用默认的UI动画层。</param>
/// <param name="lifeTime">生命周期(秒)。</param>
/// <param name="fps">动画帧率。</param>
/// <param name="xShift">X轴位移函数。</param>
/// <param name="yShift">Y轴位移函数。</param>
public void GenerateTemporaryAnimationUI(Sprite[] sprites, Vector2 position, RectTransform parent, float lifeTime = 3, float fps = 3, Func<float, float> xShift = null,
/// <returns>创建的 TemporaryAnimatorImageUI 对象。</returns>
public TemporaryAnimatorImageUI GenerateTemporaryAnimationUI(Sprite[] sprites, Vector2 position, RectTransform parent, float lifeTime = 3, float fps = 3, Func<float, float> xShift = null,
Func<float, float> yShift = null)
{
RectTransform actualParent = parent ?? (_temporaryAnimationUILevel?.transform as RectTransform);
if (actualParent == null)
if (_temporaryAnimationUILevel == null)
{
Debug.LogError("TemporaryAnimationUILevel or specified parent is not initialized or not a RectTransform. Cannot generate UI animation.");
return;
Debug.LogError("TemporaryAnimationManager: UI动画层未初始化。请确保管理器在生成UI动画前已正确初始化。");
return null;
}
var defaultUIParent = _temporaryAnimationUILevel.transform as RectTransform;
if (defaultUIParent == null)
{
Debug.LogError("TemporaryAnimationManager: 默认UI动画层不是RectTransform。无法生成UI动画。");
return null;
}
var actualParent = parent ?? defaultUIParent;
var obj = Instantiate(temporaryAnimatorImageUIPrefab);
obj.rectTransform.SetParent(actualParent, false); // SetParent with worldPositionStay: false
obj.rectTransform.SetParent(actualParent, false);
obj.rectTransform.anchoredPosition = position;
obj.Init(sprites, fps);
obj.SetAnimationFunctions(xShift, yShift);
obj.lifeTime = lifeTime;
obj.gameObject.SetActive(true);
return obj;
}
/// <summary>
/// 寻找场景中现有的 Canvas如果不存在则创建一个新的。
/// 同时确保场景中存在 EventSystem。
@@ -392,21 +540,32 @@ namespace Managers
var existingCanvas = FindFirstObjectByType<Canvas>();
if (existingCanvas != null)
{
// 如果现有Canvas需要WorldCamera但未设置尝试赋值Camera.main。
if (existingCanvas.renderMode == RenderMode.ScreenSpaceCamera || existingCanvas.renderMode == RenderMode.WorldSpace)
{
if (existingCanvas.worldCamera == null)
{
var mainCamera = Camera.main;
if (mainCamera == null)
{
Debug.LogWarning("现有Canvas处于屏幕相机/世界空间模式但未设置世界相机且主相机为空。UI动画可能无法正确渲染。");
}
existingCanvas.worldCamera = mainCamera;
}
}
EnsureEventSystemExists();
return existingCanvas;
}
// 创建 Canvas GameObject
// 在场景中创建一个新的Canvas GameObject
var canvasGO = new GameObject("Canvas");
// 将新创建的Canvas移动到当前场景根目录。
SceneManager.MoveGameObjectToScene(canvasGO, SceneManager.GetActiveScene());
canvasGO.layer = LayerMask.NameToLayer("UI");
var newCanvas = canvasGO.AddComponent<Canvas>();
newCanvas.renderMode = RenderMode.ScreenSpaceOverlay;
// 确保有相机以便RectTransformUtility.ScreenPointToLocalPointInRectangle可以工作
// 对于ScreenSpaceOverlayworldCamera可以为null但为了通用性和健壮性这里设置为Camera.main
// 以防Canvas.renderMode将来调整为ScreenSpaceCamera或WorldSpace
newCanvas.worldCamera = Camera.main;
canvasGO.AddComponent<CanvasScaler>();
canvasGO.AddComponent<GraphicRaycaster>();
EnsureEventSystemExists();
@@ -421,7 +580,10 @@ namespace Managers
if (existingEventSystem != null) return;
var eventSystemGO = new GameObject("EventSystem");
eventSystemGO.AddComponent<EventSystem>();
eventSystemGO.AddComponent<StandaloneInputModule>(); // 最常见的输入模块
eventSystemGO.AddComponent<StandaloneInputModule>();
// 将新的EventSystem移动到当前场景。
SceneManager.MoveGameObjectToScene(eventSystemGO, SceneManager.GetActiveScene());
}
}
}

View File

@@ -21,24 +21,24 @@ namespace Managers
private Dictionary<string, RuleTile> _cachedTiles = new();
// 地图生成器工作类映射Key: MapGeneratorDef.defName, Value: MapGeneratorWorkClassBase
private Dictionary<string,MapGeneratorWorkClassBase> _mapGeneratorWorkClassMap = new();
private Dictionary<string, MapGeneratorWorkClassBase> _mapGeneratorWorkClassMap = new();
public void Init()
{
// 如果已缓存瓦片,则直接返回
if (_cachedTiles.Any())
return;
var tileDefs = DefineManager.Instance.QueryDefinesByType<TileDef>();
// 第一次遍历创建RuleTile实例并填充除特定瓦片引用specificTileName外的所有简单字段
foreach (var def in tileDefs)
{
if (string.IsNullOrEmpty(def.defName))
{
Debug.LogWarning($"发现未定义名称或defName的TileDef已跳过。");
Debug.LogWarning($"发现未定义名称或defName的TileDef已跳过。");
continue;
}
var tileName = def.defName;
var ruleTile = ScriptableObject.CreateInstance<RuleTile>();
ruleTile.name = tileName; // 设置ScriptableObject的名称
@@ -46,7 +46,7 @@ namespace Managers
ruleTile.m_DefaultColliderType = def.collider;
ruleTile.m_DefaultSprite = PackagesImageManager.Instance.GetSprite(def.texture);
ruleTile.m_TilingRules = GetTileRules(def.rules);
_cachedTiles[tileName] = ruleTile;
}
@@ -54,11 +54,12 @@ namespace Managers
foreach (var mapGeneratorDef in generatorDefs)
{
var workClass = StringUtils.CreateMapGeneratorInstance(mapGeneratorDef.workClass);
if(workClass == null)
if (workClass == null)
{
Debug.LogWarning($"无法为地图生成器 '{mapGeneratorDef.defName}' 创建工作类 '{mapGeneratorDef.workClass}',已跳过。");
continue;
}
workClass.Init(mapGeneratorDef.value);
_mapGeneratorWorkClassMap[mapGeneratorDef.defName] = workClass;
}
@@ -70,16 +71,17 @@ namespace Managers
{
if (tile != null)
{
Object.Destroy(tile);
Object.Destroy(tile);
}
}
_cachedTiles.Clear();
_mapGeneratorWorkClassMap.Clear(); // 清空地图生成器工作类映射
}
private static List<RuleTile.TilingRule> GetTileRules(RuleTileRuleDef[] rules)
{
return rules.Select(GetTileRule).ToList();
return rules?.Select(GetTileRule).ToList();
}
/// <summary>
@@ -89,6 +91,9 @@ namespace Managers
/// <returns>转换后的 RuleTile.TilingRule 实例。</returns>
private static RuleTile.TilingRule GetTileRule(RuleTileRuleDef ruleDef)
{
if (ruleDef == null)
return null;
var tilingRule = new RuleTile.TilingRule
{
m_Sprites = PackagesImageManager.Instance.GetSprites(ruleDef.animationTextures.ToArray()),
@@ -100,29 +105,77 @@ namespace Managers
m_RuleTransform = ruleDef.transform
};
var tilePositions = new List<Vector3Int>();
// 使用 Dictionary 存储邻居条件,键为位置,值为邻居类型
var neighborConditionsMap = new Dictionary<Vector3Int, int>();
// 预设的8个邻居位置
var defaultPositions = new Vector3Int[]
{
new Vector3Int(-1, 1, 0),
new Vector3Int(0, 1, 0),
new Vector3Int(1, 1, 0),
new Vector3Int(-1, 0, 0),
new Vector3Int(1, 0, 0),
new Vector3Int(-1, -1, 0),
new Vector3Int(0, -1, 0),
new Vector3Int(1, -1, 0)
};
// 处理标准8个邻居条件
for (var i = 0; i < ruleDef.neighborConditions.Length && i < 8; i++)
{
switch(ruleDef.neighborConditions[i])
int neighborType;
switch (ruleDef.neighborConditions[i])
{
case RuleTileRuleDef.NeighborConditionType.NotThis:
tilingRule.m_Neighbors.Add(RuleTile.TilingRuleOutput.Neighbor.NotThis);
neighborType = RuleTile.TilingRuleOutput.Neighbor.NotThis;
neighborConditionsMap[defaultPositions[i]] = neighborType; // 添加或更新
break;
case RuleTileRuleDef.NeighborConditionType.This:
tilingRule.m_Neighbors.Add(RuleTile.TilingRuleOutput.Neighbor.This);
neighborType = RuleTile.TilingRuleOutput.Neighbor.This;
neighborConditionsMap[defaultPositions[i]] = neighborType; // 添加或更新
break;
case RuleTileRuleDef.NeighborConditionType.Any:
default:
// 忽略 Any 类型因为其不会添加到m_Neighbors中
continue;
// 如果是Any或默认则不添加到map中表示不关心此位置
break;
}
tilePositions.Add(tilingRule.m_NeighborPositions[i]);
}
tilingRule.m_NeighborPositions = tilePositions;
// 处理扩展邻居条件如果位置重复则覆盖标准8个邻居的定义
if (ruleDef.neighborConditionExtend != null)
{
foreach (var neighborConditionDef in ruleDef.neighborConditionExtend)
{
var pos = Utils.StringUtils.StringToVector3Int(neighborConditionDef.position);
int neighborType;
switch (neighborConditionDef.Type)
{
case RuleTileRuleDef.NeighborConditionType.NotThis:
neighborType = RuleTile.TilingRuleOutput.Neighbor.NotThis;
neighborConditionsMap[pos] = neighborType; // 添加或覆盖
break;
case RuleTileRuleDef.NeighborConditionType.This:
neighborType = RuleTile.TilingRuleOutput.Neighbor.This;
neighborConditionsMap[pos] = neighborType; // 添加或覆盖
break;
case RuleTileRuleDef.NeighborConditionType.Any:
default:
// 如果是Any或默认则不添加到map中
// 如果这个位置之前有定义来自defaultPositions那么Any会有效地“移除”这个定义
// 如果我们希望Any清除之前的显式定义可以在这里neighborConditionsMap.Remove(pos);
// 但通常Any意味着不关心所以保持不添加即可
break;
}
}
}
tilingRule.m_NeighborPositions = new List<Vector3Int>(neighborConditionsMap.Keys);
tilingRule.m_Neighbors = new List<int>(neighborConditionsMap.Values);
return tilingRule;
}
/// <summary>
/// 根据名称获取一个 RuleTile 实例。
/// </summary>
@@ -134,6 +187,7 @@ namespace Managers
{
return tile;
}
Debug.LogWarning($"瓦片 '{tileName}' 未在TileManager缓存中找到。");
return null;
}
@@ -142,36 +196,41 @@ namespace Managers
/// 应用指定的地图生成器,现在是非阻塞的异步操作。
/// </summary>
/// <param name="generatorName">要应用的生成器名称。</param>
/// <param name="mapGenerator">可选:指定要使用的 MapGenerator 实例。如果为 null将尝试从 Program.Instance.FocusedDimension 获取。</param>
/// <param name="landform">可选:指定要使用的 MapGenerator 实例。如果为 null将尝试从 Program.Instance.FocusedDimension 获取。</param>
/// <returns>一个表示异步操作完成的 Task。</returns>
public async Task ApplyMapGenerator(string generatorName, MapGenerator mapGenerator = null) // <-- 返回 Task并标记为 async
public async Task ApplyMapGenerator(string generatorName, Map.Landform landform = null)
{
if (mapGenerator == null)
if (landform == null)
{
// 尝试从 Program.Instance.FocusedDimension 获取 mapGenerator
// 假设 Program 和 FocusedDimension 结构存在
mapGenerator = Program.Instance?.FocusedDimension?.mapGenerator;
if (mapGenerator == null)
landform = Program.Instance?.FocusedDimension?.landform;
if (landform == null)
{
Debug.LogError($"ApplyMapGenerator: 无法找到地图生成器 '{generatorName}' 对应的 mapGenerator。Program.Instance、FocusedDimension 或其 mapGenerator 可能为空。");
Debug.LogError(
$"ApplyMapGenerator: 无法找到地图生成器 '{generatorName}' 对应的 mapGenerator。Program.Instance、FocusedDimension 或其 mapGenerator 可能为空。");
return; // async Task 方法直接 return; 意味着返回 Task.CompletedTask
}
}
if (_mapGeneratorWorkClassMap == null)
{
Debug.LogError($"ApplyMapGenerator: _mapGeneratorWorkClassMap 未初始化,无法找到生成器 '{generatorName}'。");
return;
}
if (_mapGeneratorWorkClassMap.TryGetValue(generatorName, out var mapGeneratorWorkClass))
{
if (mapGeneratorWorkClass == null)
{
Debug.LogError($"ApplyMapGenerator: 为生成器 '{generatorName}' 找到了一个空的 IMapGeneratorWorkClass 实例。这表明 _mapGeneratorWorkClassMap 配置存在问题。");
Debug.LogError(
$"ApplyMapGenerator: 为生成器 '{generatorName}' 找到了一个空的 IMapGeneratorWorkClass 实例。这表明 _mapGeneratorWorkClassMap 配置存在问题。");
return;
}
try
{
await mapGeneratorWorkClass.Process(mapGenerator); // <-- 使用 await 调用异步 Process 方法
await mapGeneratorWorkClass.Process(landform); // <-- 使用 await 调用异步 Process 方法
}
catch (Exception ex)
{
@@ -182,8 +241,14 @@ namespace Managers
}
else
{
Debug.LogError($"ApplyMapGenerator: 未找到名为 '{generatorName}' 的 IMapGeneratorWorkClass。请确保该生成器已在 _mapGeneratorWorkClassMap 中注册。");
Debug.LogError(
$"ApplyMapGenerator: 未找到名为 '{generatorName}' 的 IMapGeneratorWorkClass。请确保该生成器已在 _mapGeneratorWorkClassMap 中注册。");
}
}
public MapGeneratorWorkClassBase GetMapGeneratorWorkClass(string defName)
{
return _mapGeneratorWorkClassMap?.GetValueOrDefault(defName,null);
}
}
}