Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释,可供学习Alex教程的人参考
此代码仅为较上一P有所改变的代码
【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili
UI.cs
using UnityEngine;
public class UI : MonoBehaviour
{
[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;
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++)
{
transform.GetChild(i).gameObject.SetActive(false);
}
if (_menu != null)
{
_menu.SetActive(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)
return;
}
SwitchTo(inGameUI);
}
}
UI_InGame.cs
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class UI_InGame : MonoBehaviour
{
[SerializeField] private PlayerStats playerStats;
[SerializeField] Slider slider;
[SerializeField] private Image dashImage;
[SerializeField] private Image parryImage;
[SerializeField] private Image crystalImage;
[SerializeField] private Image swordImage;
[SerializeField] private Image blackholeImage;
[SerializeField] private Image flaskholeImage;
[SerializeField] private TextMeshProUGUI currentSouls;
private SkillManager skills;
void Start()
{
if(playerStats != null)
{
playerStats.onHealthChanged += UpdateHealthUI;
}
skills = SkillManager.instance;
}
// Update is called once per frame
void Update()
{
currentSouls.text = PlayerManager.instance.GetCurrency().ToString("#,#");
if(Input.GetKeyDown(KeyCode.LeftShift) && skills.dash.dashUnlocked)//使用技能后图标变黑
{
SetCoolDownOf(dashImage);
}
if (Input.GetKeyDown(KeyCode.Q) && skills.parry.parryUnlocked)
{
SetCoolDownOf(parryImage);
}
if (Input.GetKeyDown(KeyCode.F) && skills.crystal.crystalUnlocked)
{
SetCoolDownOf(crystalImage);
}
if (Input.GetKeyDown(KeyCode.Mouse1)&& skills.sword.swordUnlocked)
{
SetCoolDownOf(swordImage);
}
if (Input.GetKeyDown(KeyCode.R) && skills.blackhole.blackholeUnlocked)
{
SetCoolDownOf(blackholeImage);
}
if (Input.GetKeyDown(KeyCode.Alpha1) && Inventory.instance.GetEquipment(EquipmentType.Flask) != null)
{
SetCoolDownOf(flaskholeImage);
}
CheckCooldown(dashImage, skills.dash.cooldown);
CheckCooldown(parryImage, skills.parry.cooldown);
CheckCooldown(crystalImage, skills.crystal.cooldown);
CheckCooldown(swordImage, skills.sword.cooldown);
CheckCooldown(blackholeImage, skills.blackhole.cooldown);
CheckCooldown(flaskholeImage, Inventory.instance.flaskCooldown);
}
private void UpdateHealthUI()//更新血量条函数,此函数由Event触发
{
slider.maxValue = playerStats.GetMaxHealthValue();
slider.value = playerStats.currentHealth;
}
private void SetCoolDownOf(Image _image)//使用技能后使图标变黑的函数
{
if (_image.fillAmount <= 0)
_image.fillAmount = 1;
}
private void CheckCooldown(Image _image,float _cooldown)//使图标根据cd逐渐变白的函数
{
if(_image.fillAmount > 0)
{
_image.fillAmount -= 1 / _cooldown * Time.deltaTime;
}
}
}
Inventory.cs
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;
[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;
public float flaskCooldown { get; private set; }
private float armorCooldown;
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>();
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()//更新槽UI的函数
{
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]);
}
UpdateStatsUI();
}
public void UpdateStatsUI()//更新状态UI函数
{
for (int i = 0; i < statSlot.Length; i++)
{
statSlot[i].UpdateStatValueUI();
}
}
public void AddItem(ItemData _item)//添加物体的函数
{
if (_item.itemType == ItemType.Equipment && CanAddItem())//修复Inventory数量大于Slot能存放的数量时报错的Bug
{
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);//同上
}
UpdateSlotUI();
}
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数量大于Slot能存放的数量时报错的Bug
{
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 bool CanAddItem()//通过Inventory数量和Slot能存放的数量进行对比,确定是否可以添加新的装备到装备槽
{
if (inventory.Count >= inventoryItemSlot.Length)
{
return false;
}
return true;
}
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;
}
public bool CanCraft(ItemData_Equipment _itemToCraft, List<InventoryItem> _requiredMaterials)
{
List<InventoryItem> materialsToRemove = new List<InventoryItem>();
for (int i = 0; i < _requiredMaterials.Count; i++)
{
if (stashDictionary.TryGetValue(_requiredMaterials[i].data, out InventoryItem stashValue))//判断数量是否足够
{
if (stashValue.stackSize < _requiredMaterials[i].stackSize)
{
Debug.Log("not enough materials");
return false;
}
else
{
materialsToRemove.Add(stashValue);
}
}
else
{
Debug.Log("not enough materials");
return false;
}
}
for (int i = 0; i < materialsToRemove.Count; i++)
{
RemoveItem(materialsToRemove[i].data);
}
AddItem(_itemToCraft);
Debug.Log("Here is your item " + _itemToCraft.name);
return true;
}//检测材料足够制造对应装备的函数
}
Skill.cs
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
public class Skill : MonoBehaviour
{
public float cooldown;
protected float cooldownTimer;
protected Player player;//拿到player
protected virtual void Start()
{
player = PlayerManager.instance.player;//拿到player
}
protected virtual void Update()
{
cooldownTimer -= Time.deltaTime;
}
public virtual bool CanUseSkill()
{
if (cooldownTimer < 0)
{
UseSkill();
cooldownTimer = cooldown;
return true;
}
else
{
Debug.Log("Skill is on cooldown");
return false;
}
}
public virtual void UseSkill()
{
// do some skill thing
}
//整理能返回最近敌人位置的函数
protected virtual Transform FindClosestEnemy(Transform _checkTransform)
{
Collider2D[] colliders = Physics2D.OverlapCircleAll(_checkTransform.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)
{
float distanceToEnemy = Vector2.Distance(_checkTransform.position, hit.transform.position);//拿到与敌人之间的距离
if (distanceToEnemy < closestDistance)//比较距离,如果离得更近,保存这个敌人的位置,更改最近距离
{
closestDistance = distanceToEnemy;
closestEnemy = hit.transform;
}
}
}
return closestEnemy;
}
}
PlayerManager.cs
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public class PlayerManager : MonoBehaviour
{
public static PlayerManager instance;
public Player player;//这是通过在外部设置了一个组件,让这个组件能够直接把Player找到,从而减少FInd的方式所带来的高负载
public int currency;
private void Awake()
{
if(instance != null)
{
Destroy(instance.gameObject);
}
else
instance = this;
}
public bool HaveEnoughMoney(int _price)
{
if(_price > currency)
{
Debug.Log("Not enough money");
return false;
}
currency -= _price;
return true;
}
public int GetCurrency() => currency;
}