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

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

前言

欢迎来到【制作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脚本

拾取

源码

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

目录
相关文章
|
8月前
|
C# 图形学 开发者
Unity开发中使用UnityWebRequest从HTTP服务器下载资源。
总之,UnityWebRequest就是游戏开发者手中的万能钓鱼竿,既可以获取文本数据,也能钓上图片资源,甚至是那声音的涟漪。使用UnityWebRequest的时候,你需要精心准备,比如确定URL、配置请求类型和头信息;发起请求;巧妙处理钓获的数据;还需要机智面对网络波澜,处理各种可能出现的错误。按照这样的过程,数据的钓取将会是一次既轻松愉快也效率高效的编程钓鱼之旅。
417 18
|
图形学
Unity 获取鼠标位置下的UGUI或3D物体
本文总结了两种检测方法,分别用于UGUI和3D物体的检测。第一种方法`GetOverUIobj`专门用于检测鼠标悬停的UGUI元素,通过`GraphicRaycaster`实现。第二种方法`GetOverWordGameObject`则同时适用于UI和3D物体检测,利用`PhysicsRaycaster`进行射线检测。两者均返回悬停对象或null。
|
前端开发 图形学
unity UGUI跟随3D物体的坐标转换
在 Unity 中实现 UGUI 元素跟随 3D 物体,关键是将 3D 物体的世界坐标转换为屏幕或画布坐标。通过 Camera.WorldToScreenPoint 方法,可将 3D 物体位置映射到屏幕上,再更新 UGUI 元素的位置。代码示例展示了如何使用该方法,使 UGUI 图像跟随 3D 模型,并提供文字显示、图像和线条的显示/隐藏功能。
|
存储 数据挖掘 数据库
服务器数据恢复—EMC UNITY 400存储卷被误删除的数据恢复案例
EMC Unity 400存储连接了2台硬盘柜。2台硬盘柜上一共有21块硬盘(520字节)。21块盘组建了2组RAID6:一组有11块硬盘,一组有10块硬盘。 在存储运行过程中,管理员误操作删除了 2组POOL上的部分数据卷。
|
编译器 vr&ar 图形学
从零开始的unity3d入门教程(五)---- 基于Vuforia的AR项目
这是一篇Unity3D结合Vuforia实现增强现实(AR)项目的入门教程,涵盖了环境配置、Vuforia账户注册、Target数据集创建、Unity项目设置、AR程序配置、Android环境配置以及最终在手机上测试运行的全过程。
从零开始的unity3d入门教程(五)---- 基于Vuforia的AR项目
|
图形学 C#
超实用!深度解析Unity引擎,手把手教你从零开始构建精美的2D平面冒险游戏,涵盖资源导入、角色控制与动画、碰撞检测等核心技巧,打造沉浸式游戏体验完全指南
【8月更文挑战第31天】本文是 Unity 2D 游戏开发的全面指南,手把手教你从零开始构建精美的平面冒险游戏。首先,通过 Unity Hub 创建 2D 项目并导入游戏资源。接着,编写 `PlayerController` 脚本来实现角色移动,并添加动画以增强视觉效果。最后,通过 Collider 2D 组件实现碰撞检测等游戏机制。每一步均展示 Unity 在 2D 游戏开发中的强大功能。
1206 6
|
图形学 开发者 搜索推荐
Unity Asset Store资源大解密:自制与现成素材的优劣对比分析,教你如何巧用海量资产加速游戏开发进度
【8月更文挑战第31天】游戏开发充满挑战,尤其对独立开发者或小团队而言。Unity Asset Store 提供了丰富的资源库,涵盖美术、模板、音频和脚本等,能显著加快开发进度。自制资源虽具个性化,但耗时长且需专业技能;而 Asset Store 的资源经官方审核,质量可靠,可大幅缩短开发周期,使开发者更专注于核心玩法。然而,使用第三方资源需注意版权问题,且可能需调整以适应特定需求。总体而言,合理利用 Asset Store 能显著提升开发效率和项目质量。
527 1
|
图形学 C# 开发者
Unity粒子系统全解析:从基础设置到高级编程技巧,教你轻松玩转绚丽多彩的视觉特效,打造震撼游戏画面的终极指南
【8月更文挑战第31天】粒子系统是Unity引擎的强大功能,可创建动态视觉效果,如火焰、爆炸等。本文介绍如何在Unity中使用粒子系统,并提供示例代码。首先创建粒子系统,然后调整Emission、Shape、Color over Lifetime等模块参数,实现所需效果。此外,还可通过C#脚本实现更复杂的粒子效果,增强游戏视觉冲击力和沉浸感。
1416 1
|
图形学 开发者 UED
Unity游戏开发必备技巧:深度解析事件系统运用之道,从生命周期回调到自定义事件,打造高效逻辑与流畅交互的全方位指南
【8月更文挑战第31天】在游戏开发中,事件系统是连接游戏逻辑与用户交互的关键。Unity提供了多种机制处理事件,如MonoBehaviour生命周期回调、事件系统组件及自定义事件。本文介绍如何有效利用这些机制,包括创建自定义事件和使用Unity内置事件系统提升游戏体验。通过合理安排代码执行时机,如在Awake、Start等方法中初始化组件,以及使用委托和事件处理复杂逻辑,可以使游戏更加高效且逻辑清晰。掌握这些技巧有助于开发者更好地应对游戏开发挑战。
880 0
|
开发者 图形学 前端开发
绝招放送:彻底解锁Unity UI系统奥秘,五大步骤教你如何缔造令人惊叹的沉浸式游戏体验,从Canvas到动画,一步一个脚印走向大师级UI设计
【8月更文挑战第31天】随着游戏开发技术的进步,UI成为提升游戏体验的关键。本文探讨如何利用Unity的UI系统创建美观且功能丰富的界面,包括Canvas、UI元素及Event System的使用,并通过具体示例代码展示按钮点击事件及淡入淡出动画的实现过程,助力开发者打造沉浸式的游戏体验。
853 0