diff --git a/Client/Assets/Resources/Prefab/Entity/BuildingPrefab Variant.prefab b/Client/Assets/Resources/Prefab/Entity/BuildingPrefab Variant.prefab index 066190d..edb1090 100644 --- a/Client/Assets/Resources/Prefab/Entity/BuildingPrefab Variant.prefab +++ b/Client/Assets/Resources/Prefab/Entity/BuildingPrefab Variant.prefab @@ -89,6 +89,17 @@ PrefabInstance: insertIndex: 4 addedObject: {fileID: 6245381882962021176} m_SourcePrefab: {fileID: 100100000, guid: 6cd8b01a0f57372438dc30c864ae1530, type: 3} +--- !u!114 &473404334762913218 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 7296562035276564141, guid: 6cd8b01a0f57372438dc30c864ae1530, type: 3} + m_PrefabInstance: {fileID: 7193170239846001519} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e33d58195dd8450a9ec468ede64c8635, type: 3} + m_Name: + m_EditorClassIdentifier: --- !u!114 &1196523683669800718 stripped MonoBehaviour: m_CorrespondingSourceObject: {fileID: 8307348883874536545, guid: 6cd8b01a0f57372438dc30c864ae1530, type: 3} @@ -130,11 +141,12 @@ MonoBehaviour: animatorPrefab: {fileID: 2113064398104960506, guid: ea9af70ce0f4c8b4a9de58ac63074156, type: 3} imagePrefab: {fileID: 1922746734790246249, guid: a6657f26d735fab4690c8185980fda29, type: 3} healthBarPrefab: {fileID: 1196523683669800718} + visionRangeDetector: {fileID: 473404334762913218} direction: {x: 0, y: 0, z: 0} body: {fileID: 4949477718940635697} affiliation: canSelect: 1 - hitBarUIShowTime: 5 + _hitBarUIShowTime: 5 --- !u!114 &6487788869574860317 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/Client/Assets/Resources/Prefab/Entity/BulletPrefab Variant.prefab b/Client/Assets/Resources/Prefab/Entity/BulletPrefab Variant.prefab index 2f66473..b6c0483 100644 --- a/Client/Assets/Resources/Prefab/Entity/BulletPrefab Variant.prefab +++ b/Client/Assets/Resources/Prefab/Entity/BulletPrefab Variant.prefab @@ -73,6 +73,7 @@ PrefabInstance: - {fileID: 4717642781780051128, guid: 6cd8b01a0f57372438dc30c864ae1530, type: 3} m_RemovedGameObjects: - {fileID: 1328271255896522146, guid: 6cd8b01a0f57372438dc30c864ae1530, type: 3} + - {fileID: 6276067436382167109, guid: 6cd8b01a0f57372438dc30c864ae1530, type: 3} m_AddedGameObjects: [] m_AddedComponents: - targetCorrespondingSourceObject: {fileID: 887327274103887133, guid: 6cd8b01a0f57372438dc30c864ae1530, type: 3} @@ -103,7 +104,9 @@ MonoBehaviour: body: {fileID: 8200005766896563996} affiliation: canSelect: 1 - hitBarUIShowTime: 5 + _hitBarUIShowTime: 5 + bulletSource: {fileID: 0} + lifeTime: 10 --- !u!1 &8200005766896563996 stripped GameObject: m_CorrespondingSourceObject: {fileID: 2838206730318674270, guid: 6cd8b01a0f57372438dc30c864ae1530, type: 3} diff --git a/Client/Assets/Resources/Prefab/Entity/CharacterPrefab Variant.prefab b/Client/Assets/Resources/Prefab/Entity/CharacterPrefab Variant.prefab index 9b0023c..bfcbcc7 100644 --- a/Client/Assets/Resources/Prefab/Entity/CharacterPrefab Variant.prefab +++ b/Client/Assets/Resources/Prefab/Entity/CharacterPrefab Variant.prefab @@ -88,16 +88,28 @@ MonoBehaviour: animatorPrefab: {fileID: 2113064398104960506, guid: ea9af70ce0f4c8b4a9de58ac63074156, type: 3} imagePrefab: {fileID: 1922746734790246249, guid: a6657f26d735fab4690c8185980fda29, type: 3} healthBarPrefab: {fileID: 8215007830330368681} + visionRangeDetector: {fileID: 7208762515041450085} direction: {x: 0, y: 0, z: 0} body: {fileID: 2750404322221410198} affiliation: canSelect: 1 - hitBarUIShowTime: 5 + _hitBarUIShowTime: 5 --- !u!1 &2750404322221410198 stripped GameObject: m_CorrespondingSourceObject: {fileID: 2838206730318674270, guid: 6cd8b01a0f57372438dc30c864ae1530, type: 3} m_PrefabInstance: {fileID: 92380775595425480} m_PrefabAsset: {fileID: 0} +--- !u!114 &7208762515041450085 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 7296562035276564141, guid: 6cd8b01a0f57372438dc30c864ae1530, type: 3} + m_PrefabInstance: {fileID: 92380775595425480} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e33d58195dd8450a9ec468ede64c8635, type: 3} + m_Name: + m_EditorClassIdentifier: --- !u!114 &8215007830330368681 stripped MonoBehaviour: m_CorrespondingSourceObject: {fileID: 8307348883874536545, guid: 6cd8b01a0f57372438dc30c864ae1530, type: 3} diff --git a/Client/Assets/Resources/Prefab/Entity/EntityPrefab.prefab b/Client/Assets/Resources/Prefab/Entity/EntityPrefab.prefab index 956f38f..17519b9 100644 --- a/Client/Assets/Resources/Prefab/Entity/EntityPrefab.prefab +++ b/Client/Assets/Resources/Prefab/Entity/EntityPrefab.prefab @@ -123,6 +123,7 @@ Transform: - {fileID: 5549544358816209289} - {fileID: 1697214530303839877} - {fileID: 1404278780950315184} + - {fileID: 8903644794067773966} m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &3332598847335032684 @@ -252,6 +253,88 @@ Transform: m_Children: [] m_Father: {fileID: 697189026367054479} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &6276067436382167109 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8903644794067773966} + - component: {fileID: 2936989077236630990} + - component: {fileID: 7296562035276564141} + m_Layer: 6 + m_Name: Vision + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &8903644794067773966 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6276067436382167109} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 697189026367054479} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!58 &2936989077236630990 +CircleCollider2D: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6276067436382167109} + m_Enabled: 1 + serializedVersion: 3 + m_Density: 1 + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_ForceSendLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ForceReceiveLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ContactCaptureLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_CallbackLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_IsTrigger: 0 + m_UsedByEffector: 0 + m_CompositeOperation: 0 + m_CompositeOrder: 0 + m_Offset: {x: 0, y: 0} + m_Radius: 10 +--- !u!114 &7296562035276564141 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6276067436382167109} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e33d58195dd8450a9ec468ede64c8635, type: 3} + m_Name: + m_EditorClassIdentifier: + _collider: {fileID: 2936989077236630990} --- !u!1001 &3478909419655914534 PrefabInstance: m_ObjectHideFlags: 0 diff --git a/Client/Assets/RunTimeResource/MiniMap.renderTexture b/Client/Assets/RunTimeResource/MiniMap.renderTexture index cbb76a4..a1d56da 100644 --- a/Client/Assets/RunTimeResource/MiniMap.renderTexture +++ b/Client/Assets/RunTimeResource/MiniMap.renderTexture @@ -16,7 +16,7 @@ RenderTexture: m_Height: 150 m_AntiAliasing: 1 m_MipCount: -1 - m_DepthStencilFormat: 0 + m_DepthStencilFormat: 90 m_ColorFormat: 8 m_MipMap: 0 m_GenerateMips: 1 diff --git a/Client/Assets/Scenes/Game.unity b/Client/Assets/Scenes/Game.unity index 16a8c3e..4684be2 100644 --- a/Client/Assets/Scenes/Game.unity +++ b/Client/Assets/Scenes/Game.unity @@ -589,6 +589,7 @@ GameObject: m_Component: - component: {fileID: 741561707} - component: {fileID: 741561708} + - component: {fileID: 741561709} m_Layer: 0 m_Name: OutsideMap m_TagString: Untagged @@ -627,9 +628,21 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 81339b242c794b8d9d28d7111a46bfbf, type: 3} m_Name: m_EditorClassIdentifier: - baseLevel: {fileID: 1084213392} - buildLevel: {fileID: 1204950904} - plantLevel: {fileID: 71139918} +--- !u!114 &741561709 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 741561706} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1cf2b22a0f934c5b852b5cdd588d82fa, type: 3} + m_Name: + m_EditorClassIdentifier: + defaultOpen: 1 + _dimensionId: + mapGenerator: {fileID: 741561708} --- !u!1 &828003921 GameObject: m_ObjectHideFlags: 0 @@ -843,7 +856,6 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: textureLevel: {fileID: 828003924} - dataOffset: {x: 0, y: 0} --- !u!1 &1204850182 GameObject: m_ObjectHideFlags: 0 diff --git a/Client/Assets/Scripts/AI/ConditionFunctions.cs b/Client/Assets/Scripts/AI/ConditionFunctions.cs index ef23aab..f7047cc 100644 --- a/Client/Assets/Scripts/AI/ConditionFunctions.cs +++ b/Client/Assets/Scripts/AI/ConditionFunctions.cs @@ -1,4 +1,5 @@ using System; +using Data; using UnityEngine; namespace AI @@ -15,5 +16,10 @@ namespace AI { return entity.PlayerControlled; } + + public static bool HasEnemyInSight(Entity.Entity entity) + { + return entity.visionRangeDetector.GetNearestNEntitiesByRelation(1, Relation.Hostile).Count > 0; + } } } \ No newline at end of file diff --git a/Client/Assets/Scripts/AI/JobBase.cs b/Client/Assets/Scripts/AI/JobBase.cs index fb34311..a254fa3 100644 --- a/Client/Assets/Scripts/AI/JobBase.cs +++ b/Client/Assets/Scripts/AI/JobBase.cs @@ -74,222 +74,5 @@ namespace AI } } - public class TrackPlayerJob : JobBase - { - private EntityPrefab currentTarget; // 当前追踪的目标玩家 - private LinkedList players; // 玩家实体列表 - - public override void StartJob(Entity.Entity target) - { - base.StartJob(target); - UpdateTarget(); - } - - protected override void UpdateJob() - { - if (!currentTarget || currentTarget.entity.IsDead) - { - // 如果当前目标无效,则重新查找最近的玩家 - UpdateTarget(); - } - - if (currentTarget) - { - var targetPosition = new Vector3(currentTarget.Position.x, currentTarget.Position.y, 0); - entity.SetTarget(targetPosition); - entity.TryMove(); - } - } - - private void UpdateTarget() - { - players = Managers.EntityManage.Instance.FindEntitiesByFaction("Player"); - - if (players == null || players.Count == 0) - { - currentTarget = null; - StopJob(); - return; - } - currentTarget = GetNearestPlayer(players); - } - - private EntityPrefab GetNearestPlayer(LinkedList players) - { - EntityPrefab nearestPlayer = null; - float minDistance = float.MaxValue; - - foreach (var player in players) - { - if (player.entity.IsDead) continue; // 跳过无效玩家 - - float distance = Vector3.Distance( - new Vector3(player.Position.x, player.Position.y, 0), - new Vector3(entity.Position.x, entity.Position.y, 0) - ); - - if (distance < minDistance) - { - minDistance = distance; - nearestPlayer = player; - } - } - - return nearestPlayer; - } - } - public class AttackPlayerJob : JobBase - { - private EntityPrefab player; - - protected override void UpdateJob() - { - if (!player || !IsPlayerInRange()) - { - StopJob(); // 如果玩家不在范围内,停止攻击工作 - return; - } - entity.TryAttack(); - } - - private bool IsPlayerInRange() - { - float distance = Vector3.Distance( - new Vector3(player.Position.x, player.Position.y, 0), - new Vector3(entity.Position.x, entity.Position.y, 0) - ); - - return distance <= entity.attributes.attackRange; - } - - public override void StartJob(Entity.Entity target) - { - base.StartJob(target); - - // 查找最近的玩家作为目标 - LinkedList players = Managers.EntityManage.Instance.FindEntitiesByFaction("Player"); - player = GetNearestPlayer(players); - } - - private EntityPrefab GetNearestPlayer(LinkedList players) - { - EntityPrefab nearestPlayer = null; - float minDistance = float.MaxValue; - - foreach (var player in players) - { - if (!IsPlayerValid(player)) continue; - - float distance = Vector3.Distance( - new Vector3(player.Position.x, player.Position.y, 0), - new Vector3(entity.Position.x, entity.Position.y, 0) - ); - - if (distance < minDistance) - { - minDistance = distance; - nearestPlayer = player; - } - } - - return nearestPlayer; - } - - private bool IsPlayerValid(EntityPrefab player) - { - return player && !player.entity.IsDead; - } - } - public class RangedAttackJob : JobBase - { - private EntityPrefab player; - - protected override void UpdateJob() - { - if (!player || !IsPlayerValid(player)) - { - StopJob(); // 如果当前目标无效,停止工作 - return; - } - - float distance = Vector3.Distance( - new Vector3(player.Position.x, player.Position.y, 0), - new Vector3(entity.Position.x, entity.Position.y, 0) - ); - - if (distance <= entity.attributes.attackRange) - { - // 如果在攻击范围内,进行攻击 - entity.TryAttack(); - } - else if (distance > entity.attributes.attackRange && distance < MaxTrackDistance) - { - // 如果距离过远,靠近目标 - MoveTowardsPlayer(); - } - else if (distance < entity.attributes.attackRange) - { - // 如果距离过近,远离目标 - MoveAwayFromPlayer(); - } - } - - private void MoveTowardsPlayer() - { - Vector3 targetPosition = new Vector3(player.Position.x, player.Position.y, 0); - entity.SetTarget(targetPosition); - entity.TryMove(); - } - - private void MoveAwayFromPlayer() - { - Vector3 direction = entity.Position - player.Position; - direction.Normalize(); - Vector3 targetPosition = entity.Position + direction * RetreatDistance; - - entity.SetTarget(targetPosition); - entity.TryMove(); - } - - public override void StartJob(Entity.Entity target) - { - base.StartJob(target); - - // 查找最近的玩家作为目标 - var players = Managers.EntityManage.Instance.FindEntitiesByFaction("Player"); - player = GetNearestPlayer(players); - } - - private EntityPrefab GetNearestPlayer(LinkedList players) - { - EntityPrefab nearestPlayer = null; - float minDistance = float.MaxValue; - - foreach (var player in players) - { - if (!IsPlayerValid(player)) continue; - - float distance = Vector3.Distance( - new Vector3(player.Position.x, player.Position.y, 0), - new Vector3(entity.Position.x, entity.Position.y, 0) - ); - - if (distance < minDistance) - { - minDistance = distance; - nearestPlayer = player; - } - } - - return nearestPlayer; - } - - private bool IsPlayerValid(EntityPrefab player) - { - return player && !player.entity.IsDead; - } - - private const float MaxTrackDistance = 20f; // 最大追踪距离 - private const float RetreatDistance = 3f; // 后退距离 - } + } \ No newline at end of file diff --git a/Client/Assets/Scripts/AI/JobGiver.cs b/Client/Assets/Scripts/AI/JobGiver.cs index 48ce7bf..0838274 100644 --- a/Client/Assets/Scripts/AI/JobGiver.cs +++ b/Client/Assets/Scripts/AI/JobGiver.cs @@ -8,13 +8,7 @@ namespace AI } } - public class JobGiver_Enemies : AIBase - { - public override JobBase GetJob(Entity.Entity target) - { - return new TrackPlayerJob(); - } - } + public class JobGiver_RandomWander : AIBase { public override JobBase GetJob(Entity.Entity target) diff --git a/Client/Assets/Scripts/Data/ItemDef.cs b/Client/Assets/Scripts/Data/ItemDef.cs index bde94f7..a04167b 100644 --- a/Client/Assets/Scripts/Data/ItemDef.cs +++ b/Client/Assets/Scripts/Data/ItemDef.cs @@ -8,16 +8,21 @@ namespace Data Epic, Legendary } - public class ItemDef:Define + public class ItemDef : Define { public ImageDef texture; public ItemRarity rarity = ItemRarity.Common; - public int maxStack = 1; // 最大堆叠数量,默认为1 + public int maxStack = 10; // 最大堆叠数量,默认为10 public bool ssEquippable = false; // 是否可装备 } public class WeaponDef : ItemDef { public AttributesDef attributes; + public WeaponDef() // 构造函数,用于设置武器的默认属性 + { + maxStack = 1; // 武器默认最大堆叠为1 + ssEquippable = true; // 武器默认可装备 + } } } \ No newline at end of file diff --git a/Client/Assets/Scripts/Entity/Character.cs b/Client/Assets/Scripts/Entity/Character.cs index 7136db3..fe1777f 100644 --- a/Client/Assets/Scripts/Entity/Character.cs +++ b/Client/Assets/Scripts/Entity/Character.cs @@ -31,7 +31,7 @@ namespace Entity return; var buttonDef = def.Values.First(); Vector3 dir = Utils.MousePosition.GetWorldPosition(); - Managers.EntityManage.Instance.GenerateBulletEntity((BulletDef)buttonDef, Position, + Managers.EntityManage.Instance.GenerateBulletEntity(Program.Instance.focuseDimensionId,(BulletDef)buttonDef, Position, dir - Position, this); } } diff --git a/Client/Assets/Scripts/Entity/Entity.cs b/Client/Assets/Scripts/Entity/Entity.cs index 632b64e..c99fc90 100644 --- a/Client/Assets/Scripts/Entity/Entity.cs +++ b/Client/Assets/Scripts/Entity/Entity.cs @@ -29,7 +29,7 @@ namespace Entity public ProgressBarPrefab healthBarPrefab; - + public VisionRangeDetector visionRangeDetector; /// /// 人工智能行为树,定义实体的行为逻辑。 /// @@ -154,7 +154,7 @@ namespace Entity [SerializeField] private float _hitBarUIShowTime = 5; private float _hitBarUIShowTimer = 0; - private int _walkingTimer = 1; + private int _walkingTimer = 0; /// @@ -219,16 +219,17 @@ namespace Entity { targetObj = InitBodyPart(nodeDef, body); // 创建新对象 } - + stateBodyNodes[orientation] = targetObj; - // 提取动画组件(安全处理空组件情况) + // 逻辑修改:确保 stateAnimNodes[orientation] 总是被初始化为一个列表 + var animatorsForOrientation = new List(); // 总是创建一个新的列表 var animators = targetObj.GetComponentsInChildren(); if (animators.Length > 0) { - stateAnimNodes[orientation] = animators.Cast().ToList(); + animatorsForOrientation.AddRange(animators.Cast()); } - // 无动画组件时保持 stateAnimNodes[orientation] 为 null(符合原始逻辑) + stateAnimNodes[orientation] = animatorsForOrientation; } } @@ -463,13 +464,15 @@ namespace Entity _currentState = state; _currentOrientation = orientation; - - if (bodyAnimationNode.TryGetValue(_currentState, out var animationNode)) + + if (bodyAnimationNode.TryGetValue(_currentState, out var animationNode) && + animationNode.TryGetValue(_currentOrientation, out var value)) { - if (animationNode.TryGetValue(_currentOrientation, out var value)) - { - _currentAnimatorCache=value; - } + _currentAnimatorCache = value; + } + else + { + _currentAnimatorCache = new List(); // 如果没有找到动画,则使用空列表 } } diff --git a/Client/Assets/Scripts/Entity/Inventory.cs b/Client/Assets/Scripts/Entity/Inventory.cs index 5e98860..caa33dc 100644 --- a/Client/Assets/Scripts/Entity/Inventory.cs +++ b/Client/Assets/Scripts/Entity/Inventory.cs @@ -1,70 +1,149 @@ using System.Collections.Generic; +using System.Linq; using Item; +using UnityEngine; namespace Entity { public class Inventory { - public Entity from; // 物品所属实体 - public List items = new List(); // 背包中的物品列表 + public Entity From { get; private set; } + private readonly List _slots; + public int Capacity { get; private set; } - /// - /// 添加物品到背包 - /// - /// 要添加的物品 - /// 添加的数量 - public void AddItem(ItemResource resource, int count) + public event System.Action OnItemAdded; + public event System.Action OnItemRemoved; + public event System.Action OnInventoryChanged; + + public Inventory(Entity owner, int capacity = 20) { - if (count <= 0) return; // 如果数量小于等于0,直接返回 + From = owner; + Capacity = Mathf.Max(1, capacity); + _slots = new List(Capacity); + } - // 检查背包中是否已存在相同物品 - foreach (var item in items) + public int AddItem(Item.ItemResource itemResource, int quantity) + { + if (itemResource == null || quantity <= 0) { - if (item.resource.Equals(resource)) + Debug.LogWarning( + $"Inventory for {From?.ToString() ?? "Unknown"}: Attempted to add null item or zero/negative quantity."); + return quantity; + } + + var remainingQuantity = quantity; + var addedTotal = 0; + + // 1. 尝试堆叠到现有槽位 (使用 DefName 进行比较) + if (itemResource.MaxStack > 1) + { + foreach (var slot in _slots.Where(s => + s.Item != null && s.Item.DefName == itemResource.DefName && !s.IsFull)) { - item.count += count; // 增加数量 - return; + var addedToSlot = slot.AddQuantity(remainingQuantity); + remainingQuantity -= addedToSlot; + addedTotal += addedToSlot; + if (remainingQuantity <= 0) break; } } - // 如果没有找到相同物品,则创建新物品并添加到背包 - var newItem = new ItemBase { resource = resource, count = count }; - items.Add(newItem); + // 2. 如果还有剩余,尝试添加到新槽位 + while (remainingQuantity > 0 && _slots.Count < Capacity) + { + var quantityToAdd = Mathf.Min(remainingQuantity, itemResource.MaxStack); + var newSlot = new InventorySlot(itemResource, quantityToAdd); + _slots.Add(newSlot); + remainingQuantity -= quantityToAdd; + addedTotal += quantityToAdd; + } + + if (addedTotal > 0) + { + OnItemAdded?.Invoke(itemResource, addedTotal); + OnInventoryChanged?.Invoke(); + } + + if (remainingQuantity > 0) + { + Debug.LogWarning( + $"Inventory for {From?.ToString() ?? "Unknown"} is full or cannot stack. {remainingQuantity} of {itemResource.Name} (DefName: {itemResource.DefName}) could not be added."); + } + + return remainingQuantity; } - /// - /// 从背包中取出物品 - /// - /// 物品名称 - /// 取出的数量 - /// 是否成功取出 - public bool RemoveItem(string itemName, int count) + public int RemoveItem(Item.ItemResource itemResource, int quantity) { - if (count <= 0) return false; // 如果数量小于等于0,直接返回失败 - - foreach (var item in items) + if (itemResource == null || quantity <= 0) { - if (item.resource.name == itemName) - { - if (item.count >= count) - { - item.count -= count; // 减少数量 - if (item.count == 0) - { - items.Remove(item); // 如果数量为0,则移除该物品 - } + Debug.LogWarning( + $"Inventory for {From?.ToString() ?? "Unknown"}: Attempted to remove null item or zero/negative quantity."); + return quantity; + } - return true; // 成功取出 - } - else + var remainingQuantity = quantity; + var removedTotal = 0; + var slotsToClean = new List(); + + // 从后往前遍历,以便安全地移除空槽位 (使用 DefName 进行比较) + for (var i = _slots.Count - 1; i >= 0; i--) + { + var slot = _slots[i]; + if (slot.Item != null && slot.Item.DefName == itemResource.DefName) + { + var removedFromSlot = slot.RemoveQuantity(remainingQuantity); + remainingQuantity -= removedFromSlot; + removedTotal += removedFromSlot; + + if (slot.IsEmpty) { - return false; // 数量不足 + slotsToClean.Add(slot); } + + if (remainingQuantity <= 0) break; } } - return false; // 未找到物品 - } - + foreach (var slot in slotsToClean) + { + _slots.Remove(slot); + } + + if (removedTotal > 0) + { + OnItemRemoved?.Invoke(itemResource, removedTotal); + OnInventoryChanged?.Invoke(); + } + + if (remainingQuantity > 0) + { + Debug.LogWarning( + $"Inventory for {From?.ToString() ?? "Unknown"}: Not enough {itemResource.Name} (DefName: {itemResource.DefName}). {remainingQuantity} could not be removed."); + } + + return remainingQuantity; + } + + public bool HasItem(Item.ItemResource itemResource, int quantity = 1) + { + if (itemResource == null || quantity <= 0) return false; + return GetItemCount(itemResource) >= quantity; + } + + public int GetItemCount(Item.ItemResource itemResource) + { + if (itemResource == null) return 0; + // 使用 DefName 进行计数 + return _slots.Where(s => s.Item != null && s.Item.DefName == itemResource.DefName) + .Sum(s => s.Quantity); + } + + public IReadOnlyList GetSlots() + { + return _slots.AsReadOnly(); + } + + public int OccupiedSlotsCount => _slots.Count; + public int AvailableSlotsCount => Capacity - _slots.Count; } } \ No newline at end of file diff --git a/Client/Assets/Scripts/Entity/InventorySlot.cs b/Client/Assets/Scripts/Entity/InventorySlot.cs new file mode 100644 index 0000000..6188c74 --- /dev/null +++ b/Client/Assets/Scripts/Entity/InventorySlot.cs @@ -0,0 +1,78 @@ +using UnityEngine; + +namespace Entity +{ + /// + /// 表示背包中的一个槽位,存储特定类型的物品及其数量。 + /// + public class InventorySlot + { + public Item.ItemResource Item { get; private set; } // 槽位中的物品资源 + public int Quantity { get; private set; } // 槽位中物品的数量 + + /// + /// 创建一个新的背包槽位。 + /// + /// 槽位中的物品资源。 + /// 初始数量。 + public InventorySlot(Item.ItemResource item, int quantity) + { + if (item == null) + { + Debug.LogError("InventorySlot cannot be initialized with a null ItemResource."); + return; + } + + Item = item; + Quantity = Mathf.Max(0, quantity); // 确保数量不为负 + // 确保初始数量不超过最大堆叠 + if (Quantity > Item.MaxStack) + { + Debug.LogWarning( + $"Initial quantity {Quantity} for {Item.Name} exceeds MaxStack {Item.MaxStack}. Clamping to MaxStack."); + Quantity = Item.MaxStack; + } + } + + /// + /// 向槽位中添加指定数量的物品。 + /// + /// 要添加的数量。 + /// 实际添加的数量。 + public int AddQuantity(int amount) + { + if (Item == null || amount <= 0) return 0; + + var spaceLeft = Item.MaxStack - Quantity; + if (spaceLeft <= 0) return 0; // 槽位已满 + + var added = Mathf.Min(amount, spaceLeft); + Quantity += added; + return added; + } + + /// + /// 从槽位中移除指定数量的物品。 + /// + /// 要移除的数量。 + /// 实际移除的数量。 + public int RemoveQuantity(int amount) + { + if (Item == null || amount <= 0) return 0; + + var removed = Mathf.Min(amount, Quantity); + Quantity -= removed; + return removed; + } + + /// + /// 获取槽位是否为空。 + /// + public bool IsEmpty => Quantity <= 0; + + /// + /// 获取槽位是否已满。 + /// + public bool IsFull => Item != null && Quantity >= Item.MaxStack; + } +} \ No newline at end of file diff --git a/Client/Assets/Scripts/Entity/InventorySlot.cs.meta b/Client/Assets/Scripts/Entity/InventorySlot.cs.meta new file mode 100644 index 0000000..74ab2f9 --- /dev/null +++ b/Client/Assets/Scripts/Entity/InventorySlot.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4c58fcd7fb7147ce82fe20e68cb7c8d4 +timeCreated: 1756031108 \ No newline at end of file diff --git a/Client/Assets/Scripts/Entity/Outline.cs b/Client/Assets/Scripts/Entity/Outline.cs index 97dd81b..e11319f 100644 --- a/Client/Assets/Scripts/Entity/Outline.cs +++ b/Client/Assets/Scripts/Entity/Outline.cs @@ -118,7 +118,7 @@ namespace Entity private void BecomeDefault() { entity.Kill(); - Managers.EntityManage.Instance.GenerateDefaultEntity(entity.Position); + Managers.EntityManage.Instance.GenerateDefaultEntity(Program.Instance.focuseDimensionId,entity.Position); } private void StartControl() diff --git a/Client/Assets/Scripts/Entity/VisionRangeDetector.cs b/Client/Assets/Scripts/Entity/VisionRangeDetector.cs new file mode 100644 index 0000000..98165d7 --- /dev/null +++ b/Client/Assets/Scripts/Entity/VisionRangeDetector.cs @@ -0,0 +1,188 @@ +using System.Collections.Generic; +using System.Linq; +using Data; +using Managers; +using UnityEngine; + +namespace Entity +{ + + /// + /// 视野范围检测器组件。 + /// 挂载到包含触发器碰撞体的GameObject上,用于检测视野范围内的Entity。 + /// + public class VisionRangeDetector : MonoBehaviour + { + // 允许在Inspector中手动指定Collider,或在Awake时自动获取。 + [SerializeField] [Tooltip("用于检测视野范围的触发器碰撞体。如果未指定,将在Awake时自动获取。")] + private Collider2D _collider; + + // 缓存所属的Entity组件,用于获取派系信息。 + private Entity _ownerEntity; + + // 将List改为HashSet,优化添加和移除的性能。 + private HashSet _entitiesInRange = new HashSet(); // 维护在视野范围内的Entity集合 + private Transform _myTransform; // 缓存Transform组件以提高性能 + + // 【逻辑修改】用于记录上次清理的帧数,避免在同一帧内重复清理。 + private int _lastCleanupFrame = -1; + + private void Awake() + { + _myTransform = transform; // 缓存自身Transform + + // 1. 检查并确保GameObject上存在触发器碰撞体 + if (_collider == null) + { + _collider = GetComponent(); + } + + if (_collider == null) + { + Debug.LogError( + $"VisionRangeDetector on {gameObject.name} requires a Collider component. Please add one or assign it in the Inspector.", + this); + enabled = false; // 如果没有碰撞体,禁用此组件 + return; + } + + if (!_collider.isTrigger) + { + Debug.LogWarning( + $"VisionRangeDetector on {gameObject.name} works best with a Trigger Collider. Setting isTrigger to true.", + this); + _collider.isTrigger = true; + } + + // 2. 检查AffiliationManager是否存在 + if (AffiliationManager.Instance == null) + { + Debug.LogError( + "AffiliationManager.Instance is null. Please ensure an AffiliationManager exists in the scene.", + this); + enabled = false; // 如果没有派系管理器,禁用此组件 + return; // 提前返回,避免后续操作依赖于AffiliationManager + } + + // 3. 获取所属的Entity组件,并使用其affiliation。 + _ownerEntity = GetComponentInParent(); + if (_ownerEntity == null) + { + _ownerEntity = GetComponent(); + } + + if (_ownerEntity == null) + { + Debug.LogError( + $"VisionRangeDetector on {gameObject.name}: No Entity component found in parent or self. This component cannot determine its affiliation and will be disabled.", + this); + enabled = false; + return; + } + } + + // 【逻辑修改】移除LateUpdate,清理逻辑移至获取方法中。 + // private void LateUpdate() + // { + // _entitiesInRange.RemoveWhere(e => e == null); + // } + + /// + /// 【逻辑修改】在获取实体列表前,执行一次清理操作(每帧最多一次)。 + /// + private void CleanEntitiesInRange() + { + // 只有当当前帧与上次清理的帧不同时才执行清理 + if (Time.frameCount != _lastCleanupFrame) + { + _entitiesInRange.RemoveWhere(e => e == null); + _lastCleanupFrame = Time.frameCount; // 更新上次清理的帧数 + // Debug.Log($"Cleaned _entitiesInRange in frame {Time.frameCount}. Current count: {_entitiesInRange.Count}"); + } + } + + /// + /// 当有其他碰撞体进入触发器范围时调用。 + /// + /// 进入触发器的碰撞体。 + private void OnTriggerEnter(Collider other) + { + Entity entity = other.GetComponent(); + if (entity != null) + { + if (entity.gameObject == _ownerEntity.gameObject || entity.transform.IsChildOf(_ownerEntity.transform)) + { + return; + } + + if (_entitiesInRange.Add(entity)) + { + // Debug.Log($"Entity '{entity.name}' (Affiliation: {entity.affiliation}) entered range. Total: {_entitiesInRange.Count}"); + } + } + } + + /// + /// 当有其他碰撞体离开触发器范围时调用。 + /// + /// 离开触发器的碰撞体。 + private void OnTriggerExit(Collider other) + { + Entity entity = other.GetComponent(); + if (entity != null) + { + if (_entitiesInRange.Remove(entity)) + { + // Debug.Log($"Entity '{entity.name}' (Affiliation: {entity.affiliation}) exited range. Total: {_entitiesInRange.Count}"); + } + } + } + + /// + /// 获取视野范围内最近的N个实体。 + /// + /// 要获取的实体数量。 + /// 最近的N个实体列表。 + public List GetNearestNEntities(int n) + { + if (n <= 0) return new List(); + + // 【逻辑修改】在获取前清理一次集合 + CleanEntitiesInRange(); + + return _entitiesInRange + .OrderBy(e => Vector3.Distance(_myTransform.position, e.transform.position)) + .Take(n) + .ToList(); + } + + /// + /// 获取视野范围内最近的N个指定关系的实体(敌对、友好、中立)。 + /// + /// 要获取的实体数量。 + /// 目标派系关系。 + /// 最近的N个指定关系的实体列表。 + public List GetNearestNEntitiesByRelation(int n, Relation targetRelation) + { + if (AffiliationManager.Instance == null) + { + Debug.LogError( + "AffiliationManager.Instance is null. Cannot get entities by relation. VisionRangeDetector should have been disabled if this was an issue.", + this); + return new List(); + } + + if (n <= 0) return new List(); + + // 【逻辑修改】在获取前清理一次集合 + CleanEntitiesInRange(); + + return _entitiesInRange + .Where(e => AffiliationManager.Instance.GetRelation(_ownerEntity.affiliation, e.affiliation) == + targetRelation) + .OrderBy(e => Vector3.Distance(_myTransform.position, e.transform.position)) + .Take(n) + .ToList(); + } + } +} \ No newline at end of file diff --git a/Client/Assets/Scripts/Entity/VisionRangeDetector.cs.meta b/Client/Assets/Scripts/Entity/VisionRangeDetector.cs.meta new file mode 100644 index 0000000..7bd641a --- /dev/null +++ b/Client/Assets/Scripts/Entity/VisionRangeDetector.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e33d58195dd8450a9ec468ede64c8635 +timeCreated: 1756048545 \ No newline at end of file diff --git a/Client/Assets/Scripts/Item/ItemResource.cs b/Client/Assets/Scripts/Item/ItemResource.cs index 996fb8e..b8bcd77 100644 --- a/Client/Assets/Scripts/Item/ItemResource.cs +++ b/Client/Assets/Scripts/Item/ItemResource.cs @@ -1,12 +1,41 @@ +using Data; using UnityEngine; namespace Item { public class ItemResource { - public string name; - public string description; - - public Sprite icon; + public string DefName { get; protected set; } // 新增:物品的定义名称,唯一标识符 + public string Name { get; protected set; } // 物品的显示名称 (来自 label) + public string Description { get; protected set; } + public Sprite Icon { get; protected set; } + public ItemRarity Rarity { get; protected set; } + public int MaxStack { get; protected set; } + public bool IsEquippable { get; protected set; } + + // 构造函数,现在接受 defName + public ItemResource(string defName, string name, string description, Sprite icon, ItemRarity rarity, int maxStack, bool isEquippable) + { + DefName = defName; // 赋值 defName + Name = name; + Description = description; + Icon = icon; + Rarity = rarity; + MaxStack = maxStack; + IsEquippable = isEquippable; + } } + + public class WeaponResource : ItemResource + { + public AttributesDef Attributes { get; private set; } + + // 构造函数,调用基类构造函数并传递 defName + public WeaponResource(string defName, string name, string description, Sprite icon, ItemRarity rarity, int maxStack, bool isEquippable, AttributesDef attributes) + : base(defName, name, description, icon, rarity, maxStack, isEquippable) + { + Attributes = attributes; + } + } + } \ No newline at end of file diff --git a/Client/Assets/Scripts/Managers/AffiliationManager.cs b/Client/Assets/Scripts/Managers/AffiliationManager.cs index 4a34801..2299406 100644 --- a/Client/Assets/Scripts/Managers/AffiliationManager.cs +++ b/Client/Assets/Scripts/Managers/AffiliationManager.cs @@ -6,105 +6,165 @@ using UnityEngine; namespace Managers { + /// + /// 阵营管理器,负责管理游戏中的所有阵营定义及其相互关系。 + /// 继承自 ,确保全局只有一个实例。 + /// public class AffiliationManager:Utils.Singleton { - //定义名,阵营定义 + /// + /// 存储所有已加载的阵营定义,键为阵营的唯一名称,值为对应的 对象。 + /// private readonly Dictionary _affiliations = new(); - + + /// + /// 初始化阵营管理器,从 加载所有 。 + /// 在首次需要阵营数据时调用。 + /// public void Init() { + // 如果管理器已经初始化,则直接返回,避免重复加载。 + if (_affiliations.Count > 0) + { + return; + } + var affiliationList = Managers.DefineManager.Instance.QueryDefinesByType(); - if (affiliationList == null ||affiliationList.Length==0) + if (affiliationList == null || affiliationList.Length == 0) + { + // 记录警告,说明未能加载任何阵营定义。 + Debug.LogWarning("未找到任何 AffiliationDef 或阵营列表为空。"); return; + } foreach (var affiliation in affiliationList) { - _affiliations.Add(affiliation.defName, affiliation); + // 尝试将阵营定义添加到字典中。如果名称已存在,则记录错误。 + if (!_affiliations.TryAdd(affiliation.defName, affiliation)) + { + Debug.LogError($"添加阵营 '{affiliation.defName}' 失败。已存在同名阵营。"); + } } + // 验证并修复加载后的阵营关系,以确保数据一致性。 ValidateAndFixRelationships(); } + /// + /// 清除所有已加载的阵营定义。 + /// 在游戏会话结束或需要重新加载所有定义时调用。 + /// + public void Clear() + { + _affiliations.Clear(); + } + + /// + /// 根据阵营定义名称获取其默认名称。 + /// + /// 阵营的定义名称。 + /// 阵营的默认名称。 public string GetAffiliationName(string defName) { return _affiliations[defName].defName; } + + /// + /// 获取两个阵营间的关系。 + /// + /// 第一个阵营的 对象。 + /// 第二个阵营的 对象。 + /// 两个阵营之间的 public Relation GetRelation(AffiliationDef affiliation1, AffiliationDef affiliation2) { + // 如果任一阵营定义为空,则返回中立关系。 if (affiliation1 == null || affiliation2 == null) { - return Relation.Neutral; // 如果任一阵营不存在,返回中立关系 + return Relation.Neutral; } return GetRelation(affiliation1.defName, affiliation2.defName); } + + /// + /// 获取两个阵营名称之间的关系。 + /// + /// 第一个阵营的名称。 + /// 第二个阵营的名称。 + /// 两个阵营之间的 public Relation GetRelation(string factionName1, string factionName2) { - // 如果查询的是同一个派系,默认是友好关系 + // 如果查询的是同一个阵营,则默认为友好关系。 if (factionName1 == factionName2) { return Relation.Friendly; } - // 尝试获取两个派系的定义 + // 尝试获取两个阵营的定义。 if (!_affiliations.TryGetValue(factionName1, out var faction1) || !_affiliations.TryGetValue(factionName2, out _)) { + // 如果第一个阵营存在,但第二个不存在,返回第一个阵营的默认关系; + // 否则(两个都不存在或第一个不存在),返回中立。 if (faction1 != null) return faction1.defaultRelation; + // 注意:由于上面已经有一个 TryGetValue 判断, + // 此时 faction1 为 null 表示 factionName1 也不存在,所以应返回中立。 return Relation.Neutral; } - // 检查faction1是否明确将faction2列为敌对 + // 检查 faction1 是否将 faction2 明确列为敌对。 if (faction1.hostileFactions != null && faction1.hostileFactions.Contains(factionName2)) { return Relation.Hostile; } - // 检查faction1是否明确将faction2列为友好 + // 检查 faction1 是否将 faction2 明确列为友好。 if (faction1.friendlyFactions != null && faction1.friendlyFactions.Contains(factionName2)) { return Relation.Friendly; } - // 检查faction1是否明确将faction2列为中立 + // 检查 faction1 是否将 faction2 明确列为中立。 if (faction1.neutralFactions != null && faction1.neutralFactions.Contains(factionName2)) { return Relation.Neutral; } - // 如果faction1没有明确设置与faction2的关系,则使用faction1的默认关系 + // 如果 faction1 没有明确设置与 faction2 的关系,则使用 faction1 的默认关系。 return faction1.defaultRelation; } - + /// - /// 设置两个阵营之间的关系 + /// 设置两个阵营之间的关系。 /// - /// 第一个阵营名称 - /// 第二个阵营名称 - /// 要设置的关系 + /// 第一个阵营的名称。 + /// 第二个阵营的名称。 + /// 要设置的 。 + /// 当尝试设置同一个阵营的关系或其中一个阵营不存在时抛出。 + /// 当传入的关系类型无效时抛出。 public void SetRelation(string factionName1, string factionName2, Relation relation) { - // 不能设置自己与自己的关系 + // 不能设置自己与自己的关系。 if (factionName1 == factionName2) { - throw new ArgumentException("Cannot set relation between the same faction"); + throw new ArgumentException("不能设置同一个阵营之间的关系"); } - // 确保两个阵营都存在 + // 确保两个阵营都存在。 if (!_affiliations.TryGetValue(factionName1, out var faction1) || !_affiliations.TryGetValue(factionName2, out _)) { - throw new ArgumentException("One or both factions do not exist"); + throw new ArgumentException("一个或两个阵营不存在"); } - // 确保关系列表已初始化 + // 确保关系列表已初始化,避免空引用异常。 faction1.hostileFactions ??= new List(); faction1.friendlyFactions ??= new List(); faction1.neutralFactions ??= new List(); - // 先移除所有现有关系 + // 先移除 factionName2 在 faction1 所有关系列表中的现有关系。 faction1.hostileFactions.Remove(factionName2); faction1.friendlyFactions.Remove(factionName2); faction1.neutralFactions.Remove(factionName2); - // 添加新关系 + // 根据传入的关系类型,将 factionName2 添加到对应列表中。 switch (relation) { case Relation.Hostile: @@ -117,43 +177,45 @@ namespace Managers faction1.neutralFactions.Add(factionName2); break; default: + // 如果传入的关系类型无效,抛出异常。 throw new ArgumentOutOfRangeException(nameof(relation), relation, null); } } - + /// - /// 检查并修复派系关系,确保没有冲突(按友好 > 敌对 > 中立 的优先级) + /// 检查并修复所有阵营之间的关系,以确保没有冲突。 + /// 修复遵循优先级规则:友好关系优先于敌对关系,敌对关系优先于中立关系。 /// private void ValidateAndFixRelationships() { foreach (var faction in _affiliations.Values) { - // 确保所有关系列表已初始化 + // 确保所有关系列表已初始化,避免空引用异常。 faction.hostileFactions ??= new List(); faction.friendlyFactions ??= new List(); faction.neutralFactions ??= new List(); - // 检查所有敌对派系 + // 遍历并检查所有敌对阵营。由于可能修改列表,使用 ToList() 创建副本进行遍历。 foreach (var hostileFaction in faction.hostileFactions.ToList()) { - // 如果敌对派系同时存在于友好列表中,移除敌对关系(友好优先) + // 如果敌对阵营同时存在于友好列表中,则移除其敌对关系(友好关系具有更高优先级)。 if (faction.friendlyFactions.Contains(hostileFaction)) { faction.hostileFactions.Remove(hostileFaction); - continue; + continue; // 继续检查下一个敌对阵营。 } - // 如果敌对派系同时存在于中立列表中,移除中立关系(敌对优先) + // 如果敌对阵营同时存在于中立列表中,则移除其中立关系(敌对关系具有更高优先级)。 if (faction.neutralFactions.Contains(hostileFaction)) { faction.neutralFactions.Remove(hostileFaction); } } - // 检查所有中立派系 + // 遍历并检查所有中立阵营。由于可能修改列表,使用 ToList() 创建副本进行遍历。 foreach (var neutralFaction in faction.neutralFactions.ToList()) { - // 如果中立派系同时存在于友好列表中,移除中立关系(友好优先) + // 如果中立阵营同时存在于友好列表中,则移除其中立关系(友好关系具有更高优先级)。 if (faction.friendlyFactions.Contains(neutralFaction)) { faction.neutralFactions.Remove(neutralFaction); @@ -162,4 +224,4 @@ namespace Managers } } } -} \ No newline at end of file +} diff --git a/Client/Assets/Scripts/Managers/DefineManager.cs b/Client/Assets/Scripts/Managers/DefineManager.cs index 6dbf489..c3e50df 100644 --- a/Client/Assets/Scripts/Managers/DefineManager.cs +++ b/Client/Assets/Scripts/Managers/DefineManager.cs @@ -218,11 +218,11 @@ namespace Managers /// 如果找到,返回转换为目标类型的 Define 对象;否则返回 null。 public T FindDefine(string defineName) where T : Define { - foreach (var typeDict in defines.Values) + if (defines.TryGetValue(typeof(T).Name, out var typeDict)) { - if (typeDict.TryGetValue(defineName, out var define) && define is T result) + if (typeDict.TryGetValue(defineName, out var define)) { - return result; + return (T)define; } } return null; diff --git a/Client/Assets/Scripts/Managers/EntityManage.cs b/Client/Assets/Scripts/Managers/EntityManage.cs index 9b6e6a0..ccf5e7d 100644 --- a/Client/Assets/Scripts/Managers/EntityManage.cs +++ b/Client/Assets/Scripts/Managers/EntityManage.cs @@ -3,102 +3,271 @@ using System.Collections.Generic; using System.Linq; using Base; using Entity; +using Map; using Prefab; using UnityEngine; using UnityEngine.SceneManagement; -using UnityEngine.Serialization; namespace Managers { + public class EntityManage : Utils.MonoSingleton, ITick { - public Dictionary> factionEntities = new(); + // --- 新增:维度感知的实体存储结构 --- + // 外层字典:DimensionId -> 内层字典 + // 内层字典:FactionKey -> LinkedList + private Dictionary>> _dimensionFactionEntities = new(); + // --- 新增:当前场景中活跃的维度实例 --- + private Dictionary _activeDimensions = new(); + + // --- 新增:维度感知的层级缓存 --- + // DimensionId -> LayerName -> Transform + private Dictionary> _dimensionLayerCache = new(); + + // --- 待添加实体列表,现在包含 DimensionId --- + private List> + _pendingAdditions = new(); // Item1: DimensionId, Item2: FactionKey, Item3: EntityPrefab + + // --- 现有预制体 (保持不变) --- public EntityPrefab characterPrefab; public EntityPrefab buildingPrefab; public EntityPrefab bulletPrefab; - public EntityPrefab defaultEntityPrefab; - private Dictionary layerCache = new Dictionary(); - private List> pendingAdditions = new(); - - public LinkedList FindEntitiesByFaction(string factionKey) + public void RegisterDimension(Dimension dimension) { - if (factionEntities.TryGetValue(factionKey, out var entities)) + if (dimension == null || string.IsNullOrEmpty(dimension.DimensionId)) { - return entities; // 如果找到,返回对应的实体列表 + Debug.LogError("Attempted to register a null or invalid Dimension."); + return; } - return new(); // 如果未找到,返回一个空列表 + if (_activeDimensions.TryGetValue(dimension.DimensionId, out var existingDimension)) + { + if (existingDimension == dimension) + { + // 【逻辑修改】如果是同一个实例重复注册,只做信息提示,不警告 + Debug.Log($"Dimension with ID '{dimension.DimensionId}' already registered with the same instance. Skipping re-registration."); + return; // 已经注册且是同一个实例,直接返回 + } + else + { + // 【逻辑修改】如果是不同实例但ID相同,这是严重错误 + Debug.LogError( + $"CRITICAL ERROR: Dimension with ID '{dimension.DimensionId}' is already registered with a DIFFERENT instance ({existingDimension.name} vs {dimension.name}). This indicates a duplicate DimensionId in the scene. The new instance will overwrite the old one, which may lead to unexpected behavior. Please ensure all Dimension objects have unique IDs."); + // 允许覆盖,但以错误日志形式提示,强制开发者修复场景配置。 + } + } + + _activeDimensions[dimension.DimensionId] = dimension; + Debug.Log($"Dimension '{dimension.DimensionId}' registered with EntityManage."); + + // 为新注册的维度初始化其数据结构 + // 这些检查是必要的,以防止在重复注册时清空现有数据。 + if (!_dimensionFactionEntities.ContainsKey(dimension.DimensionId)) + { + _dimensionFactionEntities[dimension.DimensionId] = new Dictionary>(); + } + + if (!_dimensionLayerCache.ContainsKey(dimension.DimensionId)) + { + _dimensionLayerCache[dimension.DimensionId] = new Dictionary(); + } } - public void Tick() - { - foreach (var faction in factionEntities) - { - var entitiesToRemove = new List(); - foreach (var entityPrefab in faction.Value) + /// + /// 从实体管理器注销一个维度。 + /// + /// 要注销的维度实例。 + public void UnregisterDimension(Dimension dimension) + { + if (dimension == null || string.IsNullOrEmpty(dimension.DimensionId)) + { + Debug.LogError("Attempted to unregister a null or invalid Dimension."); + return; + } + + if (_activeDimensions.Remove(dimension.DimensionId)) + { + // 当维度被注销时,清理其所有相关的实体和层级缓存 + _dimensionFactionEntities.Remove(dimension.DimensionId); + _dimensionLayerCache.Remove(dimension.DimensionId); + + // 【逻辑修改】立即清理_pendingAdditions中属于该维度的实体 + // 创建一个新列表来存储不属于该维度的实体 + var remainingPendingAdditions = new List>(); + foreach (var pending in _pendingAdditions) { - if (entityPrefab.entity.IsDead) + if (pending.Item1 == dimension.DimensionId) { - entitiesToRemove.Add(entityPrefab); + // 销毁实体GameObject + if (pending.Item3 != null && pending.Item3.gameObject != null) // 增加gameObject的null检查 + { + Destroy(pending.Item3.gameObject); + } } else { - ITick itike = entityPrefab.entity; - itike.Tick(); + remainingPendingAdditions.Add(pending); } } + _pendingAdditions = remainingPendingAdditions; // 更新_pendingAdditions列表 + } + } - // 删除所有标记为死亡的实体 - foreach (var entityToRemove in entitiesToRemove) + + /// + /// 根据ID获取一个活跃的维度实例。 + /// + /// 维度的唯一标识符。 + /// 对应的 Dimension 实例,如果不存在则为 null。 + public Dimension GetDimension(string dimensionId) + { + _activeDimensions.TryGetValue(dimensionId, out var dimension); + return dimension; + } + + // --- 查找实体 (现在维度感知) --- + /// + /// 在指定维度中,根据派系键查找所有实体。 + /// + /// 维度的唯一标识符。 + /// 派系键。 + /// 指定派系下的实体列表,如果未找到则返回空列表。 + public LinkedList FindEntitiesByFaction(string dimensionId, string factionKey) + { + if (_dimensionFactionEntities.TryGetValue(dimensionId, out var factionDict)) + { + if (factionDict.TryGetValue(factionKey, out var entities)) { - faction.Value.Remove(entityToRemove); - Destroy(entityToRemove.gameObject); + return entities; } } - if (pendingAdditions.Any()) - { - foreach (var entity in pendingAdditions) - { - if (!factionEntities.ContainsKey(entity.Item1)) - { - factionEntities[entity.Item1] = new LinkedList(); - } + return new LinkedList(); // 如果未找到,返回一个空列表 + } - factionEntities[entity.Item1].AddLast(entity.Item2); + // --- Tick 方法 (现在维度感知) --- + public void Tick() + { + // 遍历每个活跃的维度 + foreach (var dimensionEntry in _dimensionFactionEntities.ToList()) // ToList() 避免在迭代时修改字典 + { + var dimensionId = dimensionEntry.Key; + var factionDict = dimensionEntry.Value; + + // 检查维度对象本身是否仍然活跃在场景中 + // 注意:这里检查的是 _activeDimensions,确保 Dimension 对象实例仍然存在且被管理器追踪。 + if (!_activeDimensions.ContainsKey(dimensionId)) + { + Debug.LogWarning( + $"Skipping Tick for dimension '{dimensionId}' as its Dimension object is no longer active. Clearing its entities."); + _dimensionFactionEntities.Remove(dimensionId); // 移除已失效维度的实体数据 + _dimensionLayerCache.Remove(dimensionId); // 移除已失效维度的层级缓存 + continue; } - pendingAdditions.Clear(); + foreach (var faction in factionDict) + { + var entitiesToRemove = new List(); + var currentEntities = faction.Value.ToList(); // 创建副本以避免在迭代时修改列表 + + foreach (var entityPrefab in currentEntities) + { + // 检查实体预制体或其内部实体是否已销毁或死亡 + if (entityPrefab == null || entityPrefab.entity == null || entityPrefab.entity.IsDead) + { + entitiesToRemove.Add(entityPrefab); + } + else + { + ITick itike = entityPrefab.entity; + itike.Tick(); + } + } + + // 删除所有标记为死亡的实体 + foreach (var entityToRemove in entitiesToRemove) + { + faction.Value.Remove(entityToRemove); + if (entityToRemove != null) // 确保它没有被外部销毁 + { + Destroy(entityToRemove.gameObject); + } + } + } + } + + // 处理待添加实体 (现在维度感知) + if (_pendingAdditions.Any()) + { + foreach (var pending in _pendingAdditions) + { + var dimensionId = pending.Item1; + var factionKey = pending.Item2; + var entityPrefab = pending.Item3; + + // 再次检查维度是否活跃,防止在添加前维度被注销 + if (!_dimensionFactionEntities.ContainsKey(dimensionId)) + { + Debug.LogError( + $"Attempted to add entity '{entityPrefab.name}' to unregistered or inactive dimension '{dimensionId}'. Entity will be destroyed."); + if (entityPrefab != null) Destroy(entityPrefab.gameObject); + continue; + } + + var factionDict = _dimensionFactionEntities[dimensionId]; + if (!factionDict.ContainsKey(factionKey)) + { + factionDict[factionKey] = new LinkedList(); + } + + factionDict[factionKey].AddLast(entityPrefab); + } + + _pendingAdditions.Clear(); } } /// /// 根据给定的Def生成实体对象(内部通用方法)。 /// + /// 实体所属的维度ID。 /// 要实例化的预制体 - /// 生成的父级Transform /// 生成位置 /// 实体定义对象 /// 额外的初始化操作(如子弹方向设置) /// 成功时返回EntityPrefab组件,失败时返回null private EntityPrefab GenerateEntityInternal( + string dimensionId, // 新增参数:维度ID GameObject prefab, - Transform parent, Vector3 pos, - Data.EntityDef def, // 所有Def类型需继承自BaseDef + Data.EntityDef def, Action extraInit = null) { + // 验证维度是否活跃 + if (!_activeDimensions.TryGetValue(dimensionId, out var dimension)) + { + Debug.LogError($"Cannot generate entity: Dimension '{dimensionId}' is not active or registered."); + return null; + } + + // 获取或创建实体所属的层级Transform,并确保其在维度根下 + var parentLayer = EnsureLayerExists(dimensionId, "DefaultEntityLevel"); // 使用一个默认层级名称 + if (parentLayer == null) + { + Debug.LogError($"Failed to get or create parent layer for entity in dimension '{dimensionId}'."); + return null; + } + GameObject instantiatedEntity = null; try { - // 实例化实体 - instantiatedEntity = Instantiate(prefab, pos, Quaternion.identity, parent); + // 实例化实体,并将其父级设置为维度下的层级Transform + instantiatedEntity = Instantiate(prefab, pos, Quaternion.identity, parentLayer); - // 获取并验证EntityPrefab组件 var entityComponent = instantiatedEntity.GetComponent(); if (!entityComponent) { @@ -106,204 +275,231 @@ namespace Managers $"EntityPrefab component missing on: {instantiatedEntity.name}"); } - // 初始化核心数据 entityComponent.Init(def); - - // 执行类型特有的额外初始化 extraInit?.Invoke(entityComponent); - // 管理派系列表 - var factionKey = def.attributes.defName ?? "default"; - pendingAdditions.Add(Tuple.Create(factionKey, entityComponent)); + var factionKey = def.attributes.defName ?? "default"; // 假设 attributes.defName 是派系键 + _pendingAdditions.Add(Tuple.Create(dimensionId, factionKey, entityComponent)); // 添加维度ID return entityComponent; } catch (System.Exception ex) { - // 清理失败实例 if (instantiatedEntity) Destroy(instantiatedEntity); - - Debug.LogError($"Entity generation failed: {ex.Message}\n{ex.StackTrace}"); + Debug.LogError($"Entity generation failed in dimension '{dimensionId}': {ex.Message}\n{ex.StackTrace}"); return null; } } /// - /// 动态创建层(如果层不存在) + /// 动态创建层(如果层不存在),现在是维度感知的。 + /// 每个维度有自己的层级结构,根在 Dimension.DimensionRoot 下。 /// - private Transform EnsureLayerExists(string layerName) + /// 维度的唯一标识符。 + /// 要确保存在的层级名称。 + /// 层级Transform,如果维度不存在或其根Transform为空则返回null。 + private Transform EnsureLayerExists(string dimensionId, string layerName) { - // 先从缓存中查找 - if (layerCache.TryGetValue(layerName, out var layerTransform)) + // 尝试从维度层级缓存中获取 + if (!_dimensionLayerCache.TryGetValue(dimensionId, out var layerCacheForDimension)) + { + Debug.LogError( + $"Dimension '{dimensionId}' not found in layer cache. This should not happen if dimension is registered."); + return null; + } + + if (layerCacheForDimension.TryGetValue(layerName, out var layerTransform)) { return layerTransform; } - // 如果缓存中没有,尝试通过 transform.Find 查找 - layerTransform = transform.Find(layerName); + // 如果缓存中没有,尝试在维度根下查找 + var dimension = GetDimension(dimensionId); + if (dimension == null || dimension.DimensionRoot == null) + { + Debug.LogError( + $"Dimension '{dimensionId}' or its root transform is null. Cannot create layer '{layerName}'."); + return null; + } + + layerTransform = dimension.DimensionRoot.Find(layerName); if (!layerTransform) { - // 如果层不存在,动态创建 + // 如果层不存在,动态创建并将其父级设置为维度的根Transform var layerObject = new GameObject(layerName); layerTransform = layerObject.transform; + layerTransform.SetParent(dimension.DimensionRoot); } // 将新创建的层加入缓存 - layerCache[layerName] = layerTransform; - + layerCacheForDimension[layerName] = layerTransform; return layerTransform; } + // --- 公共生成方法 (现在维度感知) --- /// - /// 根据PawnDef生成普通实体 + /// 在指定维度中,根据PawnDef生成普通实体。 /// - public void GenerateEntity(Data.EntityDef entityDef, Vector3 pos) + public void GenerateEntity(string dimensionId, Data.EntityDef entityDef, Vector3 pos) { - // 验证关键参数 if (!characterPrefab) { - Debug.LogError("entityPrefab is null! Assign a valid prefab."); - GenerateDefaultEntity(pos); + Debug.LogError("characterPrefab is null! Assign a valid prefab."); + GenerateDefaultEntity(dimensionId, pos); return; } if (entityDef == null) { Debug.LogError("EntityDef is null! Cannot generate entity."); - GenerateDefaultEntity(pos); + GenerateDefaultEntity(dimensionId, pos); return; } - // 确保层存在 - var entityLevelTransform = EnsureLayerExists("EntityLevel"); - - // 调用通用生成逻辑 var result = GenerateEntityInternal( + dimensionId, characterPrefab.gameObject, - entityLevelTransform, pos, entityDef ); - - if (!result) GenerateDefaultEntity(pos); + if (!result) GenerateDefaultEntity(dimensionId, pos); } /// - /// 生成建筑实体(位置使用Vector3Int) + /// 在指定维度中,生成建筑实体(位置使用Vector3Int)。 /// - public void GenerateBuildingEntity(Data.BuildingDef buildingDef, Vector3Int pos) + public void GenerateBuildingEntity(string dimensionId, Data.BuildingDef buildingDef, Vector3Int pos) { - // 修正:检查正确的预制体 (buildingPrefab) if (!buildingPrefab) { Debug.LogError("buildingPrefab is null! Assign a valid prefab."); - GenerateDefaultEntity(pos); + GenerateDefaultEntity(dimensionId, pos); return; } if (buildingDef == null) { Debug.LogError("BuildingDef is null! Cannot generate building."); - GenerateDefaultEntity(pos); + GenerateDefaultEntity(dimensionId, pos); return; } var worldPos = new Vector3(pos.x, pos.y, pos.z); - - // 确保层存在 - var buildingLevelTransform = EnsureLayerExists("BuildingLevel"); - var result = GenerateEntityInternal( + dimensionId, buildingPrefab.gameObject, - buildingLevelTransform, worldPos, buildingDef ); - - if (!result) GenerateDefaultEntity(worldPos); + if (!result) GenerateDefaultEntity(dimensionId, worldPos); } /// - /// 生成子弹实体(含方向设置) + /// 在指定维度中,生成子弹实体(含方向设置)。 /// - public void GenerateBulletEntity(Data.BulletDef bulletDef, Vector3 pos, Vector3 dir, + public void GenerateBulletEntity(string dimensionId, Data.BulletDef bulletDef, Vector3 pos, Vector3 dir, Entity.Entity source = null) { - // 修正:检查正确的预制体 (bulletPrefab) if (!bulletPrefab) { Debug.LogError("bulletPrefab is null! Assign a valid prefab."); - GenerateDefaultEntity(pos); + GenerateDefaultEntity(dimensionId, pos); return; } if (bulletDef == null) { Debug.LogError("BulletDef is null! Cannot generate bullet."); - GenerateDefaultEntity(pos); + GenerateDefaultEntity(dimensionId, pos); return; } - // 确保层存在 - var bulletLevelTransform = EnsureLayerExists("BulletLevel"); - var result = GenerateEntityInternal( + dimensionId, bulletPrefab.gameObject, - bulletLevelTransform, pos, bulletDef, // 子弹特有的方向设置 entityComponent => entityComponent.entity.SetTarget(pos + dir) ); - if (result.entity is Bullet bullet) + + // 确保 result 不为 null 且 entity 是 Bullet 类型 + if (result != null && result.entity is Bullet bullet) { bullet.bulletSource = source; - if (source) bullet.affiliation = source.affiliation; + if (source != null) bullet.affiliation = source.affiliation; // 确保 source 不为 null } - if (!result) GenerateDefaultEntity(pos); + if (!result) GenerateDefaultEntity(dimensionId, pos); } /// - /// 生成默认实体(错误回退) + /// 在指定维度中,生成默认实体(错误回退)。 /// - public void GenerateDefaultEntity(Vector3 pos) + public void GenerateDefaultEntity(string dimensionId, Vector3 pos) { - // 确保层存在 - var entityLevelTransform = EnsureLayerExists("EntityLevel"); + if (!_activeDimensions.ContainsKey(dimensionId)) + { + Debug.LogError( + $"Cannot generate default entity: Dimension '{dimensionId}' is not active or registered."); + return; + } - var entity = Instantiate(defaultEntityPrefab, pos, Quaternion.identity, entityLevelTransform); + var parentLayer = EnsureLayerExists(dimensionId, "DefaultEntityLevel"); + if (parentLayer == null) + { + Debug.LogError($"Failed to get parent transform for default entity in dimension '{dimensionId}'."); + return; + } + + var entity = Instantiate(defaultEntityPrefab, pos, Quaternion.identity, parentLayer); var entityComponent = entity.GetComponent(); const string factionKey = "default"; - pendingAdditions.Add(Tuple.Create(factionKey, entityComponent)); - + _pendingAdditions.Add(Tuple.Create(dimensionId, factionKey, entityComponent)); entityComponent.DefaultInit(); } - + + // --- 单例生命周期与场景切换处理 --- + protected override void OnStart() + { + SceneManager.sceneLoaded += OnSceneLoaded; + } + private void OnDestroy() { SceneManager.sceneLoaded -= OnSceneLoaded; } - protected override void OnStart() - { - SceneManager.sceneLoaded += OnSceneLoaded; - - } - + /// + /// 场景加载完成时的回调。 + /// 清理旧场景的实体数据,并重新扫描新场景中的维度。 + /// private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { - factionEntities.Clear(); - layerCache.Clear(); - pendingAdditions.Clear(); + _dimensionFactionEntities.Clear(); // 清理所有维度下的实体数据 + _dimensionLayerCache.Clear(); // 清理所有维度下的层级缓存 + _pendingAdditions.Clear(); // 清理待添加实体列表 + _activeDimensions.Clear(); // 清理活跃维度列表,因为旧场景的维度对象已被销毁 } + private void Start() { - var pre = Resources.Load("Default/DefaultEntity"); - defaultEntityPrefab = pre.GetComponent(); - layerCache.Clear(); + if (defaultEntityPrefab == null) + { + var pre = Resources.Load("Default/DefaultEntity"); + if (pre != null) + { + defaultEntityPrefab = pre.GetComponent(); + } + else + { + Debug.LogError( + "Failed to load DefaultEntity prefab from Resources/Default/DefaultEntity. Please ensure it exists at 'Assets/Resources/Default/DefaultEntity.prefab'."); + } + } } } + } \ No newline at end of file diff --git a/Client/Assets/Scripts/Managers/ItemResourceManager.cs b/Client/Assets/Scripts/Managers/ItemResourceManager.cs index 033b5c5..ab36783 100644 --- a/Client/Assets/Scripts/Managers/ItemResourceManager.cs +++ b/Client/Assets/Scripts/Managers/ItemResourceManager.cs @@ -2,46 +2,122 @@ using System.Collections.Generic; using System.Linq; using Data; using Item; +using UnityEngine; namespace Managers { - public class ItemResourceManager:Utils.Singleton + public class ItemResourceManager : Utils.Singleton { - //定义名,物品 - public Dictionary items; - - + private readonly Dictionary _items = new(); + private readonly Dictionary> _itemsByName = new(); // 保持按显示名称查找的字典 + public void Init() { - var itemDefs = Managers.DefineManager.Instance.QueryDefinesByType(); - if(itemDefs==null||itemDefs.Length==0) + var baseItemDefs = Managers.DefineManager.Instance.QueryDefinesByType(); + var weaponDefs = Managers.DefineManager.Instance.QueryDefinesByType(); + + var allDefs = new List(); + if (baseItemDefs != null) allDefs.AddRange(baseItemDefs); + if (weaponDefs != null) allDefs.AddRange(weaponDefs); + + if (allDefs.Count == 0) + { + Debug.LogWarning("ItemResourceManager: No ItemDef or WeaponDef found to initialize."); return; - foreach (var itemDef in itemDefs) - { - var item=new Item.ItemResource(); - item.name = itemDef.label; - item.description = itemDef.description; - - item.icon = Managers.PackagesImageManager.Instance.GetSprite(itemDef.texture); } + + foreach (var def in allDefs) + { + if (_items.ContainsKey(def.defName)) + { + Debug.LogError( + $"ItemResourceManager: Duplicate itemDef.defName found: {def.defName}. Skipping this item."); + continue; + } + + var itemIcon = Managers.PackagesImageManager.Instance.GetSprite(def.texture); + if (!itemIcon) + { + Debug.LogWarning( + $"ItemResourceManager: Failed to load sprite for texture '{def.texture}' for item '{def.defName}'. Icon will be null."); + } + + var itemName = string.IsNullOrEmpty(def.label) ? def.defName : def.label; + if (string.IsNullOrEmpty(def.label)) + { + Debug.LogWarning( + $"ItemResourceManager: ItemDef '{def.defName}' has an empty label. Using defName as item name."); + } + + var itemDescription = def.description ?? string.Empty; + + Item.ItemResource itemResource; + + if (def is WeaponDef currentWeaponDef) + { + itemResource = new Item.WeaponResource( + def.defName, // 传递 defName + itemName, + itemDescription, + itemIcon, + currentWeaponDef.rarity, + currentWeaponDef.maxStack, + currentWeaponDef.ssEquippable, + currentWeaponDef.attributes + ); + } + else + { + itemResource = new Item.ItemResource( + def.defName, // 传递 defName + itemName, + itemDescription, + itemIcon, + def.rarity, + def.maxStack, + def.ssEquippable + ); + } + + _items.Add(def.defName, itemResource); + + // 将物品添加到按显示名称查找的字典 (这里仍然使用 itemResource.Name,因为字典的目的是按显示名称查找) + if (!_itemsByName.ContainsKey(itemResource.Name)) + { + _itemsByName.Add(itemResource.Name, new List()); + } + + _itemsByName[itemResource.Name].Add(itemResource); + } + + Debug.Log($"ItemResourceManager: Initialized {_items.Count} items."); } - public ItemResource GetItem(string defName) + public Item.ItemResource GetItem(string defName) { - return items.GetValueOrDefault(defName,null); + return _items.GetValueOrDefault(defName, null); } - // - /// 按物品名称查找物品 - /// - /// 要查找的物品名称 - /// 找到的物品对象,如果未找到则返回 null - public ItemResource FindItemByName(string itemName) + + // FindItemByName 和 FindAllItemsByName 保持不变,因为它们是按显示名称查找的 + public Item.ItemResource FindItemByName(string itemName) { - if (string.IsNullOrEmpty(itemName)) - { - return null; - } - return items.Values.FirstOrDefault(item => item.name == itemName); + if (string.IsNullOrEmpty(itemName)) return null; + return _itemsByName.GetValueOrDefault(itemName)?.FirstOrDefault(); + } + + public List FindAllItemsByName(string itemName) + { + if (string.IsNullOrEmpty(itemName)) return new List(); + return _itemsByName.GetValueOrDefault(itemName, new List()); + } + + public void Clear() + { + _items.Clear(); + _itemsByName.Clear(); + Debug.Log("ItemResourceManager: All item resources cleared."); } } + + } \ No newline at end of file diff --git a/Client/Assets/Scripts/Map/Dimension.cs b/Client/Assets/Scripts/Map/Dimension.cs index c23b76e..ce77fd4 100644 --- a/Client/Assets/Scripts/Map/Dimension.cs +++ b/Client/Assets/Scripts/Map/Dimension.cs @@ -1,9 +1,114 @@ +using Managers; using UnityEngine; namespace Map { - public class Dimension:MonoBehaviour + /// + /// 表示游戏中的一个维度或场景区域。 + /// 实体管理器将根据此维度来组织和管理实体。 + /// + public class Dimension : MonoBehaviour { - + [SerializeField] private bool defaultOpen = false; + + [SerializeField] [Tooltip("此维度的唯一标识符。如果为空,将使用GameObject的名称。")] + private string _dimensionId; + + [SerializeField] public MapGenerator mapGenerator; + + /// + /// 获取此维度的唯一标识符。 + /// + public string DimensionId + { + get + { + if (string.IsNullOrEmpty(_dimensionId)) + { + _dimensionId = gameObject.name; // 如果未设置,默认使用GameObject名称 + Debug.LogWarning( + $"Dimension ID not explicitly set for {gameObject.name}. Using GameObject name as ID: {_dimensionId}", + this); + } + + return _dimensionId; + } + // 内部设置器,允许在Awake中初始化或编辑器中赋值 + private set => _dimensionId = value; + } + + /// + /// 此维度下所有实体的根Transform。用于组织场景层级。 + /// + public Transform DimensionRoot { get; private set; } + + private void Awake() + { + // 1. 确保 DimensionId 已初始化,这会触发 DimensionId 属性的 getter 逻辑 + var id = DimensionId; + // 2. 创建一个用于存放此维度下所有实体的根GameObject,方便管理 + var rootObj = new GameObject($"_Entities_{id}"); + rootObj.transform.SetParent(this.transform); // 将其作为Dimension对象的子对象 + DimensionRoot = rootObj.transform; + // 3. 注册此维度到 Program + if (Program.Instance != null) // 检查单例是否仍然存在 + { + Program.Instance.RegisterDimension(this); + } + else + { + Debug.LogError( + "[Dimension] Program.Instance is null during Dimension Awake. Cannot register dimension.", this); + } + + // 4. 注册此维度到 EntityManage + if (EntityManage.Instance != null) + { + EntityManage.Instance.RegisterDimension(this); + } + else + { + Debug.LogError( + "[Dimension] EntityManage.Instance is null during Dimension Awake. Cannot register dimension.", + this); + } + + // 5. 处理 defaultOpen 逻辑,设置Program的焦点维度 + // 确保在自身注册到 Program 之后再设置焦点,这样 Program 内部才能找到它 + if (defaultOpen && Program.Instance != null) + { + Program.Instance.SetFocusedDimension(_dimensionId); + // Program 现在会通过 SetFocusedDimension (string) 方法将 FocusedDimension 属性设置为这个 Dimension 实例。 + } + } + + private void OnDestroy() + { + // 当 Dimension 对象被销毁时(例如,场景卸载),从 Program 和 EntityManage 注销 + if (Program.Instance != null) // 检查单例是否仍然存在 + { + Program.Instance.UnregisterDimension(this); + } + else + { + // 在应用程序退出时,Program.Instance 可能已经为null,这通常是正常的,不打LogError + Debug.Log( + "[Dimension] Program.Instance is null during OnDestroy. Skipping unregister for Dimension: " + + DimensionId, this); + } + + if (EntityManage.Instance != null) // 检查单例是否仍然存在 + { + EntityManage.Instance.UnregisterDimension(this); + } + else + { + // 同上,但在清理阶段,这个可能也已为null,只打Log + Debug.Log( + "[Dimension] EntityManage.Instance is null during OnDestroy. Skipping unregister for Dimension: " + + DimensionId, this); + } + } } + } \ No newline at end of file diff --git a/Client/Assets/Scripts/Program.cs b/Client/Assets/Scripts/Program.cs index abb5b39..b45a0af 100644 --- a/Client/Assets/Scripts/Program.cs +++ b/Client/Assets/Scripts/Program.cs @@ -1,9 +1,134 @@ -using Logging; +using System.Collections.Generic; +using Logging; // Assuming 'Logging' is a standard or project-specific namespace. +using Map; // Assuming 'Map' is a standard or project-specific namespace. using UnityEngine; +using Utils; // Assuming 'Utils' is a standard or project-specific namespace. -public class Program : Utils.Singleton +/// +/// Program 类作为单例模式的核心管理器,负责维护和管理游戏或应用中的维度(Dimension)实例和焦点状态。 +/// 它提供了维度注册、注销、获取以及焦点维度设置的功能。 +/// +public class Program : Singleton { + /// + /// 当前聚焦的实体对象,可能为空。 + /// public Entity.Entity focusedEntity = null; - public bool needLoad = true; - -} \ No newline at end of file + + /// + /// 当前活跃焦点的维度唯一标识符,可能为空。 + /// + public string focuseDimensionId = null; + + /// + /// 当前聚焦的维度对象实例。当 不为空时,此属性指向对应的维度实例。 + /// + public Dimension FocusedDimension { get; private set; } + + /// + /// 维护所有已注册的维度实例的字典,键是维度的唯一标识符 (ID)。 + /// + private readonly Dictionary _registeredDimensions = new Dictionary(); + + /// + /// 获取所有已注册的维度。返回一个只读字典副本,防止外部直接修改。 + /// + public IReadOnlyDictionary RegisteredDimensions => _registeredDimensions; + + /// + /// 注册一个维度实例到 Program。 + /// + /// 要注册的维度实例。 + public void RegisterDimension(Dimension dimension) + { + if (dimension == null) + { + Debug.LogError("[Program] Attempted to register a null Dimension."); + return; + } + string id = dimension.DimensionId; + if (string.IsNullOrEmpty(id)) + { + Debug.LogError($"[Program] Attempted to register a Dimension with an empty or null ID: {dimension.name}"); + return; + } + if (_registeredDimensions.ContainsKey(id)) + { + Debug.LogWarning($"[Program] Dimension with ID '{id}' is already registered. Skipping duplicate registration for {dimension.name}."); + return; + } + _registeredDimensions.Add(id, dimension); + Debug.Log($"[Program] Dimension '{id}' registered successfully."); + + if (focuseDimensionId == id) + { + FocusedDimension = dimension; + Debug.Log($"[Program] FocusedDimension updated to '{id}'."); + } + } + + /// + /// 从 Program 注销一个维度实例。 + /// + /// 要注销的维度实例。 + public void UnregisterDimension(Dimension dimension) + { + if (dimension == null) + { + Debug.LogWarning("[Program] Attempted to unregister a null Dimension. Skipping."); + return; + } + string id = dimension.DimensionId; + if (_registeredDimensions.Remove(id)) + { + Debug.Log($"[Program] Dimension '{id}' unregistered successfully."); + + if (focuseDimensionId == id) + { + focuseDimensionId = null; + FocusedDimension = null; + Debug.Log($"[Program] Focused dimension '{id}' was unregistered and has been cleared."); + } + } + else + { + Debug.LogWarning($"[Program] Attempted to unregister Dimension '{id}' which was not found in the registered list."); + } + } + + /// + /// 根据ID获取一个已注册的维度。 + /// + /// 维度的唯一标识符。 + /// 对应的Dimension实例,如果未找到则返回null。 + public Dimension GetDimension(string dimensionId) + { + _registeredDimensions.TryGetValue(dimensionId, out Dimension dimension); + return dimension; + } + + /// + /// 设置当前聚焦的维度。 + /// + /// 要设置为焦点的维度的唯一标识符。如果传入null或空字符串,将清除当前焦点维度。 + public void SetFocusedDimension(string dimensionId) + { + if (string.IsNullOrEmpty(dimensionId)) + { + focuseDimensionId = null; + FocusedDimension = null; + Debug.Log("[Program] Focused dimension cleared."); + return; + } + if (_registeredDimensions.TryGetValue(dimensionId, out Dimension newFocusedDimension)) + { + focuseDimensionId = dimensionId; + FocusedDimension = newFocusedDimension; + Debug.Log($"[Program] Focused dimension set to '{dimensionId}'."); + } + else + { + Debug.LogWarning($"[Program] Attempted to set focus to unregistered dimension ID: '{dimensionId}'. Focus not changed."); + } + } +} diff --git a/Client/Assets/Scripts/Test/TestDefine.cs b/Client/Assets/Scripts/Test/TestDefine.cs deleted file mode 100644 index 55333bb..0000000 --- a/Client/Assets/Scripts/Test/TestDefine.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using Data; -using Managers; -using UnityEngine; - -namespace Test -{ - - - public class TestDefine : MonoBehaviour - { - public EntityManage entityManager; - void Awake() - { - Managers.DefineManager.Instance.Init(); - - } - - private void Start() - { - var chicken = Managers.DefineManager.Instance.FindDefine("testPawn"); - entityManager.GenerateEntity(chicken,Vector3.zero); - entityManager.GenerateDefaultEntity(Vector3.down); - Debug.Log(chicken); - } - - // Update is called once per frame - void Update() - { - - } - } - -} diff --git a/Client/Assets/Scripts/Test/TestDefine.cs.meta b/Client/Assets/Scripts/Test/TestDefine.cs.meta deleted file mode 100644 index 7d92609..0000000 --- a/Client/Assets/Scripts/Test/TestDefine.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 147e91e6929d90a4fb877c0b0a6b608c \ No newline at end of file diff --git a/Client/Assets/Scripts/UI/DevMenuUI.cs b/Client/Assets/Scripts/UI/DevMenuUI.cs index a098a1d..803bd8e 100644 --- a/Client/Assets/Scripts/UI/DevMenuUI.cs +++ b/Client/Assets/Scripts/UI/DevMenuUI.cs @@ -154,7 +154,7 @@ namespace UI { entityPlacementUI.currentAction = () => { - Managers.EntityManage.Instance.GenerateEntity(entityDef, Utils.MousePosition.GetWorldPosition()); + Managers.EntityManage.Instance.GenerateEntity(Program.Instance.focuseDimensionId,entityDef, Utils.MousePosition.GetWorldPosition()); }; entityPlacementUI.Prompt = $"当前生成器:\n名称:{entityDef.label}\n描述:{entityDef.description}"; entityPlacementUI.snapEnabled = false; @@ -165,7 +165,7 @@ namespace UI { entityPlacementUI.currentAction = () => { - Managers.EntityManage.Instance.GenerateBuildingEntity(def, Utils.MousePosition.GetSnappedWorldPosition()); + Managers.EntityManage.Instance.GenerateBuildingEntity(Program.Instance.focuseDimensionId,def, Utils.MousePosition.GetSnappedWorldPosition()); }; entityPlacementUI.Prompt = $"当前生成器:\n名称:{def.label}\n描述:{def.description}"; entityPlacementUI.snapEnabled = true;