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