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