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