(client) feat:实现技能树界面,实现地图生成器,实现维度指定,实现规则瓦片定义,实现逃跑逻辑,实现消息定义,实现武器动画,实现受击动画 fix: 修复单攻击子弹击中多个目标,修复人物属性计算错误 (#56)

Co-authored-by: m0_75251201 <m0_75251201@noreply.gitcode.com>
Reviewed-on: http://47.107.252.169:3000/Roguelite-Game-Developing-Team/Gen_Hack-and-Slash-Roguelite/pulls/56
This commit is contained in:
2025-09-19 08:26:54 +08:00
parent 78849e0cc5
commit 87a8abe86c
282 changed files with 19364 additions and 8824 deletions

View File

@@ -15,11 +15,13 @@ namespace Entity
public Attributes(AttributesDef def)
{
if (def == null)
return;
health = def.health;
moveSpeed = def.moveSpeed;
attack = def.attack;
defense = def.defense;
attackSpeed = def.attackSpeed;
attackSpeed = MathF.Max(0.0001f,def.attackSpeed);
attackRange = def.attackRange;
attackTargetCount = def.attackTargetCount;
}

View File

@@ -2,7 +2,7 @@ using UnityEngine;
namespace Entity
{
public class Building : Entity
public class Building : CombatantEntity
{
public override void SetTarget(Vector3 pos)
{

View File

@@ -6,13 +6,26 @@ namespace Entity
{
public class Bullet : Entity
{
public Entity bulletSource;
public Entity bulletSource { get; private set; }
public float lifeTime = 10;
public void SetBulletSource(Entity source)
{
bulletSource = source;
attributes.attack = source.attributes.attack;
var weapon = source.GetCurrentWeapon();
if (weapon != null)
{
lifeTime = weapon.Attributes.attackRange / attributes.moveSpeed;
}
}
public override void SetTarget(Vector3 pos)
{
base.SetTarget(pos);
RotateTransformToDirection(transform, direction);
Utils.RotateTool.RotateTransformToDirection(transform, direction);
}
protected override void AutoBehave()
@@ -28,12 +41,8 @@ namespace Entity
private void OnTriggerEnter2D(Collider2D other)
{
var entity = other.GetComponent<Entity>();
if (!entity || entity == bulletSource || entity is Pickup) return;
if (Managers.AffiliationManager.Instance.GetRelation(bulletSource.affiliation, entity.affiliation) != Relation.Friendly)
{
entity.OnHit(this);
}
else if (Setting.Instance.CurrentSettings.friendlyFire)
if (IsDead||!entity || entity == bulletSource || entity is Pickup) return;
if (Managers.AffiliationManager.Instance.GetRelation(bulletSource.affiliation, entity.affiliation) != Relation.Friendly || Setting.Instance.CurrentSettings.friendlyFire)
{
entity.OnHit(this);
}
@@ -41,22 +50,10 @@ namespace Entity
{
return; // 如果是友好关系且不允许友军伤害,则不处理
}
attributes.health -= 1;
}
// 旋转对象到指定方向
public static void RotateTransformToDirection(Transform transform, Vector3 targetDirection)
{
// 确保目标方向不是零向量
if (targetDirection == Vector3.zero)
return;
// 计算当前向上方向与目标方向之间的角度
var angle = Mathf.Atan2(targetDirection.y, targetDirection.x) * Mathf.Rad2Deg;
// 应用旋转
transform.rotation = Quaternion.Euler(0f, 0f, angle);
}
}
}

View File

@@ -7,6 +7,24 @@ namespace Entity
{
public class Character : LivingEntity
{
public override Attributes defAttributes
{
get
{
var def = base.defAttributes;
var weaponDef = GetCurrentWeapon()?.Attributes;
if (weaponDef != null)
{
weaponDef.health = def.health;
weaponDef.moveSpeed = def.moveSpeed;
return weaponDef;
}
return def;
}
}
private int _currentSelected; // 私有字段用于存储实际值
/// <summary>
@@ -30,15 +48,10 @@ namespace Entity
public override void Init(EntityDef entityDef)
{
base.Init(entityDef);
Inventory = new Inventory(this, 3);
// 初始化 currentSelected。
// 使用属性来设置,确保触发事件和范围检查。
// 如果Inventory.Capacity为0则currentSelected会被钳制到0。
// 如果Inventory.Capacity为3currentSelected=0是有效值。
Inventory.OnInventoryChanged += InventoryChange;
CurrentSelected = 0;
base.Init(entityDef);
}
/// <summary>
@@ -59,6 +72,7 @@ namespace Entity
Debug.LogError($"Character '{name}' inventory is not initialized. Cannot pickup item.");
return quantity; // 如果背包未初始化,则视为未能添加任何物品
}
var remainingQuantity = Inventory.AddItem(itemResource, quantity);
@@ -68,7 +82,12 @@ namespace Entity
public override WeaponResource GetCurrentWeapon()
{
var currentSelectItem = Inventory.GetSlot(CurrentSelected);
return (WeaponResource)currentSelectItem?.Item;
return currentSelectItem?.Item as WeaponResource;
}
private void InventoryChange()
{
InitWeaponAnimator();
}
}
}

View File

@@ -0,0 +1,7 @@
namespace Entity
{
public class CombatantEntity:Entity
{
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 08de955a61604302a4d1b9d2c7649128
timeCreated: 1756961694

View File

@@ -9,6 +9,7 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Utils;
namespace Entity
@@ -37,7 +38,8 @@ namespace Entity
/// </summary>
public GameObject weaponAnimator;
public ITick[] weaponAnimatorNodeList;
public SpriteAnimator weaponItem;
public SpriteAnimator weaponAttackAnimation;
/// <summary>
/// 人工智能行为树,定义实体的行为逻辑。
@@ -49,15 +51,26 @@ namespace Entity
/// </summary>
public JobBase currentJob;
private Attributes _attribute;
/// <summary>
/// 实体的属性定义,包括生命值、攻击力、防御力等。
/// </summary>
public virtual Attributes attributes { get; protected set; }
private Attributes _baseAttributes;
public virtual Attributes baseAttributes
public virtual Attributes attributes
{
get { return _baseAttributes ??= new Attributes(entityDef.attributes); }
get { return _attribute ??= new Attributes(baseAttributes); }
protected set => _attribute = value;
}
public virtual Attributes baseAttributes => defAttributes;
private Attributes _defAttributes;
public virtual Attributes defAttributes
{
get
{
return _defAttributes ??= entityDef == null ? new Attributes() : new Attributes(entityDef.attributes);
}
}
/// <summary>
@@ -65,6 +78,8 @@ namespace Entity
/// </summary>
public Vector3 direction;
public Vector3 attackDirection;
/// <summary>
/// 实体的身体部分,用于挂载动画和图像节点。
/// </summary>
@@ -79,11 +94,7 @@ namespace Entity
/// 表示实体是否可以被选择。
/// </summary>
public bool canSelect = true;
/// <summary>
/// 表示实体是否处于追逐状态(影响移动速度)。
/// </summary>
public bool IsChase { set; get; } = true;
public string currentDimensionId = null;
@@ -98,7 +109,6 @@ namespace Entity
{
if (value)
{
IsChase = true;
currentJob = null;
// 逻辑修改只有当存在一个不同的焦点实体时才将其PlayerControlled设为false
if (Program.Instance.FocusedEntity && Program.Instance.FocusedEntity != this)
@@ -131,7 +141,10 @@ namespace Entity
public bool IsDead => attributes.health <= 0;
public bool IsShowingHealthBarUI => _hitBarUIShowTimer > 0;
public bool IsAttacking => _attackCoroutine != null;
public bool IsAttacking => _attackTimer > 0;
private float _attackTimer = 0;
private float _attackDetectionTime = 0;
private WeaponResource currentAttackWeapon;
/// <summary>
/// 当实体受到伤害时触发的事件。
@@ -146,16 +159,9 @@ namespace Entity
public event Action<Entity> OnEntityDied;
private bool _warning = false;
/// <summary>
/// 存储不同朝向下的动画节点集合。
/// </summary>
public Dictionary<EntityState, Dictionary<Orientation, ITick[]>> bodyAnimationNode = new();
private ITick[] _currentAnimatorCache;
private GameObject wearponAttackAnimationNodeRoot = null;
private ITick[] wearponAttackAnimationNodeList;
/// <summary>
/// 存储不同朝向下的身体节点对象。
@@ -171,9 +177,7 @@ namespace Entity
/// 当前实体的状态
/// </summary>
private EntityState _currentState = EntityState.Idle;
// 协程引用
private Coroutine _attackCoroutine;
[SerializeField] private float _hitBarUIShowTime = 5;
@@ -199,22 +203,25 @@ namespace Entity
protected virtual void InitWeaponAnimator()
{
if (!weaponAnimator)
return;
for (var i = 0; i < weaponAnimator.transform.childCount; i++)
if (weaponAnimator && weaponAnimator.transform.childCount > 0)
{
Destroy(weaponAnimator.transform.GetChild(i).gameObject);
weaponAnimatorNodeList = null;
foreach (Transform child in weaponAnimator.transform)
{
Destroy(child.gameObject);
}
}
var weapon = GetCurrentWeapon();
if (weapon == null)
{
weaponAnimator.SetActive(false);
var currentWeapon = GetCurrentWeapon();
if (currentWeapon?.AttackAnimation == null)
return;
}
weaponItem=GameObjectCreate.SpriteAnimator(currentWeapon.Icon.ToArray(), weaponAnimator.transform);
weaponItem.SetFPS(currentWeapon.FPS);
weaponItem.gameObject.SetActive(true);
var weaponAnimation = weapon.InstantiateAttackAnimation(weaponAnimator.transform);
weaponAnimatorNodeList = weaponAnimation.animationComponents;
weaponAttackAnimation = GameObjectCreate.SpriteAnimator(currentWeapon.AttackAnimation.ToArray(),
weaponAnimator.transform);
weaponAttackAnimation.SetFPS(currentWeapon.FPS);
weaponAttackAnimation.gameObject.SetActive(false);
}
/// <summary>
@@ -230,30 +237,26 @@ namespace Entity
foreach (var state in states)
{
bodyNodes.TryAdd(state, new Dictionary<Orientation, GameObject>());
bodyAnimationNode.TryAdd(state, new Dictionary<Orientation, ITick[]>());
}
// 主初始化逻辑
foreach (var state in states)
{
var stateBodyNodes = bodyNodes[state];
var stateAnimNodes = bodyAnimationNode[state];
foreach (var orientation in orientations)
{
// 获取节点定义(避免重复调用)
var nodeDef = drawingOrder.GetDrawNodeDef(state, orientation, out var original);
GameObject targetObj = null;
// --- 修改点一:处理空节点定义(增加默认精灵显示) ---
GameObject targetObj;
if (nodeDef == null)
{
if (imagePrefab != null && Managers.PackagesImageManager.Instance.defaultSprite != null)
if (imagePrefab && Managers.PackagesImageManager.Instance.defaultSprite != null)
{
// 实例化imagePrefab作为默认占位符
targetObj = Instantiate(imagePrefab.gameObject, body.transform);
targetObj.name = $"{state}_{orientation}_Default";
targetObj.transform.localPosition = Vector3.zero;
var imagePrefabCom = targetObj.GetComponent<ImagePrefab>();
if (imagePrefabCom != null)
if (imagePrefabCom)
{
imagePrefabCom.SetSprite(Managers.PackagesImageManager.Instance.defaultSprite);
}
@@ -261,14 +264,12 @@ namespace Entity
{
Debug.LogWarning(
$"InitBody: 默认ImagePrefab中无法获取ImagePrefab组件状态: {state}, 朝向: {orientation}");
// 降级为普通GameObject
targetObj = new GameObject { name = $"{state}_{orientation}_Empty" };
targetObj.transform.SetParent(body.transform, false);
}
}
else
{
// 如果没有imagePrefab或defaultSprite则创建空GameObject
targetObj = new GameObject { name = $"{state}_{orientation}_Empty" };
targetObj.transform.SetParent(body.transform, false);
}
@@ -282,29 +283,19 @@ namespace Entity
}
else
{
targetObj = InitBodyPart(nodeDef, body); // 创建新对象
targetObj = GameObjectCreate.InitBodyPart(nodeDef, body); // 创建新对象
}
}
if (targetObj != null)
if (targetObj)
{
stateBodyNodes[orientation] = targetObj;
// 逻辑说明:确保 stateAnimNodes[orientation] 总是被初始化为一个列表
var animatorsForOrientation = new List<ITick>(); // 总是创建一个新的列表
var animators = targetObj.GetComponentsInChildren<SpriteAnimator>();
if (animators.Length > 0)
{
animatorsForOrientation.AddRange(animators);
}
stateAnimNodes[orientation] = animatorsForOrientation.ToArray();
}
else
{
Debug.LogError($"InitBody: 无法为状态 {state}, 朝向 {orientation} 创建或找到有效的GameObject。");
stateBodyNodes[orientation] = new GameObject($"ErrorNode_{state}_{orientation}"); // 提供一个错误占位符
stateBodyNodes[orientation].transform.SetParent(body.transform, false);
stateAnimNodes[orientation] = Array.Empty<ITick>();
}
}
}
@@ -321,147 +312,7 @@ namespace Entity
SetBodyTexture(EntityState.Idle, Orientation.Down); // 激活默认朝向
}
/// <summary>
/// 递归初始化单个绘图节点及其子节点,具有更强的健壮性和错误处理。
/// </summary>
/// <param name="drawNode">绘图节点定义。</param>
/// <param name="parent">父节点对象。</param>
/// <returns>创建的GameObject如果失败则返回null</returns>
protected virtual GameObject InitBodyPart(DrawNodeDef drawNode, GameObject parent)
{
try
{
// 参数验证
if (drawNode == null)
{
Debug.LogWarning("InitBodyPart: drawNode参数为null");
return null;
}
if (parent == null)
{
Debug.LogWarning($"InitBodyPart: 父节点为null (节点名: {drawNode.nodeName})");
return null;
}
GameObject nodeObject = null;
// 根据纹理数量创建不同类型的节点
switch (drawNode.textures?.Count ?? 0)
{
case 0:
// 无纹理节点
nodeObject = new GameObject(drawNode.nodeName);
nodeObject.transform.SetParent(parent.transform, false);
break;
case 1:
// 单纹理节点
if (imagePrefab == null)
{
Debug.LogError($"InitBodyPart: imagePrefab未设置 (节点名: {drawNode.nodeName})");
return null;
}
nodeObject = Instantiate(imagePrefab.gameObject, parent.transform);
var texture =
Managers.PackagesImageManager.Instance.GetSprite(drawNode.packID,
drawNode.textures[0]); // --- 修改点二:移除 ?. ---
if (!texture)
{
Debug.LogWarning(
$"InitBodyPart: 无法获取纹理 (节点名: {drawNode.nodeName}, 纹理ID: {drawNode.textures[0]})");
}
var imagePrefabCom = nodeObject.GetComponent<ImagePrefab>();
if (imagePrefabCom != null)
{
imagePrefabCom.SetSprite(texture);
}
else
{
Debug.LogWarning($"InitBodyPart: 无法获取ImagePrefab组件 (节点名: {drawNode.nodeName})");
}
break;
default:
// 多纹理动画节点
if (!animatorPrefab)
{
Debug.LogError($"InitBodyPart: animatorPrefab未设置 (节点名: {drawNode.nodeName})");
return null;
}
nodeObject = Instantiate(animatorPrefab.gameObject, parent.transform);
var animator = nodeObject.GetComponent<SpriteAnimator>();
if (animator == null)
{
Debug.LogWarning($"InitBodyPart: 无法获取SpriteAnimator组件 (节点名: {drawNode.nodeName})");
break;
}
animator.SetFPS(drawNode.FPS);
var animatedSprites = new List<Sprite>();
foreach (var textureId in drawNode.textures)
{
try
{
var sprite =
Managers.PackagesImageManager.Instance.GetSprite(drawNode.packID,
textureId); // --- 修改点二:移除 ?. ---
if (sprite != null)
{
animatedSprites.Add(sprite);
}
else
{
Debug.LogWarning(
$"InitBodyPart: 无法获取动画纹理 (节点名: {drawNode.nodeName}, 纹理ID: {textureId})");
}
}
catch (Exception ex)
{
Debug.LogError(
$"InitBodyPart: 加载动画纹理时出错 (节点名: {drawNode.nodeName}, 纹理ID: {textureId}): {ex.Message}");
}
}
if (animatedSprites.Count > 0)
{
animator.SetSprites(animatedSprites.ToArray());
}
else
{
Debug.LogWarning($"InitBodyPart: 没有有效的动画纹理 (节点名: {drawNode.nodeName})");
}
break;
}
// 设置节点属性
if (!nodeObject) return nodeObject;
nodeObject.transform.localPosition = drawNode.position;
nodeObject.name = drawNode.nodeName ?? "UnnamedNode";
// 递归初始化子节点
if (drawNode.nodes == null) return nodeObject;
foreach (var child in drawNode.nodes)
{
try
{
InitBodyPart(child, nodeObject);
}
catch (Exception ex)
{
Debug.LogError($"InitBodyPart: 初始化子节点失败 (父节点: {drawNode.nodeName}): {ex.Message}");
}
}
return nodeObject;
}
catch (Exception ex)
{
Debug.LogError($"InitBodyPart: 初始化节点时发生未处理的异常 (节点名: {drawNode?.nodeName}): {ex}");
return null;
}
}
/// <summary>
/// 更新实体的逻辑,包括玩家控制和自动行为。
@@ -486,29 +337,6 @@ namespace Entity
AutoBehave();
}
if (_currentAnimatorCache != null)
{
foreach (var animator in _currentAnimatorCache)
{
animator.Tick();
}
}
if (wearponAttackAnimationNodeList != null)
{
foreach (var tick in wearponAttackAnimationNodeList)
{
tick.Tick();
}
}
if (weaponAnimatorNodeList != null)
{
foreach (var tick in weaponAnimatorNodeList)
{
tick.Tick();
}
}
if (IsShowingHealthBarUI)
{
_hitBarUIShowTimer -= Time.deltaTime;
@@ -517,6 +345,30 @@ namespace Entity
HideHealthBar();
}
}
if (_attackTimer > 0)
{
_attackTimer -= Time.deltaTime;
if (currentAttackWeapon != null && _attackTimer <= _attackDetectionTime)
{
if (currentAttackWeapon.Type == WeaponType.Melee)
{
ExecuteMeleeAttack(currentAttackWeapon);
}
else
{
ExecuteRangedAttack(currentAttackWeapon);
}
currentAttackWeapon = null;
}
if (_attackTimer <= 0)
{
SetBodyTexture(EntityState.Idle, _currentOrientation);
}
}
}
/// <summary>
@@ -536,21 +388,40 @@ namespace Entity
return;
}
// 启动基于武器的攻击协程
_attackCoroutine = StartCoroutine(AttackFlow(currentWeapon));
StartAttack(currentWeapon);
}
private void StartAttack(WeaponResource weaponResource)
{
_attackTimer = weaponResource.AttackCooldown;
_attackDetectionTime =Mathf.Max(0,weaponResource.AttackCooldown- weaponResource.AttackDetectionTime);
currentAttackWeapon = weaponResource;
if (weaponResource.AttackAnimationTime > 0)
{
TemporaryAnimationManager.Instance.GenerateTemporaryAnimation(
weaponResource.AttackAnimation.ToArray(), Position,transform, weaponResource.AttackAnimationTime,
weaponResource.FPS);
}
if (weaponResource.UseEntityAttackAnimation)
{
SetBodyTexture(
weaponResource.Type == WeaponType.Melee ? EntityState.MeleeAttack : EntityState.RangedAttack,
_currentOrientation);
}
else
{
HideCurrentBodyTexture();
}
}
public virtual void SetBodyTexture(EntityState state, Orientation orientation)
public void SetBodyTexture(EntityState state, Orientation orientation)
{
if (bodyNodes.TryGetValue(_currentState, out var stateNode))
{
if (stateNode.TryGetValue(_currentOrientation, out var node))
{
node.SetActive(false);
}
}
HideCurrentBodyTexture();
if (IsAttacking && currentAttackWeapon is { UseEntityAttackAnimation: false })
return;
if (bodyNodes.TryGetValue(state, out var showStateNode))
{
if (showStateNode.TryGetValue(orientation, out var showNode))
@@ -561,15 +432,14 @@ namespace Entity
_currentState = state;
_currentOrientation = orientation;
}
if (bodyAnimationNode.TryGetValue(_currentState, out var animationNode) &&
animationNode.TryGetValue(_currentOrientation, out var value))
public void HideCurrentBodyTexture()
{
if (!bodyNodes.TryGetValue(_currentState, out var stateNode)) return;
if (stateNode.TryGetValue(_currentOrientation, out var node))
{
_currentAnimatorCache = value;
}
else
{
_currentAnimatorCache = new ITick[] { }; // 如果没有找到动画,则使用空列表
node.SetActive(false);
}
}
@@ -578,9 +448,9 @@ namespace Entity
/// </summary>
public virtual void TryMove()
{
if (IsAttacking)
return;
transform.position += direction * (attributes.moveSpeed * Time.deltaTime * (IsChase ? 1 : 0.5f));
// if (IsAttacking)
// return;
transform.position += direction * (attributes.moveSpeed * Time.deltaTime);
SetBodyTexture(EntityState.Walking, _currentOrientation);
_walkingTimer = 2;
}
@@ -596,7 +466,6 @@ namespace Entity
{
return;
}
var hit = from.attributes.attack - attributes.defense;
if (hit < 0)
hit = from.attributes.attack / 100;
@@ -616,7 +485,15 @@ namespace Entity
OnEntityDied?.Invoke(this);
}
ShowHealthBar(); // 无论是否死亡都更新血条UI
if (Setting.Instance.CurrentSettings.showHealthBarByHit)
{
ShowHealthBar();
}
if (Setting.Instance.CurrentSettings.showHitNumber)
{
TemporaryAnimationManager.Instance.GenerateTemporaryAnimation(hit.ToString(), Position);
}
}
public void ShowHealthBar()
@@ -675,6 +552,10 @@ namespace Entity
}
SetBodyTexture(_currentState, ori);
if (!PlayerControlled)
{
attackDirection=direction;
}
}
/// <summary>
@@ -710,9 +591,18 @@ namespace Entity
/// </summary>
protected virtual void UpdatePlayerControls()
{
// 检测 Shift 键状态
var isHoldingShift = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
IsChase = !isHoldingShift; // 按住 Shift 时 IsChase = false否则 true
if (Input.GetMouseButton(0))
{
var mousePos = MousePosition.GetWorldPosition();
attackDirection = new Vector3(mousePos.x,mousePos.y) - Position;
if (weaponItem)
RotateTool.RotateTransformToDirection(weaponItem.transform, attackDirection);
}
if (Input.GetKeyDown(KeyCode.V))
{
weaponItem.gameObject.SetActive(!weaponItem.gameObject.activeSelf);
}
// 获取当前键盘输入状态2D 移动,只使用 X 和 Y 轴)
var inputDirection = Vector2.zero;
@@ -756,97 +646,9 @@ namespace Entity
// 调用 TryMove 方法处理实际移动逻辑
TryMove();
}
// NEW: ShakeInDirectionCoroutine 签名修改以接收持续时间
private IEnumerator ShakeInDirectionCoroutine(float duration)
{
var originalPosition = transform.position; // 记录原始位置
// 在攻击动画持续时间内进行抖动效果
transform.position += direction * 0.5f;
yield return new WaitForSeconds(duration);
transform.position = originalPosition;
}
// NEW: AttackFlow 现在接收 WeaponResource 参数
protected IEnumerator AttackFlow(WeaponResource weapon) // 将可见性改为 protected允许子类访问
{
// STEP 1: 激活武器动画节点
if (weapon.AttackAnimationDef != null)
{
var animation = weapon.InstantiateAttackAnimation(body.transform);
wearponAttackAnimationNodeRoot = animation.root;
wearponAttackAnimationNodeList = animation.animationComponents;
}
// STEP 4: 等待到攻击判定时间
var elapsedTime = 0f;
while (elapsedTime < weapon.AttackDetectionTime)
{
if (IsDead)
{
/* 如果实体在此期间死亡,立刻中断 */
break;
}
elapsedTime += Time.deltaTime;
yield return null; // 等待一帧
}
// 如果实体在等待期间死亡,清理并退出
if (IsDead)
{
CleanupAttack(weapon);
yield break;
}
ExecuteWeaponAction(weapon);
var remainingAnimationTime = weapon.AttackAnimationTime - elapsedTime;
if (remainingAnimationTime > 0)
{
yield return new WaitForSeconds(remainingAnimationTime);
}
else if (weapon.AttackAnimationTime > 0)
{
yield return new WaitForSeconds(weapon.AttackAnimationTime - elapsedTime);
}
// STEP 7: 清理攻击状态
CleanupAttack(weapon);
}
private void CleanupAttack(WeaponResource weapon)
{
if (wearponAttackAnimationNodeRoot)
{
Destroy(wearponAttackAnimationNodeRoot);
wearponAttackAnimationNodeRoot = null;
}
wearponAttackAnimationNodeList = null;
_attackCoroutine = null;
}
protected virtual void ExecuteWeaponAction(WeaponResource weapon) // 将可见性改为 protected允许子类重写
{
if (weapon == null) return; // 安全检查
switch (weapon.Type)
{
case WeaponType.Melee:
ExecuteMeleeAttack(weapon);
break;
case WeaponType.Ranged:
ExecuteRangedAttack(weapon);
break;
default:
Debug.LogWarning($"未知武器类型: {weapon.Type} for {name}");
break;
}
}
private void ExecuteMeleeAttack(WeaponResource weapon)
{
if (weapon.Attributes == null)
@@ -888,13 +690,7 @@ namespace Entity
// 获取子弹方向。这里使用实体当前的移动方向作为子弹发射方向
// 更复杂的逻辑可能根据鼠标位置、目标位置等确定
var bulletDirection = direction; // 实体当前的朝向
if (PlayerControlled && Input.GetMouseButton(0)) // 玩家控制时,如果鼠标按下,尝试朝鼠标方向发射
{
var mouseWorldPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
mouseWorldPos.z = transform.position.z; // 保持Z轴一致
bulletDirection = (mouseWorldPos - transform.position).normalized;
}
var bulletDirection = attackDirection; // 实体当前的朝向
// 如果没有明确的方向,给一个默认值以防万一
if (bulletDirection == Vector3.zero) bulletDirection = Vector3.down;

View File

@@ -80,35 +80,7 @@ namespace Entity
// 确保阶段列表按开始时间排序,以便正确判断当前阶段。
if (this.def.stages == null) this.def.stages = new List<HediffStageDef>();
this.def.stages = this.def.stages.OrderBy(s => s.start).ToList();
// 实例化所有定义的组件。
if (def.comps != null)
{
foreach (var compDef in def.comps)
{
if (compDef.compClass != null && typeof(HediffComp).IsAssignableFrom(compDef.compClass))
{
try
{
// 使用 Activator.CreateInstance 动态创建组件实例,并传入构造函数参数。
// HediffComp 的构造函数需要接受 Hediff parentHediff 和 HediffCompDef def。
var comp = (HediffComp)Activator.CreateInstance(compDef.compClass, this, compDef);
Comps.Add(comp);
comp.Initialize(); // 初始化组件
}
catch (Exception ex)
{
Debug.LogError(
$"实例化健康状态组件 '{compDef.compClass?.Name ?? ""}' 失败,所属健康状态 '{def.defName ?? def.GetType().Name}'{ex.Message}");
}
}
else
{
Debug.LogWarning(
$"警告:健康状态组件定义 '{compDef.compClass?.Name ?? ""}' 无效或未继承自 HediffComp所属健康状态 '{def.defName ?? def.GetType().Name}'。");
}
}
}
// 初始化时确定第一个阶段,这会触发 SetDirty()。
UpdateStageIndex();

View File

@@ -17,7 +17,7 @@ namespace Entity
/// <summary>
/// 表示一个具有生命周期、属性和可受健康状态Hediff影响的实体。
/// </summary>
public class LivingEntity : Entity
public class LivingEntity : CombatantEntity
{
// 存储应用于此实体的所有健康状态Hediff列表。
protected List<Hediff> hediffs = new List<Hediff>();

View File

@@ -12,6 +12,7 @@ namespace Entity
public class Pickup : Entity
{
public ItemResource itemResource;
protected override void AutoBehave()
{
}
@@ -30,7 +31,6 @@ namespace Entity
foreach (var state in states)
{
bodyNodes.TryAdd(state, new Dictionary<Orientation, GameObject>());
bodyAnimationNode.TryAdd(state, new Dictionary<Orientation, ITick[]>());
}
var texture = itemResource.Icon;
@@ -49,10 +49,8 @@ namespace Entity
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);
}