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

效果演示

文章目录

系列目录

前言

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

人物和视角基本控制

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

这里我就直接贴出代码了

人物移动控制

csharp 复制代码
[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);
    }
}

视角控制

csharp 复制代码
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

物品插槽背景框

物品插槽,可以把物品插槽做出预制体,后面好修改

准星图像和文本

脚本控制

物品信息脚本

csharp 复制代码
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;//物品最大堆叠数量
}

背包插槽脚本

csharp 复制代码
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;
    }
}

库存系统脚本

csharp 复制代码
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是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~

相关推荐
皮皮陶1 小时前
Unity WebGL交互通信
unity·交互·webgl
程序员正茂2 小时前
PICO+Unity MR空间网格
unity·mr·pico
程序员正茂2 小时前
PICO+Unity MR空间锚点
unity·pico·空间锚点
龙中舞王4 小时前
Unity学习笔记(2):场景绘制
笔记·学习·unity
mirrornan5 小时前
产品如何3D建模?如何根据使用场景选购3D扫描仪?
科技·3d·3d建模·3d模型·三维扫描
兔老大的胡萝卜6 小时前
关于 3D Engine Design for Virtual Globes(三维数字地球引擎设计)
人工智能·3d
深蓝学院6 小时前
无需姿态,即刻重建!NoPoSplat,重新定义3DGS的重建Pipeline
3d
智方科技6 小时前
cesium 3DTiles之pnts格式详解
3d
mirrornan7 小时前
3D看车如何实现?有哪些功能特点和优势?
3d·3d模型·3d交互展示·3d看车
虾球xz7 小时前
游戏引擎学习第五天
学习·算法·游戏引擎