CharacterStats.cs
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting.Antlr3.Runtime.Misc;
using UnityEngine;
public enum StatType
{
strength,
agility,
intelligence,
vitality,
damage,
critChance,
critPower,
Health,
armor,
evasion,
magicResistance,
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 点 魔法伤害 1点魔抗
public Stat vitality;//加血的
[Header("Offensive stats")]
public Stat damage;
public Stat critChance; // 暴击率
public Stat critPower; //150% 爆伤
[Header("Defensive stats")]
public Stat Health;
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 isChilded; // 削弱护甲 20%
public bool isShocked; // 降低敌人命中率
[SerializeField] private float ailmentsDuration = 4;
private float ignitedTimer;
private float chilledTimer;
private float shockedTimer;
private float igniteDamageCooldown = .3f;
private float ignitedDamageTimer;
private int igniteDamage;
[SerializeField] private GameObject shockStrikePrefab;
private int shockDamage;
public System.Action onHealthChanged;//使角色在Stat里调用UI层的函数
//此函数调用了更新HealthUI函数
public bool isDead { get; private set; }
public bool inInvincible { get; private set; }//无敌状态
private bool isVulnerable;//脆弱效果
[SerializeField] public int currentHealth;
protected virtual void Start()
{
critPower.SetDefaultValue(150);//设置默认爆伤
currentHealth = GetMaxHealthValue();
fx = GetComponent<EntityFX>();
}
public void MakeVulnerableFor(float _duration) => StartCoroutine(VulnerableCorutine(_duration));//脆弱效果函数
private IEnumerator VulnerableCorutine(float _duration)
{
isVulnerable = true;
yield return new WaitForSeconds(_duration);
isVulnerable = false;
}
protected virtual void Update()
{
//所有的状态都设置上默认持续时间,持续过了就结束状态
ignitedTimer -= Time.deltaTime;
chilledTimer -= Time.deltaTime;
shockedTimer -= Time.deltaTime;
ignitedDamageTimer -= Time.deltaTime;
if (ignitedTimer < 0)
isIgnited = false;
if (chilledTimer < 0)
isChilded = false;
if (shockedTimer < 0)
isShocked = false;
//被点燃后,出现多段伤害后点燃停止
if(isIgnited)
ApplyIgnitedDamage();
}
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)//计算后造成伤害函数
{
bool criticalStrike = false;
if (TargetCanAvoidAttack(_targetStats))设置闪避
{
return;
}
_targetStats.GetComponent<Entity>().SetupKnockbackDir(transform);
int totleDamage = damage.GetValue() + strength.GetValue();
//爆伤设置
if (CanCrit())
{
totleDamage = CalculateCriticalDamage(totleDamage);
criticalStrike = true;
}
fx.CreateHitFX(_targetStats.transform,criticalStrike);
totleDamage = CheckTargetArmor(_targetStats, totleDamage);//设置防御
_targetStats.TakeDamage(totleDamage);
DoMagicaDamage(_targetStats); // 可以去了也可以不去
}
protected virtual void Die()
{
isDead = true;
}
public void KillEntity()
{
if(!isDead)
Die();
}//用于掉落死亡的函数
public virtual void TakeDamage(int _damage)//造成伤害是出特效
{
if (inInvincible)
return;
fx.StartCoroutine("FlashFX");//IEnumertor本质就是将一个函数分块执行,只有满足某些条件才能执行下一段代码,此函数有StartCoroutine调用
//https://www.zhihu.com/tardis/bd/art/504607545?source_id=1001
DecreaseHealthBy(_damage);
GetComponent<Entity>().DamageImpact();
if (currentHealth < 0 && !isDead)
Die();
}
public virtual void IncreaseHealthBy(int _amount)//添加回血函数
{
currentHealth += _amount;
if (currentHealth > GetMaxHealthValue())
currentHealth = GetMaxHealthValue();
if (onHealthChanged != null)
onHealthChanged();
}
protected virtual void DecreaseHealthBy(int _damage)//此函数用来改变当前生命值,不调用特效
{
if (isVulnerable)
_damage = Mathf.RoundToInt(_damage * 1.1f);
currentHealth -= _damage;
if (onHealthChanged != null)
{
onHealthChanged();
}
}
public void MakeInvincible(bool _inInvincible)//设置无敌状态的函数
{
inInvincible = _inInvincible;
}
#region Magical damage and ailements
private void ApplyIgnitedDamage()
{
if (ignitedDamageTimer < 0 )
{
DecreaseHealthBy(igniteDamage);
if (currentHealth < 0 && !isDead)
Die();
ignitedDamageTimer = igniteDamageCooldown;
}
}被点燃后,出现多段伤害后点燃停止
public virtual void DoMagicaDamage(CharacterStats _targetStats)//法伤计算和造成元素效果调用的地方
{
int _fireDamage = fireDamage.GetValue();
int _iceDamage = iceDamage.GetValue();
int _lightingDamage = lightingDamage.GetValue();
int totleMagicalDamage = _fireDamage + _iceDamage + _lightingDamage + intelligence.GetValue();
totleMagicalDamage = CheckTargetResistance(_targetStats, totleMagicalDamage);
_targetStats.TakeDamage(totleMagicalDamage);
//防止循环在所有元素伤害为0时出现死循环
if (Mathf.Max(_fireDamage, _iceDamage, _lightingDamage) <= 0)
return;
//让元素效果取决与伤害
//为了防止出现元素伤害一致而导致无法触发元素效果
//循环判断触发某个元素效果
AttemptyToApplyAilement(_targetStats, _fireDamage, _iceDamage, _lightingDamage);
}
private void AttemptyToApplyAilement(CharacterStats _targetStats, int _fireDamage, int _iceDamage, int _lightingDamage)
{
bool canApplyIgnite = _fireDamage > _iceDamage && _fireDamage > _lightingDamage;
bool canApplyChill = _iceDamage > _lightingDamage && _iceDamage > _fireDamage;
bool canApplyShock = _lightingDamage > _fireDamage && _lightingDamage > _iceDamage;
while (!canApplyIgnite && !canApplyChill && !canApplyShock)
{
if (Random.value < .25f)
{
canApplyIgnite = true;
Debug.Log("Ignited");
_targetStats.ApplyAilments(canApplyIgnite, canApplyChill, canApplyShock);
return;
}
if (Random.value < .35f)
{
canApplyChill = true;
Debug.Log("Chilled");
_targetStats.ApplyAilments(canApplyIgnite, canApplyChill, canApplyShock);
return;
}
if (Random.value < .55f)
{
canApplyShock = true;
Debug.Log("Shocked");
_targetStats.ApplyAilments(canApplyIgnite, canApplyChill, canApplyShock);
return;
}
}
if (canApplyIgnite)
{
_targetStats.SetupIgniteDamage(Mathf.RoundToInt(_fireDamage * .2f));
}
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 && !isChilded && !isShocked;
bool canApplyChill = !isIgnited && !isChilded && !isShocked;
bool canApplyShock = !isIgnited && !isChilded;
//使当isShock为真时Shock里的函数仍然可以调用
if (_ignite && canApplyIgnite)
{
isIgnited = _ignite;
ignitedTimer = ailmentsDuration;
fx.IgniteFxFor(ailmentsDuration);
}
if (_chill && canApplyChill)
{
isChilded = _chill;
chilledTimer = ailmentsDuration;
float slowPercentage = .2f;
GetComponent<Entity>().SlowEntityBy(slowPercentage, ailmentsDuration);
fx.ChillFxFor(ailmentsDuration);
}
if (_shock && canApplyShock)
{
if(!isShocked)
{
ApplyShock(_shock);
}
else
{
if (GetComponent<Player>() != null)//防止出现敌人使玩家进入shock状态后也出现闪电
return;
HitNearestTargetWithShockStrike();
}//isShock为真时反复执行的函数为寻找最近的敌人,创建闪电实例并传入数据
}
}
public void ApplyShock(bool _shock)
{
if (isShocked)
return;
isShocked = _shock;
shockedTimer = ailmentsDuration;
fx.ShockFxFor(ailmentsDuration);
}//触电变色效果
private void HitNearestTargetWithShockStrike()
{
Collider2D[] colliders = Physics2D.OverlapCircleAll(transform.position, 25);//找到环绕自己的所有碰撞器
float closestDistance = Mathf.Infinity;//正无穷大的表示形式(只读)
Transform closestEnemy = null;
//https://docs.unity3d.com/cn/current/ScriptReference/Mathf.Infinity.html
foreach (var hit in colliders)
{
if (hit.GetComponent<Enemy>() != null && Vector2.Distance(transform.position, hit.transform.position) > 1)// 防止最近的敌人就是Shock状态敌人自己
{
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>());
}
}//给最近的敌人以雷劈
public void SetupIgniteDamage(int _damage) => igniteDamage = _damage;//给点燃伤害赋值
public void SetupShockStrikeDamage(int _damage) => shockDamage = _damage;//雷电伤害赋值
#endregion
#region Stat calculations
private int CheckTargetResistance(CharacterStats _targetStats, int totleMagicalDamage)//法抗计算
{
totleMagicalDamage -= _targetStats.magicResistance.GetValue() + (_targetStats.intelligence.GetValue() * 3);
totleMagicalDamage = Mathf.Clamp(totleMagicalDamage, 0, int.MaxValue);
return totleMagicalDamage;
}
protected static int CheckTargetArmor(CharacterStats _targetStats, int totleDamage)//防御计算
{
//被冰冻后,角色护甲减少
if (_targetStats.isChilded)
totleDamage -= Mathf.RoundToInt(_targetStats.armor.GetValue() * .8f);
else
totleDamage -= _targetStats.armor.GetValue();
totleDamage = Mathf.Clamp(totleDamage, 0, int.MaxValue);
return totleDamage;
}
public virtual void OnEvasion()
{
}//可继承成功闪避触发的函数
protected bool TargetCanAvoidAttack(CharacterStats _targetStats)//闪避计算
{
int totleEvation = _targetStats.evasion.GetValue() + _targetStats.agility.GetValue();
//我被麻痹后
//敌人的闪避率提升
if (isShocked)
totleEvation += 20;
if (Random.Range(0, 100) < totleEvation)
{
_targetStats.OnEvasion();
return true;
}
return false;
}
protected bool CanCrit()//判断是否暴击
{
int totleCriticalChance = critChance.GetValue() + agility.GetValue();
if (Random.Range(0, 100) <= totleCriticalChance)
{
return true;
}
return false;
}
protected int CalculateCriticalDamage(int _damage)//计算暴击后伤害
{
float totleCirticalPower = (critPower.GetValue() + strength.GetValue()) * .01f;
float critDamage = _damage * totleCirticalPower;
return Mathf.RoundToInt(critDamage);//返回舍入为最近整数的
}
public int GetMaxHealthValue()
{
return Health.GetValue() + vitality.GetValue() * 10;
}//统计生命值函数
public Stat GetStats(StatType _statType)
{
if (_statType == StatType.strength) return strength;
else if (_statType == StatType.agility) return agility;
else if (_statType == StatType.intelligence) return intelligence;
else if (_statType == StatType.vitality) return vitality;
else if (_statType == StatType.damage) return damage;
else if (_statType == StatType.critChance) return critChance;
else if (_statType == StatType.critPower) return critPower;
else if (_statType == StatType.Health) return Health;
else if (_statType == StatType.armor) return armor;
else if (_statType == StatType.evasion) return evasion;
else if (_statType == StatType.magicResistance) return magicResistance;
else if (_statType == StatType.fireDamage) return fireDamage;
else if (_statType == StatType.iceDamage) return iceDamage;
else if (_statType == StatType.lightingDamage) return lightingDamage;
return null;
}
#endregion
}
EntityFX.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EntityFX : MonoBehaviour
{
private SpriteRenderer sr;//定义SR组件来保持要用的组件
[Header("After image fx")]
[SerializeField] private GameObject afterImagePrefab;
[SerializeField] private float colorLooseRate;
[SerializeField] private float afterImageCooldown;
private float afterImageCooldownTimer;
[Header("Flash FX")]
[SerializeField] private Material hitMat;//要改成的材料
[SerializeField] private float flashDuration;//闪光的时间
private Material originalMat;//原来的材料
[Header("Aliment colors")]
[SerializeField] private Color[] chillColor;
[SerializeField] private Color[] igniteColor;
[SerializeField] private Color[] shockColor;
[Header("Aliment particles")]
[SerializeField] private ParticleSystem igniteFX;
[SerializeField] private ParticleSystem chillFX;
[SerializeField] private ParticleSystem shockFX;
[Header("HitFX")]
[SerializeField] private GameObject hitFX;
[SerializeField] private GameObject criticalHitFX;
[Space]
[SerializeField] private ParticleSystem dushFX;
private void Start()
{
sr = GetComponentInChildren<SpriteRenderer>();//从子组件中拿到SR组件
originalMat = sr.material;//拿到原来的材料
}
private void Update()
{
afterImageCooldownTimer -= Time.deltaTime;
}
public void CreateAfterImage()//创建残影函数
{
if(afterImageCooldownTimer < 0)
{
afterImageCooldownTimer = afterImageCooldown;
GameObject newAfterImage = Instantiate(afterImagePrefab, transform.position, transform.rotation);
newAfterImage.GetComponent<AfterImageFX>().SetupAfterImage(colorLooseRate, sr.sprite);
}
}
public void MakeTransprent(bool isClear)
{
if (isClear)
sr.color = Color.clear;
else
sr.color = Color.white;
}
private IEnumerator FlashFX()//被打后该触发的函数
{
sr.material = hitMat;
//修复在元素效果期间击中,颜色变红的情况
Color currentColor = sr.color;
sr.color = Color.white;
yield return new WaitForSeconds(flashDuration);
sr.color = currentColor;
sr.material = originalMat;
} //IEnumertor本质就是将一个函数分块执行,只有满足某些条件才能执行下一段代码,此函数有StartCoroutine调用
//https://www.zhihu.com/tardis/bd/art/504607545?source_id=1001
private void RedColorBlink()//使角色闪烁的函数
{
if (sr.color != Color.white)
{
sr.color = Color.white;
}
else
{
sr.color = Color.red;
}
}
private void CancelColorChange()//使角色停止闪烁的函数
{
CancelInvoke();//取消该 MonoBehaviour 上的所有 Invoke 调用。
//https://docs.unity3d.com/cn/current/ScriptReference/MonoBehaviour.CancelInvoke.html
sr.color = Color.white;
igniteFX.Stop();
chillFX.Stop();
shockFX.Stop();
}
public void ShockFxFor(float _second)
{
shockFX.Play();
InvokeRepeating("ShockColorFx", 0, .3f);
Invoke("CancelColorChange", _second);
}
public void ChillFxFor(float _second)
{
chillFX.Play();
InvokeRepeating("ChillColor", 0, .3f);
Invoke("CancelColorChange", _second);
}
public void IgniteFxFor(float _second)
{
igniteFX.Play();
InvokeRepeating("IgniteColorFX", 0, .3f);
Invoke("CancelColorChange", _second);
}
private void IgniteColorFX()
{
if (sr.color != igniteColor[0])
sr.color = igniteColor[0];
else
sr.color = igniteColor[1];
}
private void ShockColorFx()
{
if (sr.color != shockColor[0])
sr.color = shockColor[0];
else
sr.color = shockColor[1];
}
private void ChillColor()
{
if (sr.color != chillColor[0])
sr.color = chillColor[0];
else
sr.color = chillColor[1];
}
public void CreateHitFX(Transform _target,bool _critical)//在打击后创建实体
{
float zRotation = Random.Range(-90, 90);
float xPosition = Random.Range(-.5f, .5f);
float yPosition = Random.Range(-.5f, .5f);
Vector3 hitFXRotation = new Vector3(0, 0, zRotation);
GameObject hitPrefab = hitFX;
if (_critical)
{
hitPrefab = criticalHitFX;
float yRotation = 0;
zRotation = Random.Range(-45, 45);
if(GetComponent<Entity>().facingDir == -1)
{
yRotation = 180;
}
hitFXRotation = new Vector3(0, yRotation, zRotation);
}
GameObject newHitFx = Instantiate(hitPrefab, _target.position + new Vector3(xPosition,yPosition),Quaternion.identity);
newHitFx.transform.Rotate(hitFXRotation);
Destroy(newHitFx,.5f);
}
public void PlayDustFX()//扔剑产生灰尘
{
if (dushFX != null)
dushFX.Play();
}
}
AfterImageFX.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AfterImageFX : MonoBehaviour//冲刺残影
{
private SpriteRenderer sr;
private float colorLooseRate;
public void SetupAfterImage(float _loosingSpeed,Sprite _spriteImage)//设置残影
{
sr = GetComponent<SpriteRenderer>();
sr.sprite = _spriteImage;
colorLooseRate = _loosingSpeed;
}
private void Update()
{
float alpha = sr.color.a - colorLooseRate * Time.deltaTime;
sr.color = new Color(sr.color.r, sr.color.g, sr.color.b, alpha);
if (sr.color.a <= 0)
Destroy(gameObject);
}
}
PlayerDashState.cs
public class PlayerDashState : PlayerState
{ //由Ground进入
public PlayerDashState(Player _player, PlayerStateMachine _stateMachine, string _animBoolName) : base(_player, _stateMachine, _animBoolName)
{
}
public override void Enter()
{
base.Enter();
player.skill.dash.CloneOnDash();//调用克隆函数,并传入当前位置已调试clone的位置
stateTimer = player.dashDuration;设置Dash可以保持的值
player.stats.MakeInvincible(true);
}
public override void Exit()
{
base.Exit();
player.stats.MakeInvincible(false);
player.skill.dash.CloneOnArrival();
player.SetVelocity(0, rb.velocity.y);//当退出时使速度为0防止动作结束后速度不变导致的持续移动
}
public override void Update()
{
base.Update();
if (!player.IsGroundDetected() && player.IsWallDetected())//修复无法在空中dash直接进入wallSlide,修复在wallslide可以dash
//因为一旦在wallSlide里dash,就会直接进wallSlide,当然还有更简单的方法
{
stateMachine.ChangeState(player.wallSlide);
}
player.SetVelocity(player.dashSpeed * player.dashDir, 0);//这个写在Update里,防止在x轴方向减速了,同时Y轴写成0,以防止dash还没有完成就从空中掉下去了
if (stateTimer < 0)//当timer小于0,切换
{
stateMachine.ChangeState(player.idleState);
}
player.fx.CreateAfterImage();
}
}
PlayerCatchSwordState.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//通过player的CatchTheSword进入,及当剑消失的瞬间进入
public class PlayerCatchSwordState : PlayerState
{
private Transform sword;
public PlayerCatchSwordState(Player _player, PlayerStateMachine _stateMachine, string _animBoolName) : base(_player, _stateMachine, _animBoolName)
{
}
public override void Enter()
{
base.Enter();
player.fx.PlayDustFX();
sword = player.sword.transform;//通过player里的sword拿到剑的位置,作者创建了另一个Transform sword的来保存player里的sword
if (player.transform.position.x > sword.position.x && player.facingDir == 1)//和aim里一样的调用Flip函数
{
player.Flip();
}
else if (player.transform.position.x < sword.position.x && player.facingDir == -1)
{
player.Flip();
}
rb.velocity = new Vector2(player.swordReturnImpact * -player.facingDir, rb.velocity.y);//用rb.velocity设置速度即可(不用SetVelocity因为这回导致角色翻转,而角色只是朝着面向的反方向移动
}
public override void Exit()
{
base.Exit();
player.StartCoroutine("BusyFor", .1f); //设置角色在瞄准后的一瞬间和拿回剑的一瞬间时不能移动
}
public override void Update()
{
base.Update();
if(triggerCalled == true)//通过triggerCalled控制退出进入idleState,及catch动画结束退出
{
stateMachine.ChangeState(player.idleState);
}
}
}