Unity类银河恶魔城学习记录10-14 p102 Applying damage to skills and clean up源代码

 Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释,可供学习Alex教程的人参考
此代码仅为较上一P有所改变的代码

【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili

Entity.cs
 using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Entity : MonoBehaviour
{
    [Header("Knockback info")]
    [SerializeField] protected Vector2 knockbackDirection;//被击打后的速度信息
    [SerializeField] protected float knockbackDuration;//被击打的时间
    protected bool isKnocked;//此值通过卡住SetVelocity函数的方式用来阻止当一个角色被攻击时,会乱动的情况

    [Header("Collision Info")]
    public Transform attackCheck;//transform类,代表的时物体的位置,用来控制攻击检测的位置
    public float attackCheckRadius;//检测半径
    [SerializeField] protected Transform groundCheck;//transform类,代表的时物体的位置,后面会来定位子组件的位置    
    [SerializeField] protected float groundCheckDistance;
    [SerializeField] protected Transform wallCheck;//transform类,代表的时物体的位置,后面会来定位子组件的位置    
    [SerializeField] protected float wallCheckDistance;
    [SerializeField] protected LayerMask whatIsGround;//LayerMask类,与Raycast配合,https://docs.unity3d.com/cn/current/ScriptReference/Physics.Raycast.html

    #region 定义Unity组件
    public SpriteRenderer sr { get; private set; }
    public Animator anim { get; private set; }//这样才能配合着拿到自己身上的animator的控制权
    public Rigidbody2D rb { get; private set; }//配合拿到身上的Rigidbody2D组件控制权
    public EntityFX fx { get; private set; }//拿到EntityFX
    public CharacterStats stats { get; private set; }
    public CapsuleCollider2D cd { get; private set; }
    #endregion
    public int facingDir { get; private set; } = 1;
    protected bool facingRight = true;//判断是否朝右


    public System.Action onFlipped;//一个自身不用写函数,只是接受其他函数并调用他们的函数
    //https://blog.csdn.net/weixin_44299531/article/details/131343583

    protected virtual void Awake()
    {
        
    }
    protected virtual void Start()
    {
        anim = GetComponentInChildren<Animator>();//拿到自己子组件身上的animator的控制权
        sr = GetComponentInChildren<SpriteRenderer>();
        fx = GetComponent<EntityFX>();拿到的组件上的EntityFX控制权
        rb = GetComponent<Rigidbody2D>();
        stats = GetComponent<CharacterStats>();
        cd = GetComponent<CapsuleCollider2D>();
    }
    protected virtual void Update()
    {

    }

    protected virtual void Exit()
    {

    }
    public virtual void DamageImpact()
    {

        
        StartCoroutine("HitKnockback");//调用被击打后产生后退效果的函数
        //Debug.Log(gameObject.name+"was damaged");
    }
    
    public virtual void SlowEntityBy(float _slowPercentage,float flowDuration)//减缓一切速度函数
    {
        
    }

    protected virtual void ReturnDefaultSpeed()//动画速度恢复正常函数
    {
        anim.speed = 1;
    }

    protected virtual IEnumerator HitKnockback()
    {
        isKnocked = true;//此值通过卡住SetVelocity函数的方式用来阻止当一个角色被攻击时,会乱动的情况
        rb.velocity = new Vector2(knockbackDirection.x * -facingDir, knockbackDirection.y);
        yield return new WaitForSeconds(knockbackDuration);
        isKnocked = false;
    }
    //被击打后产生后退效果的函数
    #region 速度函数Velocity
    public virtual void SetZeroVelocity()
    {
        if(isKnocked)
        {
            return;
        }
        rb.velocity = new Vector2(0, 0);
    }//设置速度为0函数

    public virtual void SetVelocity(float _xVelocity, float _yVelocity)
    {
        if(isKnocked)
            return;此值通过卡住SetVelocity函数的方式用来阻止当一个角色被攻击时,会乱动的情况

        rb.velocity = new Vector2(_xVelocity, _yVelocity);//将rb的velocity属性设置为对应的想要的二维向量。因为2D游戏的速度就是二维向量
        FlipController(_xVelocity);//在其他设置速度的时候调用翻转控制器
    }//控制速度的函数,此函数在其他State中可能会使用,但仅能通过player.SeVelocity调用
    #endregion
    #region 翻转函数Flip
    public virtual void Flip()
    {
        facingDir = facingDir * -1;
        facingRight = !facingRight;
        transform.Rotate(0, 180, 0);//旋转函数,transform不需要额外定义,因为他是自带的

        if(onFlipped != null)
          onFlipped();

    }//翻转函数

    public virtual void FlipController(float _x)//目前设置x,目的时能在空中时也能转身
    {
        if (_x > 0 && !facingRight)//当速度大于0且没有朝右时,翻转
        {
            Flip();
        }
        else if (_x < 0 && facingRight)
        {
            Flip();
        }
    }
    #endregion
    #region 碰撞函数Collision
    public virtual bool IsGroundDetected()
    {
        return Physics2D.Raycast(groundCheck.position, Vector2.down, groundCheckDistance, whatIsGround);
    }//通过RayCast检测是否挨着地面,https://docs.unity3d.com/cn/current/ScriptReference/Physics2D.Raycast.html
    //xxxxxxxx()   => xxxxxxxx  == xxxxxxxxxx() return xxxxxxxxx;
    public virtual bool IsWallDetected()
    {
        return Physics2D.Raycast(wallCheck.position, Vector2.right * facingDir, wallCheckDistance, whatIsGround);
    }//通过RayCast检测是否挨着地面,https://docs.unity3d.com/cn/current/ScriptReference/Physics2D.Raycast.html
    //xxxxxxxx()   => xxxxxxxx  == xxxxxxxxxx() return xxxxxxxxx;
    protected virtual void OnDrawGizmos()
    {
        Gizmos.DrawLine(groundCheck.position, new Vector3(groundCheck.position.x, groundCheck.position.y - groundCheckDistance));//绘制一条从 from(前面的) 开始到 to(后面的) 的线。
        Gizmos.DrawLine(wallCheck.position, new Vector3(wallCheck.position.x + wallCheckDistance, wallCheck.position.y));//绘制一条从 from(前面的) 开始到 to(后面的) 的线。
        Gizmos.DrawWireSphere(attackCheck.position, attackCheckRadius);//https://docs.unity3d.com/2022.3/Documentation/ScriptReference/Gizmos.DrawWireSphere.html
        //绘制具有中心和半径的线框球体。
    }//画图函数
    #endregion
    

    public virtual void Die()
    {

    }
}

EntityFX.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EntityFX : MonoBehaviour
{
    private SpriteRenderer sr;//定义SR组件来保持要用的组件
    [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;

    private void Start()
    {
        sr = GetComponentInChildren<SpriteRenderer>();//从子组件中拿到SR组件


        originalMat = sr.material;//拿到原来的材料

    }

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

    public void ShockFxFor(float _second)
    {
        InvokeRepeating("ShockColorFx", 0, .3f);
        Invoke("CancelColorChange", _second);
    }

    public void ChillFxFor(float _second)
    {
        InvokeRepeating("ChillColor", 0, .3f);
        Invoke("CancelColorChange", _second);
    }



    public void IgniteFxFor(float _second)
    {
        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];
    }

   
}
CharacterStats.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

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 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 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层的函数
    protected bool isDead;

    [SerializeField] public int currentHealth;


    protected virtual void Start()
    {
        critPower.SetDefaultValue(150);//设置默认爆伤
        currentHealth = GetMaxHealthValue();

        fx = GetComponent<EntityFX>();
    }

    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 DoDamage(CharacterStats _targetStats)//计算后造成伤害函数
    {
        if (TargetCanAvoidAttack(_targetStats))设置闪避
        {
            return;
        }



        int totleDamage = damage.GetValue() + strength.GetValue();

        //爆伤设置
        if (CanCrit())
        {
            totleDamage = CalculateCriticalDamage(totleDamage);
        }

        totleDamage = CheckTargetArmor(_targetStats, totleDamage);//设置防御

        _targetStats.TakeDamage(totleDamage);

        //DoMagicaDamage(_targetStats);
    }
    protected virtual void Die()
    {
        isDead = true;
    }
    public virtual void TakeDamage(int _damage)//造成伤害是出特效
    {
        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();

    }
    protected virtual void DecreaseHealthBy(int _damage)//此函数用来改变当前生命值,不调用特效
    {
        currentHealth -= _damage;

        if (onHealthChanged != null)
        {
            onHealthChanged();
        }
    }

    #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;
    }
    
   
   
    private 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;
    }

    private bool TargetCanAvoidAttack(CharacterStats _targetStats)//闪避计算
    {
        int totleEvation = _targetStats.evasion.GetValue() + _targetStats.agility.GetValue();

        //我被麻痹后
        //敌人的闪避率提升
        if (isShocked)
            totleEvation += 20;

        if (Random.Range(0, 100) < totleEvation)
        {
            return true;
        }
        return false;
    }

    private bool CanCrit()//判断是否暴击
    {
        int totleCriticalChance = critChance.GetValue() + agility.GetValue();

        if (Random.Range(0, 100) <= totleCriticalChance)
        {
            return true;
        }

        return false;
    }

    private int CalculateCriticalDamage(int _damage)//计算暴击后伤害
    {
        float totleCirticalPower = (critPower.GetValue() + strength.GetValue()) * .01f;

        float critDamage = _damage * totleCirticalPower;

        return Mathf.RoundToInt(critDamage);//返回舍入为最近整数的
    }
    public int GetMaxHealthValue()
    {

        return maxHealth.GetValue() + vitality.GetValue() * 10;

    }//统计生命值函数

    #endregion
}


PlayerStat.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerStat : CharacterStats
{
    private Player player;
    

    

    protected override void Start()
    {
        player = GetComponent<Player>();
        base.Start();
    }
    public override void DoDamage(CharacterStats _targetStats)
    {
        base.DoDamage(_targetStats);

    }
    public override void TakeDamage(int _damage)
    {
        base.TakeDamage(_damage);
    }
    protected override void Die()
    {
        base.Die();

        player.Die();
    }
}

EnemyStat.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyStat : CharacterStats
{
    private Enemy enemy;
    public override void DoDamage(CharacterStats _targetStats)
    {
        base.DoDamage(_targetStats);
    }

    protected override void Die()
    {
        base.Die();
        enemy.Die();
    }

    protected override void Start()
    {
        enemy = GetComponent<Enemy>();
        base.Start();
    }

    public override void TakeDamage(int _damage)
    {
        base.TakeDamage(_damage);

    }
}
Sword_Skill_Controller.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Sword_Skill_Controller : MonoBehaviour
{
    private float returnSpeed;
    private bool isReturning;

    private Animator anim;
    private Rigidbody2D rb;
    private CircleCollider2D cd;
    private Player player;

    private bool canRotate = true;

    private float freezeTimeDuration;
    [Header("Piece info")]
    [SerializeField] float pierceAmount;

    [Header("Bounce info")]
    private float bounceSpeed;//设置弹跳速度
    private bool isBouncing;//判断是否可以弹跳
    private int bounceAmount;//弹跳的次数
    public List<Transform> enemyTargets;//保存所有在剑范围内的敌人的列表
    private int targetIndex;//设置targetIndex作为敌人计数器

    [Header("Spin info")]
    private float maxTravelDistance;//最大攻击距离
    private float spinDuration;//持续时间
    private float spinTimer;//计时器
    private bool wasStopped;//是否停止
    private bool isSpinning;

    private float hitTimer;
    private float hitColldown;

    private float spinDirection;//用来不断推进剑在x轴上移动的值
   
    private void Awake()
    {
        anim = GetComponentInChildren<Animator>();
        rb = GetComponent<Rigidbody2D>();
        cd = GetComponent<CircleCollider2D>();
    }

    private void DestroyMe()
    {
        Destroy(gameObject);
    }
    public void ReturnSword()
    {
        rb.constraints = RigidbodyConstraints2D.FreezeAll;//修复剑只要不触碰到物体就无法收回的bug
        //rb.isKinematic = false;
        transform.parent = null;
        isReturning = true;
    }
    public void SetupSword(Vector2 _dir,float _gravityScale,Player _player,float _freezeTimeDuration,float _returnSpeed)
    {
        player = _player;
        rb.velocity = _dir;
        rb.gravityScale = _gravityScale;
        freezeTimeDuration = _freezeTimeDuration;
        returnSpeed = _returnSpeed;
        if(pierceAmount <= 0)
          anim.SetBool("Rotation", true); //其次在pierceAmount > 0时不启动旋转动画
        spinDirection = Mathf.Clamp(rb.velocity.x, -1, 1);用来不断推进剑在x轴上移动的值
        //Mathf.Clamp,当剑使向右飞的时候,direction为1,反之为-1
        //https://docs.unity3d.com/cn/current/ScriptReference/Mathf.Clamp.html
        Invoke("DestroyMe",7);//在一定时间后自动销毁剑
    }

    public void SetupBounce(bool _isBouncing,int _bounceAmount,float _bounceSpeed)
    {
        isBouncing = _isBouncing;
        bounceAmount = _bounceAmount;
        bounceSpeed = _bounceSpeed;
    }

    public void SetupPierce(int _pierceAomunt)
    {
        pierceAmount = _pierceAomunt;
    }

    public void SetupSpin(bool _isSpinning, float _maxTravelDistance,float _spinDuration,float _hitCooldown)
    {
        isSpinning = _isSpinning;
        maxTravelDistance = _maxTravelDistance;
        spinDuration = _spinDuration;
        hitColldown = _hitCooldown;
    }

    private void Update()
    {
        if (canRotate)
            transform.right = rb.velocity;//使武器随着速度而改变朝向

        if (isReturning)
        {
            transform.position = Vector2.MoveTowards(transform.position, player.transform.position, returnSpeed * Time.deltaTime);//从原来的位置返回到player的位置
                                                                                                                                  //并且随着时间增加而增加速度
            if (Vector2.Distance(transform.position, player.transform.position) < 1)//当剑与player的距离小于一定距离,清除剑
            {
                player.CatchTheSword();
            }
        }


        BounceLogic();
        SpinLogic();
    }

    private void SpinLogic()
    {
        if (isSpinning)//首先当isSpining为true才可进入
        {
            if (Vector2.Distance(player.transform.position, transform.position) > maxTravelDistance && !wasStopped)//当剑与角色到达最大攻击距离,stop为true,停止剑运动,给剑一个倒计时
            {
                StopWhenSpinning();
            }
            if (wasStopped)//当stop为true,倒计时过了,回归角色
            {
                spinTimer -= Time.deltaTime;                                                                    //spinDirection一直是1或者-1,但position是变化的
                transform.position = Vector2.MoveTowards(transform.position, new Vector2(transform.position.x + spinDirection, transform.position.y), 1.5f * Time.deltaTime);//让剑在x轴上移动的函数
                if (spinTimer < 0)
                {
                    isReturning = true;
                    isSpinning = false;
                }

                hitTimer -= Time.deltaTime;

                if (hitTimer < 0)
                {
                    hitTimer = hitColldown;
                    Collider2D[] colliders = Physics2D.OverlapCircleAll(transform.position, 1);
                    foreach (var hit in colliders)
                    {
                        if (hit.GetComponent<Enemy>() != null)
                        {
                            hit.GetComponent<Enemy>().DamageImpact();
                        }
                    }
                }
            }
        }
    }

    private void StopWhenSpinning()
    {
        wasStopped = true;
        rb.constraints = RigidbodyConstraints2D.FreezeAll;
        spinTimer = spinDuration;
    }

    private void BounceLogic()
    {
        if (isBouncing && enemyTargets.Count > 0)
        {

            transform.position = Vector2.MoveTowards(transform.position, enemyTargets[targetIndex].position, bounceSpeed * Time.deltaTime);
            //3.当可以弹跳且列表内数量大于0,调用ToWords,这将使剑能够跳到敌人身上
            if (Vector2.Distance(transform.position, enemyTargets[targetIndex].position) < .1f)
            {
                enemyTargets[targetIndex].GetComponent<Enemy>().DamageImpact();
                targetIndex++;
                bounceAmount--;//设置弹跳次数
                if (bounceAmount <= 0)
                {
                    isBouncing = false;
                    isReturning = true;
                }//这样会使当弹跳次数为0时,返回到角色手中
                if (targetIndex >= enemyTargets.Count)
                {
                    targetIndex = 0;
                }
            }//利用与目标距离的判断,使剑接近目标距离时切换到下一个目标。
             //且如果所有目标都跳完了,切回第一个
        }
    }

    private void OnTriggerEnter2D(Collider2D collision)//传入碰到的物体的碰撞器
    {
        if (isReturning)
        {
            return;
        }//修复在返回时扔出时没有碰到任何物体,但返回时碰到了物体导致剑的一些性质发生改变的问题,及回来的时候调用了OnTriggerEnter2D

        if(collision.GetComponent<Enemy>()!=null)
        {
            Enemy enemy = collision.GetComponent<Enemy>();
            SwordSkillDamage(enemy);
        }


        SetupTargetsForBounce(collision);

        StuckInto(collision);
    }//打开IsTrigger时才可使用该函数

    private void SwordSkillDamage(Enemy enemy)
    {
        player.stats.DoDamage(enemy.GetComponent<CharacterStats>());
        enemy.StartCoroutine("FreezeTimeFor", freezeTimeDuration);
    }//造成伤害函数

    private void SetupTargetsForBounce(Collider2D collision)
    {
        if (collision.GetComponent<Enemy>() != null)//首先得碰到敌人,只有碰到敌人才会跳
        {
            if (isBouncing && enemyTargets.Count <= 0)
            {
                Collider2D[] colliders = Physics2D.OverlapCircleAll(transform.position, 10);
                foreach (var hit in colliders)
                {
                    if (hit.GetComponent<Enemy>() != null)
                    {
                        enemyTargets.Add(hit.transform);
                    }
                }
            }
        }
    }

    //https://docs.unity3d.com/cn/current/ScriptReference/Collider2D.OnTriggerEnter2D.html

    private void StuckInto(Collider2D collision)
    {
        if(pierceAmount > 0 && collision.GetComponent<Enemy>()!= null)//本质就是能穿过敌人,在amount>0时不执行能让剑卡在敌人里的语句就行
        {
            pierceAmount--;
            return;
        }

        if (isSpinning)
        {
            StopWhenSpinning();
            return;
        }//防止卡在敌人身上

        canRotate = false;
        cd.enabled = false;//相当于关闭碰撞后触发函数。//https://docs.unity3d.com/cn/current/ScriptReference/Collision2D-enabled.html

        rb.isKinematic = true;//开启物理学反应 https://docs.unity3d.com/cn/current/ScriptReference/Rigidbody2D-isKinematic.html
        rb.constraints = RigidbodyConstraints2D.FreezeAll;//冻结所有移动

        if (isBouncing&&enemyTargets.Count>0)//修复剑卡不到墙上的bug
            return;

        //终止对动画的改变和终止附在敌人上
        transform.parent = collision.transform;//设置sword的父组件为碰撞到的物体
        anim.SetBool("Rotation", false);
    }
    
}

Sword_Skill.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum SwordType
{
    Regular,
    Bounce,
    Pierce,
    Spin
}

public class Sword_Skill : Skill
{
    public SwordType swordType = SwordType.Regular;//创建应一个enum列表,后面用来判断切换状态


    [Header("Bounce Info")]
    [SerializeField] private int bounceAmount;
    [SerializeField] private float bounceGravity;
    [SerializeField] private float bounceSpeed;

    [Header("Pierce info")]
    [SerializeField] private int pierceAmount;
    [SerializeField] private float pierceGravity;

    [Header("Skill Info")]
    [SerializeField] private GameObject swordPrefab;//sword预制体
    [SerializeField] private Vector2 launchForce;//发射力度
    [SerializeField] private float swordGravity;//发射体的重力
    [SerializeField] private float freezeTimeDuration;
    [SerializeField] private float returnSpeed;

    [Header("Spin info")]
    [SerializeField] private float hitCooldown = .35f;//攻击冷却
    [SerializeField] private float maxTravelDistance = 7;//最大攻击距离
    [SerializeField] private float spinDuration = 2;//持续时间
    [SerializeField] private float spinGravity = 1;

    [Header("Aim dots")]
    [SerializeField] private int numberOfDots;//需要的点的数量
    [SerializeField] private float spaceBetweenDots;//相隔的距离
    [SerializeField] private GameObject dotPrefab;//dot预制体
    [SerializeField] private Transform dotsParent;//不是很懂

    private GameObject[] dots;//dot组
    private Vector2 finalDir;
    protected override void Start()
    {
        base.Start();

        GenerateDots();//生成点函数

        SetupGravity();
    }

    private void SetupGravity()//每个剑的状态都对应了不同的剑重力
    {
        if(swordType == SwordType.Pierce)
        {
            swordGravity = pierceGravity;
        }
        if(swordType == SwordType.Bounce)
        {
            swordGravity = bounceGravity;
        }
        if(swordType == SwordType.Spin)
        {
            swordGravity = spinGravity;
        }
    }

    protected override void Update()
    {
        base.Update();
        if (Input.GetKeyUp(KeyCode.Mouse1))
        {
            finalDir = new Vector2(AimDirection().normalized.x * launchForce.x, AimDirection().normalized.y * launchForce.y);
            //将位移量改为单位向量分别与力度的x,y相乘作为finalDir
        }

        if (Input.GetKey(KeyCode.Mouse1))
        {
            for (int i = 0; i < dots.Length; i++)
            {
                dots[i].transform.position = DotsPosition(i * spaceBetweenDots);//用循环为每个点以返回值赋值(传入值为每个点的顺序i*点间距
            }
        }
    }
    public void CreateSword()
    {
        GameObject newSword = Instantiate(swordPrefab, player.transform.position, transform.rotation);//创造实例,初始位置为此时player的位置
        Sword_Skill_Controller newSwordScript = newSword.GetComponent<Sword_Skill_Controller>();//获得Controller
        
        if(swordType == SwordType.Bounce)
        {
            newSwordScript.SetupBounce(true, bounceAmount,returnSpeed);
        }
        else if(swordType == SwordType.Pierce)
        {
            newSwordScript.SetupPierce(pierceAmount);
        }
        else if(swordType == SwordType.Spin)
        {
            newSwordScript.SetupSpin(true, maxTravelDistance, spinDuration,hitCooldown);
        }


        
        newSwordScript.SetupSword(finalDir, swordGravity, player,freezeTimeDuration,bounceSpeed);//调用Controller里的SetupSword函数,给予其速度和重力和player实例

        player.AssignNewSword(newSword);//调用在player中保存通过此方法创造出的sword实例
        DotsActive(false);//关闭点的显示
    }
    #region Aim region
    public Vector2 AimDirection()
    {
        Vector2 playerPosition = player.transform.position;//拿到玩家此时的位置
        Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);//https://docs.unity3d.com/cn/current/ScriptReference/Camera.ScreenToWorldPoint.html
                                                                                    //大概就是返回屏幕上括号里的参数的位置,这里返回了鼠标的位置
                                                                                    //拿到此时鼠标的位置
        Vector2 direction = mousePosition - playerPosition;//获得距离的绝对向量

        return direction;//返回距离向量
    }

    public void DotsActive(bool _isActive)
    {
        for (int i = 0; i < dots.Length; i++)
        {
            dots[i].SetActive(_isActive);//设置每个点是否显示函数
        }
    }
    private void GenerateDots()//生成点函数
    {
        dots = new GameObject[numberOfDots];//为dot赋予实例数量
        for (int i = 0; i < numberOfDots; i++)
        {
            dots[i] = Instantiate(dotPrefab, player.transform.position, Quaternion.identity, dotsParent);//对象与世界轴或父轴完全对齐
            //https://docs.unity3d.com/cn/current/ScriptReference/Quaternion-identity.html
            dots[i].SetActive(false);//关闭dot
        }
    }

    private Vector2 DotsPosition(float t)//传入顺序相关的点间距
    {
        Vector2 position = (Vector2)player.transform.position +
            new Vector2
            (AimDirection().normalized.x * launchForce.x,
             AimDirection().normalized.y * launchForce.y) * t  //基本间距
             + .5f * (Physics2D.gravity * swordGravity) * (t * t)//重力影响
             ;
        //t是控制之间点间距的
        return position;//返回位置
    }//设置点间距函数
    #endregion
}
Clone_Skill_Controller.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//Clone_Skill_Controller是绑在Clone体上的也就是Prefah上的
public class Clone_Skill_Controller : MonoBehaviour
{
    private Player player;
    private Animator anim;//声明animator
    private SpriteRenderer sr;//定义Sr
    [SerializeField] private float colorLoosingSpeed;//加速消失时间 

    private float cloneTimer;//定时器
    [SerializeField]private Transform attackCheck;
    [SerializeField] private float attackCheckRadius = .8f;
    private Transform closestEnemy;
    
    private int facingDir = 1;//这个是控制位置的,产生的克隆体的位置能在敌人外侧

    private bool canDuplicateClone;
    private float chanceToDuplicate;

    private void Awake()
    {
        sr = GetComponent<SpriteRenderer>();//拿到Sr
        anim = GetComponent<Animator>();//拿到anim
    }
    private void Update()
    {
        cloneTimer -= Time.deltaTime;

        if(cloneTimer < 0)
        {
            sr.color = new Color(1, 1, 1,sr.color.a-(Time.deltaTime * colorLoosingSpeed));//设置sr消失
        }
        if(sr.color.a<0)
        {
            Destroy(gameObject);
        }
    }
    public void SetupClone(Transform _newTransform,float _cloneDuration,bool _canAttack,Vector3 _offset,Transform _closestEnemy,bool _canDuplicateClone,float _chanceToDuplicate,Player _player)
    {

        if(_canAttack)
        {                                     
            anim.SetInteger("AttackNumber", Random.Range(1, 3));//返回[minInclusive..maxInclusive](范围包括在内)内的随机浮点值。如果minInclusive大于maxInclusive,则数字会自动交换。
        }

        player = _player;
        //Random.Range()//https://docs.unity3d.com/cn/current/ScriptReference/Random.Range.html
        transform.position = _newTransform.position+_offset;//这个函数实现了将克隆出来的对象的位置与Dash之前的位置重合的效果


        cloneTimer = _cloneDuration;
        
        closestEnemy = _closestEnemy;
        canDuplicateClone = _canDuplicateClone;
        chanceToDuplicate = _chanceToDuplicate;


        FaceCloseTarget();
    }

    private void AnimationTrigger()
    {
        cloneTimer = -.1f;
    }
    private void AttackTrigger()
    {
        Collider2D[] colliders = Physics2D.OverlapCircleAll(attackCheck.position, attackCheckRadius);//创建一个碰撞器组,保存所有圈所碰到的碰撞器
        //https://docs.unity3d.com/2022.3/Documentation/ScriptReference/Physics2D.OverlapCircleAll.html
        foreach (var hit in colliders)//https://blog.csdn.net/m0_52358030/article/details/121722077
        {
            if (hit.GetComponent<Enemy>() != null)
            {
                player.stats.DoDamage(hit.GetComponent<CharacterStats>());

                //使角色克隆体的攻击有概率产生新的克隆体
                if (canDuplicateClone)
                {
                    if(Random.Range(1,100)<chanceToDuplicate)
                    {
                        SkillManager.instance.clone.CreateClone(hit.transform, new Vector3(1.5f*facingDir, 0));
                    }
                }
            }
        }
    }

    private void FaceCloseTarget()
    {
        if(closestEnemy != null)
        {
            if(transform.position.x>closestEnemy.position.x)//敌人在左面,转一圈
            {
                facingDir = -1;//这个是控制位置的
                transform.Rotate(0,180,0);
              
            }
        }

    }
}

Clone_Skill.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Clone_Skill : Skill
{
    [Header("Clone Info")]
    [SerializeField] private GameObject clonePrefab;//克隆原型
    [SerializeField] private float cloneDuration;//克隆持续时间

    [SerializeField] private bool canAttack;// 判断是否可以攻击

    [SerializeField] private bool createCloneOnDashStart;
    [SerializeField] private bool createCloneOnDashOver;
    [SerializeField] private bool canCreateCloneOnCounterAttack;

    [Header("Clone can duplicate")]
    [SerializeField] private bool canDuplicateClone;
    [SerializeField] private float chanceToDuplicate;

    [Header("Crystal instead of clone")]
    public bool crystalInsteadOfClone;


    public void CreateClone(Transform _clonePosition,Vector3 _offset)//传入克隆位置
    {
        if(crystalInsteadOfClone)
        {
            SkillManager.instance.crystal.CreateCrystal();
           
            return;
        }//让所有的生成克隆的技能都变成生成水晶


        GameObject newClone = Instantiate(clonePrefab);//创建新的克隆//克隆 original 对象并返回克隆对象。
        //https://docs.unity3d.com/cn/current/ScriptReference/Object.Instantiate.html

        newClone.GetComponent<Clone_Skill_Controller>().SetupClone(_clonePosition,cloneDuration,canAttack,_offset,FindClosestEnemy(newClone.transform),canDuplicateClone,chanceToDuplicate,player);//调试clone的位置,同时调试克隆持续时间                                                                                            //Controller绑在克隆原型上的,所以用GetComponent                                                                                        
    }

    //让冲刺留下来的克隆在开始和结束各有一个
    public void CreateCloneOnDashStart()
    {
        if (createCloneOnDashStart)
            CreateClone(player.transform, Vector3.zero);
    }

    public void CreateCloneOnDashOver()
    {
        if(createCloneOnDashOver)
            CreateClone(player.transform, Vector3.zero);
    }

    //反击后产生一个克隆被刺敌人
    public void CanCreateCloneOnCounterAttack(Transform _enemyTransform)
    {
        if (canCreateCloneOnCounterAttack)
            StartCoroutine(CreateCloneWithDelay(_enemyTransform, new Vector3(1 * player.facingDir, 0, 0)));
    }
    //整个延迟生成
    private IEnumerator CreateCloneWithDelay(Transform _enemyTransform, Vector3 _offset)
    {
        yield return new WaitForSeconds(.4f);
        CreateClone(_enemyTransform, _offset);

    }
}

Blackhole_Skill_Controller.cs
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;

public class Blackhole_Skill_Controller : MonoBehaviour
{
    [SerializeField] private GameObject hotKeyPrefab;
    [SerializeField] private List<KeyCode> KeyCodeList;

    private float maxSize;//最大尺寸
    private float growSpeed;//变大速度
    private float shrinkSpeed;//缩小速度
    private float blackholeTimer;

    private bool canGrow = true;//是否可以变大
    private bool canShrink;//缩小
    private bool canCreateHotKeys = true;专门控制后面进入的没法生成热键
    private bool cloneAttackReleased;
    private bool playerCanDisaper = true;

    private int amountOfAttacks = 4;
    private float cloneAttackCooldown = .3f;
    private float cloneAttackTimer;

    private List<Transform> targets = new List<Transform>();
    private List<GameObject> createdHotKey = new List<GameObject>();

    public bool playerCanExitState { get; private set; }

    
    public void SetupBlackhole(float _maxSize,float _growSpeed,float _shrinkSpeed,int _amountOfAttacks,float _cloneAttackCooldown,float _blackholeDuration)
    {
        maxSize = _maxSize;
        growSpeed = _growSpeed;
        shrinkSpeed = _shrinkSpeed;
        amountOfAttacks = _amountOfAttacks;
        cloneAttackCooldown = _cloneAttackCooldown;
        blackholeTimer = _blackholeDuration;

        if (SkillManager.instance.clone.crystalInsteadOfClone)//释放水晶时角色不消失
            playerCanDisaper = false;

            
    }

    private void Update()
    {
        blackholeTimer -= Time.deltaTime;
        cloneAttackTimer -= Time.deltaTime;

        if(blackholeTimer <= 0)
        {
            blackholeTimer = Mathf.Infinity;//防止重复检测
            if (targets.Count > 0)//只有有target才释放攻击
            {
                ReleaseCloneAttack();//释放攻击
            }
            else
               
                FinishBlackholeAbility();//缩小黑洞
        }

        if (Input.GetKeyDown(KeyCode.R)&& targets.Count > 0)
        {
            
            ReleaseCloneAttack();
        }

        CloneAttackLogic();

        if (canGrow && !canShrink)
        {
            //这是控制物体大小的参数
            transform.localScale = Vector2.Lerp(transform.localScale, new Vector2(maxSize, maxSize), growSpeed * Time.deltaTime);
            //类似MoveToward,不过是放大到多少大小 https://docs.unity3d.com/cn/current/ScriptReference/Vector2.Lerp.html
        }
        if (canShrink)
        {
            transform.localScale = Vector2.Lerp(transform.localScale, new Vector2(0, 0), shrinkSpeed * Time.deltaTime);

            if (transform.localScale.x <= 1f)
            {
                Destroy(gameObject);
            }
        }
    }

    //释放技能
    private void ReleaseCloneAttack()
    {
        cloneAttackReleased = true;
        canCreateHotKeys = false;

        DestroyHotKeys();

        if(playerCanDisaper)
        {
            playerCanDisaper = false;
            PlayerManager.instance.player.fx.MakeTransprent(true);
        }
       
    }

    private void CloneAttackLogic()
    {
        if (cloneAttackTimer < 0 && cloneAttackReleased&&amountOfAttacks>0)
        {
            cloneAttackTimer = cloneAttackCooldown;

            int randomIndex = Random.Range(0, targets.Count);


            //限制攻击次数和设置攻击偏移量
            float _offset;

            if (Random.Range(0, 100) > 50)
                _offset = 1.5f;
            else
                _offset = -1.5f;

           
            if (SkillManager.instance.clone.crystalInsteadOfClone)
            {
                SkillManager.instance.crystal.CreateCrystal(); //让生成克隆变成生成水晶
                SkillManager.instance.crystal.CurrentCrystalChooseRandomTarget(); //让黑洞里替换出来的水晶能够随机选择目标

            }
            else
            {
                SkillManager.instance.clone.CreateClone(targets[randomIndex], new Vector3(_offset, 0, 0));
            }
            
            amountOfAttacks--;


            if (amountOfAttacks <= 0)
            {
                Invoke("FinishBlackholeAbility", 0.5f);
            }
        }
    }

    //完成黑洞技能后
    private void FinishBlackholeAbility()
    {
        DestroyHotKeys();
        canShrink = true;
        cloneAttackReleased = false;
        playerCanExitState = true;
        
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if(collision.GetComponent<Enemy>()!=null)
        {
            collision.GetComponent<Enemy>().FreezeTime(true);
            CreateHotKey(collision);
        }
    }
    private void OnTriggerExit2D(Collider2D collision)
    {
        if (collision.GetComponent<Enemy>() != null)
        {
            collision.GetComponent<Enemy>().FreezeTime(false);
           
        }
    }

    //创建QTE函数
    private void CreateHotKey(Collider2D collision)
    {
        if(KeyCodeList.Count == 0)//当所有的KeyCode都被去除,就不在创建实例
        {
            return;
        }

        if(!canCreateHotKeys)//这是当角色已经开大了,不在创建实例
        {
            return;
        }
        
        //创建实例
        GameObject newHotKey = Instantiate(hotKeyPrefab, collision.transform.position + new Vector3(0, 2), Quaternion.identity);

        //将实例添加进列表
        createdHotKey.Add(newHotKey);


        //随机KeyCode传给HotKey,并且传过去一个毁掉一个
        KeyCode choosenKey = KeyCodeList[Random.Range(0, KeyCodeList.Count)];

        KeyCodeList.Remove(choosenKey);

        Blackhole_Hotkey_Controller newHotKeyScript = newHotKey.GetComponent<Blackhole_Hotkey_Controller>();

        newHotKeyScript.SetupHotKey(choosenKey, collision.transform, this);
    }

    //添加点击hotkey后对应的敌人进入敌人列表
    public void AddEnemyToList(Transform _myEnemy)
    {
        targets.Add(_myEnemy);
    }

    //销毁Hotkey
    private void DestroyHotKeys()
    {

        if(createdHotKey.Count <= 0)
        {
            return;
        }
        for (int i = 0; i < createdHotKey.Count; i++)
        {
            Destroy(createdHotKey[i]); 
        }

    }
    
}

PlayerBlackholeState.cs
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

public class PlayerBlackholeState : PlayerState
{
    private float flyTime = .4f;//飞行时间
    private bool skillUsed;//技能是否在被使用
    private float defaultGravity;

    public PlayerBlackholeState(Player _player, PlayerStateMachine _stateMachine, string _animBoolName) : base(_player, _stateMachine, _animBoolName)
    {
    }

    public override void AnimationFinishTrigger()
    {
        base.AnimationFinishTrigger();
    }

    public override void Enter()
    {
        base.Enter();

        skillUsed = false;
        stateTimer = flyTime;
        defaultGravity = rb.gravityScale;
        rb.gravityScale = 0;
    }

    public override void Exit()
    {
        base.Exit();
        rb.gravityScale = defaultGravity;
        player.fx.MakeTransprent(false);
    }

    public override void Update()
    {
        base.Update();

        //使角色释放技能后能飞起来
        if (stateTimer > 0)
        {
            rb.velocity = new Vector2(0, 15);
        }

        if(stateTimer < 0)
        {
            rb.velocity = new Vector2(0, -.1f);

            if(!skillUsed)
            {
                if(player.skill.blackhole.CanUseSkill())//创建实体
                skillUsed = true;
            }
        }

        if(player.skill.blackhole.SkillCompleted())
        {
            stateMachine.ChangeState(player.airState);
        }
    }
    
    
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/467501.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

生成微信小程序二维码

首页 -> 统计 可以通过上面二个地方配置&#xff0c;生成小程序的二维码&#xff0c;并且在推广分析里&#xff0c;有详细的分析数据&#xff0c;

【神经网络 基本知识整理】(激活函数) (梯度+梯度下降+梯度消失+梯度爆炸)

神经网络 基本知识整理 激活函数sigmoidtanhsoftmaxRelu 梯度梯度的物理含义梯度下降梯度消失and梯度爆炸 激活函数 我们知道神经网络中前一层与后面一层的连接可以用y wx b表示&#xff0c;这其实就是一个线性表达&#xff0c;即便模型有无数的隐藏层&#xff0c;简化后依旧…

跳绳计数,YOLOV8POSE

跳绳计数&#xff0c;YOLOV8POSE 通过计算腰部跟最初位置的上下波动&#xff0c;计算跳绳的次数

使用Python进行数据库连接与操作SQLite和MySQL【第144篇—SQLite和MySQL】

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 使用Python进行数据库连接与操作&#xff1a;SQLite和MySQL 在现代应用程序开发中&#xf…

Github 2024-03-18开源项目日报Top10

根据Github Trendings的统计,今日(2024-03-18统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目7TypeScript项目3非开发语言项目1Solidity项目1《Hello 算法》:动画图解、一键运行的数据结构与算法教程 创建周期:476 天协议类型…

ubuntu下在vscode中配置matplotlibcpp

ubuntu下在vscode中配置matplotlibcpp 系统&#xff1a;ubuntu IDE&#xff1a;vscode 库&#xff1a;matplotlib-cpp matplotlibcpp.h文件可以此网址下载&#xff1a;https://github.com/lava/matplotlib-cpp 下载的压缩包中有该头文件&#xff0c;以及若干实例程序。 参考…

无人机助力智慧农田除草新模式,基于YOLOv7【tiny/l/x】不同系列参数模型开发构建无人机航拍场景下的农田杂草检测识别系统

科技发展到今天&#xff0c;无人机喷洒药物已经不是一件新鲜事情了&#xff0c;在很多高危的工作领域中&#xff0c;比如高空电力设备除冰&#xff0c;电力设备部件传送更换等等&#xff0c;无人机都可以扮演非常出色的作用&#xff0c;前面回到老家一段时间&#xff0c;最近正…

笔记本固态硬盘损坏数据恢复两种方法 笔记本固态硬盘损坏如何恢复

大家好&#xff01;今天要跟大家分享的是笔记本固态硬盘损坏数据恢复的两种方法。相信很多小伙伴都遇到过这种情况&#xff0c;电脑突然蓝屏或者死机&#xff0c;再开机后发现自己的数据不见了&#xff0c;这时候该怎么办呢&#xff1f;这可真是让人头疼。毕竟&#xff0c;我们…

设计模式学习笔记 - 设计原则与思想总结:2.运用学过的设计原则和思想完善之前性能计数器项目

概述 在 《设计原则 - 10.实战&#xff1a;针对非业务的通用框架开发&#xff0c;如何做需求分析和设计及如何实现一个支持各种统计规则的性能计数器》中&#xff0c;我们讲解了如何对一个性能计数器框架进行分析、设计与实现&#xff0c;并且实践了一些设计原则和设计思想。当…

ASP.NET通过Appliaction和Session统计在人数和历史访问量

目录 背景: Appliaction&#xff1a; Session&#xff1a; 过程&#xff1a; 数据库&#xff1a; Application_Start&#xff1a; Session_Start&#xff1a; Session_End&#xff1a; Application_End&#xff1a; 背景: 事件何时激发Application_Start在调用当前应用…

REDHAWK——连接(续)

文章目录 前言一、突发 IO1、数据传输①、输入②、输出 2、突发信号相关信息 (SRI)3、多输出端口4、使用复数数据①、在 C 中转换复数数据 5、时间戳6、端口统计①、C 二、消息传递1、消息生产者①、创建一个消息生产者②、发送消息 2、消息消费者①、创建消息消费者②、注册接…

Ruoyi前后端分离项目部署至Tomcat上

项目部署 4.1.前端打包 disaster-ui目录下为本项目的前端所在位置&#xff0c;在命令行窗口进入该目录&#xff0c;然后输入npm run build:prod部署前端Vue项目,或者直接在disaster-ui/bin目录下双击build.bat文件部署前端。 图 4-1 前端部署图 4.2 环境变量 在MySQL可视化…

Vmware虚拟机配置虚拟网卡

背景 今天同事咨询了我一个关于虚拟机的问题&#xff0c;关于内网用Vmware安装的虚拟机&#xff0c;无法通过本机访问虚拟上的Jenkins的服务。   验证多次后发现有如下几方面问题。 Jenkins程序包和JDK版本不兼容&#xff08;JDK1.8对应Jenkins不要超过2.3.57&#xff09;虚…

LeetCode每日一题[C++]-303.区域和检索-数组不可变

题目描述 给定一个整数数组 nums&#xff0c;处理以下类型的多个查询: 计算索引 left 和 right &#xff08;包含 left 和 right&#xff09;之间的 nums 元素的 和 &#xff0c;其中 left < right 实现 NumArray 类&#xff1a; NumArray(int[] nums) 使用数组 nums 初…

微信小程序简单实现手势左右滑动和点击滑动步骤条功能

使用微信小程序实现左右滑动功能&#xff0c;自定义顶部图案&#xff0c;点击文字滑动和手势触屏滑动&#xff0c;功能简单&#xff0c;具体实现代码如下所示&#xff1a; 1、wxss代码&#xff1a; /* 步骤条 */ .tab-box {display: flex;flex-direction: row;position: fix…

LVS+Keepalived 高可用群集--部署

实际操作 LVS Keepalived 高可用群集 环境设备 LVS1192.168.6.88 &#xff08;MASTER&#xff09;LVS2192.168.6.87 &#xff08;BACKUP&#xff09;web1192.168.6.188web2192.168.6.189客户端192.168.6.86VIP192.168.6.180 &#xff08;一&#xff09;web服务器 首先配置…

华为汽车业务迎关键节点,长安深蓝加入HI模式,车BU预计今年扭亏

‍编辑 |HiEV 一年之前&#xff0c;同样是在电动汽车百人会的论坛上&#xff0c;余承东在外界对于华为和AITO的质疑声中&#xff0c;第一次公开阐释了华为选择走智选车模式的逻辑。 一年之后&#xff0c;伴随问界M7改款、问界M9上市&#xff0c;华为智选车模式的面貌已经发生了…

Python基于深度学习的中文情感分析系统,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

Javaweb的学习19_CSS概念+css与html的结合方式

CSS CSS&#xff1a;页面美化和布局控制 1. 概念&#xff1a;Cascading Style Sheets 层叠样式表 层叠&#xff1a;多个样式可以作用在同一个html的元素(标签)上&#xff0c;同时生效 2. 好处&#xff1a; 1.功能强大 2.将内容展示(HTML)和样式控制(CSS)分离 *降低耦合度。解耦…

第十八届全国大学生智能汽车竞赛——摄像头算法(附带个人经验)

文章目录 前言一、摄像头图像处理1、摄像头图像采集2、图像二值化与大津算法 二、左右边界&#xff0c;中线扫描 前言 参加了第十六&#xff0c;十七和第十八届全国大学生智能车竞赛&#xff0c;对摄像头的学习有部分心得&#xff0c;分享给大家&#xff0c;三届车赛&#xff…