# 背包系统总结
前言:这一部分并不复杂,所以我将通过功能模块的区分来记录而不是细分每一个文件的功能与彼此之间的逻辑来记录
# 1. 存储模块
ItemData.cs1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public enum ItemType { Material, Equipment } [CreateAssetMenu(fileName = "New Item Data", menuName = "Data/Item")] public class ItemData : ScriptableObject { public ItemType itemType; public string itemName; public Sprite icon; [Range(0,100)] public float dropChance; }
|
首先创建一个 itemdata 的基类,用来存储一些所有物品都会有的属性,其次在此基础上通过继承来创建一些其他的类,比如装备等
ItemData_Equipment.cs1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
| using System.Collections.Generic; using Unity.VisualScripting; 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; public int agility; public int intelligence; public int vitality;
[Header("Offensive stats")] public int damage; public int critChance; public int critPower;
[Header("Defensive stats")] public int health; public int armor; public int evasion; public int magicResistance;
[Header("Magic stats")] public int fireDamage; public int iceDamage; public int lightingDamage;
[Header("Craft requirements")] public List<InventoryItem> craftingMaterials;
public void Effect(Transform _enemyPosition) { foreach (var item in itemEffects) { item.ExecuteEffect(_enemyPosition); } }
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(health); 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(health); playerStats.armor.RemoveModifier(armor); playerStats.evasion.RemoveModifier(evasion); playerStats.magicResistance.RemoveModifier(magicResistance);
playerStats.fireDamage.RemoveModifier(fireDamage); playerStats.iceDamage.RemoveModifier(iceDamage); playerStats.lightingDamage.RemoveModifier(lightingDamage); } }
|
在此基础上通过 InventoryItem 来作为某个种类的仓库 (?) 来进行入栈出栈的操作以存储数据
InventoryItem.cs1 2 3 4 5 6 7 8 9 10 11 12 13 14
| using System; [Serializable] public class InventoryItem { public ItemData data; public int stackSize; public InventoryItem(ItemData _newItemData) { data = _newItemData; AddStack(); } public void AddStack() => stackSize++; public void RemoveStack() => stackSize--; }
|
最后通过 Inventory 来联动仓库与 UI 的变动
# 2. 人物装备模块
主要通过这部分代码来实现装备与仓库之间的联动与单个装备只能装备一个的功能
Inventory.cs1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public void EquipItem(ItemData _item) { ItemData_Equipment newEquipment = _item as ItemData_Equipment; InventoryItem newItem = new InventoryItem(newEquipment);
ItemData_Equipment oldEquipment = null;
foreach (KeyValuePair<ItemData_Equipment, InventoryItem> item in equipmentDictionary) { if (item.Key.equipmentType == newEquipment.equipmentType) oldEquipment = item.Key; }
if (oldEquipment != null) { UnequipItem(oldEquipment); AddItem(oldEquipment); }
equipment.Add(newItem); equipmentDictionary.Add(newEquipment, newItem); newEquipment.AddModifiers();
RemoveItem(_item);
UpdateSlotUI(); }
|
UI 当中在仓库点击装备
UI_ItemSlot1 2 3 4 5 6 7 8 9 10
| public virtual void OnPointerDown(PointerEventData eventData) { if (Input.GetKey(KeyCode.LeftControl)) { Inventory.instance.RemoveItem(item.data); return; } if (item.data.itemType == ItemType.Equipment) Inventory.instance.EquipItem(item.data); }
|
在 UI 当中装备栏点击卸下
UI_EquipmentSlot.cs1 2 3 4 5 6
| public override void OnPointerDown(PointerEventData eventData) { Inventory.instance.UnequipItem(item.data as ItemData_Equipment); Inventory.instance.AddItem(item.data as ItemData_Equipment); CleanUpSlot(); }
|
# 3. 敌人 / 玩家死亡掉落系统
# 敌人掉落
挂载 ItemDrop 方法在敌人身上,控制掉落
ItemDrop.cs1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public virtual void GenerateDrop() { for (int i = 0; i < possibleDrop.Length; i++) { if (Random.Range(0, 100) <= possibleDrop[i].dropChance) dropList.Add(possibleDrop[i]); } for (int i = 0; i < possibleItemDrop; i++) { ItemData randomItem = dropList[Random.Range(0, dropList.Count - 1)]; dropList.Remove(randomItem); DropItem(randomItem); } } protected void DropItem(ItemData _itemData) { GameObject newDrop = Instantiate(dropPrefab, transform.position, Quaternion.identity); Vector2 randomVelocity = new Vector2(Random.Range(-5, 5), Random.Range(15, 20)); newDrop.GetComponent<ItemObject>().SetupItem(_itemData, randomVelocity); }
|
然后在 EnemyStatus 中敌人死去时下方挂上 DropItem 方法
EnemyStatus1 2 3 4 5 6
| protected override void Die() { base.Die(); enemy.Die(); myDropSystem.GenerateDrop(); }
|
# 玩家掉落
玩家掉落这块重写了一个方法,因为玩家的掉落需要考虑是否有装备掉落或是材料掉落
PlayerItemDrop.cs1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| public class PlayerItemDrop : ItemDrop { [Header("Player's drop")] [SerializeField] private float chanceToLooseItems; [SerializeField] private float chanceToLooseMaterials;
public override void GenerateDrop() { Inventory inventory = Inventory.instance;
List<InventoryItem> itemsToUnequip = new List<InventoryItem>(); List<InventoryItem> materialsToLoose = new List<InventoryItem>();
foreach (InventoryItem item in inventory.GetEquipmentList()) { if (Random.Range(0, 100) <= chanceToLooseItems) { DropItem(item.data); itemsToUnequip.Add(item); } }
for (int i = 0; i < itemsToUnequip.Count; i++) { inventory.UnequipItem(itemsToUnequip[i].data as ItemData_Equipment); }
foreach (InventoryItem item in inventory.GetStashList()) { if (Random.Range(0, 100) <= chanceToLooseMaterials) { DropItem(item.data); materialsToLoose.Add(item); } }
for (int i = 0; i < materialsToLoose.Count; i++) { inventory.RemoveItem(materialsToLoose[i].data); } } }
|
# 4. 特殊装备系统
我们需要一些装备拥有一些特殊的性能,比如可以造成电击,抑或是可以恢复血量。
创建一个基类存储方法
ItemEffect.cs1 2 3 4 5 6 7
| public class ItemEffect : ScriptableObject { public virtual void ExecuteEffect(Transform _enemyPosition) { Debug.Log("Effect executed!"); } }
|
在 Equipment 中创建数组储存 Effect 方法并创建一个方法用于调用这些方法
ItemData_Equipment.cs1 2 3 4 5 6 7 8 9 10 11 12
| public ItemEffect[] itemEffects;
public void Effect(Transform _enemyPosition) { foreach (var item in itemEffects) { item.ExecuteEffect(_enemyPosition); } }
|
同时在 Inventory 中创建获取指定装备的方法
Inventory.cs1 2 3 4 5 6 7 8 9 10 11
| public ItemData_Equipment GetEquipment(EquipmentType _type) { ItemData_Equipment equipedItem = null; foreach (KeyValuePair<ItemData_Equipment, InventoryItem> item in equipmentDictionary) { if (item.Key.equipmentType == _type) equipedItem = item.Key; } return equipedItem; }
|
然后在 PlayerAnimationTriggers 中攻击时调用效果
PlayerAnimationTriggers.cs1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| private void AttackTrigger() { Collider2D[] colliders = Physics2D.OverlapCircleAll(player.attackCheck.position, player.attackCheckRadius);
foreach (var hit in colliders) { if (hit.GetComponent<Enemy>() != null) { EnemyStats _target = hit.GetComponent<EnemyStats>();
if(_target != null) player.stats.DoDamage(_target);
ItemData_Equipment weaponData = Inventory.instance.GetEquipment(EquipmentType.Weapon);
if (weaponData != null) weaponData.Effect(_target.transform); } } }
|
# 5. 一些特殊能力的实现
这边以雷击效果举例,其他效果将不再赘述
将 controller 文件绑定在雷击动画上,并将其作为预制件
ThunderStrike_Controller.cs1 2 3 4 5 6 7 8 9 10 11 12
| public class ThunderStrike_Controller : MonoBehaviour { protected virtual void OnTriggerEnter2D(Collider2D collision) { if(collision.GetComponent<Enemy>() != null) { PlayerStats playerStats = PlayerManager.instance.player.GetComponent<PlayerStats>(); EnemyStats enemyTarget = collision.GetComponent<EnemyStats>(); playerStats.DoMagicalDamage(enemyTarget); } } }
|
然后,将预制件放在 effect 的 data 中,再将 effect 放在武器的 data 中,达成绑定效果。
![]()
![]()
然后是电击后的自动索敌效果,同理创建了一个 ShockStrike_Controller 控制类来实现逻辑
ShockStrike_Controller.cs1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| using System.Collections; using System.Collections.Generic; using UnityEngine;
public class ShockStrike_Controller : MonoBehaviour { [SerializeField] private CharacterStats targetStats; [SerializeField] private float speed; private int damage;
private Animator anim; private bool triggered;
void Start() { anim = GetComponentInChildren<Animator>(); }
public void Setup(int _damage, CharacterStats _targetStats) { damage = _damage; targetStats = _targetStats; }
void Update() { if (!targetStats) return;
if (triggered) return;
transform.position = Vector2.MoveTowards(transform.position, targetStats.transform.position, speed * Time.deltaTime); transform.right = transform.position - targetStats.transform.position;
if (Vector2.Distance(transform.position, targetStats.transform.position) < .1f) { anim.transform.localPosition = new Vector3(0, .5f); anim.transform.localRotation = Quaternion.identity;
transform.localRotation = Quaternion.identity; transform.localScale = new Vector3(3, 3);
Invoke("DamageAndSelfDestroy", .2f); triggered = true; anim.SetTrigger("Hit"); } }
private void DamageAndSelfDestroy() { targetStats.ApplyShock(true); targetStats.TakeDamage(1); Destroy(gameObject, .4f); } }
|