【unity实战】unity3D中的PRG库存系统和换装系统(附项目源码)

news2024/9/22 5:31:50

文章目录

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

有哪些相见恨晚的stm32学习的方法?

有哪些相见恨晚的stm32学习的方法&#xff1f; 单片机用处这么广&#xff0c;尤其是STM32生态这么火&#xff01;如何快速上手学习呢&#xff1f; 你要考虑的是&#xff0c;要用STM32实现什么&#xff1f;为什么使用STM32而不是用8051&#xff1f;是因为51的频率太低&#xff…

五分钟搭建开源ERP:Odoo,并实现公网远程访问

文章目录 前言1. 下载安装Odoo&#xff1a;2. 实现公网访问Odoo本地系统&#xff1a;3. 固定域名访问Odoo本地系统 前言 Odoo是全球流行的开源企业管理套件&#xff0c;是一个一站式全功能ERP及电商平台。 开源性质&#xff1a;Odoo是一个开源的ERP软件&#xff0c;这意味着企…

建筑可视化中的 3D 纹理

在线工具推荐&#xff1a; 三维数字孪生场景工具 - GLTF/GLB在线编辑器 - Three.js AI自动纹理化开发 - YOLO 虚幻合成数据生成器 - 3D模型在线转换 - 3D模型预览图生成服务 1、什么是 3D 纹理&#xff1f; 纹理是将二维图像添加到三维模型的技术艺术。虽然对物体进行纹…

Python懒羊羊

目录 系列文章 写在前面 绘图基础 懒羊羊 写在后面 系列文章 序号文章目录直达链接表白系列1浪漫520表白代码https://want595.blog.csdn.net/article/details/1306668812满屏表白代码https://want595.blog.csdn.net/article/details/1297945183跳动的爱心https://want595…

2023年A特种设备相关管理(锅炉压力容器压力管道)证模拟考试题库及A特种设备相关管理(锅炉压力容器压力管道)理论考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年A特种设备相关管理&#xff08;锅炉压力容器压力管道&#xff09;证模拟考试题库及A特种设备相关管理&#xff08;锅炉压力容器压力管道&#xff09;理论考试试题是由安全生产模拟考试一点通提供&#xff0c;A特…

【带头学C++】----- 七、链表 ---- 7.5 学生管理系统(链表--下)

目录 1.补充上节插入节点的第三种方法&#xff08;按序插入&#xff09; 图示说明需求原理&#xff1a; 代码实现&#xff1a; 实际效果&#xff1a; 2.查询链表节点 1.方法调用 2.搜索函数实现 3.搜索功能结果展示测试 3.删除链表 1.图示删除链表的原理 ​编辑 2…

Thinkphp6实现定时任务功能

本文主要介绍命令启动定时任务的功能&#xff0c;按照CRMEB标准版的程序为大家详细的进行实现过程的介绍 首先创建安装Worker&#xff0c;执行composer require topthink/think-worker 安装在config/console.php中定义指令 timer > \crmeb\command\Timer::class 3. 对应图1…

vue3-组合式API

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;Vue篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来vue篇专栏内容:vue3-组合式API 目录 组合式API 1.1 什么是组合式API 1.2 为什么使用它 1.2.1 更好的逻辑复用#…

AutoSAR CANIF层配置代码分析

CAN物理控制单元 配置&#xff1a; 生成的代码&#xff1a; CanIf_CtrlStates 解析 类型&#xff1a; typedef union CanIf_CtrlStatesUTag {CanIf_CtrlStatesType raw[3];CanIf_CtrlStatesStructSType str; }CanIf_CtrlStatesUType;typedef struct sCanIf_CtrlStatesType {C…

Sublime Text:代码编辑器的卓越典范

Sublime Text是一款高效、强大且灵活的代码编辑器&#xff0c;在开发社区中广受欢迎。它不仅提供了丰富的功能&#xff0c;还具备美观的界面和卓越的性能&#xff0c;成为了众多开发者的首选工具。 Sublime Text的优点 高性能&#xff1a;Sublime Text具有极高的启动速度和响…

软件测试入门很容易,但想要深造就还是要费功夫

现如今&#xff0c;越来越多的外行人员开始转战到软件测试岗位&#xff0c;而这也让许多不了解软件测试人疑惑“软件测试有那么好学吗&#xff1f;为什么都开始转行到软件测试呢&#xff1f;” 而关于这两个问题的答案&#xff0c;作者在以下为大家进行了讲解&#xff0c;希望…

ts学习04-Es5中的类和静态方法 继承

最简单的类 function Person() {this.name "张三";this.age 20; } var p new Person(); console.log(p.name);//张三构造函数和原型链里面增加方法 function Person(){this.name张三; /*属性*/this.age20;this.runfunction(){console.log(this.name在运动);} }…

公网环境下使用VNC远程连接Ubuntu系统桌面

文章目录 前言1. ubuntu安装VNC2. 设置vnc开机启动3. windows 安装VNC viewer连接工具4. 内网穿透4.1 安装cpolar【支持使用一键脚本命令安装】4.2 创建隧道映射4.3 测试公网远程访问 5. 配置固定TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网TCP端口地址5.3 测试…

ARCGIS网络分析

一、实验名称&#xff1a; 网络分析 二、实验目的&#xff1a; 通过本实验练习&#xff0c;掌握空间数据网络分析的基本方法。 三、实验内容和要求&#xff1a; 实验内容&#xff1a; 利用ARCGIS软件网络分析工具及相关空间数据&#xff0c;查找距离“名人故居”、“博物…

open3d ICP 配准

文章目录 Three common registration techniquesPoint-to-point techniquePoint-to-plane registration ICP registrationHelper visualization functionInputGlobal registrationExtract geometric featureInputRANSAC Point-to-point ICPPoint-to-plane ICP References Three…

2023年Java核心技术大会(Core Java Week 2023)-核心PPT资料下载

一、峰会简介 人工智能在22年、23年的再次爆发让Python成为编程语言里最大的赢家&#xff1b;云原生的持续普及令Go、Rust等新生的语言有了进一步叫板传统技术体系的资本与底气。我们必须承认在近几年里&#xff0c;Java阵营的确受到了前所未有的挑战&#xff0c;出现了更多更…

解决 Python requests 库中 SSL 错误转换为 Timeouts 问题

解决 Python requests 库中 SSL 错误转换为 Timeouts 问题&#xff1a;理解和处理 SSL 错误的关键 在使用Python的requests库进行HTTPS请求时&#xff0c;可能会遇到SSL错误&#xff0c;这些错误包括但不限于证书不匹配、SSL层出现问题等。如果在requests库中设置verifyFalse&…

RAID技术复习笔记

Raid&#xff08;Redundant Array of independent Disks&#xff09;独立磁盘冗余阵列&#xff1a;磁盘阵列 Raid 分为:软raid、硬raid、软硬混合三种。 软Raid&#xff1a;所有的功能均有操作系统和CPU来完成&#xff0c;没有独立的raid控制、处理芯片和IO处理处理芯片。 硬R…

5.1异常处理

5.1异常处理 1. 什么是异常2. 异常分类2.1 Error2.2 Exception 3. 异常处理3.1 try-catch-finally终止finally执行的方法return关键字在异常处理的作用 1. 什么是异常 2. 异常分类 2.1 Error 2.2 Exception 2.2.1 非检查异常 2.2.2 检查异常 3. 异常处理 3.1 try-catch-fina…

R语言:利用biomod2进行生态位建模

在这里主要是分享一个不错的代码&#xff0c;喜欢的可以慢慢研究。我看了一遍&#xff0c;觉得里面有很多有意思的东西&#xff0c;供大家学习和参考。 利用PCA轴总结的70个环境变量&#xff0c;利用biomod2进行生态位建模&#xff1a; #------------------------------------…