(client) feat:添加临时动画组件,添加逃跑逻辑

This commit is contained in:
m0_75251201
2025-09-06 12:25:55 +08:00
parent f43aeffebf
commit 15cdd2b244
73 changed files with 3420 additions and 6055 deletions

View File

@ -19,7 +19,7 @@ namespace Entity
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

@ -29,11 +29,7 @@ namespace Entity
{
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 (Managers.AffiliationManager.Instance.GetRelation(bulletSource.affiliation, entity.affiliation) != Relation.Friendly || Setting.Instance.CurrentSettings.friendlyFire)
{
entity.OnHit(this);
}
@ -41,6 +37,7 @@ namespace Entity
{
return; // 如果是友好关系且不允许友军伤害,则不处理
}
attributes.health -= 1;
}

View File

@ -30,15 +30,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是有效值。
CurrentSelected = 0;
base.Init(entityDef);
}
/// <summary>
@ -59,6 +54,7 @@ namespace Entity
Debug.LogError($"Character '{name}' inventory is not initialized. Cannot pickup item.");
return quantity; // 如果背包未初始化,则视为未能添加任何物品
}
var remainingQuantity = Inventory.AddItem(itemResource, quantity);

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,24 @@ namespace Entity
/// </summary>
public JobBase currentJob;
private Attributes _attribute;
/// <summary>
/// 实体的属性定义,包括生命值、攻击力、防御力等。
/// </summary>
public virtual Attributes attributes { get; protected set; }
public virtual Attributes attributes
{
get { return _attribute ??= new Attributes(baseAttributes); }
protected set => _attribute = value;
}
private Attributes _baseAttributes;
public virtual Attributes baseAttributes
{
get { return _baseAttributes ??= new Attributes(entityDef.attributes); }
get
{
return _baseAttributes ??= entityDef == null ? new Attributes() : new Attributes(entityDef.attributes);
}
}
/// <summary>
@ -79,11 +90,7 @@ namespace Entity
/// 表示实体是否可以被选择。
/// </summary>
public bool canSelect = true;
/// <summary>
/// 表示实体是否处于追逐状态(影响移动速度)。
/// </summary>
public bool IsChase { set; get; } = true;
public string currentDimensionId = null;
@ -98,7 +105,6 @@ namespace Entity
{
if (value)
{
IsChase = true;
currentJob = null;
// 逻辑修改只有当存在一个不同的焦点实体时才将其PlayerControlled设为false
if (Program.Instance.FocusedEntity && Program.Instance.FocusedEntity != this)
@ -146,16 +152,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>
/// 存储不同朝向下的身体节点对象。
@ -199,22 +198,24 @@ 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 (GameObject child in weaponAnimator.transform)
{
Destroy(child);
}
}
var weapon = GetCurrentWeapon();
if (weapon == null)
{
weaponAnimator.SetActive(false);
var currentWeapon = GetCurrentWeapon();
if (currentWeapon?.AttackAnimationDef == null)
return;
}
weaponItem=GameObjectCreate.SpriteAnimator(currentWeapon.Icon.ToArray(), weaponAnimator.transform);
weaponItem.SetFPS(currentWeapon.FPS);
var weaponAnimation = weapon.InstantiateAttackAnimation(weaponAnimator.transform);
weaponAnimatorNodeList = weaponAnimation.animationComponents;
weaponAttackAnimation = GameObjectCreate.SpriteAnimator(currentWeapon.AttackAnimationDef.ToArray(),
weaponAnimator.transform);
weaponAttackAnimation.SetFPS(currentWeapon.FPS);
weaponAttackAnimation.gameObject.SetActive(false);
}
/// <summary>
@ -230,30 +231,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 +258,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 +277,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 +306,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>
/// 更新实体的逻辑,包括玩家控制和自动行为。
@ -485,30 +330,7 @@ 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;
@ -561,16 +383,6 @@ namespace Entity
_currentState = state;
_currentOrientation = orientation;
if (bodyAnimationNode.TryGetValue(_currentState, out var animationNode) &&
animationNode.TryGetValue(_currentOrientation, out var value))
{
_currentAnimatorCache = value;
}
else
{
_currentAnimatorCache = new ITick[] { }; // 如果没有找到动画,则使用空列表
}
}
/// <summary>
@ -580,7 +392,7 @@ namespace Entity
{
if (IsAttacking)
return;
transform.position += direction * (attributes.moveSpeed * Time.deltaTime * (IsChase ? 1 : 0.5f));
transform.position += direction * (attributes.moveSpeed * Time.deltaTime);
SetBodyTexture(EntityState.Walking, _currentOrientation);
_walkingTimer = 2;
}
@ -617,6 +429,7 @@ namespace Entity
}
ShowHealthBar(); // 无论是否死亡都更新血条UI
TemporaryAnimationManager.Instance.GenerateTemporaryAnimation(hit.ToString(), Position);
}
public void ShowHealthBar()
@ -710,9 +523,6 @@ namespace Entity
/// </summary>
protected virtual void UpdatePlayerControls()
{
// 检测 Shift 键状态
var isHoldingShift = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
IsChase = !isHoldingShift; // 按住 Shift 时 IsChase = false否则 true
// 获取当前键盘输入状态2D 移动,只使用 X 和 Y 轴)
var inputDirection = Vector2.zero;
@ -772,17 +582,10 @@ namespace Entity
// 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)
while (elapsedTime < weapon.AttackCooldown)
{
if (IsDead)
{
@ -824,8 +627,7 @@ namespace Entity
Destroy(wearponAttackAnimationNodeRoot);
wearponAttackAnimationNodeRoot = null;
}
wearponAttackAnimationNodeList = null;
_attackCoroutine = null;
}

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

@ -30,7 +30,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,9 +48,6 @@ 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);