本章节制作了冲刺状态无敌
源代码
CharacterStats.cs
新增代码
public bool isInvincible { get; private set; } //无敌状态
//设置是否无敌
public void MakeInvincible(bool _invincible) => isInvincible = _invincible;
public virtual void TakeDamage(int _damage)
{
//无敌则伤害无效
if (isInvincible)
return;
DecreaseHealthBy(_damage);
GetComponent<Entity>().DamageImpact();
fx.StartCoroutine("FlashFX");
if (currentHealth < 0 && !isDead)
Die();
}
完整代码
using System.Collections;
using UnityEngine;
public enum StatType
{
strength,
agility,
intelgence,
vitality,
damage,
critChance,
critPower,
health,
armor,
evasion,
magicRes,
fireDamage,
iceDamage,
lightingDamage
}
public class CharacterStats : MonoBehaviour
{
private EntityFX fx;
[Header("Major stats")]
public Stat strength; //力量 提升1点伤害和1点暴击伤害
public Stat agility; //敏捷 提升1点闪避和1点暴击率
public Stat intelligence; //智力 提升1点魔法伤害和3点魔法抗性
public Stat vitality; //活力 提升5点生命
[Header("Offensive stats")]
public Stat damage; //伤害
public Stat critChance; //暴击率
public Stat critPower; //暴击伤害 初始150
[Header("Defensive stats")]
public Stat maxHealth; //最大生命
public Stat armor; //护甲
public Stat evasion; //闪避
public Stat magicResistance; //魔抗
[Header("Magic stats")]
public Stat fireDamage;
public Stat iceDamage;
public Stat lightingDamage;
public bool isIgnited; //持续伤害
public bool isChilled; //降低护甲
public bool isShocked; //降低20命中率
[SerializeField] private float ailmentDuration = 2;
private float ignitedTimer;
private float chilledTimer;
private float shockedTimer;
private float igniteDamageCooldown = .3f;
private float igniteDamageTimer;
private int igniteDamage;
[SerializeField] private GameObject shockStrikePrefab;
private int shockDamage;
public int currentHealth;
public System.Action onHealthChanged;
public bool isDead { get; private set; } //死亡状态
public bool isInvincible { get; private set; } //无敌状态
private bool isVlnerable;
protected virtual void Start()
{
critPower.SetDefaultValue(150);
currentHealth = GetMaxHealth();
fx = GetComponent<EntityFX>();
}
protected virtual void Update()
{
ignitedTimer -= Time.deltaTime;
chilledTimer -= Time.deltaTime;
shockedTimer -= Time.deltaTime;
igniteDamageTimer -= Time.deltaTime;
if (ignitedTimer < 0)
isIgnited = false;
if (chilledTimer < 0)
isChilled = false;
if (shockedTimer < 0)
isShocked = false;
if (isIgnited)
ApplyIgniteDamage();
}
public void MakeVulnerableFor(float _duration) => StartCoroutine(VulnerableCorutine(_duration));
private IEnumerator VulnerableCorutine(float _duration)
{
isVlnerable = true;
yield return new WaitForSeconds(_duration);
isVlnerable = false;
}
public virtual void IncreaseStatBy(int _modifier, float _duration, Stat _statToModify)
{
StartCoroutine(StatModCoroutine(_modifier, _duration, _statToModify));
}
private IEnumerator StatModCoroutine(int _modifier, float _duration, Stat _statToModify)
{
_statToModify.AddModifier(_modifier);
yield return new WaitForSeconds(_duration);
_statToModify.RemoveModifier(_modifier);
}
public virtual void DoDamage(CharacterStats _targetStats)
{
if (TargetCanAvoidAttack(_targetStats))
return;
//设置击退方向
_targetStats.GetComponent<Entity>().SetupKnockbackDir(transform);
int totalDamage = damage.GetValue() + strength.GetValue();
if (CanCrit())
{
totalDamage = CalculaterCriticalDamage(totalDamage);
}
totalDamage = CheckTargetArmor(_targetStats, totalDamage);
_targetStats.TakeDamage(totalDamage);
DoMagicDamage(_targetStats); //测试魔法伤害
}
#region Magical damage and ailments
public virtual void DoMagicDamage(CharacterStats _targetStats)
{
int _fireDamage = fireDamage.GetValue();
int _iceDamage = iceDamage.GetValue();
int _lightingDamage = lightingDamage.GetValue();
int totalMagicalDamage = _fireDamage + _iceDamage + _lightingDamage + intelligence.GetValue();
totalMagicalDamage = CheckTargetResistance(_targetStats, totalMagicalDamage);
_targetStats.TakeDamage(totalMagicalDamage);
if (Mathf.Max(_fireDamage, _iceDamage, _lightingDamage) <= 0)
return;
AttemptyToApplyAilments(_targetStats, _fireDamage, _iceDamage, _lightingDamage);
}
private void AttemptyToApplyAilments(CharacterStats _targetStats, int _fireDamage, int _iceDamage, int _lightingDamage)
{
bool canApplyIgnite = _fireDamage > _iceDamage && _fireDamage > _lightingDamage;
bool canApplyChill = _iceDamage > _fireDamage && _iceDamage > _lightingDamage;
bool canApplyShock = _lightingDamage > _fireDamage && _lightingDamage > _iceDamage;
while (!canApplyIgnite && !canApplyChill && !canApplyShock)
{
if (Random.value < .3f && _fireDamage > 0)
{
canApplyIgnite = true;
_targetStats.ApplyAilments(canApplyIgnite, canApplyChill, canApplyShock);
return;
}
if (Random.value < .5f && _iceDamage > 0)
{
canApplyChill = true;
_targetStats.ApplyAilments(canApplyIgnite, canApplyChill, canApplyShock);
return;
}
if (Random.value < .5f && _lightingDamage > 0)
{
canApplyShock = true;
_targetStats.ApplyAilments(canApplyIgnite, canApplyChill, canApplyShock);
return;
}
}
if (canApplyIgnite)
_targetStats.SetupIgniteDamage(Mathf.RoundToInt(_fireDamage * 0.15f));
if (canApplyShock)
_targetStats.SetupShockStrikeDamage(Mathf.RoundToInt(_lightingDamage * .1f));
_targetStats.ApplyAilments(canApplyIgnite, canApplyChill, canApplyShock);
}
public void ApplyAilments(bool _ignite, bool _chill, bool _shock)
{
bool canApplyIgnite = !isIgnited && !isChilled && !isShocked;
bool canApplyChill = !isIgnited && !isChilled && !isShocked;
bool canApplyShock = !isIgnited && !isChilled;
if (_ignite && canApplyIgnite)
{
isIgnited = _ignite;
ignitedTimer = ailmentDuration;
fx.IgniteFxFor(ailmentDuration);
}
if (_chill && canApplyChill)
{
isChilled = _chill;
chilledTimer = ailmentDuration;
float slowPrecentage = .2f;
GetComponent<Entity>().SlowEnitityBy(slowPrecentage, ailmentDuration);
fx.ChillFxFor(ailmentDuration);
}
if (_shock && canApplyShock)
{
if (!isShocked)
{
ApplyShock(_shock);
}
else
{
if (GetComponent<Player>() != null)
return;
HitNearestTargetWithShockStrike();
}
}
}
public void ApplyShock(bool _shock)
{
if (isShocked)
return;
isShocked = _shock;
shockedTimer = ailmentDuration;
fx.ShockFxFor(ailmentDuration);
}
private void HitNearestTargetWithShockStrike()
{
Collider2D[] colliders = Physics2D.OverlapCircleAll(transform.position, 25);
float closestDistance = Mathf.Infinity;
Transform closestEnemy = null;
foreach (var hit in colliders)
{
if (hit.GetComponent<Enemy>() != null && Vector2.Distance(transform.position, hit.transform.position) > 1)
{
float distanceToEnemy = Vector2.Distance(transform.position, hit.transform.position);
if (distanceToEnemy < closestDistance)
{
closestDistance = distanceToEnemy;
closestEnemy = hit.transform;
}
}
if (closestEnemy == null)
closestEnemy = transform;
}
if (closestEnemy != null)
{
GameObject newShockStrike = Instantiate(shockStrikePrefab, transform.position, Quaternion.identity);
newShockStrike.GetComponent<ShockStrike_Controller>().Setup(shockDamage, closestEnemy.GetComponent<CharacterStats>());
}
}
private void ApplyIgniteDamage()
{
if (igniteDamageTimer < 0)
{
DecreaseHealthBy(igniteDamage);
if (currentHealth < 0 && !isDead)
Die();
igniteDamageTimer = igniteDamageCooldown;
}
}
public void SetupIgniteDamage(int _damage) => igniteDamage = _damage;
public void SetupShockStrikeDamage(int _damage) => shockDamage = _damage;
#endregion
public virtual void TakeDamage(int _damage)
{
//无敌则伤害无效
if (isInvincible)
return;
DecreaseHealthBy(_damage);
GetComponent<Entity>().DamageImpact();
fx.StartCoroutine("FlashFX");
if (currentHealth < 0 && !isDead)
Die();
}
public virtual void IncreaseHealthBy(int _amoout)
{
currentHealth += _amoout;
if (currentHealth > GetMaxHealth())
currentHealth = GetMaxHealth();
if (onHealthChanged != null)
onHealthChanged();
}
protected virtual void DecreaseHealthBy(int _damage)
{
if (isVlnerable)
_damage = Mathf.RoundToInt(_damage * 1.1f);
currentHealth -= _damage;
if (onHealthChanged != null)
onHealthChanged();
}
protected virtual void Die()
{
isDead = true;
}
//掉落悬崖
public void KillEntity()
{
if (!isDead)
Die();
}
//设置是否无敌
public void MakeInvincible(bool _invincible) => isInvincible = _invincible;
#region Stat calculations
protected int CheckTargetArmor(CharacterStats _targetStats, int totalDamage)
{
if (_targetStats.isChilled)
totalDamage -= Mathf.RoundToInt(_targetStats.armor.GetValue() * .8f);
else
totalDamage -= _targetStats.armor.GetValue();
totalDamage = Mathf.Clamp(totalDamage, 0, int.MaxValue);
return totalDamage;
}
private int CheckTargetResistance(CharacterStats _targetStats, int totalMagicalDamage)
{
totalMagicalDamage -= _targetStats.magicResistance.GetValue() + (_targetStats.intelligence.GetValue() * 3);
totalMagicalDamage = Mathf.Clamp(totalMagicalDamage, 0, int.MaxValue);
return totalMagicalDamage;
}
public virtual void OnEvasion(Transform _enemy)
{
}
protected bool TargetCanAvoidAttack(CharacterStats _targetStats)
{
int totalEvasion = _targetStats.evasion.GetValue() + _targetStats.agility.GetValue();
if (isShocked)
totalEvasion += 20;
if (Random.Range(0, 100) < totalEvasion)
{
_targetStats.OnEvasion(this.transform);
return true;
}
return false;
}
protected bool CanCrit()
{
int totalCriticalChance = critChance.GetValue() + agility.GetValue();
if (Random.Range(0, 100) <= totalCriticalChance)
{
return true;
}
return false;
}
protected int CalculaterCriticalDamage(int _damage)
{
float totalCriticalPower = ((float)critPower.GetValue() + (float)strength.GetValue()) * .01f;
float critDamage = _damage * totalCriticalPower;
return Mathf.RoundToInt(critDamage);
}
public int GetMaxHealth()
{
return maxHealth.GetValue() + vitality.GetValue() * 5;
}
#endregion
public Stat GetStat(StatType _statType)
{
switch (_statType)
{
case StatType.strength: return strength;
case StatType.agility: return agility;
case StatType.intelgence: return intelligence;
case StatType.vitality: return vitality;
case StatType.damage: return damage;
case StatType.critChance: return critChance;
case StatType.critPower: return critPower;
case StatType.health: return maxHealth;
case StatType.armor: return armor;
case StatType.evasion: return evasion;
case StatType.magicRes: return magicResistance;
case StatType.fireDamage: return fireDamage;
case StatType.iceDamage: return iceDamage;
case StatType.lightingDamage: return lightingDamage;
}
return null;
}
}
PlayerDashState.cs
在进入和离开状态时设置无敌状态
public class PlayerDashState : PlayerState
{
public PlayerDashState(Player _player, PlayerStateMachine _stateMachine, string _animBoolName) : base(_player, _stateMachine, _animBoolName)
{
}
public override void Enter()
{
base.Enter();
player.skill.dash.CloneOnDash();
stateTimer = player.dashDuration;
//设置无敌
player.stats.MakeInvincible(true);
}
public override void Exit()
{
base.Exit();
player.skill.dash.CloneOnArrival();
player.SetVelocity(0, rb.velocity.y);
//取消无敌
player.stats.MakeInvincible(false);
}
public override void Update()
{
base.Update();
player.SetVelocity(player.dashSpeed * player.dashDir, 0);
if (!player.IsGroundDetected() && player.IsWallDetected())
{
stateMachine.ChangeState(player.wallSlideState);
}
if (stateTimer < 0)
{
stateMachine.ChangeState(player.idleState);
}
}
}