一:角色受伤
1:攻击定义
对于造成伤害的武器,为其赋予一个AttackDefination的脚本,其中定义了攻击的来源角色,攻击的基本伤害,体力衰减,为武器添加Trigger碰撞体,在脚本中利用OnTriggerEnter函数触发人物角色TakeDamage函数。
[RequireComponent(typeof(Collider))]
public class AttackDefinition : MonoBehaviour
{
public float attackBaseDamage;
public float energyAmount; //如果对方是敌人 或者 对方正在普通格挡 会造成对方的体力消耗
public CharacterStats attacker;
public DamageType damageType;
public float DamageAmount => attackBaseDamage + damageType switch
{
DamageType.Physical => attacker.CurPhysicalDamage,
DamageType.Magical => attacker.CurMagicDamage,
DamageType.True => 0,
_ => 0
};
private void OnTriggerEnter(Collider other)
{
other.GetComponent<CharacterStats>()?.TakeDamage(this);
//碰撞体挂载在子物体上的特殊情况
if (other.GetComponentInParent<MetalonController>())
other.GetComponentInParent<CharacterStats>()?.TakeDamage(this);
}
}
在角色的状态控制器CharacterStats中定义TakeDamge函数:由于涉及到无敌,格挡,音效,虚弱状态,敌人和主角的逻辑不完全相同,暂时分开完成。
通用的部分:
public virtual void TakeDamage(AttackDefinition attacker)
{
if (isDead) return;
if (IsInvincible) return;
//设置无敌
InvincibleAfterHit = true;
invincibleAfterHitTimeCounter = InvincibleTimeAfterHit;
//播放音效:受击音效 或者 格挡音效
PlayHitAudio();
//后续内容在子类中实现:人物转向 实际受到伤害,格挡的影响,是否进入虚弱状态
}
在PlayHitAudio中,根据Hit的状态分成3中:受伤,普通格挡,完美格挡。
后续的部分主要是计算受到的伤害,如果死亡,则触发OnDeath事件;如果受伤,则触发OnHurt事件;如果体力到0,则进入虚弱状态,规定虚弱状态可以被处决。
主角的受伤逻辑:规定完美格挡不掉血不掉体力,普通格挡掉的血量减少,但是会消耗体力。
(格挡的实现在后面章节说明)
public override void TakeDamage(AttackDefinition attacker)
{
//如果当前人物不是处于锁定状态 则转向
if (playerController.GetCurrentLockEnemy == null)
{
Vector3 attackerPos = attacker.attacker.transform.position;
Quaternion toRotation = Quaternion.LookRotation(
new Vector3(attackerPos.x, transform.position.y, attackerPos.z) - transform.position);
StartCoroutine(RotateToAttacker(toRotation));
}
//通过玩家的动画判断是否处于完美格挡
IsPerfectGuard = playerAnimationInfo.IsPerfectParry();
base.TakeDamage(attacker);
//执行 掉血 + OnTakeDamage事件
float costHealth = attacker.DamageAmount - attacker.damageType switch
{
DamageType.Physical => CurPhysicalDefensive,
DamageType.Magical => CurMagicalDefensive,
DamageType.True => 0,
_ => 0
};
costHealth = Mathf.Clamp(costHealth, 0, MaxHealth);
//如果当前处于格挡状态
if (IsGuard)
{
//如果是完美格挡的话:不掉血 + 不掉体力
if (IsPerfectGuard) costHealth = 0;
//普通格挡:只掉原先1/5的血,但会消耗体力
else
{
costHealth /= 3;
if (!IsExecuted)
ExpendEnergy(attacker.energyAmount);
}
}
if (costHealth >= CurHealth)
{
CurHealth = 0;
isDead = true;
OnDie?.Invoke();
return;
}
else
{
CurHealth -= costHealth;
OnTakeDamage?.Invoke();
}
//如果角色还没有死亡,且体力值为0,则进入为期1秒的虚弱状态,敌人处于虚弱状态时可以被处决
if (CurEnergy == 0 && !IsWeakState)
{
IsWeakState = true;
PoolManager.Instance.TakeGameObject("Timer").GetComponent<Timer>().CreateTime(2f, () =>
{
IsWeakState = false;
});
}
}
敌人的受伤逻辑:大差不差,暂时没有给敌人设置完美格挡。
public override void TakeDamage(AttackDefinition attacker)
{
base.TakeDamage(attacker);
Vector3 attackerPos = attacker.attacker.transform.position;
Quaternion toRotation =
Quaternion.LookRotation(
new Vector3(attackerPos.x, transform.position.y, attackerPos.z) - transform.position);
StartCoroutine(RotateToAttacker(toRotation));
//执行 掉血 + OnTakeDamage事件
float costHealth = attacker.DamageAmount - attacker.damageType switch
{
DamageType.Physical => CurPhysicalDefensive,
DamageType.Magical => CurMagicalDefensive,
DamageType.True => 0,
_ => 0
};
costHealth = Mathf.Clamp(costHealth, 0, MaxHealth);
//如果当前处于格挡状态(默认为普通格挡)
if (IsGuard) costHealth /= 3;
//如果不处于被处决的状态 (敌人默认直接扣除体力)
if (!IsExecuted)
ExpendEnergy(attacker.energyAmount);
if (costHealth >= CurHealth)
{
CurHealth = 0;
isDead = true;
//GlobalEvent.CallOnEnemyDeath(enemy); 已经再敌人动画结束后调用
OnDie?.Invoke();
}
else
{
CurHealth -= costHealth;
OnTakeDamage?.Invoke();
}
enemyStatsBarUI.UpdateStats(CurHealth, MaxHealth,CurEnergy,MaxEnergy);
//如果角色还没有死亡,且体力值为0,则进入为期1秒的虚弱状态,敌人处于虚弱状态时可以被处决
if (!isDead && CurEnergy == 0 && !IsWeakState)
{
IsWeakState = true;
//将该敌人加入到虚弱敌人表中
GameManager.Instance.AddWeakEnemy(enemy);
GlobalEvent.CallEnemyEnterWeakState(enemy);
PoolManager.Instance.TakeGameObject("Timer").GetComponent<Timer>().CreateTime(2f, () =>
{
if(enemy!=null)
{
if (!enemy.IsExecuted)
{
IsWeakState = false;
GlobalEvent.CallEnemyExitWeakState(enemy);
CurEnergy = MaxEnergy;
}
GameManager.Instance.RemoveWeakEnemy(enemy);
}
});
}
}
说明:GOPoolManager是对象池,从中拿取了一个计时器,简化延迟操作的逻辑。
二:处决敌人
在GameManager中保存两个哈希集合:
public HashSet<EnemyController> enemies; //记录所有的敌人
public HashSet<EnemyController> weakEnemies; //记录所有处于虚弱状态的敌人
其中enemies用于实现锁定功能,weakEnemies用于实现处决功能。
在上述敌人的受伤逻辑中,如果敌人的体力降到0之后会进入一个虚弱的状态,并且加入到weakEnemies中。
在人物的普通攻击判断中,额外判断是否能够进行处决
//判定是否能够对于处于虚弱状态的敌人进行处决
if (SelectWeakEnemy() != null)
{
//执行实际的处决
anim.SetTrigger("Execution");
}
SelectWeakEnemy用于实现查找位于角色前方,且距离小于某个值的处于虚弱状态的敌人:
(查找规则:在满足距离的条件下,优先选择角度最接近的敌人)
//综合判断是否有敌人可以被处决
private EnemyController SelectWeakEnemy()
{
//具体要求:距离小于某个值 + 角度为人物的前方 (根据角度选择最佳的目标)
EnemyController currentEnemy = null;
float minAngle = 180;
foreach(var enemy in GameManager.Instance.weakEnemies)
{
if(Vector3.Distance(transform.position, enemy.transform.position) < 3f
&& transform.IsFacingTarget(enemy.transform))
{
//enemy满足要求 比较得到更好的敌人
if (currentEnemy == null) currentEnemy = enemy;
else
{
if (Vector3.Angle(transform.position, enemy.transform.position) < minAngle)
currentEnemy = enemy;
}
}
}
if(currentEnemy!=null)
{
currentEnemy.IsExecuted = true;
}
return currentEnemy;
}
在查找到虚弱敌人后,进行处决操作,同时设置敌人的处决状态:
public bool IsExecuted { get => isExecuted;
set
{
isExecuted = value;
if (anim != null) anim.SetBool("IsExecuted", value);
if (value)
{
GOPoolManager.Instance.TakeGameObject("Timer").GetComponent<Timer>().CreateTime(4f,
() => { IsExecuted = false;enemyCharacterStats.IsWeakState = false; IsHurt = false; }) ;
}
}
}
上述代码实现了最简单的处决。具体的处决动画,敌人被处决动画可以自行决定,参数和处决方式也可以进行额外的扩充。