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();//释放攻击}elseFinishBlackholeAbility();//缩小黑洞}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);}//销毁Hotkeyprivate 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.htmlnewClone.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){//修复攻击范围内没有敌人会报错的bugif(closestTarget != null){transform.position = Vector2.MoveTowards(transform.position, closestTarget.position, moveSpeed * Time.deltaTime);if (Vector2.Distance(transform.position, closestTarget.position) < 1){FinishCrystal();canMove = false;}}elsetransform.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();}
}