Unity类银河恶魔城学习记录9-7 p88 Crystal instead of Clone源代码

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

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

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

    }
    
}

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

public class Blackhole_Skill : Skill
{

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

    [SerializeField] private GameObject blackholePrefab;
    [Space]

    [SerializeField] private float blackholeDuration;
    [SerializeField] int amountOfAttacks = 4;
    [SerializeField] float cloneAttackCooldown = .3f;


    



    Blackhole_Skill_Controller currentBlackhole;

    public override bool CanUseSkill()
    {
        return base.CanUseSkill();
    }

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

        GameObject newBlackhole = Instantiate(blackholePrefab,player.transform.position,Quaternion.identity);

        currentBlackhole = newBlackhole.GetComponent<Blackhole_Skill_Controller>();

        currentBlackhole.SetupBlackhole(maxSize,growSpeed,shrinkSpeed,amountOfAttacks,cloneAttackCooldown,blackholeDuration);
    }

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

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

    public bool SkillCompleted()
    {
        if(currentBlackhole == null)
         return false;
        if (currentBlackhole.playerCanExitState)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    //把随机敌人半径改成黑洞半径的一半就行
    public float GetBlackholeRadius()
    {
        return maxSize / 2;
    }
}

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

public class Crystal_Skill : Skill
{
    [SerializeField] private GameObject crystalPrefab;
    [SerializeField] private float crystalDuration;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);//调试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);

    }
}

    private GameObject currentCrystal;

    [Header("Crystal mirage")]
    [SerializeField] private bool cloneInsteadOfCrystal;

    [Header("Explosive crystal")]
    [SerializeField] private bool canExplode;

    [Header("Moving crystal")]
    [SerializeField] private bool canMoveToEnemy;
    [SerializeField] private float moveSpeed;

    [Header("Multi stacking crystal")]
    [SerializeField] private bool canUseMultiStacks;
    [SerializeField] private int amountOfStacks;
    [SerializeField] private float multiStackCooldown;
    [SerializeField] private List<GameObject> crystalLeft = new List<GameObject>();//水晶列表
    [SerializeField] private float useTimeWindow;

    public override bool CanUseSkill()
    {
        return base.CanUseSkill();
    }

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


        if (CanUseMultiCrystal())
            return;

        if (currentCrystal == null)
        {
            CreateCrystal();
        }
        else
        {
            //限制玩家在水晶可以移动时瞬移
            if (canMoveToEnemy)
                return;
            //爆炸前与角色交换位置
            Vector2 playerPos = player.transform.position;
            player.transform.position = currentCrystal.transform.position;
            currentCrystal.transform.position = playerPos;


            //水晶互换时在水晶处出现clone,水晶消失
            if (cloneInsteadOfCrystal)
            {
                SkillManager.instance.clone.CreateClone(currentCrystal.transform,Vector3.zero);
                Destroy(currentCrystal);
            }
            else
            {
                currentCrystal.GetComponent<Crystal_Skill_Controller>()?.FinishCrystal();
            }       
        }
    }

    public void CreateCrystal()
    {
        currentCrystal = Instantiate(crystalPrefab, player.transform.position, Quaternion.identity);

        Crystal_Skill_Controller currentCrystalScripts = currentCrystal.GetComponent<Crystal_Skill_Controller>();

        currentCrystalScripts.SetupCrystal(crystalDuration, canExplode, canMoveToEnemy, moveSpeed, FindClosestEnemy(currentCrystal.transform));

       
    }

    public void CurrentCrystalChooseRandomTarget() => currentCrystal.GetComponent<Crystal_Skill_Controller>().ChooseRandomEnemy();

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

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

    private bool CanUseMultiCrystal()//将List里的东西实例化函数
    {
        if(canUseMultiStacks)
        {
            if(crystalLeft.Count > 0&&cooldownTimer<0)
            {
                if(crystalLeft.Count == amountOfStacks)
                {
                    Invoke("ResetAbility", useTimeWindow);// 设置自动补充水晶函数
                }

                cooldown = 0;
                GameObject crystalToSpawn = crystalLeft[crystalLeft.Count - 1];
                GameObject newCrystal = Instantiate(crystalToSpawn, player.transform.position, Quaternion.identity);

                crystalLeft.Remove(crystalToSpawn);

                newCrystal.GetComponent<Crystal_Skill_Controller>().SetupCrystal(crystalDuration, canExplode, canMoveToEnemy, moveSpeed, FindClosestEnemy(newCrystal.transform));

                //当水晶发射完设置冷却时间和使用补充水晶
                if (crystalLeft.Count<=0)
                {
                    cooldown = multiStackCooldown;

                    RefilCrystal();
                }
            }
            return true;
        }

        return false;
    }
    private void RefilCrystal()//给List填充Prefab函数
    {
        int amountToAdd = amountOfStacks - crystalLeft.Count;
        for (int i = 0;i < amountToAdd; i++)
        {
            crystalLeft.Add(crystalPrefab);
        }
    }

    private void ResetAbility()//自动补充水晶函数
    {
        

        if (cooldownTimer > 0)
            return;

        cooldown = multiStackCooldown;
        RefilCrystal();
    }
}
Crystal_Skill_Controller
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Crystal_Skill_Controller : MonoBehaviour
{
    private Animator anim => GetComponent<Animator>();
    private CircleCollider2D cd => GetComponent<CircleCollider2D>();

    private float crystalExitTimer;

    private bool canExplode;
    private bool canMove;
    private float moveSpeed;

    private bool canGrow;
    private float growSpeed = 5;

    private Transform closestTarget;

    [SerializeField] private LayerMask whatIsEnemy;


   public void SetupCrystal(float _crystalDuration,bool _canExplode,bool _canMove,float _moveSpeed,Transform _closestTarget)
    {
        crystalExitTimer = _crystalDuration;
        canExplode = _canExplode;
        canMove = _canMove;
        moveSpeed = _moveSpeed;
        closestTarget = _closestTarget;

    }

    //让黑洞里替换出来的水晶能够随机选择目标

    public void ChooseRandomEnemy()//Ctrl里写函数,让最近敌人改成列表里的随机敌人
    {
        float radius = SkillManager.instance.blackhole.GetBlackholeRadius();//把随机敌人半径改成黑洞半径的一半就行
        Collider2D[] colliders = Physics2D.OverlapCircleAll(transform.position, 50, whatIsEnemy);

        if(colliders.Length >= 0)
        {
            closestTarget = colliders[Random.Range(0,colliders.Length)].transform;
            Debug.Log("Give Random");
        }
    }
    private void Update()
    {
        crystalExitTimer -= Time.deltaTime;
        if (crystalExitTimer < 0)
        {
            FinishCrystal();
        }

        //可以运动就靠近敌人后爆炸,范围小于1时爆炸,并且爆炸时不能移动
        if (canMove)
        {
            //修复攻击范围内没有敌人会报错的bug
            if(closestTarget != null)
            {
                transform.position = Vector2.MoveTowards(transform.position, closestTarget.position, moveSpeed * Time.deltaTime);
                if (Vector2.Distance(transform.position, closestTarget.position) < 1)
                {
                    FinishCrystal();
                    canMove = false;
                }
            }
            else
                transform.position = Vector2.MoveTowards(transform.position, transform.position+new Vector3(5,0,0), moveSpeed * Time.deltaTime);
           
        }

        //爆炸瞬间变大
        if (canGrow)
            transform.localScale = Vector2.Lerp(transform.localScale, new Vector2(3, 3), growSpeed * Time.deltaTime);

    }

    //爆炸造成伤害
    private void AnimationExplodeEvent()
    {
        Collider2D[] colliders = Physics2D.OverlapCircleAll(transform.position, cd.radius);
        foreach(var hit in colliders)
        {
            if (hit.GetComponent<Enemy>() != null)
                hit.GetComponent<Enemy>().Damage();
        }
          
    }

    public void FinishCrystal()
    {
        if (canExplode)
        {
            canGrow = true;
            anim.SetBool("Explode",true);
        }
        else
        {
            SelfDestory();
        }
    }

    public void SelfDestory() => Destroy(gameObject);
}

Crystal_Skill
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Crystal_Skill : Skill
{
    [SerializeField] private GameObject crystalPrefab;
    [SerializeField] private float crystalDuration;
    private GameObject currentCrystal;

    [Header("Crystal mirage")]
    [SerializeField] private bool cloneInsteadOfCrystal;

    [Header("Explosive crystal")]
    [SerializeField] private bool canExplode;

    [Header("Moving crystal")]
    [SerializeField] private bool canMoveToEnemy;
    [SerializeField] private float moveSpeed;

    [Header("Multi stacking crystal")]
    [SerializeField] private bool canUseMultiStacks;
    [SerializeField] private int amountOfStacks;
    [SerializeField] private float multiStackCooldown;
    [SerializeField] private List<GameObject> crystalLeft = new List<GameObject>();//水晶列表
    [SerializeField] private float useTimeWindow;

    public override bool CanUseSkill()
    {
        return base.CanUseSkill();
    }

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


        if (CanUseMultiCrystal())
            return;

        if (currentCrystal == null)
        {
            CreateCrystal();
        }
        else
        {
            //限制玩家在水晶可以移动时瞬移
            if (canMoveToEnemy)
                return;
            //爆炸前与角色交换位置
            Vector2 playerPos = player.transform.position;
            player.transform.position = currentCrystal.transform.position;
            currentCrystal.transform.position = playerPos;


            //水晶互换时在水晶处出现clone,水晶消失
            if (cloneInsteadOfCrystal)
            {
                SkillManager.instance.clone.CreateClone(currentCrystal.transform,Vector3.zero);
                Destroy(currentCrystal);
            }
            else
            {
                currentCrystal.GetComponent<Crystal_Skill_Controller>()?.FinishCrystal();
            }       
        }
    }

    public void CreateCrystal()
    {
        currentCrystal = Instantiate(crystalPrefab, player.transform.position, Quaternion.identity);

        Crystal_Skill_Controller currentCrystalScripts = currentCrystal.GetComponent<Crystal_Skill_Controller>();

        currentCrystalScripts.SetupCrystal(crystalDuration, canExplode, canMoveToEnemy, moveSpeed, FindClosestEnemy(currentCrystal.transform));

       
    }

    public void CurrentCrystalChooseRandomTarget() => currentCrystal.GetComponent<Crystal_Skill_Controller>().ChooseRandomEnemy();

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

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

    private bool CanUseMultiCrystal()//将List里的东西实例化函数
    {
        if(canUseMultiStacks)
        {
            if(crystalLeft.Count > 0&&cooldownTimer<0)
            {
                if(crystalLeft.Count == amountOfStacks)
                {
                    Invoke("ResetAbility", useTimeWindow);// 设置自动补充水晶函数
                }

                cooldown = 0;
                GameObject crystalToSpawn = crystalLeft[crystalLeft.Count - 1];
                GameObject newCrystal = Instantiate(crystalToSpawn, player.transform.position, Quaternion.identity);

                crystalLeft.Remove(crystalToSpawn);

                newCrystal.GetComponent<Crystal_Skill_Controller>().SetupCrystal(crystalDuration, canExplode, canMoveToEnemy, moveSpeed, FindClosestEnemy(newCrystal.transform));

                //当水晶发射完设置冷却时间和使用补充水晶
                if (crystalLeft.Count<=0)
                {
                    cooldown = multiStackCooldown;

                    RefilCrystal();
                }
            }
            return true;
        }

        return false;
    }
    private void RefilCrystal()//给List填充Prefab函数
    {
        int amountToAdd = amountOfStacks - crystalLeft.Count;
        for (int i = 0;i < amountToAdd; i++)
        {
            crystalLeft.Add(crystalPrefab);
        }
    }

    private void ResetAbility()//自动补充水晶函数
    {
        

        if (cooldownTimer > 0)
            return;

        cooldown = multiStackCooldown;
        RefilCrystal();
    }
}

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-11 14:12:01       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-11 14:12:01       100 阅读
  3. 在Django里面运行非项目文件

    2024-03-11 14:12:01       82 阅读
  4. Python语言-面向对象

    2024-03-11 14:12:01       91 阅读

热门阅读

  1. QT状态机入门

    2024-03-11 14:12:01       40 阅读
  2. 【设计模式】概述及七大设计原则

    2024-03-11 14:12:01       50 阅读
  3. error:0308010C:digital envelope routines::unsupported

    2024-03-11 14:12:01       44 阅读
  4. 关于Mac宿主机无法ping通Docker容器的问题

    2024-03-11 14:12:01       41 阅读
  5. Android调用浏览器打开指定页面

    2024-03-11 14:12:01       50 阅读
  6. antd Tree拖拽位置说明

    2024-03-11 14:12:01       36 阅读
  7. C++基础语法和概念

    2024-03-11 14:12:01       32 阅读
  8. mysql 分组取前10条数据

    2024-03-11 14:12:01       41 阅读
  9. MySql的CURRENT_TIMESTAMP和ON UPDATE CURRENT_TIMESTAMP

    2024-03-11 14:12:01       41 阅读