【制作100个unity游戏之25】3D背包、库存、制作、快捷栏、存储系统、砍伐树木获取资源、随机战利品宝箱1(附带项目源码)

效果演示

在这里插入图片描述

文章目录

  • 效果演示
  • 系列目录
  • 前言
  • 人物和视角基本控制
  • 简单的背包系统和物品交互
    • 绘制背包UI
    • 脚本控制
  • 源码
  • 完结

系列目录

前言

欢迎来到【制作100个Unity游戏】系列!本系列将引导您一步步学习如何使用Unity开发各种类型的游戏。在这第25篇中,我们将探索如何用unity制作一个3D背包、库存、制作、快捷栏、存储系统、砍伐树木获取资源、随机战利品宝箱等功能,我会附带项目源码,以便你更好理解它。

人物和视角基本控制

具体可以看我这篇文章:
【unity小技巧】unity最完美的CharacterController 3d角色控制器,实现移动、跳跃、下蹲、奔跑、上下坡、物理碰撞效果,复制粘贴即用

这里我就直接贴出代码了

人物移动控制

[RequireComponent(typeof(CharacterController))]
public class MovementScript : MonoBehaviour
{
    [Tooltip("角色控制器")] public CharacterController characterController;
    [Tooltip("重力加速度")] private float Gravity = -19.8f;
    private float horizontal;
    private float vertical;

    [Header("移动")]
    [Tooltip("角色行走的速度")] public float walkSpeed = 6f;
    [Tooltip("角色奔跑的速度")] public float runSpeed = 9f;
    [Tooltip("角色移动的方向")] private Vector3 moveDirection;
    [Tooltip("当前速度")] private float speed;
    [Tooltip("是否奔跑")] private bool isRun;

    [Header("地面检测")]
    [Tooltip("是否在地面")] private bool isGround;

    [Header("跳跃")]
    [Tooltip("角色跳跃的高度")] public float jumpHeight = 8f;
    private float _verticalVelocity;


    void Start()
    {
        speed = walkSpeed;
    }

    void Update()
    {
        horizontal = Input.GetAxis("Horizontal");
        vertical = Input.GetAxis("Vertical");

        //地面检测
        isGround = characterController.isGrounded;

        SetSpeed();

        SetRun();

        SetMove();

        SetJump();
    }

    //速度设置
    void SetSpeed()
    {
        if (isRun)
        {
            speed = runSpeed;
        }
        else
        {
            speed = walkSpeed;
        }
    }

    //控制奔跑
    void SetRun()
    {
        if (Input.GetKey(KeyCode.LeftShift))
        {
            isRun = true;
        }
        else
        {
            isRun = false;
        }
    }

    //控制移动
    void SetMove()
    {
        moveDirection = transform.right * horizontal + transform.forward * vertical; // 计算移动方向
        moveDirection = moveDirection.normalized; // 归一化移动方向,避免斜向移动速度过快  
    }

    //控制跳跃
    void SetJump()
    {
        bool jump = Input.GetButtonDown("Jump");
        if (isGround)
        {
            // 在着地时阻止垂直速度无限下降
            if (_verticalVelocity < 0.0f)
            {
                _verticalVelocity = -2f;
            }

            if (jump)
            {
                _verticalVelocity = jumpHeight;
            }
        }
        else
        {
            //随时间施加重力
            _verticalVelocity += Gravity * Time.deltaTime;
        }
        characterController.Move(moveDirection * speed * Time.deltaTime + new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime);
    }
}

视角控制

public class MouseLook : MonoBehaviour
{
    // 鼠标灵敏度
    public float mouseSensitivity = 500f;

    // 玩家的身体Transform组件,用于旋转
    public Transform playerBody;

    // x轴的旋转角度
    float xRotation = 0f;
    void Start()
    {
        // 锁定光标到屏幕中心,并隐藏光标
        Cursor.lockState = CursorLockMode.Locked;
    }

    // Update在每一帧调用
    void Update()
    {
        // 执行自由视角查看功能
        FreeLook();
    }

    // 自由视角查看功能的实现
    void FreeLook()
    {
        // 获取鼠标X轴和Y轴的移动量,乘以灵敏度和时间,得到平滑的移动速率
        float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity * Time.deltaTime;
        float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity * Time.deltaTime;
        //限制旋转角度在-90到90度之间,防止过度翻转
        xRotation = Mathf.Clamp(xRotation, -90f, 90f);

        // 累计x轴上的旋转量
        xRotation -= mouseY;

        // 应用摄像头的x轴旋转
        transform.localRotation = Quaternion.Euler(xRotation, 0f, 0f);

        // 应用玩家身体的y轴旋转
        playerBody.Rotate(Vector3.up * mouseX);
    }
}

效果
在这里插入图片描述

简单的背包系统和物品交互

对UI知识还不太懂的小伙伴可以看这篇基础篇文件:【Unity游戏开发教程】零基础带你从小白到超神30——UI组件和布局的使用

绘制背包UI

物品插槽背景框
在这里插入图片描述
物品插槽,可以把物品插槽做出预制体,后面好修改
在这里插入图片描述

准星图像和文本
在这里插入图片描述

脚本控制

物品信息脚本

public class Item : MonoBehaviour
{
    public new string name = "New Item";//物品名称
    [TextArea]
    public string description = "New Description";//物品描述
    public Sprite icon;//物品图标
    public int currentQuantity = 1;//物品当前数量
    public int maxQuantity = 16;//物品最大堆叠数量
}

背包插槽脚本

public class Slot : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
    public bool hovered; // 鼠标是否悬停在该槽位上的标志
    private Item heldItem; // 当前槽位持有的物品

    private Color opaque = new Color(1, 1, 1, 1); // 不透明颜色
    private Color transparent = new Color(1, 1, 1, 0); // 透明颜色

    private Image thisSlotImage; // 该槽位的图像组件

    public TMP_Text thisSlotQuantityText; // 用于显示物品数量的文本组件

    // 初始化槽位
    public void initialiseSlot()
    {
        thisSlotImage = gameObject.GetComponent<Image>();
        thisSlotQuantityText = transform.GetChild(0).GetComponent<TMP_Text>();
        thisSlotImage.sprite = null;
        thisSlotImage.color = transparent;
        setItem(null);
    }

    // 设置槽位中的物品
    public void setItem(Item item)
    {
        heldItem = item;

        if (item != null)
        {
            thisSlotImage.sprite = heldItem.icon;
            thisSlotImage.color = opaque;
            updateData();
        }
        else 
        {
            thisSlotImage.sprite = null;
            thisSlotImage.color = transparent;
            updateData();
        }
    }

    // 获取当前槽位持有的物品
    public Item getItem()
    {
        return heldItem;
    }

    // 当前槽位是否持有的物品
    public bool hasItem()
    {
        return heldItem ? true : false;
    }

    // 更新槽位显示的数据
    public void updateData()
    {
        if (heldItem != null) // 如果持有物品
            thisSlotQuantityText.text = heldItem.currentQuantity.ToString(); // 显示物品的数量
        else // 如果不持有物品
            thisSlotQuantityText.text = "";
    }

    // 当鼠标指针进入槽位区域时调用
    public void OnPointerEnter(PointerEventData pointerEventData)
    {
        hovered = true;
    }

    // 当鼠标指针离开槽位区域时调用
    public void OnPointerExit(PointerEventData pointerEventData)
    {
        hovered = false;
    }
}

库存系统脚本

public class Inventory : MonoBehaviour
{
    [Header("UI")]
    public GameObject inventory; // 游戏中的背包界面
    public List<Slot> allInventorySlots = new List<Slot>(); // 所有的槽位列表
    public List<Slot> inventorySloats = new List<Slot>();//背包的的槽位列表
    public Image crosshair; // 准星图像
    public TMP_Text itemHoverText; // 当中心悬停在物品上时显示物品名称的文本

    [Header("射线检测")]
    public float raycastDistance = 5f; // 射线检测的距离
    public LayerMask itemLayer; // 射线检测的目标层,用于识别物品

    public void Start()
    {
        toggleInventory(false); // 初始时关闭背包界面

        //合并槽位
        allInventorySlots.AddRange(inventorySloats);

        foreach (Slot uiSlot in allInventorySlots) // 初始化所有槽位
        {
            uiSlot.initialiseSlot();
        }
    }

    public void Update()
    {
        itemRaycast(Input.GetKeyDown(KeyCode.E)); // 显示物品名称和按E拾取物品

        if (Input.GetKeyDown(KeyCode.Tab)) // 按下tab键切换背包界面的显示状态
            toggleInventory(!inventory.activeInHierarchy);
    }

    private void itemRaycast(bool hasClicked = false)
    {
        itemHoverText.text = ""; // 默认不显示任何物品名称
        Ray ray = Camera.main.ScreenPointToRay(crosshair.transform.position); // 从准星位置发出射线
        RaycastHit hit;

        if (Physics.Raycast(ray, out hit, raycastDistance, itemLayer)) // 如果射线检测到物品层的对象
        {
            if (hit.collider != null)
            {
                if (hasClicked) // 如果是按了操作,尝试捡起物品
                {
                    Item newItem = hit.collider.GetComponent<Item>();
                    if (newItem)
                    {
                        addItemToInventory(newItem); // 将物品添加到背包中
                    }
                }
                else // 否则,仅获取物品名称以显示
                {
                    Item newItem = hit.collider.GetComponent<Item>();
                    if (newItem)
                    {
                        itemHoverText.text = newItem.name; // 显示物品名称
                    }
                }
            }
        }
    }


    //将物品添加到背包中
    private void addItemToInventory(Item itemToAdd)
    {
        int leftoverQuantity = itemToAdd.currentQuantity; // 剩余需要添加到背包的物品数量
        Slot openSlot = null; // 记录一个空的槽位
        for (int i = 0; i < allInventorySlots.Count; i++) // 遍历所有槽位
        {
            Item heldItem = allInventorySlots[i].getItem();

            if (heldItem != null && itemToAdd.name == heldItem.name) // 如果槽位中有相同名称的物品
            {
                int freeSpaceInSlot = heldItem.maxQuantity - heldItem.currentQuantity; // 计算槽位中的剩余空间

                if (freeSpaceInSlot >= leftoverQuantity) // 如果剩余空间足够
                {
                    heldItem.currentQuantity += leftoverQuantity; // 添加物品到该槽位
                    Destroy(itemToAdd.gameObject); // 销毁场景中的物品对象
                    allInventorySlots[i].updateData(); // 更新槽位显示的数据
                    return;
                }
                else // 如果剩余空间不足
                {
                    heldItem.currentQuantity = heldItem.maxQuantity; // 填满当前槽位
                    leftoverQuantity -= freeSpaceInSlot; // 更新剩余需要添加的物品数量
                }
            }
            else if (heldItem == null) // 如果槽位为空
            {
                if (!openSlot)
                    openSlot = allInventorySlots[i]; // 记录第一个空槽位
            }

            allInventorySlots[i].updateData(); // 更新槽位显示的数据
        }

        if (leftoverQuantity > 0 && openSlot) // 如果还有剩余物品且找到了空槽位
        {
            openSlot.setItem(itemToAdd); // 将物品添加到空槽位
            itemToAdd.currentQuantity = leftoverQuantity; // 更新物品的数量
            itemToAdd.gameObject.SetActive(false); // 隐藏场景中的物品对象
        }
        else
        {
            itemToAdd.currentQuantity = leftoverQuantity; // 更新物品的数量
        }
    }

    private void toggleInventory(bool enable)
    {
        //关闭背包时,关闭所有鼠标悬停在该槽位上的标志
        if (!enable)
        {
            foreach (Slot curSlot in allInventorySlots)
            {
                curSlot.hovered = false;
            }
        }

        inventory.SetActive(enable); // 根据参数显示或隐藏背包界面

        Cursor.lockState = enable ? CursorLockMode.None : CursorLockMode.Locked; // 根据背包界面的状态锁定或解锁鼠标指针
        Cursor.visible = enable; // 设置鼠标指针的可见性

        // 禁用或启用相机的旋转控制
        Camera.main.GetComponent<MouseLook>().enabled = !enable;

    }
}

物品挂载Item脚本,配置参数,记得添加碰撞体并修改图层为Item
在这里插入图片描述
背包插槽挂载Slot脚本
在这里插入图片描述
角色上挂载Inventory脚本
在这里插入图片描述

拾取
在这里插入图片描述

源码

源码不出意外的话我会放在最后一节

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,以便我第一时间收到反馈,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,出于兴趣爱好,最近开始自学unity,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!php是工作,unity是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~

在这里插入图片描述

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

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

相关文章

【c++基础】扑克牌组合

说明 小明从一副扑克牌中&#xff08;没有大小王&#xff0c;J认为是数字11&#xff0c;Q是12&#xff0c;K是13&#xff0c;A是1&#xff09;抽出2张牌求和&#xff0c;请问能够组合出多少个不相等的数&#xff0c;按照由小到大输出这些数。 输入数据 第一行是一个整数n代表…

2-8 单链表+双链表+模拟栈+模拟队列

今天给大家用数组来实现链表栈和队列 单链表&#xff1a; 首先要明白是如何用数组实现&#xff0c; 在这里需要用到几个数组&#xff0c;head表示头节点的下标&#xff0c;e[i]表示表示下标为i的值&#xff0c;ne[i]表示当前节点下一个节点的下标。idx表示当前已经用到那个点…

抛弃Spring Cloud Gateway,得物 使用Netty架构100Wqps网关

说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;很多小伙伴拿到一线互联网企业如阿里、网易、有赞、希音、百度、滴滴的面试资格。 最近&#xff0c;尼恩指导一个小伙伴简历&#xff0c;写了一个《高并发网关项目》&#xff0c;此项目帮这个小伙拿到 字节/阿里/…

线程池7个参数描述

所谓的线程池的 7 大参数是指&#xff0c;在使用 ThreadPoolExecutor 创建线程池时所设置的 7 个参数&#xff0c;如以下源码所示&#xff1a; public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable&…

检查链表是否回文

根据回文对称的特点&#xff0c;不难想到将链表分成前后两部分&#xff0c;然后将其中一部分反转的方法。 可以使用快慢指针的方式找到链表的中点&#xff0c;其中快指针每次移动两步&#xff0c;慢指针每次移动一步。当快指针到达链表末尾时&#xff0c;慢指针指向的位置即为链…

[LeetCode周赛复盘] 第 384 场周赛20240211

[LeetCode周赛复盘] 第 384 场周赛20240211 一、本周周赛总结100230. 修改矩阵1. 题目描述2. 思路分析3. 代码实现 100219. 回文字符串的最大数量1. 题目描述2. 思路分析3. 代码实现 100198. 匹配模式数组的子数组数目 II1. 题目描述2. 思路分析3. 代码实现 参考链接 一、本周…

前端JavaScript篇之ajax、axios、fetch的区别

目录 ajax、axios、fetch的区别AjaxAxiosFetch总结注意 ajax、axios、fetch的区别 在Web开发中&#xff0c;ajax、axios和fetch都是用于与服务器进行异步通信的技术&#xff0c;但它们在实现方式和功能上有所不同。 Ajax 定义与特点&#xff1a;Ajax是一种在无需重新加载整个…

【c++基础】国王的魔镜

说明 国王有一个魔镜&#xff0c;可以把任何接触镜面的东西变成原来的两倍——只是&#xff0c;因为是镜子嘛&#xff0c;增加的那部分是反的。 比如一条项链&#xff0c;我们用AB来表示&#xff0c;不同的字母表示不同颜色的珍珠。如果把B端接触镜面的话&#xff0c;魔镜会把…

小游戏和GUI编程(5) | SVG图像格式简介

小游戏和GUI编程(5) | SVG图像格式简介 0. 问题 Q1: SVG 是什么的缩写&#xff1f;Q2: SVG 是一种图像格式吗&#xff1f;Q3: SVG 相对于其他图像格式的优点和缺点是什么&#xff1f;Q4: 哪些工具可以查看 SVG 图像&#xff1f;Q5: SVG 图像格式的规范是怎样的&#xff1f;Q6…

中文GPTS详尽教程,字节扣子Coze插件使用全输出

今天&#xff0c;斜杠君和大家分享如何在字节扣子Coze中创建插件&#xff0c;并在创建后如何使用这个插件。 01 新建插件 首先&#xff0c;进入到插件页面&#xff0c;创建一个插件。 https://www.coze.cn/home 点击左侧的个人空间。 在上面选择”插件“标签&#xff0c;来到…

精灵图,字体图标,CSS3三角

精灵图 1.1为什么需要精灵图 一个网页中往往会应用很多小的背景图像作为修饰&#xff0c;当网页中的图像过多时&#xff0c;服务器就会频繁的接受和发送请求图片&#xff0c;造成服务器请求压力过大&#xff0c;这将大大降低页面的加载速度。 因此&#xff0c;为了有效地减少…

《计算思维导论》笔记:10.4 关系模型-关系运算

《大学计算机—计算思维导论》&#xff08;战德臣 哈尔滨工业大学&#xff09; 《10.4 关系模型-关系运算》 一、引言 本章介绍数据库的基本数据模型&#xff1a;关系模型-关系运算。 二、什么是关系运算 在数据库理论中&#xff0c;关系运算&#xff08;Relational Operatio…

读书笔记之《运动改造大脑》:运动是最佳的健脑丸

《运动改造大脑》的作者是约翰•瑞迪&#xff08;John Ratey&#xff09; / 埃里克•哈格曼&#xff08;Eric Hagerman&#xff09;&#xff0c;原著名称为&#xff1a;Spark&#xff1a;the revolutionary new science of exercise and the brain&#xff0c;于 2013年出版约翰…

Shell脚本编程

文章目录 一、简介二、变量变量命名使用变量只读变量删除变量变量种类 三、数组四、算数运算五、条件测试数值测试字符串测试文件测试组合测试 六、选择执行七、用户交互read命令 八、循环语句for循环while循环until循环 九、函数十、调试脚本十一、环境配置bash配置文件案例&a…

服务器解析漏洞及任意文件下载

1.服务器文件解析漏洞 文件解析漏洞,是指Web容器&#xff08;Apache、nginx、iis等&#xff09;在解析文件时出现了漏洞,以其他格式执行出脚本格式的效果。从而,黑客可以利用该漏洞实现非法文件的解析。 &#xff08;1) Apache linux系统中的apache的php配置文件在/etc/apac…

备战蓝桥杯---动态规划(基础1)

先看几道比较简单的题&#xff1a; 直接f[i][j]f[i-1][j]f[i][j-1]即可&#xff08;注意有马的地方赋值为0&#xff09; 下面是递推循环方式实现的AC代码&#xff1a; #include<bits/stdc.h> using namespace std; #define int long long int a[30][30]; int n,m,x,y; …

Hive窗口函数详解

一、 窗口函数知识点 1.1 窗户函数的定义 窗口函数可以拆分为【窗口函数】。窗口函数官网指路&#xff1a; LanguageManual WindowingAndAnalytics - Apache Hive - Apache Software Foundationhttps://cwiki.apache.org/confluence/display/Hive/LanguageManual%20Windowing…

【Linux】make和Makefile

目录 make和Makefile make和Makefile 我们使用vim编辑器的时候&#xff0c;在一个文件里写完代码要进行编译&#xff0c;要自己输入编译的指令。有没有一种可以进行自动化编译的方法——makefile文件&#xff0c;它可以指定具体的编译操作&#xff0c;写好makefile文件&#x…

项目02《游戏-13-开发》Unity3D

基于 项目02《游戏-12-开发》Unity3D &#xff0c; 任务 &#xff1a;宠物系统 及 人物头像血条 首先在主面板MainPanel预制体中新建一个Panel&#xff0c; 命名为PlayerInfo 新建Image&#xff0c;作为头像 新建Slider&#xff0c;作为血条 对Panel组件添加一个水…

案例:CentOS8 在 MySQL8.0 实现半同步复制

异步复制 MySQL 默认的复制即是异步的&#xff0c;主库在执行完客户端提交的事务后会立即将结果返给给客户端&#xff0c;并不关心从库是否已经接收并处理&#xff0c;这样就会有一个问题&#xff0c;主节点如果 crash 掉了&#xff0c;此时主节点上已经提交的事务可能并没有传…