【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;  // 与此库存相关联的UI

    private 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;
    }

    // 添加物品到库存中,并更新UI
    public 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);
    }

    // 从库存中移除物品,并更新UI
    public 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>();  // 将物品映射到插槽的字典

    // 初始化库存UI
    public 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。如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/177472.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

利用Python进行数据分析【送书第六期:文末送书】

&#x1f468;‍&#x1f393;博主简介 &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01; &#x1f40b; 希望大家多多支…

MQTT协议消息代理服务远程连接

目录 1. Linux 搭建 Mosquitto 2. Linux 安装Cpolar 3. 创建MQTT服务公网连接地址 4. 客户端远程连接MQTT服务 5. 代码调用MQTT服务 6. 固定连接TCP公网地址 7. 固定地址连接测试 Mosquitto是一个开源的消息代理&#xff0c;它实现了MQTT协议版本3.1和3.1.1。它可以在不…

HCIP-三、VRRP+BFD

三、VRRPBFD 实验拓扑实验需求及解法 实验拓扑 实验需求及解法 //本实验模拟某公司网关冗余结构&#xff0c;按以下要求完成配置&#xff1a; //1.如图所示&#xff0c;配置 R1/2/3 的设备名称及 IP 地址。 //2.内外网通信。 //2.1 在 R1/2 上配置默认路由&#xff0c;保证 R1…

爆款文章有诀窍,内容创作者如何能持续产出优质内容

内容营销人有没有这么一种共鸣&#xff1a;10 万 那么多&#xff0c;为什么不能多我一个&#xff1f; 通常&#xff0c;我们把浏览量 / 阅读量高、转评赞数量高的内容看作爆款&#xff0c;而数据如果达到 10 万 则是超级爆款。因为&#xff0c;阅读量高意味着内容得到了大量的曝…

2023年03月 Scratch(二级)真题解析#中国电子学会#全国青少年软件编程等级考试

Scratch等级考试(1~4级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 小猫的程序如图所示,积木块的颜色与球的颜色一致。点击绿旗执行程序后,下列说法正确的是?( ) A:小猫一直在左右移动,嘴里一直说着“抓到了”。 B:小猫会碰到球,然后停止。…

[点云分割] 条件欧氏聚类分割

介绍 条件欧氏聚类分割是一种基于欧氏距离和条件限制的点云分割方法。它通过计算点云中点与点之间的欧氏距离&#xff0c;并结合一定的条件限制来将点云分割成不同的区域或聚类。 在条件欧氏聚类分割中&#xff0c;通常会定义以下两个条件来判断两个点是否属于同一个聚类&…

记录小白第一次EDUsrc:任意密码漏洞

目录 一、漏洞说明&#xff1a; 二、漏洞复现&#xff1a; 三、漏洞修复建议&#xff1a; 一、漏洞说明&#xff1a; xxxx学院身份认证系统有严重的逻辑设计缺陷&#xff1a;账户登录、手机登录、密码找回三个接口找到n个逻辑漏洞包括任意账号密码修改、信息泄露&#xff0…

AIGC ChatGPT4总结SQL优化细节操作

数据库SQL优化是一个复杂的过程,它通常涉及到许多不同的技术和方法。以下是一些常用的SQL优化策略: 1. **索引使用**:索引可以极大地加速查询速度。但是,索引并不总是有好处的,因为它们需要额外的空间来存储,并且在插入和更新数据时可能会减慢速度。因此,选择正确的字段…

windows系统安装ubuntu22.04虚拟机

镜像文件准备 镜像文件 官网 企业开源和Linux | Ubuntu 镜像下载地址 https://cn.ubuntu.com/download/server/step1 选择合适的版本下载 虚拟机安装 文件-- 新建虚拟机 选择镜像 修改安装路径 修改大小&#xff0c;最好60g&#xff0c;大一点 设置用户信息 设置虚拟机网络…

羊大师提示,羊奶都有哪些惊人功效?

羊奶不仅是一种美味的健康饮品&#xff0c;在近年来备受瞩目的的健康圈子里&#xff0c;羊奶还被赋予了更多的功效&#xff0c;成为一种备受推崇的保健品。羊奶不但富含营养&#xff0c;而且还有着非常多的益处&#xff0c;它能够用来美容、保健&#xff0c;甚至还可以治疗某些…

Java8新特性 ----- Lambda表达式和方法引用/构造器引用详解

前言 在讲一下内容之前,我们需要引入函数式接口的概念 什么是函数式接口呢? 函数式接口&#xff1a;有且仅有一个抽象方法的接口 java中函数式编程的体现就是Lambda表达式,你可以认为函数式接口就是适用于Lambda表达式的接口. 也可以加上注解来在编译层次上限制函数式接口 Fun…

音频采集的相关基础知识

本文引注: https://zhuanlan.zhihu.com/p/652629744 1.麦克风的种类 (1)模拟麦克风 ECM麦克风&#xff1a;驻极体电容麦克风(ECM)&#xff0c;典型的汽车ECM麦克风是一种将ECM单元与小型放大器电路整合在单个外壳中的装置。放大器提供一个模拟信号&#xff0c;其电压电平允许…

反编译-ApkTool

ApkTool下载地址&#xff1a; Apktool | ApktoolA tool for reverse engineering Android apk fileshttps://apktool.org/ 1、使用 apktool 解包 执行 java -jar apktool_2.4.1.jar d demo.apk -o demo 命令 java -jar apktool_2.4.1.jar d demo.apk -o demo 其中 d 后面是…

postman设置接口关联这样做,薪资直接涨3k

postman设置接口关联 在实际的接口测试中&#xff0c;后一个接口经常需要用到前一个接口返回的结果&#xff0c; 从而让后一个接口能正常执行&#xff0c;这个过程的实现称为关联。 在postman中实现关联操作的步骤如下&#xff1a; 1、利用postman获取上一个接口指定的返回值…

Android JNI 异常定位(2)—— addr2line

Android native报错有时候只有一句 signal 11 (SIGSEGV)&#xff0c;这种情况仅通过log是很难定位到问题的。不过Android 在/data/tombstones目录保存了错误的堆栈信息&#xff0c;为定位bug提供了路径。不过一般这里的log都无法像java一样直接定位的出错的行数。如下图&#x…

YOLO目标检测——卫星遥感舰船检测数据集下载分享【含对应voc、coco和yolo三种格式标签】

实际项目应用&#xff1a;卫星遥感舰船检测数据集说明&#xff1a;卫星遥感舰船检测数据集&#xff0c;真实场景的高质量图片数据&#xff0c;数据场景丰富&#xff0c;含船一个类别标签说明&#xff1a;使用lableimg标注软件标注&#xff0c;标注框质量高&#xff0c;含voc(xm…

HTTPS协议的加密流程

目录 一&#xff0c;HTTPS是什么 二&#xff0c;两种加密方式 三&#xff0c;HTTPS的加密过程 3.1 引入对称加密 3.2 引入非对称加密 3.3 引入证书 一&#xff0c;HTTPS是什么 HTTPS也是一个应用层协议&#xff0c;它是在HTTP协议的基础上引入了一个加密层。因为HTTP协议…

边云协同架构设计

文章目录 一. "边云协同"是什么&#xff1f;二. "边云协同"主要包括6种协同2.1 资源协同2.2 数据协同2.3 智能协同2.4 应用管理协同2.5 业务管理协同2.6 服务协同 三. "边云协同"的优势 其它相关推荐&#xff1a; 系统架构之微服务架构 系统架构…

自动化测试学习指南

软件自动化测试的学习步骤 大概步骤如下&#xff1a; 1. 做好手工测试&#xff08;了解各种测试的知识&#xff09;-> 2. 学习编程语言-> 3. 学习Web基础&#xff08;HTML,HTTP,CSS,DOM,Javascript&#xff09;或者 学习Winform -> 4. 学习自动化测试工具 ->5.…

C++ Day04 this指针,友元函数,重载

this指针 概念 谁调用 this 所在的函数 ,this 就存储谁的地址 特点 1, 在当前类的非静态成员函数中调用本类非静态成员时 , 默认有 this 关键字 2, 静态成员函数 , 没有 this 指针。 示例 #include <iostream> #include <cstring> using namespace std; class S…