Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释,可供学习Alex教程的人参考
此代码仅为较上一P有所改变的代码
【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili
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;//定时器
private float attackMultiplier;
[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,float _attackMultiplier)
{
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之前的位置重合的效果
attackMultiplier = _attackMultiplier;
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)
{
hit.GetComponent<Entity>().SetupKnockbackDir(transform);
PlayerStats playerStats = player.GetComponent<PlayerStats>();
EnemyStats enemyStats = hit.GetComponent<EnemyStats>();
playerStats.CloneDoDamage(enemyStats, attackMultiplier);
if(player.skill.clone.canApplyOnHitEffect)
{
if(Inventory.instance.GetEquipment(EquipmentType.Weapon) != null)//攻击带装备特效
Inventory.instance.GetEquipment(EquipmentType.Weapon).Effect(hit.transform);
}
//使角色克隆体的攻击有概率产生新的克隆体
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);
}
}
}
}
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 Player player;
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,Player _player)
{
crystalExitTimer = _crystalDuration;
canExplode = _canExplode;
canMove = _canMove;
moveSpeed = _moveSpeed;
closestTarget = _closestTarget;
player = _player;
}
//让黑洞里替换出来的水晶能够随机选择目标
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<Entity>().SetupKnockbackDir(transform);
player.stats.DoMagicaDamage(hit.GetComponent<CharacterStats>());
ItemData_Equipment equipment = Inventory.instance.GetEquipment(EquipmentType.Amulet);
if(equipment != null)
{
equipment.Effect(hit.transform);
}
}
}
}
public void FinishCrystal()
{
if (canExplode)
{
canGrow = true;
anim.SetBool("Explode",true);
}
else
{
SelfDestory();
}
}
public void SelfDestory() => Destroy(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; }
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 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();
}
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();
}
}
#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
}
PlayerStat.cs
using UnityEngine;
public class PlayerStats : 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();
GameManager.instance.lostCurrencyAmount = PlayerManager.instance.currency;
PlayerManager.instance.currency = 0;
GetComponent<PlayerItemDrop>()?.GenerateDrop();
}
protected override void DecreaseHealthBy(int _damage)
{
base.DecreaseHealthBy(_damage);
if(_damage > GetMaxHealthValue() *.3f)
{
player.SetupKnockbackPower(new Vector2(10,7));
int randomSound = Random.Range(34, 35);
AudioManager.instance.PlaySFX(randomSound,null);
}
ItemData_Equipment currentArmor = Inventory.instance.GetEquipment(EquipmentType.Armor);
if (currentArmor != null)
{
currentArmor.Effect(player.transform);
}
}
public override void OnEvasion()
{
player.skill.dogge.CreateMirageOnDoDogge();
}
public void CloneDoDamage(CharacterStats _targetStats, float _multiplier)
{
if (TargetCanAvoidAttack(_targetStats))设置闪避
{
return;
}
int totleDamage = damage.GetValue() + strength.GetValue();
if (_multiplier > 0)
{
totleDamage = Mathf.RoundToInt(totleDamage * _multiplier);
}
//爆伤设置
if (CanCrit())
{
totleDamage = CalculateCriticalDamage(totleDamage);
}
totleDamage = CheckTargetArmor(_targetStats, totleDamage);//设置防御
_targetStats.TakeDamage(totleDamage);
DoMagicaDamage(_targetStats); // 可以去了也可以不去
}
}
Entity.cs
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
public class Entity : MonoBehaviour
{
[Header("Knockback info")]
[SerializeField] protected Vector2 knockbackPower;//被击打后的速度信息
[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
public int konckbackDir { get; private set; }
#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;
}
public virtual void SetupKnockbackDir(Transform _damageDirection)
{
if(_damageDirection.position.x > transform.position.x)//如果我的位置大于敌人的位置,及我在右边,使其朝左边
{
konckbackDir = -1;
}
else
{
konckbackDir = 1;
}
}
public void SetupKnockbackPower(Vector2 _knockbackPower)//被造成大量伤害被击退函数
{
knockbackPower = _knockbackPower;
}
protected virtual IEnumerator HitKnockback()
{
isKnocked = true;//此值通过卡住SetVelocity函数的方式用来阻止当一个角色被攻击时,会乱动的情况
rb.velocity = new Vector2(knockbackPower.x * konckbackDir, knockbackPower.y);
yield return new WaitForSeconds(knockbackDuration);
isKnocked = false;
SetupZeroKnockbackPower();
}
//被击打后产生后退效果的函数
#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()
{
}
protected virtual void SetupZeroKnockbackPower()
{
}
}
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
{
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);
}
}
PlayerWallSlideState.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerWallSlideState : PlayerState
{
//在空中碰墙时切换为滑墙状态
public PlayerWallSlideState(Player _player, PlayerStateMachine _stateMachine, string _animBoolName) : base(_player, _stateMachine, _animBoolName)
{
}
public override void Enter()
{
base.Enter();
}
public override void Exit()
{
base.Exit();
}
public override void Update()
{
base.Update();
if(player.IsWallDetected() == false)//修复当墙不存在也没有落地时,依旧做滑墙动作的bug
{
stateMachine.ChangeState(player.airState);
}
if(Input.GetKeyDown(KeyCode.Space))
{
stateMachine.ChangeState(player.wallJump);
return; //由于都是从wallSlide进入,光摁Space会进入其他State,故由return控制
}
if(xInput!= 0&&player.facingDir!=xInput)//这样做是为了保证在没有接触地面的时候也可以切换回idleState,使控制更加灵活
{
stateMachine.ChangeState(player.idleState);
}
if(player.IsGroundDetected())//接触地面切回idleState
{
stateMachine.ChangeState(player.idleState);
}
if (yInput < 0)
rb.velocity = new Vector2(0,rb.velocity.y);//玩家可以控制是否加速滑墙
else
rb.velocity = new Vector2(0, rb.velocity.y * .7f);//玩家没控制时在墙上滑行时速度减慢一点
}
}