Unity类银河恶魔城学习记录 17-3,4,5,6 p168 雪特效,p169 Hit and Critical particles,p170 Dust fx,p171 After image fx

 

 

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);
        }
    }
}

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值