Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释,可供学习Alex教程的人参考
此代码仅为较上一P有所改变的代码
【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili
FreezeEnemies_Effect.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "Freeze enemies effect", menuName = "Data/Item effect/Freeze enemies")]
public class FreezeEnemies_Effect : ItemEffect
{
[SerializeField] private float duration;
public override void ExecuteEffect(Transform _respawnPosition)
{
PlayerStats playerStats = PlayerManager.instance.player.GetComponent<PlayerStats>();
if (playerStats.currentHealth > playerStats.GetMaxHealthValue()*.1f)
return;
if (!Inventory.instance.CanUseArmor())
return;
Collider2D[] colliders = Physics2D.OverlapCircleAll(_respawnPosition.position, 2);
foreach (var hit in colliders)
{
if (hit.GetComponent<Enemy>() != null)
{
hit.GetComponent<Enemy>()?.FreezeTimerFor(duration);//?是为了保证有这个组件
}
}
}
}
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 float lastTimeUsedArmor;
private float flaskCooldown;
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>();
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 + 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;
}
}
PlayerStat.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerStats : CharacterStats
{
private Player player;
protected override void Start()
{
player = GetComponent<Player>();
base.Start();
}
public override void DoDamage(CharacterStats _targetStats)
{
base.DoDamage(_targetStats);
}
public override void TakeDamage(int _damage)
{
base.TakeDamage(_damage);
}
protected override void Die()
{
base.Die();
player.Die();
GetComponent<PlayerItemDrop>()?.GenerateDrop();
}
protected override void DecreaseHealthBy(int _damage)
{
base.DecreaseHealthBy(_damage);
ItemData_Equipment currentArmor = Inventory.instance.GetEquipment(EquipmentType.Armor);
if(currentArmor != null)
{
currentArmor.Effect(player.transform);
}
}
}
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;//攻击冷却
[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的线
}
}