UGUI 实现Inventory背包系统

本文介绍如何使用Unity的UGUI实现游戏中的背包系统,包括界面设计、物品跟随鼠标移动及拖拽功能的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

UGUI 实现Inventory背包系统

一、效果展示

在这里插入图片描述

二、实现过程

大致实现过程参考自师兄的博客https://blog.kinpzz.com/2016/05/21/unity3d-ugui-Inventory/,其中加入了自己的不少修改,包括项目结构和具体的实现逻辑。

界面主要分为两部分,第一部分是UI,包括右侧背包栏和左侧装备栏,用单独一个相机渲染,且该相机只负责UI层的渲染;第二部分是背景和人物,用另一个相机渲染,该相机渲染除了UI层外的其他层。
这样就不会出现两个相机相互遮挡的情况了。

首先实现UI部分

创建一个Canvas,命名为myCanvas,而后设置如下
在这里插入图片描述
在myCanvas中依次创建三个Panel,分别命名为Wear、Bag、Items,表示装备栏、背包栏和物品栏,锚点都设置为正中间。
自行调整他们在myCanvas中的位置和大小,可以参照我的方式,装备栏在左侧,背包栏和物品栏在右侧。(初始状态下的物品栏就在背包栏正前方,所以可以先只设置装备栏和背包栏,而后拷贝背包栏作轻微调整即可得到物品栏)

接下来,为背包栏添加格子,所谓格子就是一个个Image元件。
首先为Bag添加Grid Layout Group组件,这是一个自动布局工具,默认从左上角开始将新增加的元件加到Panel中。
然后增加16个Image对象作为Bag的子对象,每个Image对象同样设置锚点为中心,设置Image对象的大小,使得它们差不多恰好填充整个Bag。增加后将Grid Layout Group组件删除,继续微调每个Image对象的大小和位置,再微调Bag的大小和位置,如下图。
在这里插入图片描述
同理,得到Wear和Item。
在这里插入图片描述
布局完成,接下来用代码实现物品随鼠标移动旋转。
代码逻辑很简单,根据鼠标在屏幕空间中的位置相对于整个屏幕的位置来控制几个Panel的旋转角度,其中鼠标坐标x转换为Panel绕x轴旋转的角度,坐标y转换为Panel绕y轴旋转的角度。
将代码拖到三个Panel上。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FollowMouse : MonoBehaviour
{
	public Vector2 range = new Vector2(5f, 3f);//摆动幅度
	Transform mTrans;
	Quaternion mStart;
	Vector2 mRot = Vector2.zero;
	void Start ()
	{
		mTrans = transform;
		mStart = mTrans.localRotation;
	}
	void Update ()
	{
		Vector3 pos = Input.mousePosition;
		float halfWidth = Screen.width * 0.5f;
		float halfHeight = Screen.height * 0.5f;
		float x = Mathf.Clamp((pos.x - halfWidth) / halfWidth, -1f, 1f);
		float y = Mathf.Clamp((pos.y - halfHeight) / halfHeight, -1f, 1f);
		mRot = Vector2.Lerp(mRot, new Vector2(x, y), Time.deltaTime * 5f);
		//Debug.Log(mRot);
		mTrans.localRotation = mStart * Quaternion.Euler(-mRot.y * range.y, mRot.x * range.x, 0f);
	}
}

再写一个代码,实现物品的拖动。
主要实现三个函数,分别表示鼠标开始拖动时、拖动中、释放时做的处理。
鼠标开始拖动时要记住拖动前物体的位置,这样当物品被拖到空区域时可以回到初始位置,其次要将canvasGroup.blocksRaycasts设置为false,让event trigger忽略鼠标按着的物体,这样才可以检测到它下面一层的对象,如装备栏或背包栏格子等。(如果物品对象没有添加canvasGroup组件要先加上,否则运行会报错)。

还有一个细节,即将物品设置为同一级对象中的最后一个对象,这样它就不会被其他对象遮挡。

鼠标拖动时主要控制移动到的格子上的背景色变化,当鼠标进入一个格子上时,该格子背景色高亮,鼠标离开时格子背景色恢复成普通。

鼠标释放时根据被拖动物品此刻所在位置决定操作,如果移动到空区域,则物品自动回到初始位置,如果移动到格子上,则物品放到该格子中。

using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class DragItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    private Transform myTransform;
    private RectTransform myRectTransform;
    // 用于event trigger对自身检测的开关
    private CanvasGroup canvasGroup;
    // 拖拽操作前的有效位置,拖拽到有效位置时更新
    public Vector3 originalPosition;
    // 记录上一帧所在物品格子
    private GameObject lastEnter = null;
    // 物品格子的正常颜色
    private Color NormalColor = Color.white;
    // 物品格子的高亮颜色
    private Color highLightColor = Color.cyan;
    void Start()
    {
        myTransform = this.transform;
        myRectTransform = GetComponent<RectTransform>();

        canvasGroup = GetComponent<CanvasGroup>();
        originalPosition = myTransform.position;
    }
    void Update()
    {
    }
    public void OnBeginDrag(PointerEventData eventData)
    {
        canvasGroup.blocksRaycasts = false;//让event trigger忽略自身,这样才可以让event trigger检测到它下面一层的对象,如包裹或物品格子等
        lastEnter = eventData.pointerEnter;
        //originalPosition = myTransform.position;//拖拽前记录起始位置
        originalPosition = myRectTransform.anchoredPosition3D;//拖拽前记录起始位置
        gameObject.transform.SetAsLastSibling();//保证当前操作的对象能够优先渲染,即不会被其它对象遮挡住
    }
    public void OnDrag(PointerEventData eventData)
    {
        //被拖拽的物体跟着鼠标走
        Vector3 globalMousePos;
        if (RectTransformUtility.ScreenPointToWorldPointInRectangle(myRectTransform, eventData.position, eventData.pressEventCamera, out globalMousePos))
        {
            myRectTransform.position = globalMousePos;
        }

        //curEnter随鼠标的移动而变化
        GameObject curEnter = eventData.pointerEnter;
        //Debug.Log(curEnter);
        if (curEnter == lastEnter) return;
        if (lastEnter != null && (lastEnter.tag == "grid" || lastEnter.tag == "item")) {
            lastEnter.GetComponent<Image>().color = NormalColor;
        }
        if (curEnter != null && (curEnter.tag == "grid" || curEnter.tag == "item")) {
            curEnter.GetComponent<Image>().color = highLightColor;
        }
        lastEnter = curEnter;
        
    }
    public void OnEndDrag(PointerEventData eventData)
    {
        GameObject curEnter = eventData.pointerEnter;
        //拖拽到的空区域中(如包裹外),恢复原位
        if (curEnter == null)
        {
            //myTransform.position = originalPosition;
            myRectTransform.anchoredPosition3D = originalPosition;
        }
        else
        {
            //移动至物品格子上
            if (curEnter.tag == "grid")
            {
                // myTransform.position = curEnter.transform.position;
                // originalPosition = myTransform.position;
                myRectTransform.anchoredPosition3D = curEnter.GetComponent<RectTransform>().anchoredPosition3D;
                originalPosition = myRectTransform.anchoredPosition3D;
				curEnter.GetComponent<Image>().color = NormalColor;//当前格子恢复正常颜色
            }
            else
            {
                //移动至包裹中的其它物品上
                if (curEnter.tag == eventData.pointerDrag.tag && curEnter != eventData.pointerDrag)
                {
                    // Vector3 targetPostion = curEnter.transform.position;
                    // curEnter.transform.position = originalPosition;
                    // myTransform.position = targetPostion;
                    // originalPosition = myTransform.position;
                    curEnter.GetComponent<Image>().color = NormalColor;
                    Vector3 targetPostion = curEnter.GetComponent<RectTransform>().anchoredPosition3D;
                    curEnter.GetComponent<RectTransform>().anchoredPosition3D = originalPosition;
                    myRectTransform.anchoredPosition3D = targetPostion;
                    originalPosition = myRectTransform.anchoredPosition3D;
                }
                else//拖拽至其它对象上面(包裹上的其它区域)
                {
                    //myTransform.position = originalPosition;
                    myRectTransform.anchoredPosition3D = originalPosition;
                }
            }
        }
		//lastEnter.GetComponent<Image>().color = lastEnterNormalColor;//上一帧的格子恢复正常颜色
        canvasGroup.blocksRaycasts = true;//确保event trigger下次能检测到当前对象
    }
    
}

将上面的代码拖到每一个物品上,然后将列表中的所有物品标签设置为item,格子标签设置为grid。

跑一下,发现似乎不行,当物品从背包栏移到装备栏格子或者反过来时会有bug,我们回过头看OnendGrag的实现,当出现这种情况时,被拖动物品的RectTransform.anchoredPosition3D被设置为目标格子的RectTransform.anchoredPosition3D,但是两者的父对象是不同的!

解决方法很简单,将它们的父对象的RectTransform.anchoredPosition3D都设为(0,0,0)即可,这时物体和格子对象的RectTransform.anchoredPosition3D会自动地变化,但仍保持原来的布局,与此同时,现在它们相当于有同一个参照系了,那就是Panel上一级的Canvas。

再运行一下,UI部分搞定了!

增加背景和人物

从assets商店下载一个任务模型,我用的是LowPolySoldiers_demo,并添加了一点动画。
在这里插入图片描述

背景来自另一个资源,BodyGuards。
创建一个空对象命名为CharacterAndBackground,并将人物模型和背景模型都拉到该对象下,调整一下对象的坐标。
在这里插入图片描述
前面提到过UI层和其它层要分开用两个相机分别渲染,不然会有相互遮挡。
UI相机只渲染UI层。
在这里插入图片描述
主相机渲染除了UI层外的其它层。
在这里插入图片描述
运行,就能看到最终的效果了!

三、项目地址

https://github.com/jiushiwola/unity-3D/tree/master/hw9

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值