【unity实战】unity3D中的PRG库存系统和换装系统(附项目源码)
文章目录
- 先来看看最终效果
- 前言
- 素材
- 简单绘制库存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。如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~

相关文章:
【unity实战】unity3D中的PRG库存系统和换装系统(附项目源码)
文章目录 先来看看最终效果前言素材简单绘制库存UI前往mixamo获取人物模型动画获取一些自己喜欢的装备物品模型库存系统换装系统装备偏移问题添加消耗品最终效果源码完结 先来看看最终效果 前言 之前2d的换装和库存系统我们都做过不少了,这次就来学习一个3d版本的&…...
编程语言发展史:C语言的诞生及其影响
预计更新 第一部分:早期编程语言 1.1布尔代数和机器语言 1.2汇编语言的出现和发展 1.3高级语言的兴起 第二部分:主流编程语言 1.1 C语言的诞生及其影响 1.2 C语言的发展和应用 1.3 Java语言的出现和发展 1.4 Python语言的兴起和特点 1.5 JavaScript语言…...
(二)pytest自动化测试框架之添加测试用例步骤(@allure.step())
前言 在编写自动化测试用例的时候经常会遇到需要编写流程性测试用例的场景,一般流程性的测试用例的测试步骤比较多,我们在测试用例中添加详细的步骤会提高测试用例的可阅读性。 allure提供的装饰器allure.step()是allure测试报告框架非常有用的功能&am…...
【用unity实现100个游戏之16】Unity程序化生成随机2D地牢游戏2(附项目源码)
文章目录 先看看最终效果前言生成走廊生成房间修复死胡同增加走廊宽度获取走廊位置信息集合方法一方法二 源码完结 先看看最终效果 前言 上期已经实现了房间的生成,本期紧跟着上期内容,生成走廊并结合上期内容生成连通的房间。 生成走廊 修改Procedur…...
潮玩宇宙大逃杀游戏开发源码说明
潮玩宇宙大逃杀游戏是一款简单而刺激的游戏。玩家在倒计时结束前从8个房间中选择一个房间并投入宝石。倒计时结束后,系统会自动生成一个敌人,然后随机挑选一个房间并清除这个房间内的人。其余7个房间内的玩家就可以按照投入比例获得被清除掉玩家的宝石。…...
UE5 操作WebSocket
插件:https://www.unrealengine.com/marketplace/zh-CN/product/websocket-client 参考:http://dascad.net/html/websocket/bp_index.html 1. 安装Plugings 2.测试websocket服务器 http://www.websocket-test.com/ 3.连接服务器 如果在Level BP里使用&a…...
Linux文件
目录 一、基本概念 二、研究进程和被打开文件的关系 (一)w方式 (二)a方式 三、认识系统接口,操作文件 (一)认识文件描述符 (二)举例 (三)…...
素短语的定义
素短语,是指至少含有一个终结符的短语,并且除自身外,不包含更小的素短语。 最左素短语是句型中最左边的素短语。...
【华为OD题库-033】经典屏保-java
题目 DVD机在视频输出时,为了保护电视显像管,在待机状态会显示"屏保动画”,如下图所示,DVD Logo在屏幕内来回运动,碰到边缘会反弹:请根据如下要求,实现屏保Logo坐标的计算算法 1、屏幕是一个800 * 600像素的矩形&…...
clang+llvm多进程gdb调试
clangllvm多进程gdb调试 前言1. 命令行gdb2. 父进程调试3. 子进程调试4. 返回父进程 前言 在学习新增llvm的优化pass时,需要跟踪clang及llvm的调用栈。然而llvm通过posix_spawn()创建了新进程,这使得gdb调试必须有一定的技巧了。 1. 命令行gdb 以下命…...
PHP反序列化简单使用
注:比较简陋,仅供参考。 编写PHP代码,实现反序列化的时候魔法函数自动调用计算器 PHP反序列化 serialize(); 将对象序列化成字符串 unserialize(); 将字符串反序列化回对象 创建类 class Stu{ public $name; public $age; public $sex; publi…...
专业课140+总分420+东南大学920专业综合考研,信息学院通信专业考研分享
专业课140总分420东南大学920专业综合考研,信息学院通信专业考研分享 我是三月开始系统考研备战,寒假先看的高数全书,奈何在家效率极其低下,才草草看了前三四章。回校后学习的比较认真,每天大概保持10个小时左右&…...
数据结构与算法编程题11
已知两个链表A和B分别表示两个集合,其元素递增排列。 请设计算法求出A与B的交集,并存放于A链表中。 a: 1, 2, 2, 4, 5, 7, 8, 9, 10 b: 1, 2, 3, 6, 7, 8 #include <iostream> using namespace std;typedef int Elemtype; #define ERROR 0; #defin…...
【LeetCode刷题】--40.组合总和II
40.组合总和II 本题详解:回溯算法 class Solution {public List<List<Integer>> combinationSum2(int[] candidates, int target) {int len candidates.length;List<List<Integer>> res new ArrayList<>();if (len 0) {return re…...
mysql面试内容点
left join和inner join的区别 1.返回不同 innerjoin只返回两个表中联结字段相等的行。left join返回包括左表中的所有记录和右表中联结字段相等的记录。 2.数量不同 inner join的数量小于等于左表和右表中的记录数量。left join的数量以左表中的记录数量相同。 3.记录属性不同…...
msvcp140.dll是什么?msvcp140.dll丢失的有哪些解决方法
在计算机使用过程中,我们经常会遇到一些错误提示,其中之一就是“msvcp140.dll丢失”。这个错误通常会导致某些应用程序无法正常运行。为了解决这个问题,我们需要采取一些措施来修复丢失的msvcp140.dll文件。本文将详细介绍5个解决msvcp140.dl…...
数字图像处理(冈萨雷斯)学习笔记
目录 一.机器视觉和计算机视觉二.图像处理基础1.什么是图像2.如何访问图像 三.图像仿射变换四.灰度变换 一.机器视觉和计算机视觉 机器视觉(Machine Vision,MV)和计算机视觉(Computer Vision,CV)的区别和联系: 机器视觉更注重广义图像信号(激光ÿ…...
MES系统管理范围及标准
一、计划管理 1.1计划分为:月度计划>周计划>日计划; 1.2MES系统一般都会直接精确到日计划(生产工单及生产指令); 1.3MES系统日计划分为三阶排产方式: 1.3.1日计划直接排到车间,由车间自行安排任务; 1.3.2日计划排到产线或设备,对应的班组长按照计划直接生产; 1.…...
vscode运行dlv报错超时
描述 点击F5运行dlv调试go代码时报错:couldnt start dlv dap: connection timeout 解决方式 在网上搜索这个报错,据说是dlv的配置问题,修改配置后还是不行。有人说是dlv和go的版本不匹配,就朝这个方向试试 go版本改为1.19之后…...
【Leetcode合集】1. 两数之和
1. 两数之和 1. 两数之和 代码仓库地址: https://github.com/slience-me/Leetcode 个人博客 :https://slienceme.xyz 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
10-Oracle 23 ai Vector Search 概述和参数
一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...
AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别
【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而,传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案,能够实现大范围覆盖并远程采集数据。尽管具备这些优势…...
MySQL 8.0 事务全面讲解
以下是一个结合两次回答的 MySQL 8.0 事务全面讲解,涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容,并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念(ACID) 事务是…...
Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换
目录 关键点 技术实现1 技术实现2 摘要: 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式(自动驾驶、人工驾驶、远程驾驶、主动安全),并通过实时消息推送更新车…...
Web中间件--tomcat学习
Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机,它可以执行Java字节码。Java虚拟机是Java平台的一部分,Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...
