(client)feat:视野范围检测,
This commit is contained in:
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ namespace Entity
|
||||
|
||||
public ProgressBarPrefab healthBarPrefab;
|
||||
|
||||
|
||||
public VisionRangeDetector visionRangeDetector;
|
||||
/// <summary>
|
||||
/// 人工智能行为树,定义实体的行为逻辑。
|
||||
/// </summary>
|
||||
@ -154,7 +154,7 @@ namespace Entity
|
||||
|
||||
[SerializeField] private float _hitBarUIShowTime = 5;
|
||||
private float _hitBarUIShowTimer = 0;
|
||||
private int _walkingTimer = 1;
|
||||
private int _walkingTimer = 0;
|
||||
|
||||
|
||||
/// <summary>
|
||||
@ -219,16 +219,17 @@ namespace Entity
|
||||
{
|
||||
targetObj = InitBodyPart(nodeDef, body); // 创建新对象
|
||||
}
|
||||
|
||||
|
||||
stateBodyNodes[orientation] = targetObj;
|
||||
|
||||
// 提取动画组件(安全处理空组件情况)
|
||||
// 逻辑修改:确保 stateAnimNodes[orientation] 总是被初始化为一个列表
|
||||
var animatorsForOrientation = new List<ITick>(); // 总是创建一个新的列表
|
||||
var animators = targetObj.GetComponentsInChildren<SpriteAnimator>();
|
||||
if (animators.Length > 0)
|
||||
{
|
||||
stateAnimNodes[orientation] = animators.Cast<ITick>().ToList();
|
||||
animatorsForOrientation.AddRange(animators.Cast<ITick>());
|
||||
}
|
||||
// 无动画组件时保持 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<ITick>(); // 如果没有找到动画,则使用空列表
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<ItemBase> items = new List<ItemBase>(); // 背包中的物品列表
|
||||
public Entity From { get; private set; }
|
||||
private readonly List<InventorySlot> _slots;
|
||||
public int Capacity { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 添加物品到背包
|
||||
/// </summary>
|
||||
/// <param name="item">要添加的物品</param>
|
||||
/// <param name="count">添加的数量</param>
|
||||
public void AddItem(ItemResource resource, int count)
|
||||
public event System.Action<Item.ItemResource, int> OnItemAdded;
|
||||
public event System.Action<Item.ItemResource, int> 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<InventorySlot>(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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从背包中取出物品
|
||||
/// </summary>
|
||||
/// <param name="itemName">物品名称</param>
|
||||
/// <param name="count">取出的数量</param>
|
||||
/// <returns>是否成功取出</returns>
|
||||
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<InventorySlot>();
|
||||
|
||||
// 从后往前遍历,以便安全地移除空槽位 (使用 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<InventorySlot> GetSlots()
|
||||
{
|
||||
return _slots.AsReadOnly();
|
||||
}
|
||||
|
||||
public int OccupiedSlotsCount => _slots.Count;
|
||||
public int AvailableSlotsCount => Capacity - _slots.Count;
|
||||
}
|
||||
}
|
78
Client/Assets/Scripts/Entity/InventorySlot.cs
Normal file
78
Client/Assets/Scripts/Entity/InventorySlot.cs
Normal file
@ -0,0 +1,78 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示背包中的一个槽位,存储特定类型的物品及其数量。
|
||||
/// </summary>
|
||||
public class InventorySlot
|
||||
{
|
||||
public Item.ItemResource Item { get; private set; } // 槽位中的物品资源
|
||||
public int Quantity { get; private set; } // 槽位中物品的数量
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的背包槽位。
|
||||
/// </summary>
|
||||
/// <param name="item">槽位中的物品资源。</param>
|
||||
/// <param name="quantity">初始数量。</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向槽位中添加指定数量的物品。
|
||||
/// </summary>
|
||||
/// <param name="amount">要添加的数量。</param>
|
||||
/// <returns>实际添加的数量。</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从槽位中移除指定数量的物品。
|
||||
/// </summary>
|
||||
/// <param name="amount">要移除的数量。</param>
|
||||
/// <returns>实际移除的数量。</returns>
|
||||
public int RemoveQuantity(int amount)
|
||||
{
|
||||
if (Item == null || amount <= 0) return 0;
|
||||
|
||||
var removed = Mathf.Min(amount, Quantity);
|
||||
Quantity -= removed;
|
||||
return removed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取槽位是否为空。
|
||||
/// </summary>
|
||||
public bool IsEmpty => Quantity <= 0;
|
||||
|
||||
/// <summary>
|
||||
/// 获取槽位是否已满。
|
||||
/// </summary>
|
||||
public bool IsFull => Item != null && Quantity >= Item.MaxStack;
|
||||
}
|
||||
}
|
3
Client/Assets/Scripts/Entity/InventorySlot.cs.meta
Normal file
3
Client/Assets/Scripts/Entity/InventorySlot.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c58fcd7fb7147ce82fe20e68cb7c8d4
|
||||
timeCreated: 1756031108
|
@ -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()
|
||||
|
188
Client/Assets/Scripts/Entity/VisionRangeDetector.cs
Normal file
188
Client/Assets/Scripts/Entity/VisionRangeDetector.cs
Normal file
@ -0,0 +1,188 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Data;
|
||||
using Managers;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Entity
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 视野范围检测器组件。
|
||||
/// 挂载到包含触发器碰撞体的GameObject上,用于检测视野范围内的Entity。
|
||||
/// </summary>
|
||||
public class VisionRangeDetector : MonoBehaviour
|
||||
{
|
||||
// 允许在Inspector中手动指定Collider,或在Awake时自动获取。
|
||||
[SerializeField] [Tooltip("用于检测视野范围的触发器碰撞体。如果未指定,将在Awake时自动获取。")]
|
||||
private Collider2D _collider;
|
||||
|
||||
// 缓存所属的Entity组件,用于获取派系信息。
|
||||
private Entity _ownerEntity;
|
||||
|
||||
// 将List改为HashSet,优化添加和移除的性能。
|
||||
private HashSet<Entity> _entitiesInRange = new HashSet<Entity>(); // 维护在视野范围内的Entity集合
|
||||
private Transform _myTransform; // 缓存Transform组件以提高性能
|
||||
|
||||
// 【逻辑修改】用于记录上次清理的帧数,避免在同一帧内重复清理。
|
||||
private int _lastCleanupFrame = -1;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_myTransform = transform; // 缓存自身Transform
|
||||
|
||||
// 1. 检查并确保GameObject上存在触发器碰撞体
|
||||
if (_collider == null)
|
||||
{
|
||||
_collider = GetComponent<Collider2D>();
|
||||
}
|
||||
|
||||
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<Entity>();
|
||||
if (_ownerEntity == null)
|
||||
{
|
||||
_ownerEntity = GetComponent<Entity>();
|
||||
}
|
||||
|
||||
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);
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// 【逻辑修改】在获取实体列表前,执行一次清理操作(每帧最多一次)。
|
||||
/// </summary>
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当有其他碰撞体进入触发器范围时调用。
|
||||
/// </summary>
|
||||
/// <param name="other">进入触发器的碰撞体。</param>
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
Entity entity = other.GetComponent<Entity>();
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当有其他碰撞体离开触发器范围时调用。
|
||||
/// </summary>
|
||||
/// <param name="other">离开触发器的碰撞体。</param>
|
||||
private void OnTriggerExit(Collider other)
|
||||
{
|
||||
Entity entity = other.GetComponent<Entity>();
|
||||
if (entity != null)
|
||||
{
|
||||
if (_entitiesInRange.Remove(entity))
|
||||
{
|
||||
// Debug.Log($"Entity '{entity.name}' (Affiliation: {entity.affiliation}) exited range. Total: {_entitiesInRange.Count}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取视野范围内最近的N个实体。
|
||||
/// </summary>
|
||||
/// <param name="n">要获取的实体数量。</param>
|
||||
/// <returns>最近的N个实体列表。</returns>
|
||||
public List<Entity> GetNearestNEntities(int n)
|
||||
{
|
||||
if (n <= 0) return new List<Entity>();
|
||||
|
||||
// 【逻辑修改】在获取前清理一次集合
|
||||
CleanEntitiesInRange();
|
||||
|
||||
return _entitiesInRange
|
||||
.OrderBy(e => Vector3.Distance(_myTransform.position, e.transform.position))
|
||||
.Take(n)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取视野范围内最近的N个指定关系的实体(敌对、友好、中立)。
|
||||
/// </summary>
|
||||
/// <param name="n">要获取的实体数量。</param>
|
||||
/// <param name="targetRelation">目标派系关系。</param>
|
||||
/// <returns>最近的N个指定关系的实体列表。</returns>
|
||||
public List<Entity> 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<Entity>();
|
||||
}
|
||||
|
||||
if (n <= 0) return new List<Entity>();
|
||||
|
||||
// 【逻辑修改】在获取前清理一次集合
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
3
Client/Assets/Scripts/Entity/VisionRangeDetector.cs.meta
Normal file
3
Client/Assets/Scripts/Entity/VisionRangeDetector.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e33d58195dd8450a9ec468ede64c8635
|
||||
timeCreated: 1756048545
|
Reference in New Issue
Block a user