文章目录
- 先来看看最终效果
- 前言
- 素材
- 简单绘制库存UI
- 前往mixamo获取人物模型动画
- 获取一些自己喜欢的装备物品模型
- 库存系统
- 换装系统
- 装备偏移问题
- 添加消耗品
- 最终效果
- 源码
- 完结
先来看看最终效果
前言
之前2d的换装和库存系统我们都做过不少了,这次就来学习一个3d版本的,其实逻辑和思维都是共通的,但是也会有些细节不同,毕竟3d多了一个轴,废话少说,我们一起开始吧!
素材
https://assetstore.unity.com/packages/2d/gui/fantasy-wooden-gui-free-103811
简单绘制库存UI
前往mixamo获取人物模型动画
mixamo网站我之前也推荐过:免费获取游戏素材、工具、国内宝藏游戏博主分享
地址:https://www.mixamo.com/
下载自己喜欢的人物动作模型
拖入角色
获取一些自己喜欢的装备物品模型
https://sketchfab.com/Trueform/collections/downloadable-8e49931974d24a8f9b5f77d94328540b
导入模型的材质可能丢失
手动创建一个材质
配置对应纹理
挂载材质
同样的方法,配置其他不同类型的装备物品
库存系统
新增脚本InventoryItem
[CreateAssetMenu(menuName = "ScriptableObjects/库存系统/物品")]
public class InventoryItem : ScriptableObject
{[SerializeField] private GameObject itemPrefab; // 存储物品的预制体[SerializeField] private Sprite itemSprite; // 存储物品的精灵[SerializeField] private string itemName; // 存储物品的名称[SerializeField] private Vector3 itemLocalPosition; // 存储物品的局部位置[SerializeField] private Vector3 itemLocalRotation; // 存储物品的局部旋转// 返回存储的物品精灵public Sprite GetSprite(){return itemSprite;}// 返回存储的物品名称public string GetName(){return itemName;}// 返回存储的物品预制体public GameObject GetPrefab(){return itemPrefab;}// 返回存储的物品局部位置public Vector3 GetLocalPosition(){return itemLocalPosition;}// 返回存储的物品局部旋转(以四元数表示)public Quaternion GetLocalRotation(){return Quaternion.Euler(itemLocalRotation);}
}
配置不同物品信息
新增InventoryItemWrapper
// 使用[System.Serializable]属性将该类标记为可序列化,以便在Unity编辑器中进行序列化
[System.Serializable]
public class InventoryItemWrapper
{[SerializeField] private InventoryItem item; // 存储物品信息的对象[SerializeField] private int count; // 存储物品数量// 返回存储的物品信息public InventoryItem GetItem(){return item;}// 返回存储的物品数量public int GetItemCount(){return count;}
}
新增Inventory
[CreateAssetMenu(menuName = "ScriptableObjects/库存系统/库存")]
public class Inventory : ScriptableObject
{[SerializeField] private List<InventoryItemWrapper> items = new List<InventoryItemWrapper>(); // 存储物品及其数量的列表[SerializeField] private InventoryUI inventoryUIPrefab;private InventoryUI _inventoryUI; // 与此库存相关联的UIprivate InventoryUI inventoryUI{get{if (!_inventoryUI){_inventoryUI = Instantiate(inventoryUIPrefab, playerEquipment.GetUIParent());}return _inventoryUI;}}private Dictionary<InventoryItem, int> itemToCountMap = new Dictionary<InventoryItem, int>(); // 将物品映射到数量的字典private PlayerEquipmentController playerEquipment;// 初始化库存,将物品及其数量添加到映射中public void InitInventory(PlayerEquipmentController playerEquipment){this.playerEquipment = playerEquipment;for (int i = 0; i < items.Count; i++){itemToCountMap.Add(items[i].GetItem(), items[i].GetItemCount());}}//开启背包public void OpenInventoryUI(){inventoryUI.gameObject.SetActive(true);inventoryUI.InitInventoryUI(this);}// 分配物品给玩家public void AssignItem(InventoryItem item){Debug.Log("点击了物品:" + item.GetName());}// 返回所有物品及其数量的映射public Dictionary<InventoryItem, int> GetAllItemsMap(){return itemToCountMap;}// 添加物品到库存中,并更新UIpublic void AddItem(InventoryItem item, int count){int currentItemCount;if (itemToCountMap.TryGetValue(item, out currentItemCount)){itemToCountMap[item] = currentItemCount + count;}else{itemToCountMap.Add(item, count);}inventoryUI.CreateOrUpdateSlot(this, item, count);}// 从库存中移除物品,并更新UIpublic void RemoveItem(InventoryItem item, int count){int currentItemCount;if (itemToCountMap.TryGetValue(item, out currentItemCount)){itemToCountMap[item] = currentItemCount - count;if (currentItemCount - count <= 0){inventoryUI.DestroySlot(item);}else{inventoryUI.UpdateSlot(item, currentItemCount - count);}}else{Debug.Log("Can't remove item");}}
}
配置库存信息
新增InventorySlot,控制物品插槽信息显示
public class InventorySlot : MonoBehaviour
{[SerializeField] private Image itemImage; // 物品图像[SerializeField] private TextMeshProUGUI itemNameText; // 物品名称文本[SerializeField] private TextMeshProUGUI itemCountText; // 物品数量文本[SerializeField] private Button slotButton; // 插槽按钮// 初始化插槽的可视化表示public void InitSlotVisualisation(Sprite itemSprite, string itemName, int itemCount){itemImage.sprite = itemSprite;itemNameText.text = itemName;UpdateSlotCount(itemCount);}// 更新插槽中物品的数量显示public void UpdateSlotCount(int itemCount){itemCountText.text = itemCount.ToString();}// 分配插槽按钮的回调函数public void AssignSlotButtonCallback(System.Action onClickCallback){slotButton.onClick.AddListener(() => onClickCallback());}
}
挂载脚本并配置信息
新增InventoryUI,控制显示背包插槽信息
public class InventoryUI : MonoBehaviour
{[SerializeField] private Transform slotsParent; // 插槽的父级对象[SerializeField] private InventorySlot slotPrefab; // 插槽的预制体private Dictionary<InventoryItem, InventorySlot> itemToSlotMap = new Dictionary<InventoryItem, InventorySlot>(); // 将物品映射到插槽的字典// 初始化库存UIpublic void InitInventoryUI(Inventory inventory){var itemsMap = inventory.GetAllItemsMap();foreach (var kvp in itemsMap){CreateOrUpdateSlot(inventory, kvp.Key, kvp.Value);}}// 创建或更新物品插槽public void CreateOrUpdateSlot(Inventory inventory, InventoryItem item, int itemCount){if (!itemToSlotMap.ContainsKey(item)){var slot = CreateSlot(inventory, item, itemCount);itemToSlotMap.Add(item, slot);}else{UpdateSlot(item, itemCount);}}// 更新已存在的物品插槽public void UpdateSlot(InventoryItem item, int itemCount){itemToSlotMap[item].UpdateSlotCount(itemCount);}// 创建物品插槽private InventorySlot CreateSlot(Inventory inventory, InventoryItem item, int itemCount){var slot = Instantiate(slotPrefab, slotsParent);slot.InitSlotVisualisation(item.GetSprite(), item.GetName(), itemCount);slot.AssignSlotButtonCallback(() => inventory.AssignItem(item));return slot;}// 销毁物品插槽public void DestroySlot(InventoryItem item){Destroy(itemToSlotMap[item].gameObject);itemToSlotMap.Remove(item);}
}
挂载脚本配置信息
新增PlayerEquipmentController,初始化库存
public class PlayerEquipmentController : MonoBehaviour
{[SerializeField] private Inventory inventory; // 玩家的库存[SerializeField] private Transform inventoryUIParent; // 库存UI的父级对象private void Start(){inventory.InitInventory(this); // 初始化玩家库存inventory.OpenInventoryUI(); // 打开库存UI}// 获取UI父级对象public Transform GetUIParent(){return inventoryUIParent;}
}
挂载脚本,并配置信息
效果
换装系统
修改InventoryItem,将InventoryItem 定义为所有物品的抽象父类,AssignItemToPlayer方法声明为抽象方法。这意味着所有继承自InventoryItem的子类都必须实现这个方法。这样可以确保每个具体的物品类在被分配给玩家时都有自己特定的行为
public abstract class InventoryItem : ScriptableObject
{ //。。。//将物品分配给玩家public abstract void AssignItemToPlayer(PlayerEquipmentController playerEquipment);
}
修改Inventory,调用AssignItemToPlayer方法
// 分配物品给玩家
public void AssignItem(InventoryItem item)
{// Debug.Log("点击了物品:" + item.GetName());//将物品分配给玩家item.AssignItemToPlayer(playerEquipment);
}
新增HelmetInventoryItem,定义头盔物品类
[CreateAssetMenu(menuName = "ScriptableObjects/库存系统/物品/头盔")]
public class HelmetInventoryItem : InventoryItem
{// 将物品分配给玩家public override void AssignItemToPlayer(PlayerEquipmentController playerEquipment) {playerEquipment.AssignHelmetItem(this);}
}
新增HandInventoryItem,定义手部物品类
public enum Hand
{LEFT, // 左手RIGHT // 右手
}[CreateAssetMenu(menuName = "ScriptableObjects/库存系统/物品/手部物品")]
public class HandInventoryItem : InventoryItem
{public Hand hand; // 物品所属的手部类型,左手或右手// 将物品分配给玩家public override void AssignItemToPlayer(PlayerEquipmentController playerEquipment) {playerEquipment.AssignHandItem(this);}
}
新增ArmorInventoryItem,定义护甲物品类
[CreateAssetMenu(menuName = "ScriptableObjects/库存系统/物品/护甲")]
public class ArmorInventoryItem : InventoryItem
{// 将物品分配给玩家public override void AssignItemToPlayer(PlayerEquipmentController playerEquipment){playerEquipment.AssignArmorItem(this);}
}
修改PlayerEquipmentController,定义不同部位物品数据处理逻辑
[SerializeField] private Transform helmetAnchor; // 头盔装备点
[SerializeField] private Transform leftAnchor; // 左手装备点
[SerializeField] private Transform rightAnchor; // 右手装备点
[SerializeField] private Transform armorAnchor; // 盔甲装备点
private GameObject currentHelmetObj; // 当前头盔对象
private GameObject currentLeftHandObj; // 当前左手对象
private GameObject currentRightHandObj; // 当前右手对象
private GameObject currentArmorObj; // 当前盔甲对象// 分配头盔物品给玩家
public void AssignHelmetItem(HelmetInventoryItem item)
{DestroyIfNotNull(currentHelmetObj); // 如果当前有头盔对象,则销毁currentHelmetObj = CreateNewItemInstance(item, helmetAnchor); // 创建新的头盔实例并赋值给当前头盔对象
}// 创建新的装备实例
private GameObject CreateNewItemInstance(InventoryItem item, Transform anchor)
{var itemInstance = Instantiate(item.GetPrefab(), anchor); // 实例化物品的预制体,并放置在指定的装备点itemInstance.transform.localPosition = item.GetLocalPosition(); // 设置物品相对于装备点的本地坐标itemInstance.transform.localRotation = item.GetLocalRotation(); // 设置物品相对于装备点的本地旋转return itemInstance; // 返回创建的物品实例
}// 销毁物体,如果不为空
private void DestroyIfNotNull(GameObject obj)
{if (obj != null){Destroy(obj);}
}// 分配手部物品给玩家
public void AssignHandItem(HandInventoryItem item)
{switch (item.hand){case Hand.LEFT:DestroyIfNotNull(currentLeftHandObj);currentLeftHandObj = CreateNewItemInstance(item, leftAnchor);break;case Hand.RIGHT:DestroyIfNotNull(currentRightHandObj);currentRightHandObj = CreateNewItemInstance(item, rightAnchor);break;default:break;}
}// 分配盔甲物品给玩家
public void AssignArmorItem(ArmorInventoryItem item)
{DestroyIfNotNull(currentArmorObj); // 如果当前有盔甲对象,则销毁currentArmorObj = CreateNewItemInstance(item, armorAnchor); // 创建新的盔甲实例并赋值给当前盔甲对象
}
配置
添加新的库存物品配置,删除旧的
运行效果
装备偏移问题
可以看到装备物品存在偏移,运行修改装备到合适位置,复制装备位置和旋转进对应装备的偏移参数
效果
添加消耗品
新增HealthPotionInventoryItem,定义生命药水物品类
[CreateAssetMenu(menuName = "ScriptableObjects/库存系统/物品/生命药水")]
public class HealthPotionInventoryItem : InventoryItem
{[SerializeField] private int healthPoints; // 生命药水的恢复生命值public override void AssignItemToPlayer(PlayerEquipmentController playerEquipment){playerEquipment.AssingHealthPotionItem(this);}public int GetHealthPoints() // 获取生命药水的恢复生命值{return healthPoints;}
}
修改PlayerEquipmentController
private int playerHealth = 0;// 分配生命药水物品给玩家
public void AssingHealthPotionItem(HealthPotionInventoryItem item)
{inventory.RemoveItem(item, 1);// 消耗物品playerHealth += item.GetHealthPoints();//加血Debug.Log("玩家现在生命值" + playerHealth);
}
创建生命药水物品,这里我就用苹果和饮料代替,配置对应的恢复生命值
加入库存
运行效果
最终效果
源码
整理好会放上来
完结
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注
,以便我第一时间收到反馈,你的每一次支持
都是我不断创作的最大动力。当然如果你发现了文章中存在错误
或者有更好的解决方法
,也欢迎评论私信告诉我哦!
好了,我是向宇
,https://xiangyu.blog.csdn.net
一位在小公司默默奋斗的开发者,出于兴趣爱好,于是最近才开始自习unity。如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~