Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释,可供学习Alex教程的人参考
此代码仅为较上一P有所改变的代码
【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili
Sword_Skill_Controller.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Burst.CompilerServices;
using UnityEngine;
public class Sword_Skill_Controller : MonoBehaviour
{
private float returnSpeed;
private bool isReturning;
private Animator anim;
private Rigidbody2D rb;
private CircleCollider2D cd;
private Player player;
private bool canRotate = true;
private float freezeTimeDuration;
[Header("Piece info")]
[SerializeField] float pierceAmount;
[Header("Bounce info")]
private float bounceSpeed;//设置弹跳速度
private bool isBouncing;//判断是否可以弹跳
private int bounceAmount;//弹跳的次数
public List<Transform> enemyTargets;//保存所有在剑范围内的敌人的列表
private int targetIndex;//设置targetIndex作为敌人计数器
[Header("Spin info")]
private float maxTravelDistance;//最大攻击距离
private float spinDuration;//持续时间
private float spinTimer;//计时器
private bool wasStopped;//是否停止
private bool isSpinning;
private float hitTimer;
private float hitColldown;
private float spinDirection;//用来不断推进剑在x轴上移动的值
private void Awake()
{
anim = GetComponentInChildren<Animator>();
rb = GetComponent<Rigidbody2D>();
cd = GetComponent<CircleCollider2D>();
}
private void DestroyMe()
{
Destroy(gameObject);
}
public void ReturnSword()
{
rb.constraints = RigidbodyConstraints2D.FreezeAll;//修复剑只要不触碰到物体就无法收回的bug
//rb.isKinematic = false;
transform.parent = null;
isReturning = true;
}
public void SetupSword(Vector2 _dir,float _gravityScale,Player _player,float _freezeTimeDuration,float _returnSpeed)
{
player = _player;
rb.velocity = _dir;
rb.gravityScale = _gravityScale;
freezeTimeDuration = _freezeTimeDuration;
returnSpeed = _returnSpeed;
if(pierceAmount <= 0)
anim.SetBool("Rotation", true); //其次在pierceAmount > 0时不启动旋转动画
spinDirection = Mathf.Clamp(rb.velocity.x, -1, 1);用来不断推进剑在x轴上移动的值
//Mathf.Clamp,当剑使向右飞的时候,direction为1,反之为-1
//https://docs.unity3d.com/cn/current/ScriptReference/Mathf.Clamp.html
Invoke("DestroyMe",7);//在一定时间后自动销毁剑
}
public void SetupBounce(bool _isBouncing,int _bounceAmount,float _bounceSpeed)
{
isBouncing = _isBouncing;
bounceAmount = _bounceAmount;
bounceSpeed = _bounceSpeed;
}
public void SetupPierce(int _pierceAomunt)
{
pierceAmount = _pierceAomunt;
}
public void SetupSpin(bool _isSpinning, float _maxTravelDistance,float _spinDuration,float _hitCooldown)
{
isSpinning = _isSpinning;
maxTravelDistance = _maxTravelDistance;
spinDuration = _spinDuration;
hitColldown = _hitCooldown;
}
private void Update()
{
if (canRotate)
transform.right = rb.velocity;//使武器随着速度而改变朝向
if (isReturning)
{
transform.position = Vector2.MoveTowards(transform.position, player.transform.position, returnSpeed * Time.deltaTime);//从原来的位置返回到player的位置
//并且随着时间增加而增加速度
if (Vector2.Distance(transform.position, player.transform.position) < 1)//当剑与player的距离小于一定距离,清除剑
{
player.CatchTheSword();
}
}
BounceLogic();
SpinLogic();
}
private void SpinLogic()
{
if (isSpinning)//首先当isSpining为true才可进入
{
if (Vector2.Distance(player.transform.position, transform.position) > maxTravelDistance && !wasStopped)//当剑与角色到达最大攻击距离,stop为true,停止剑运动,给剑一个倒计时
{
StopWhenSpinning();
}
if (wasStopped)//当stop为true,倒计时过了,回归角色
{
spinTimer -= Time.deltaTime; //spinDirection一直是1或者-1,但position是变化的
transform.position = Vector2.MoveTowards(transform.position, new Vector2(transform.position.x + spinDirection, transform.position.y), 1.5f * Time.deltaTime);//让剑在x轴上移动的函数
if (spinTimer < 0)
{
isReturning = true;
isSpinning = false;
}
hitTimer -= Time.deltaTime;
if (hitTimer < 0)
{
hitTimer = hitColldown;
Collider2D[] colliders = Physics2D.OverlapCircleAll(transform.position, 1);
foreach (var hit in colliders)
{
if (hit.GetComponent<Enemy>() != null)
{
hit.GetComponent<Enemy>().DamageImpact();
}
}
}
}
}
}
private void StopWhenSpinning()
{
wasStopped = true;
rb.constraints = RigidbodyConstraints2D.FreezeAll;
spinTimer = spinDuration;
}
private void BounceLogic()
{
if (isBouncing && enemyTargets.Count > 0)
{
transform.position = Vector2.MoveTowards(transform.position, enemyTargets[targetIndex].position, bounceSpeed * Time.deltaTime);
//3.当可以弹跳且列表内数量大于0,调用ToWords,这将使剑能够跳到敌人身上
if (Vector2.Distance(transform.position, enemyTargets[targetIndex].position) < .1f)
{
enemyTargets[targetIndex].GetComponent<Enemy>().DamageImpact();
targetIndex++;
bounceAmount--;//设置弹跳次数
if (bounceAmount <= 0)
{
isBouncing = false;
isReturning = true;
}//这样会使当弹跳次数为0时,返回到角色手中
if (targetIndex >= enemyTargets.Count)
{
targetIndex = 0;
}
}//利用与目标距离的判断,使剑接近目标距离时切换到下一个目标。
//且如果所有目标都跳完了,切回第一个
}
}
private void OnTriggerEnter2D(Collider2D collision)//传入碰到的物体的碰撞器
{
if (isReturning)
{
return;
}//修复在返回时扔出时没有碰到任何物体,但返回时碰到了物体导致剑的一些性质发生改变的问题,及回来的时候调用了OnTriggerEnter2D
if(collision.GetComponent<Enemy>()!=null)
{
Enemy enemy = collision.GetComponent<Enemy>();
SwordSkillDamage(enemy);
}
SetupTargetsForBounce(collision);
StuckInto(collision);
}//打开IsTrigger时才可使用该函数
private void SwordSkillDamage(Enemy enemy)
{
player.stats.DoDamage(enemy.GetComponent<CharacterStats>());
enemy.StartCoroutine("FreezeTimeFor", freezeTimeDuration);
ItemData_Equipment equipment = Inventory.instance.GetEquipment(EquipmentType.Amulet);
if (equipment != null)
{
equipment.Effect(enemy.transform);
}
}//造成伤害函数
private void SetupTargetsForBounce(Collider2D collision)
{
if (collision.GetComponent<Enemy>() != null)//首先得碰到敌人,只有碰到敌人才会跳
{
if (isBouncing && enemyTargets.Count <= 0)
{
Collider2D[] colliders = Physics2D.OverlapCircleAll(transform.position, 10);
foreach (var hit in colliders)
{
if (hit.GetComponent<Enemy>() != null)
{
enemyTargets.Add(hit.transform);
}
}
}
}
}
//https://docs.unity3d.com/cn/current/ScriptReference/Collider2D.OnTriggerEnter2D.html
private void StuckInto(Collider2D collision)
{
if(pierceAmount > 0 && collision.GetComponent<Enemy>()!= null)//本质就是能穿过敌人,在amount>0时不执行能让剑卡在敌人里的语句就行
{
pierceAmount--;
return;
}
if (isSpinning)
{
StopWhenSpinning();
return;
}//防止卡在敌人身上
canRotate = false;
cd.enabled = false;//相当于关闭碰撞后触发函数。//https://docs.unity3d.com/cn/current/ScriptReference/Collision2D-enabled.html
rb.isKinematic = true;//开启物理学反应 https://docs.unity3d.com/cn/current/ScriptReference/Rigidbody2D-isKinematic.html
rb.constraints = RigidbodyConstraints2D.FreezeAll;//冻结所有移动
if (isBouncing&&enemyTargets.Count>0)//修复剑卡不到墙上的bug
return;
//终止对动画的改变和终止附在敌人上
transform.parent = collision.transform;//设置sword的父组件为碰撞到的物体
anim.SetBool("Rotation", false);
}
}
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)
{
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);
}
ItemEffect.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ItemEffect : ScriptableObject
{
public virtual void ExecuteEffect(Transform _respawnPosition)
{
Debug.Log("Effect executed");
}
}
Heal_Effect.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "New Item Data", menuName = "Data/Item effect/Heal Effect")]
public class Heal_Effect : ItemEffect
{
[Range(0f, 1f)]
[SerializeField] private float healPercent;
public override void ExecuteEffect(Transform _respawnPosition)
{
PlayerStats playerStats = PlayerManager.instance.player.GetComponent<PlayerStats>();
int healAmount =Mathf.RoundToInt( playerStats.GetMaxHealthValue() * healPercent);
playerStats.IncreaseHealthBy(healAmount);//调用回血函数
}
}
Inventory.cs
using Newtonsoft.Json.Linq;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Inventory : MonoBehaviour
{
public static Inventory instance;
public List<ItemData> startingItem;
public List<InventoryItem> equipment;//inventoryItems类型的列表
public Dictionary<ItemData_Equipment, InventoryItem> equipmentDictionary;//以ItemData为Key寻找InventoryItem的字典
public List<InventoryItem> inventory;//inventoryItems类型的列表
public Dictionary<ItemData, InventoryItem> inventoryDictionary;//以ItemData为Key寻找InventoryItem的字典
public List<InventoryItem> stash;
public Dictionary<ItemData, InventoryItem> stashDictionary;
[Header("Inventory UI")]
[SerializeField] private Transform inventorySlotParent;
[SerializeField] private Transform stashSlotParent;
[SerializeField] private Transform equipmentSlotParent;
private UI_itemSlot[] inventoryItemSlot;//UI Slot的数组
private UI_itemSlot[] stashItemSlot;
private UI_equipementSlots[] equipmentSlot;
[Header("Items cooldown")]
private float lastTimeUsedFlask;
private void Awake()
{
if (instance == null)
instance = this;
else
Destroy(gameObject);
//防止多次创建Inventory
}
public void Start()
{
inventory = new List<InventoryItem>();
inventoryDictionary = new Dictionary<ItemData, InventoryItem>();
stash = new List<InventoryItem>();
stashDictionary = new Dictionary<ItemData, InventoryItem>();
equipment = new List<InventoryItem>();
equipmentDictionary = new Dictionary<ItemData_Equipment, InventoryItem>();
inventoryItemSlot = inventorySlotParent.GetComponentsInChildren<UI_itemSlot>();//拿到的方式有点绕,显示拿到Canvas 里的 Inventory 然后通过GetComponentsInChildren拿到其下的使用UISlot
stashItemSlot = stashSlotParent.GetComponentsInChildren<UI_itemSlot>();
equipmentSlot = equipmentSlotParent.GetComponentsInChildren<UI_equipementSlots>();
AddStartingItems();
}
private void AddStartingItems()
{
for (int i = 0; i < startingItem.Count; i++)
{
AddItem(startingItem[i]);
}
}//设置初始物品
public void EquipItem(ItemData _item)
{
//解决在itemdata里拿不到子类equipment里的enum的问题
ItemData_Equipment newEquipment = _item as ItemData_Equipment;//https://www.bilibili.com/read/cv15551811/
//将父类转换为子类
InventoryItem newItem = new InventoryItem(newEquipment);
ItemData_Equipment oldEquipment = null;
foreach (KeyValuePair<ItemData_Equipment, InventoryItem> item in equipmentDictionary)//这种方法可以同时拿到key和value保存到item里面
{
if (item.Key.equipmentType == newEquipment.equipmentType)//将拿到的key与转换成itemdata_equipment类型的_item的type对比拿到存在的key
{
oldEquipment = item.Key;//此key需保存在外部的data类型里
//equipment.Remove(item.Value);
//equipmentDictionary.Remove(item.Key);
}
}//好像用foreach里的value和key无法对外部的list和字典进行操作
if (oldEquipment != null)
{
AddItem(oldEquipment);
Unequipment(oldEquipment);
}
equipment.Add(newItem);
equipmentDictionary.Add(newEquipment, newItem);
RemoveItem(_item);
newEquipment.AddModifiers();
}//装备装备的函数
public void Unequipment(ItemData_Equipment itemToRemove)//装备其他同类型的装备时。去除已装备的装备
{
if (equipmentDictionary.TryGetValue(itemToRemove, out InventoryItem value))
{
equipment.Remove(value);
equipmentDictionary.Remove(itemToRemove);
itemToRemove.RemoveModifiers();
UpdateSlotUI();
}
}
private void UpdateSlotUI()
{
for (int i = 0; i < equipmentSlot.Length; i++)
{
//此步骤用于将对应类型的武器插入对应的槽内
foreach (KeyValuePair<ItemData_Equipment, InventoryItem> item in equipmentDictionary)//这种方法可以同时拿到key和value保存到item里面
{
if (item.Key.equipmentType == equipmentSlot[i].slotType)
{
equipmentSlot[i].UpdateSlots(item.Value);
}
}
}
//解决出现UI没有跟着Inventory变化的bug
for (int i = 0; i < inventoryItemSlot.Length;i++)
{
inventoryItemSlot[i].CleanUpSlot();
}
for (int i = 0; i < stashItemSlot.Length; i++)
{
stashItemSlot[i].CleanUpSlot();
}
for (int i = 0; i < inventory.Count; i++)
{
inventoryItemSlot[i].UpdateSlots(inventory[i]);
}
for (int i = 0; i < stash.Count; i++)
{
stashItemSlot[i].UpdateSlots(stash[i]);
}
}//更新UI函数
public void AddItem(ItemData _item)
{
if (_item.itemType == ItemType.Equipment)
{
AddToInventory(_item);
}
else if (_item.itemType == ItemType.Material)
{
AddToStash(_item);
}
UpdateSlotUI();
}//添加物体的函数
private void AddToStash(ItemData _item)//向stash加物体的函数
{
if (stashDictionary.TryGetValue(_item, out InventoryItem value))//只有这种方法才能在查找到是否存在key对应value是否存在的同时,能够同时拿到value,其他方法的拿不到value
{
value.AddStack();
}//字典的使用,通过ItemData类型的数据找到InventoryItem里的与之对应的同样类型的数据
else//初始时由于没有相同类型的物体,故调用else是为了初始化库存,使其中含有一个基本的值
{
InventoryItem newItem = new InventoryItem(_item);
stash.Add(newItem);//填进列表里只有一次
stashDictionary.Add(_item, newItem);//同上
}
}
private void AddToInventory(ItemData _item)
{
if (inventoryDictionary.TryGetValue(_item, out InventoryItem value))//只有这种方法才能在查找到是否存在key对应value是否存在的同时,能够同时拿到value,其他方法的拿不到value
{
value.AddStack();
}//字典的使用,通过ItemData类型的数据找到InventoryItem里的与之对应的同样类型的数据
else//初始时由于没有相同类型的物体,故调用else是为了初始化库存,使其中含有一个基本的值
{
InventoryItem newItem = new InventoryItem(_item);
inventory.Add(newItem);//填进列表里只有一次
inventoryDictionary.Add(_item, newItem);//同上
}
}//将物体存入Inventory的函数
public void RemoveItem(ItemData _item)//将物体剔除Inventory的函数
{
if (inventoryDictionary.TryGetValue(_item, out InventoryItem value))
{
if (value.stackSize <= 1)
{
inventory.Remove(value);
inventoryDictionary.Remove(_item);
}
else
value.RemoveStack();
}
if (stashDictionary.TryGetValue(_item, out InventoryItem stashValue))
{
if (stashValue.stackSize <= 1)
{
stash.Remove(stashValue);
stashDictionary.Remove(_item);
}
else
stashValue.RemoveStack();
}
UpdateSlotUI();
}
public List<InventoryItem> GetEquipmentList() => equipment;
public List<InventoryItem> GetStashList() => stash;
public ItemData_Equipment GetEquipment(EquipmentType _Type)//通过Type找到对应的已装备装备的函数
{
ItemData_Equipment equipedItem = null;
foreach (KeyValuePair<ItemData_Equipment, InventoryItem> item in equipmentDictionary)
if (item.Key.equipmentType == _Type)
{
equipedItem = item.Key;
}
return equipedItem;
}
public void UseFlask()
{
ItemData_Equipment currentFlask = GetEquipment(EquipmentType.Flask);
if (currentFlask == null)
return;
//使用药瓶设置冷却时间
bool canUseFlask = Time.time > lastTimeUsedFlask + currentFlask.itemCooldown;
if(canUseFlask)
{
currentFlask.Effect(null);
lastTimeUsedFlask = Time.time;
}
else
{
Debug.Log("Flask is Cooldown");
}
}//使用药瓶函数
}
ItemData_Equipment.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum EquipmentType
{
Weapon,
Armor,
Amulet,
Flask
}
[CreateAssetMenu(fileName = "New Item Data", menuName = "Data/Equipment")]
public class ItemData_Equipment : ItemData
{
public EquipmentType equipmentType;
public float itemCooldown;
public ItemEffect[] itemEffects;
[Header("Major stats")]
public int strength; // 力量 增伤1点 爆伤增加 1% 物抗
public int agility;// 敏捷 闪避 1% 闪避几率增加 1%
public int intelligence;// 1 点 魔法伤害 1点魔抗
public int vitality;//加血的
[Header("Offensive stats")]
public int damage;
public int critChance; // 暴击率
public int critPower; //150% 爆伤
[Header("Defensive stats")]
public int maxHealth;
public int armor;
public int evasion;//闪避值
public int magicResistance;
[Header("Magic stats")]
public int fireDamage;
public int iceDamage;
public int lightingDamage;
public void AddModifiers()
{
PlayerStats playerStats = PlayerManager.instance.player.GetComponent<PlayerStats>();
playerStats.strength.AddModifier(strength);
playerStats.agility.AddModifier(agility);
playerStats.intelligence.AddModifier(intelligence);
playerStats.vitality.AddModifier(vitality);
playerStats.damage.AddModifier(damage);
playerStats.critChance.AddModifier(critChance);
playerStats.critPower.AddModifier(critPower);
playerStats.maxHealth.AddModifier(maxHealth);
playerStats.armor.AddModifier(armor);
playerStats.evasion.AddModifier(evasion);
playerStats.magicResistance.AddModifier(magicResistance);
playerStats.fireDamage.AddModifier(fireDamage);
playerStats.iceDamage.AddModifier(iceDamage);
playerStats.lightingDamage.AddModifier(lightingDamage);
}
public void RemoveModifiers()
{
PlayerStats playerStats = PlayerManager.instance.player.GetComponent<PlayerStats>();
playerStats.strength.RemoveModifier(strength);
playerStats.agility.RemoveModifier(agility);
playerStats.intelligence.RemoveModifier(intelligence);
playerStats.vitality.RemoveModifier(vitality);
playerStats.damage.RemoveModifier(damage);
playerStats.critChance.RemoveModifier(critChance);
playerStats.critPower.RemoveModifier(critPower);
playerStats.maxHealth.RemoveModifier(maxHealth);
playerStats.armor.RemoveModifier(armor);
playerStats.evasion.RemoveModifier(evasion);
playerStats.magicResistance.RemoveModifier(magicResistance);
playerStats.fireDamage.RemoveModifier(fireDamage);
playerStats.iceDamage.RemoveModifier(iceDamage);
playerStats.lightingDamage.RemoveModifier(lightingDamage);
}
public void Effect(Transform _enemyPosition)//循环调用Effect类里的Effect的函数
{
foreach(var item in itemEffects)
{
item.ExecuteEffect(_enemyPosition);
}
}
}
CharacterStats.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
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 maxHealth;
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; }
[SerializeField] public int currentHealth;
protected virtual void Start()
{
critPower.SetDefaultValue(150);//设置默认爆伤
currentHealth = GetMaxHealthValue();
fx = GetComponent<EntityFX>();
}
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 DoDamage(CharacterStats _targetStats)//计算后造成伤害函数
{
if (TargetCanAvoidAttack(_targetStats))设置闪避
{
return;
}
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)//此函数用来改变当前生命值,不调用特效
{
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;
}
private 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;
}
private bool TargetCanAvoidAttack(CharacterStats _targetStats)//闪避计算
{
int totleEvation = _targetStats.evasion.GetValue() + _targetStats.agility.GetValue();
//我被麻痹后
//敌人的闪避率提升
if (isShocked)
totleEvation += 20;
if (Random.Range(0, 100) < totleEvation)
{
return true;
}
return false;
}
private bool CanCrit()//判断是否暴击
{
int totleCriticalChance = critChance.GetValue() + agility.GetValue();
if (Random.Range(0, 100) <= totleCriticalChance)
{
return true;
}
return false;
}
private int CalculateCriticalDamage(int _damage)//计算暴击后伤害
{
float totleCirticalPower = (critPower.GetValue() + strength.GetValue()) * .01f;
float critDamage = _damage * totleCirticalPower;
return Mathf.RoundToInt(critDamage);//返回舍入为最近整数的
}
public int GetMaxHealthValue()
{
return maxHealth.GetValue() + vitality.GetValue() * 10;
}//统计生命值函数
#endregion
}
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.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 (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);
}
}