(client) feat:实现掉落物,UI显示,维度
This commit is contained in:
@ -30,7 +30,7 @@ namespace Entity
|
||||
private void OnTriggerEnter2D(Collider2D other)
|
||||
{
|
||||
var entity = other.GetComponent<Entity>();
|
||||
if (!entity || entity == bulletSource) return;
|
||||
if (!entity || entity == bulletSource || entity is Pickup) return;
|
||||
if (Managers.AffiliationManager.Instance.GetRelation(bulletSource.affiliation, entity.affiliation) != Relation.Friendly)
|
||||
{
|
||||
entity.OnHit(this);
|
||||
|
@ -1,89 +1,110 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Data;
|
||||
using Item;
|
||||
using Managers;
|
||||
using UnityEngine;
|
||||
using Utils;
|
||||
// 添加 System 命名空间以使用 Action
|
||||
|
||||
namespace Entity
|
||||
{
|
||||
public class Character : Entity
|
||||
{
|
||||
// ------------- 新增 / 修改部分 START -------------
|
||||
private int _currentSelected; // 私有字段用于存储实际值
|
||||
|
||||
// 声明 Inventory 字段。更改为 private set 以确保 Inventory 实例只能在 Character 内部设置。
|
||||
public Inventory Inventory { get; private set; }
|
||||
/// <summary>
|
||||
/// 当前选中的背包槽位索引。
|
||||
/// 当此值被设置时,如果与旧值不同,将触发 OnCurrentSelectedChanged 事件。
|
||||
/// </summary>
|
||||
public int currentSelected
|
||||
{
|
||||
get => _currentSelected;
|
||||
set
|
||||
{
|
||||
var maxIndex = Inventory != null && Inventory.Capacity > 0 ? Inventory.Capacity - 1 : 0;
|
||||
var clampedValue = Mathf.Clamp(value, 0, maxIndex);
|
||||
|
||||
if (_currentSelected != clampedValue)
|
||||
{
|
||||
_currentSelected = clampedValue;
|
||||
OnCurrentSelectedChanged?.Invoke(_currentSelected); // 触发事件
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Inventory Inventory { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当当前选中的槽位索引改变时触发的事件。
|
||||
/// 参数:新的选中索引。
|
||||
/// </summary>
|
||||
public event Action<int> OnCurrentSelectedChanged;
|
||||
|
||||
public override void Init(EntityDef entityDef)
|
||||
{
|
||||
base.Init(entityDef);
|
||||
|
||||
// 1. 初始化背包:
|
||||
// 在这里创建并初始化 Inventory 实例。
|
||||
// 'this' 指的是当前 Character 实例,作为 Inventory 的拥有者。
|
||||
// 容量可以根据 entityDef 或一个默认值来设定。这里使用默认值 20。
|
||||
Inventory = new Inventory(this, 3);
|
||||
Inventory = new Inventory(this, 3);
|
||||
|
||||
// (可选) 可以在此订阅背包事件,以便 Character 对背包变化做出响应,
|
||||
// 例如更新角色状态、播放音效或更新UI。
|
||||
// Inventory.OnItemAdded += HandleItemAddedToInventory;
|
||||
// Inventory.OnItemRemoved += HandleItemRemovedFromInventory;
|
||||
// Inventory.OnInventoryChanged += HandleInventoryChanged;
|
||||
// 初始化 currentSelected。
|
||||
// 使用属性来设置,确保触发事件和范围检查。
|
||||
// 如果Inventory.Capacity为0,则currentSelected会被钳制到0。
|
||||
// 如果Inventory.Capacity为3,currentSelected=0是有效值。
|
||||
currentSelected = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试将指定物品添加到角色的背包中。
|
||||
/// 尝试将指定物品添加到角色的背包中。
|
||||
/// </summary>
|
||||
/// <param name="itemResource">要尝试添加的物品资源。</param>
|
||||
/// <param name="quantity">要尝试添加的数量。</param>
|
||||
/// <returns>未成功添加到背包的物品数量。
|
||||
/// 如果返回0,表示所有物品都成功添加;
|
||||
/// 如果返回quantity,表示未能添加任何物品;
|
||||
/// 如果返回一个介于0和quantity之间的值,表示部分物品被添加。</returns>
|
||||
public int TryPickupItem(Item.ItemResource itemResource, int quantity)
|
||||
/// <returns>
|
||||
/// 未成功添加到背包的物品数量。
|
||||
/// 如果返回0,表示所有物品都成功添加;
|
||||
/// 如果返回quantity,表示未能添加任何物品;
|
||||
/// 如果返回一个介于0和quantity之间的值,表示部分物品被添加。
|
||||
/// </returns>
|
||||
public int TryPickupItem(ItemResource itemResource, int quantity)
|
||||
{
|
||||
if (Inventory == null)
|
||||
{
|
||||
Debug.LogError($"Character '{this.name}' inventory is not initialized. Cannot pickup item.");
|
||||
Debug.LogError($"Character '{name}' inventory is not initialized. Cannot pickup item.");
|
||||
return quantity; // 如果背包未初始化,则视为未能添加任何物品
|
||||
}
|
||||
|
||||
// 直接调用 Inventory 实例的 AddItem 方法。
|
||||
// AddItem 方法已经包含了严谨的逻辑来处理物品堆叠、新槽位分配、容量检查以及无效输入的警告。
|
||||
int remainingQuantity = Inventory.AddItem(itemResource, quantity);
|
||||
|
||||
var remainingQuantity = Inventory.AddItem(itemResource, quantity);
|
||||
|
||||
return remainingQuantity;
|
||||
}
|
||||
|
||||
// (可选) 示例事件处理方法,用于展示如何响应背包事件
|
||||
// private void HandleItemAddedToInventory(Item.ItemResource item, int count)
|
||||
// {
|
||||
// Debug.Log($"{DefName} picked up {count} x {item.Name}. Current total: {Inventory.GetItemCount(item)}");
|
||||
// // 例如更新UI、播放音效、完成任务等
|
||||
// }
|
||||
|
||||
// private void HandleItemRemovedFromInventory(Item.ItemResource item, int count)
|
||||
// {
|
||||
// Debug.Log($"{DefName} removed {count} x {item.Name}. Current total: {Inventory.GetItemCount(item)}");
|
||||
// // 例如更新UI、播放音效、更新制作状态等
|
||||
// }
|
||||
|
||||
// private void HandleInventoryChanged()
|
||||
// {
|
||||
// Debug.Log($"{DefName}'s inventory changed. Occupied slots: {Inventory.OccupiedSlotsCount}/{Inventory.Capacity}");
|
||||
// // 例如通知背包UI刷新显示
|
||||
// }
|
||||
|
||||
// ------------- 新增 / 修改部分 END -------------
|
||||
|
||||
|
||||
public override void TryAttack()
|
||||
{
|
||||
if (IsAttacking)
|
||||
return;
|
||||
if (!Managers.DefineManager.Instance.defines.TryGetValue(nameof(BulletDef), out var def))
|
||||
if (!DefineManager.Instance.defines.TryGetValue(nameof(BulletDef), out var def))
|
||||
return;
|
||||
var buttonDef = def.Values.First();
|
||||
Vector3 dir = Utils.MousePosition.GetWorldPosition();
|
||||
Managers.EntityManage.Instance.GenerateBulletEntity(Program.Instance.focuseDimensionId,(BulletDef)buttonDef, Position,
|
||||
// 修正:First() 可能会在一个空的 Values 集合上抛出异常。
|
||||
// 更好的做法是使用 TryGetValue 或 FirstOrDefault 并检查结果。
|
||||
// 这里假设至少有一个 BulletDef 存在,如果不是,需要更复杂的错误处理。
|
||||
var bulletDefEntry = def.Values.FirstOrDefault();
|
||||
if (bulletDefEntry == null)
|
||||
{
|
||||
Debug.LogError("No BulletDef found in DefineManager. Cannot attack.");
|
||||
return;
|
||||
}
|
||||
|
||||
var bulletDef = (BulletDef)bulletDefEntry;
|
||||
|
||||
Vector3 dir = MousePosition.GetWorldPosition();
|
||||
EntityManage.Instance.GenerateBulletEntity(Program.Instance.FocusedDimensionId, bulletDef, Position,
|
||||
dir - Position, this);
|
||||
}
|
||||
|
||||
public override WeaponResource GetCurrentWeapon()
|
||||
{
|
||||
var currentSelectItem = Inventory.GetSlot(currentSelected);
|
||||
return (WeaponResource)currentSelectItem.Item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ using System.Linq;
|
||||
using AI;
|
||||
using Base;
|
||||
using Data;
|
||||
using Item;
|
||||
using Prefab;
|
||||
using UnityEngine;
|
||||
|
||||
@ -86,19 +87,19 @@ namespace Entity
|
||||
IsChase = true;
|
||||
currentJob = null;
|
||||
// 逻辑修改:只有当存在一个不同的焦点实体时,才将其PlayerControlled设为false
|
||||
if (Program.Instance.focusedEntity && Program.Instance.focusedEntity != this)
|
||||
if (Program.Instance.FocusedEntity && Program.Instance.FocusedEntity != this)
|
||||
{
|
||||
Program.Instance.focusedEntity.PlayerControlled = false;
|
||||
Program.Instance.FocusedEntity.PlayerControlled = false;
|
||||
}
|
||||
Program.Instance.focusedEntity = this;
|
||||
Program.Instance.SetFocusedEntity(this);
|
||||
}
|
||||
// 逻辑修改:确保只有当自身是焦点实体时才取消焦点,避免不必要的逻辑执行
|
||||
else if (Program.Instance.focusedEntity == this)
|
||||
else if (Program.Instance.FocusedEntity == this)
|
||||
{
|
||||
Program.Instance.focusedEntity = null;
|
||||
Program.Instance.SetFocusedEntity(null);
|
||||
}
|
||||
}
|
||||
get => Program.Instance.focusedEntity == this;
|
||||
get => Program.Instance.FocusedEntity == this;
|
||||
}
|
||||
|
||||
|
||||
@ -115,18 +116,27 @@ namespace Entity
|
||||
public bool IsDead => attributes.health <= 0;
|
||||
public bool IsShowingHealthBarUI=>_hitBarUIShowTimer > 0;
|
||||
public bool IsAttacking => _attackCoroutine != null;
|
||||
|
||||
/// <summary>
|
||||
/// 当实体受到伤害时触发的事件。
|
||||
/// 可以订阅此事件来响应实体的生命值变化,例如更新UI或播放受击特效。
|
||||
/// </summary>
|
||||
public event Action<EntityHitEventArgs> OnEntityHit;
|
||||
/// <summary>
|
||||
/// 当实体死亡时触发的事件。
|
||||
/// 只在实体首次进入死亡状态时触发一次。
|
||||
/// </summary>
|
||||
public event Action<Entity> OnEntityDied;
|
||||
private bool _warning = false;
|
||||
|
||||
/// <summary>
|
||||
/// 存储不同朝向下的动画节点集合。
|
||||
/// </summary>
|
||||
public Dictionary<EntityState, Dictionary<Orientation, List<ITick>>> bodyAnimationNode = new();
|
||||
private List<ITick> _currentAnimatorCache=new ();
|
||||
public Dictionary<EntityState, Dictionary<Orientation, ITick[]>> bodyAnimationNode = new();
|
||||
private ITick[] _currentAnimatorCache;
|
||||
/// <summary>
|
||||
/// 存储不同朝向下的身体节点对象。
|
||||
/// </summary>
|
||||
private Dictionary<EntityState, Dictionary<Orientation,GameObject>> bodyNodes = new();
|
||||
protected Dictionary<EntityState, Dictionary<Orientation,GameObject>> bodyNodes = new();
|
||||
|
||||
/// <summary>
|
||||
/// 当前实体的朝向。
|
||||
@ -186,7 +196,7 @@ namespace Entity
|
||||
foreach (var state in states)
|
||||
{
|
||||
bodyNodes.TryAdd(state, new Dictionary<Orientation, GameObject>());
|
||||
bodyAnimationNode.TryAdd(state, new Dictionary<Orientation, List<ITick>>());
|
||||
bodyAnimationNode.TryAdd(state, new Dictionary<Orientation, ITick[]>());
|
||||
}
|
||||
|
||||
// 主初始化逻辑
|
||||
@ -229,7 +239,7 @@ namespace Entity
|
||||
{
|
||||
animatorsForOrientation.AddRange(animators);
|
||||
}
|
||||
stateAnimNodes[orientation] = animatorsForOrientation;
|
||||
stateAnimNodes[orientation] = animatorsForOrientation.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
@ -472,7 +482,7 @@ namespace Entity
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentAnimatorCache = new List<ITick>(); // 如果没有找到动画,则使用空列表
|
||||
_currentAnimatorCache = new ITick[]{}; // 如果没有找到动画,则使用空列表
|
||||
}
|
||||
}
|
||||
|
||||
@ -487,19 +497,38 @@ namespace Entity
|
||||
SetBodyTexture(EntityState.Walking,_currentOrientation);
|
||||
_walkingTimer = 2;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 处理实体受到攻击的逻辑。
|
||||
/// </summary>
|
||||
/// <param name="from">攻击来源实体。</param>
|
||||
public virtual void OnHit(Entity from)
|
||||
{
|
||||
// MODIFIED: 收到攻击时触发回调
|
||||
if (IsDead) // 如果已经死亡,则不再处理伤害 nor 触发事件
|
||||
{
|
||||
return;
|
||||
}
|
||||
var hit = from.attributes.attack - attributes.defense;
|
||||
if (hit < 0)
|
||||
hit = from.attributes.attack / 100;
|
||||
// 确保伤害不为负,最小为0
|
||||
hit = Mathf.Max(0, hit);
|
||||
attributes.health -= hit;
|
||||
var wasFatal = IsDead; // 检查这次攻击是否导致实体死亡
|
||||
// 触发 OnEntityHit 事件
|
||||
OnEntityHit?.Invoke(new EntityHitEventArgs(
|
||||
this, from, hit, attributes.health, entityDef.attributes.health, wasFatal));
|
||||
currentJob?.StopJob();
|
||||
ShowHealthBar();
|
||||
if (wasFatal)
|
||||
{
|
||||
// 如果是首次死亡,则触发 OnEntityDied 事件
|
||||
// MODIFIED: 停止所有活动,包括当前工作
|
||||
currentJob = null; // 清除当前工作
|
||||
OnEntityDied?.Invoke(this);
|
||||
}
|
||||
|
||||
ShowHealthBar(); // 无论是否死亡,都更新血条UI
|
||||
}
|
||||
|
||||
public void ShowHealthBar()
|
||||
@ -523,7 +552,17 @@ namespace Entity
|
||||
/// </summary>
|
||||
public virtual void Kill()
|
||||
{
|
||||
attributes.health = 0;
|
||||
if (IsDead)
|
||||
{
|
||||
return;
|
||||
}
|
||||
attributes.health = 0; // 直接设置生命值为0
|
||||
// MODIFIED: 停止所有活动,包括当前工作
|
||||
currentJob?.StopJob();
|
||||
currentJob = null; // 清除当前工作
|
||||
// 触发 OnEntityDied 事件
|
||||
OnEntityDied?.Invoke(this);
|
||||
ShowHealthBar();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -684,5 +723,10 @@ namespace Entity
|
||||
attackCount--;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual WeaponResource GetCurrentWeapon()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
25
Client/Assets/Scripts/Entity/EntityHitEventArgs.cs
Normal file
25
Client/Assets/Scripts/Entity/EntityHitEventArgs.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System;
|
||||
|
||||
namespace Entity
|
||||
{
|
||||
public class EntityHitEventArgs : EventArgs
|
||||
{
|
||||
public Entity AttackedEntity { get; } // 被攻击的实体
|
||||
public Entity AttackerEntity { get; } // 攻击者
|
||||
public int DamageDealt { get; } // 这次攻击造成的实际伤害
|
||||
public float CurrentHealth { get; } // 攻击后的当前生命值
|
||||
public float MaxHealth { get; } // 最大生命值
|
||||
public bool WasFatal { get; } // 这次攻击是否致命(导致死亡)
|
||||
|
||||
public EntityHitEventArgs(Entity attacked, Entity attacker, int damage, float currentHealth, float maxHealth, bool wasFatal)
|
||||
{
|
||||
AttackedEntity = attacked;
|
||||
AttackerEntity = attacker;
|
||||
DamageDealt = damage;
|
||||
CurrentHealth = currentHealth;
|
||||
MaxHealth = maxHealth;
|
||||
WasFatal = wasFatal;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
3
Client/Assets/Scripts/Entity/EntityHitEventArgs.cs.meta
Normal file
3
Client/Assets/Scripts/Entity/EntityHitEventArgs.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5d160d50370b46b299421e6c05a83beb
|
||||
timeCreated: 1756199871
|
@ -142,6 +142,25 @@ namespace Entity
|
||||
{
|
||||
return _slots.AsReadOnly();
|
||||
}
|
||||
public InventorySlot GetSlot(Item.ItemResource itemResource)
|
||||
{
|
||||
if (itemResource == null) return null;
|
||||
|
||||
foreach (var slot in _slots)
|
||||
{
|
||||
if (slot.Item != null && slot.Item.DefName == itemResource.DefName)
|
||||
{
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
public InventorySlot GetSlot(int i)
|
||||
{
|
||||
i = Mathf.Clamp(i, 0, Capacity - 1);
|
||||
return _slots[i];
|
||||
}
|
||||
|
||||
public int OccupiedSlotsCount => _slots.Count;
|
||||
public int AvailableSlotsCount => Capacity - _slots.Count;
|
||||
|
@ -116,7 +116,7 @@ namespace Entity
|
||||
private void BecomeDefault()
|
||||
{
|
||||
entity.Kill();
|
||||
EntityManage.Instance.GenerateDefaultEntity(Program.Instance.focuseDimensionId,entity.Position);
|
||||
EntityManage.Instance.GenerateDefaultEntity(Program.Instance.FocusedDimensionId,entity.Position);
|
||||
}
|
||||
|
||||
private void StartControl()
|
||||
|
@ -1,6 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Base;
|
||||
using Data;
|
||||
using Item;
|
||||
using Prefab;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Entity
|
||||
@ -11,6 +15,48 @@ namespace Entity
|
||||
protected override void AutoBehave()
|
||||
{
|
||||
}
|
||||
|
||||
public override void SetTarget(Vector3 pos)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void Init(ItemDef itemDef)
|
||||
{
|
||||
itemResource = Managers.ItemResourceManager.Instance.GetItem(itemDef.defName);
|
||||
// 预缓存枚举值(避免每次循环重复调用 Enum.GetValues)
|
||||
var states = Enum.GetValues(typeof(EntityState)).Cast<EntityState>().ToArray();
|
||||
// 预初始化字典结构(减少内层循环的字典检查)
|
||||
foreach (var state in states)
|
||||
{
|
||||
bodyNodes.TryAdd(state, new Dictionary<Orientation, GameObject>());
|
||||
bodyAnimationNode.TryAdd(state, new Dictionary<Orientation, ITick[]>());
|
||||
}
|
||||
|
||||
var texture = itemResource.Icon;
|
||||
if (texture.Count == 1)
|
||||
{
|
||||
var imageObj = Instantiate(imagePrefab.gameObject, body.transform);
|
||||
imageObj.transform.localPosition = Vector3.zero;
|
||||
bodyNodes[EntityState.Idle][Orientation.Down] = imageObj;
|
||||
var image = imageObj.GetComponent<ImagePrefab>();
|
||||
image.SetSprite(texture[0]);
|
||||
}
|
||||
else if (texture.Count > 1)
|
||||
{
|
||||
var animatorObj = Instantiate(animatorPrefab.gameObject, body.transform);
|
||||
animatorObj.transform.localPosition = Vector3.zero;
|
||||
bodyNodes[EntityState.Idle][Orientation.Down] = animatorObj;
|
||||
var animator = animatorObj.GetComponent<SpriteAnimator>();
|
||||
animator.SetSprites(texture.ToArray());
|
||||
ITick[] ticks = { animator };
|
||||
|
||||
bodyAnimationNode[EntityState.Idle][Orientation.Down] = ticks;
|
||||
}
|
||||
|
||||
SetBodyTexture(EntityState.Idle, Orientation.Down);
|
||||
}
|
||||
|
||||
private void OnTriggerEnter2D(Collider2D other)
|
||||
{
|
||||
var entity = other.GetComponent<Character>();
|
||||
|
Reference in New Issue
Block a user