Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释,可供学习Alex教程的人参考
此代码仅为较上一P有所改变的代码
【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili
UI_Statslot.cs
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;public class UI_Statslot : MonoBehaviour
{[SerializeField] private StatType statType;[SerializeField] private TextMeshProUGUI statValueText;[SerializeField] private TextMeshProUGUI statNameText;private void OnValidate(){gameObject.name = "Stat - " + statType.ToString();if(statNameText != null){statNameText.text = statType.ToString();}}private void Start(){UpdateStatValueUI();}public void UpdateStatValueUI(){PlayerStats playerStats = PlayerManager.instance.player.GetComponent<PlayerStats>();if(playerStats != null){statValueText.text = playerStats.GetStats(statType).GetValue().ToString();}}
}
UI_equipementSlots.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;public class UI_equipementSlots : UI_itemSlot
{public EquipmentType slotType;//这怎么拿到的private void OnValidate(){gameObject.name = "Equipment slot -" + slotType.ToString();}public override void OnPointerDown(PointerEventData eventData){if (item == null || item.data == null)//修复点击空白处会报错的bugreturn;//点击装备槽后卸下装备Inventory.instance.AddItem(item.data as ItemData_Equipment);Inventory.instance.Unequipment(item.data as ItemData_Equipment); CleanUpSlot();}
}
UI_itemSlot.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using UnityEngine.EventSystems;public class UI_itemSlot : MonoBehaviour ,IPointerDownHandler
{[SerializeField] private Image itemImage;[SerializeField] private TextMeshProUGUI itemText;public InventoryItem item;public void UpdateSlots(InventoryItem _newItem){item = _newItem;itemImage.color = Color.white;if (item != null){itemImage.sprite = item.data.icon;if (item.stackSize > 1){itemText.text = item.stackSize.ToString();}else{itemText.text = "";}}}public void CleanUpSlot()//解决出现UI没有跟着Inventory变化的bug{item = null;itemImage.sprite = null;itemImage.color = Color.clear;itemText.text = "";}public virtual void OnPointerDown(PointerEventData eventData){if(item == null)//修复点击空白处会报错的bug{return;}if(Input.GetKey(KeyCode.LeftControl)){Inventory.instance.RemoveItem(item.data);return;}if (item.data.itemType == ItemType.Equipment)Inventory.instance.EquipItem(item.data);}
}
Buff_Effcet.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;[CreateAssetMenu(fileName = "BUff effect", menuName = "Data/Item effect/Buff effect")]public class Buff_Effect :ItemEffect
{private PlayerStats stats;[SerializeField] private StatType buffType;[SerializeField] private float buffDuration;[SerializeField] private int buffAmount;public override void ExecuteEffect(Transform _respawnPosition){stats = PlayerManager.instance.player.GetComponent<PlayerStats>();stats.IncreaseStatBy(buffAmount, buffDuration, stats.GetStats(buffType));}}
Inventory.cs
using Newtonsoft.Json.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
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;[SerializeField] private Transform statSlotParent;private UI_itemSlot[] inventoryItemSlot;//UI Slot的数组private UI_itemSlot[] stashItemSlot;private UI_equipementSlots[] equipmentSlot;private UI_Statslot[] statSlot;[Header("Items cooldown")]private float lastTimeUsedFlask;private float lastTimeUsedArmor;private float flaskCooldown;private float armorCooldown;private void Awake(){if (instance == null)instance = this;elseDestroy(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拿到其下的使用UISlotstashItemSlot = stashSlotParent.GetComponentsInChildren<UI_itemSlot>();equipmentSlot = equipmentSlotParent.GetComponentsInChildren<UI_equipementSlots>();statSlot = statSlotParent.GetComponentsInChildren<UI_Statslot>();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();UpdateSlotUI();}//装备装备的函数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变化的bugfor (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]);}for(int i = 0; i < statSlot.Length;i++){statSlot[i].UpdateStatValueUI();}}//更新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);}elsevalue.RemoveStack();}if (stashDictionary.TryGetValue(_item, out InventoryItem stashValue)){if (stashValue.stackSize <= 1){stash.Remove(stashValue);stashDictionary.Remove(_item);}elsestashValue.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 + flaskCooldown;if(canUseFlask){flaskCooldown = currentFlask.itemCooldown;currentFlask.Effect(null);lastTimeUsedFlask = Time.time;}else{Debug.Log("Flask is Cooldown");}}//使用药瓶函数public bool CanUseArmor(){ItemData_Equipment currentArmor = GetEquipment(EquipmentType.Armor);if(Time.time > lastTimeUsedArmor + armorCooldown){lastTimeUsedArmor = Time.time;armorCooldown = currentArmor.itemCooldown;return true;}Debug.Log("Armor on cooldown");return false;}
}
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; }[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 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;}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=1001DecreaseHealthBy(_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 ailementsprivate 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.htmlforeach (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 calculationsprivate 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);elsetotleDamage -= _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 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
}