Unity类银河恶魔城学习记录16-3,4,5,6,p162 Improving skeleton p163 Game pause p164 Dead zone p165 冲刺无敌

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

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

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

public class Enemy : Entity
{
    [SerializeField] protected LayerMask whatIsPlayer;
    [Header("Stun Info")]
    public float stunnedDuration;//stunned持续时间
    public Vector2 stunnedDirection;//stunned改变后的速度
    protected bool canBeStunned;//判断是否可以被反击
    [SerializeField] protected GameObject counterImage;//一个代表着是否可以被反击的信号

    [Header("Move Info")]
    public float moveSpeed;
    public float idleTime;
    public float battleTime;//多久能从battle状态中退出来
    private float defaultMoveSpeed;

    [Header("Attack Info")]
    public float attackDistance;
    public float attackCooldown;
    public float minAttackCooldown;
    public float maxAttackCooldown;
    [HideInInspector] public float lastTimeAttacked;//最后一次攻击的时间
    

    #region 类
    public EnemyStateMachine stateMachine { get; private set; }

    public string lastingAnimBoolName{ get; private set; }

    public virtual void AssignLastAnimName(string _animBoolName)
    {
        lastingAnimBoolName = _animBoolName;
    }
    #endregion

    protected override void Awake()
    {
        base.Awake();
        stateMachine = new EnemyStateMachine();
        defaultMoveSpeed = moveSpeed;

    }
    protected override void Start()
    {
        base.Start();

    }


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

        stateMachine.currentState.Update();

        //Debug.Log(IsPlayerDetected().collider.gameObject.name + "I see");//这串代码会报错,可能使版本的物体,因为在没有找到Player的时候物体是空的,NULL,你想让他在控制台上显示就报错了
    }
    public override void SlowEntityBy(float _slowPercentage, float _slowDuration)
    {
        base.SlowEntityBy(_slowPercentage, _slowDuration);
        moveSpeed = moveSpeed * (1 - _slowPercentage);
        anim.speed = anim.speed * (1 - _slowPercentage);

        Invoke("ReturnDefaultSpeed", _slowDuration);

    }

    protected override void ReturnDefaultSpeed()
    {
        base.ReturnDefaultSpeed();
        moveSpeed = defaultMoveSpeed;

    }
    public virtual void FreezeTime(bool _timeFrozen)
    {
        if(_timeFrozen)
        {
            moveSpeed = 0;
            anim.speed = 0;
        }
        else
        {
            moveSpeed = defaultMoveSpeed;
            anim.speed = 1;
        }
    }
    public virtual void FreezeTimerFor(float _duration) => StartCoroutine(FreezeTimeCoroutine(_duration));
    protected virtual IEnumerator FreezeTimeCoroutine(float _seconds)
    {
        FreezeTime(true);

        yield return new WaitForSeconds(_seconds);

        FreezeTime(false);
    }
    #region Counter Attack Window
    public virtual void OpenCounterAttackWindow()//打开可以反击的信号的函数
    {
        canBeStunned = true;
        counterImage.SetActive(true);
    }
    public virtual void CloseCounterAttackWindow()//关闭可以反击的信号的函数
    {
        canBeStunned = false;
        counterImage.SetActive(false);
    }
    #endregion


    
    public virtual bool CanBeStunned()//这里的主要目的是完成在被反击过后能立刻关闭提示窗口
    {
        if (canBeStunned)
        {
            CloseCounterAttackWindow();
            return true;
        }
        return false;

    }
    public virtual void AnimationFinishTrigger() => stateMachine.currentState.AnimationFinishTrigger();//动画完成时调用的函数,与Player相同
    public virtual RaycastHit2D IsPlayerDetected() => Physics2D.Raycast(wallCheck.position, Vector2.right * facingDir, 7, whatIsPlayer);//用于从射线投射获取信息的结构。
    //该函数的返回值可以变,可以只返回bool,也可以是碰到的结构

    protected override void OnDrawGizmos()
    {
        base.OnDrawGizmos();

        Gizmos.color = Color.yellow;//把线改成黄色
        Gizmos.DrawLine(transform.position, new Vector3(transform.position.x + attackDistance * facingDir, transform.position.y));//用来判别是否进入attackState的线
    }


}
SkeletonBattleState.cs
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
//从ground进来的
public class SkeletonBattleState : EnemyState
{
    private Transform player;//用于给Player定位,好判断怎么跟上他
    private Enemy_Skeleton enemy;
    private int moveDir;
    public SkeletonBattleState(Enemy _enemyBase, EnemyStateMachine _stateMachine, string _animBoolName,Enemy_Skeleton _enemy ) : base(_enemyBase, _stateMachine, _animBoolName)
    {
        enemy = _enemy;
    }

    public override void Enter()
    {
        base.Enter();
        
        //player = GameObject.Find("Player").transform;//全局找Player位置
        player = PlayerManager.instance.player.transform;

        if (player.GetComponent<PlayerStats>().isDead)
            stateMachine.ChangeState(enemy.moveState);
    }

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

    public override void Update()
    {
        base.Update();
        //退出此状态的方式
        if(enemy.IsPlayerDetected())
        {
            stateTimer = enemy.battleTime;
            if (enemy.IsPlayerDetected().distance < enemy.attackDistance)//当距离小于攻击距离,变为攻击状态
            {
                if (CanAttack())
                    stateMachine.ChangeState(enemy.attackState);
            }
        }
        else//当没有看见player后,才会根据没有看到的时间来使其退出battle状态
        {
            if(stateTimer < 0||Vector2.Distance(player.transform.position,enemy.transform.position)>7)//根据距离来判断是否结束battle状态
            {
                stateMachine.ChangeState(enemy.idleState);
            }
        }
        

        //下面为移动方向设置
        if(player.position.x > enemy.transform.position.x)//在右,向右移动
        {
            moveDir = 1;
        }
        else if(player.position.x<enemy.transform.position.x)//在左,向左移动
        {
            moveDir = -1;
        }

        if(Vector2.Distance(player.transform.position,enemy.transform.position)>1)
        enemy.SetVelocity(enemy.moveSpeed * moveDir, rb.velocity.y);
        else
        {
            enemy.SetZeroVelocity();
        }//我自己设置了一个敌人接近一定距离就停下来的设置,防止出现敌人乱晃的情况
    }

    private bool CanAttack()
    {
        if(Time.time > enemy.lastTimeAttacked + enemy.attackCooldown)
        {
            enemy.attackCooldown = Random.Range(enemy.minAttackCooldown, enemy.maxAttackCooldown);
            enemy.lastTimeAttacked = Time.time;
            return true;
        }
        
        return false;
    }
}

 

GameManager.cs
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.SceneManagement;//关于场景的操作

public class GameManager : MonoBehaviour, ISaveManager
{
    public static GameManager instance;

    private Transform player;

    [SerializeField] private Checkpoint[] checkpoints;
    [SerializeField] private string closestCheckpointId;

    [Header("Lost currency")]
    [SerializeField] private GameObject lostCurrencyPerfab;
    public int lostCurrencyAmount;
    [SerializeField] private float lostCurrencyX;
    [SerializeField] private float lostCurrencyY;
    private void Awake()
    {
        if (instance != null)
        {
            Destroy(instance.gameObject);
        }
        else
            instance = this;

        checkpoints = FindObjectsOfType<Checkpoint>();
        player = PlayerManager.instance.player.transform;

    }

    private void Start()
    {
        checkpoints = FindObjectsOfType<Checkpoint>();

    }
    public void RestratScene()//场景重开函数
    {
        SaveManager.instance.SaveGame();
        Scene scene = SceneManager.GetActiveScene();//获得初始场景
        SceneManager.LoadScene(scene.name);//获取的场景必须通过字符串载入
    }

    public void LoadData(GameData _data)
    {
        


        foreach (KeyValuePair<string, bool> pair in _data.checkpoints)
        {

            foreach (Checkpoint checkpoint in checkpoints)
            {

                if (checkpoint.id == pair.Key && pair.Value == true)
                {
                    checkpoint.ActivateCheckpoint();

                }
            }
        }
        closestCheckpointId = _data.closestCheckpointId;

        PlacePlayerAtClosestCheckpoint();
        LoadLostCurrency(_data);
    }

    private void LoadLostCurrency(GameData _data)//产生可以捡到的钱尸体函数
    {

        lostCurrencyAmount = _data.lostCurrencyAmount;
        lostCurrencyX = _data.lostCurrencyX;
        lostCurrencyY = _data.lostCurrencyY;

        if(lostCurrencyAmount > 0)
        {

            GameObject newLostCurrency = Instantiate(lostCurrencyPerfab, new Vector3(lostCurrencyX, lostCurrencyY), Quaternion.identity);
            newLostCurrency.GetComponent<LostCurrencyController>().currency = lostCurrencyAmount;
        }

        lostCurrencyAmount = 0;
    }


    private void PlacePlayerAtClosestCheckpoint()//传送至最近检查点函数
    {

        foreach (Checkpoint checkpoint in checkpoints)
        {

            if (closestCheckpointId == checkpoint.id && checkpoint.activationStatus)
            {

                PlayerManager.instance.player.transform.position = checkpoint.transform.position;
            }
        }
    }

    public void SaveData(ref GameData _data)
    {
        _data.lostCurrencyAmount = lostCurrencyAmount;//此地方的钱是player死后stats赋值的
        _data.lostCurrencyX = player.position.x;
        _data.lostCurrencyY = player.position.y;

        if(FindClosestCheckpoint()!=null)
            _data.closestCheckpointId = FindClosestCheckpoint().id;//Save后调用
        _data.checkpoints.Clear();
        foreach (Checkpoint checkpoint in checkpoints)
        {
            _data.checkpoints.Add(checkpoint.id, checkpoint.activationStatus);
        }
        
    }

    private Checkpoint FindClosestCheckpoint()//寻找最近检查点的函数
    {
        float closetDistance = Mathf.Infinity;
        Checkpoint closestCheckpoint = null;

        foreach (var checkpoint in checkpoints)//遍历检查点比较距离寻找最近的检查点
        {
            float distanceToCheckpoint = Vector2.Distance(PlayerManager.instance.player.transform.position, checkpoint.transform.position);
            if (distanceToCheckpoint < closetDistance && checkpoint.activationStatus == true)
            {
                closetDistance = distanceToCheckpoint;
                closestCheckpoint = checkpoint;
            }
        }

        return closestCheckpoint;

    }

    public void PauseGame(bool _pause)
    {
        if (_pause)
            Time.timeScale = 0;
        else
            Time.timeScale = 1;
    }
}
Player.cs
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

public class Player : Entity
{
    [Header("Attack Details")]
    public Vector2[] attackMovement;//每个攻击时获得的速度组
    public float counterAttackDuration = .2f;
    


    public bool isBusy{ get; private set; }//防止在攻击间隔中进入move
    //
    [Header("Move Info")]
    public float moveSpeed;//定义速度,与xInput相乘控制速度的大小
    public float jumpForce;
    public float swordReturnImpact;//在player里设置swordReturnImpact作为击退的参数
    private float defaultMoveSpeed;
    private float defaultJumpForce;

    [Header("Dash Info")]
    [SerializeField] private float dashCooldown;
    private float dashUsageTimer;//为dash设置冷却时间,在一定时间内不能连续使用
    public float dashSpeed;//冲刺速度
    public float dashDuration;//持续时间
    private float defaultDashSpeed;

    public float dashDir { get; private set; }


    
    
    #region 定义States
    public PlayerStateMachine stateMachine { get; private set; }
    public PlayerIdleState idleState { get; private set; }
    public PlayerMoveState moveState { get; private set; }
    public PlayerJumpState jumpState { get; private set; }
    public PlayerAirState airState { get; private set; }
    public PlayerDashState dashState { get; private set; }
    public PlayerWallSlideState wallSlide { get; private set; }
    public PlayerWallJumpState wallJump { get; private set; }
    public PlayerDeadState deadState { get; private set; }
    


    public PlayerPrimaryAttackState primaryAttack { get; private set; }
    public PlayerCounterAttackState counterAttack { get; private set; }

    public PlayerAimSwordState aimSword { get; private set; }
    public PlayerCatchSwordState catchSword { get; private set; }
    public PlayerBlackholeState blackhole { get; private set; }


    public SkillManager skill { get; private set; }
    public GameObject sword{ get; private set; }//声明sword


   
    #endregion
    protected override void Awake()
    {

        base.Awake();
        stateMachine = new PlayerStateMachine();
        //通过构造函数,在构造时传递信息
        idleState = new PlayerIdleState(this, stateMachine, "Idle");
        moveState = new PlayerMoveState(this, stateMachine, "Move");
        jumpState = new PlayerJumpState(this, stateMachine, "Jump");
        airState = new PlayerAirState(this, stateMachine, "Jump");
        dashState = new PlayerDashState(this, stateMachine, "Dash");
        wallSlide = new PlayerWallSlideState(this, stateMachine, "WallSlide");
        wallJump = new PlayerWallJumpState(this, stateMachine, "Jump");//wallJump也是Jump动画
        deadState = new PlayerDeadState(this, stateMachine, "Die");


        primaryAttack = new PlayerPrimaryAttackState(this, stateMachine, "Attack");
        counterAttack = new PlayerCounterAttackState(this, stateMachine, "CounterAttack");

        aimSword = new PlayerAimSwordState(this,stateMachine, "AimSword");
        catchSword = new PlayerCatchSwordState(this, stateMachine, "CatchSword");
        blackhole = new PlayerBlackholeState(this, stateMachine, "Jump");

        //this 就是 Player这个类本身
    }//Awake初始化所以State,为所有State传入各自独有的参数,及animBool,以判断是否调用此动画(与animatoin配合完成)
    protected override void Start()
    {
        base.Start();
        stateMachine.Initialize(idleState);
        skill = SkillManager.instance;

        defaultMoveSpeed = moveSpeed;
        defaultJumpForce = jumpForce;
        defaultDashSpeed = dashSpeed;
        

    }

    protected override void Update()//在mano中update会自动刷新但其他没有mano的不会故,需要在这个updata中调用其他脚本中的函数stateMachine.currentState.update以实现 //stateMachine中的update
    {
        if (Time.timeScale == 0)
            return;

        base.Update();
        stateMachine.currentState.Update();//反复调用CurrentState的Update函数
        CheckForDashInput();

        if(Input.GetKeyDown(KeyCode.F)&&skill.crystal.crystalUnlocked)
        {
            skill.crystal.CanUseSkill();
        }

        if(Input.GetKeyDown(KeyCode.Alpha1))//血瓶回血
        {
            Inventory.instance.UseFlask();
        }
        
    }
    public override void SlowEntityBy(float _slowPercentage, float flowDuration)//减缓一切速度函数
    {
        base.SlowEntityBy(_slowPercentage, flowDuration);
        moveSpeed = moveSpeed * (1 - _slowPercentage);
        jumpForce = jumpForce * (1 - _slowPercentage);
        dashSpeed = dashSpeed * (1 - _slowPercentage);
        anim.speed = anim.speed * (1 - _slowPercentage);

        Invoke("ReturnDefaultSpeed", flowDuration);

    }
    protected override void ReturnDefaultSpeed()//全部速度恢复正常函数
    {
        base.ReturnDefaultSpeed();

        moveSpeed = defaultMoveSpeed;
        jumpForce = defaultJumpForce;
        dashSpeed = defaultDashSpeed;
    }
    public void AssignNewSword(GameObject _newSword)//保持创造的sword实例的函数
    {
        sword = _newSword;
    }

    public void CatchTheSword()//通过player的CatchTheSword进入,及当剑消失的瞬间进入
    {
        stateMachine.ChangeState(catchSword);
        Destroy(sword);
    }
    
    public IEnumerator BusyFor(float _seconds)//https://www.zhihu.com/tardis/bd/art/504607545?source_id=1001
    {
        isBusy = true;
        yield return new WaitForSeconds(_seconds);
        isBusy = false;
    
    }//p39 4.防止在攻击间隔中进入move,通过设置busy值,在使用某些状态时,使其为busy为true,抑制其进入其他state
     //IEnumertor本质就是将一个函数分块执行,只有满足某些条件才能执行下一段代码,此函数有StartCoroutine调用
    public void AnimationTrigger() => stateMachine.currentState.AnimationFinishTrigger();
    //从当前状态拿到AnimationTrigger进行调用的函数

    public void CheckForDashInput()
    {

        
        if (IsWallDetected())
        {
            return;
        }//修复在wallslide可以dash的BUG

        if (skill.dash.dashUnlocked == false)
            return;

        if (Input.GetKeyDown(KeyCode.LeftShift) && skill.dash.CanUseSkill())//将DashTimer<0 的判断 改成DashSkill里的判断
        {

            
            dashDir = Input.GetAxisRaw("Horizontal");//设置一个值,可以将dash的方向改为你想要的方向而不是你的朝向
            if (dashDir == 0)
            {
                dashDir = facingDir;//只有当玩家没有控制方向时才使用默认朝向
            }
            stateMachine.ChangeState(dashState);
        }

    }//将Dash切换设置成一个函数,使其在所以情况下都能使用


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

        stateMachine.ChangeState(deadState);
    }
    protected override void SetupZeroKnockbackPower()//重置被攻击后的被击退的距离函数
    {
        knockbackPower = new Vector2(0, 0);
    }
}
UI.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class UI : MonoBehaviour,ISaveManager
{
    [Header("End screen")]
    [SerializeField] private GameObject fadeScreen;
    [SerializeField] private GameObject endText;
    [SerializeField] private GameObject restartButton;
    [Space]

    [SerializeField] private GameObject characterUI;
    [SerializeField] private GameObject skillTreeUI;
    [SerializeField] private GameObject craftUI;
    [SerializeField] private GameObject optionsUI;
    [SerializeField] private GameObject inGameUI;

    public UI_itemTooltip itemToolTip;
    public UI_statToolTip statToopTip;
    public Ui_SkillToolTip skillToolTip;

    public UI_CraftWindow craftWindow;

    [SerializeField] private UI_VolumeSlider[] volumeSettings;

    public void Awake()
    {
        SwitchTo(skillTreeUI);//修复可能出现skill没法加载成功的bug

    }
    public void Start()
    {
        
        SwitchTo(inGameUI);
        itemToolTip.gameObject.SetActive(false);
        statToopTip.gameObject.SetActive(false);
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.C))
        {
            SwitchWithKeyTo(characterUI);
        }

        if (Input.GetKeyDown(KeyCode.B))
        {
            SwitchWithKeyTo(craftUI);
        }

        if (Input.GetKeyDown(KeyCode.K))
        {
            SwitchWithKeyTo(skillTreeUI);
        }

        if (Input.GetKeyDown(KeyCode.O))
        {
            SwitchWithKeyTo(optionsUI);
        }
    }

    public void SwitchTo(GameObject _menu)//切换窗口函数
    {

        for (int i = 0; i < transform.childCount; i++)
        {
            bool fadeScreen = transform.GetChild(i).GetComponent<UI_FadeScreen>() != null;//保证存在淡入淡出效果的函数时才会为真,才会使darkScreen保持存在

            if (!fadeScreen)
                transform.GetChild(i).gameObject.SetActive(false);
        }

        if (_menu != null)
        {
            AudioManager.instance.PlaySFX(7,null);
            _menu.SetActive(true);
        }

        if(GameManager.instance != null)
        {
            if(_menu == inGameUI)
            {
                GameManager.instance.PauseGame(false);
            }
            else
            {
                if (fadeScreen.GetComponent<Image>().color != Color.clear)
                    GameManager.instance.PauseGame(true);
                else
                    Invoke("PauseGame", 3f);
            }
        }
    }
    private void PauseGame() => GameManager.instance.PauseGame(true);
    public void SwitchWithKeyTo(GameObject _menu)//键盘切换窗口函数
    {
        if (_menu != null && _menu.activeSelf)//通过判断是否传入mune和mune是否激活来决定使设置为可视或不可使
        {
            _menu.SetActive(false);
            CheckForInGameUI();
            return;
        }
        SwitchTo(_menu);
    }

    private void CheckForInGameUI()//当其他UI不在时自动切换值InGameUI函数
    {
        for (int i = 0; i < transform.childCount; i++)
        {
            if (transform.GetChild(i).gameObject.activeSelf && transform.GetChild(i).GetComponent<UI_FadeScreen>() == null) //修复InGameUI在fadeScreen打开后,没法存在的问题
                return;
        }

        SwitchTo(inGameUI);
    }
     
    public void SwitchOnEndScreen()//死亡综合效果函数
    {
        SwitchTo(null);
        fadeScreen.GetComponent<UI_FadeScreen>().FadeOut();
        StartCoroutine(EndScreenCorutine());
    }

    IEnumerator EndScreenCorutine()//死亡显示文本函数
    {
        yield return new WaitForSeconds(1);
        endText.SetActive(true);
        yield return new WaitForSeconds(1.5f);
        restartButton.SetActive(true);
    }

    public void RestartGameButton()//场景重开函数
    {
        GameManager.instance.RestratScene();//调用GameManager的重开函数
    }

    public void LoadData(GameData _data)
    {
        foreach(KeyValuePair<string,float> pair in _data.volumeSettings)
        {
            foreach(UI_VolumeSlider item in volumeSettings)
            {
                if(item.parametr == pair.Key)
                {
                    item.LoadSlider(pair.Value);
                }    
            }
        }
    }

    public void SaveData(ref GameData _data)
    {
        _data.volumeSettings.Clear();
        foreach(UI_VolumeSlider item in volumeSettings)
        {
            _data.volumeSettings.Add(item.parametr, item.slider.value);
        }
    }
}

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

public class DeadZone : MonoBehaviour
{
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.GetComponent<CharacterStats>() != null)
            collision.GetComponent<CharacterStats>().KillEntity();
        else
            Destroy(collision.gameObject);//这是摧毁物体的
    }
}
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)//计算后造成伤害函数
    {
        if (TargetCanAvoidAttack(_targetStats))设置闪避
        {
            return;
        }

        _targetStats.GetComponent<Entity>().SetupKnockbackDir(transform);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值