UGUI实现背包系统
本次实现过程参考了师兄的博客
制作过程
画布(Cavas)是绘图区域, 同时是 ui 元素的容器。 容器中 ui 元素及其子 UI 元素都将绘制在其上。 拥有Canvas组件的游戏对象都有一个画布,它空间中的子对象,如果是 UI 元素将渲染在画布上。
因此,为了显示背包系统中的众多UI元素,先选择2D视图,而后建立一张画布命名为MyCanvas,该canvas的Render Camera 设置为GUI Camera,这个相机只会渲染UI层,因此将其Culling Mask 设置为UI。其它相关设置如下:
为了实现将物品从背包拖动到装备栏的效果,显然,我们需要一个背包(Bag)、一个装备栏(Wear),于是先在Mycanvas下建立两个panel,分别命名为Bag和Wear,设置其锚点为中心,由于我的背包大小是4x4,而装备栏最多只能装4个装备,因此设置二者大小如下:
这里两个panel的x轴为800,为什么会是这个数值呢?稍后再解答。
为这两个panel都添加Grid Layout Group组件,使其可以自动布局,然后往这两个panel填充image子对象,分别填充16个和4个,每个image的大小设置为100,通过设置Grid Layout Group中的Spacing属性,可以设置image子对象的间隔,我设置了每个子对象间的间隔为5。
效果如下:
现在有了背包和装备栏,还需要有装备,装备可以放在背包或装备栏上,因此,再建立一个大小为4x4的panel,命名为Items,同样设置16个image子对象,为每一个image设置相应的装备图片,需要注意的是,需要先将图片格式从Default修改为Sprite
效果如下:
接下来实现panel随鼠标摆动,代码如下,代码逻辑是先获取鼠标在屏幕中的坐标,panel根据鼠标在x轴和y轴的坐标绕x轴和y轴转动相应的幅度。将该脚本分别拖到3个panel上。
using UnityEngine;
public class TiltWindow : 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);
mTrans.localRotation = mStart * Quaternion.Euler(-mRot.y * range.y, mRot.x * range.x, 0f);
}
}
物品的拖动
这里的代码主要参考了利用UGUI制作的包裹系统(一)
先给出代码:
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 lastEnterNormalColor;
// 拖拽至新的物品格子时,该物品格子的高亮颜色
private Color highLightColor = Color.cyan;
void Start()
{
myTransform = this.transform;
myRectTransform = this.transform as RectTransform;
canvasGroup = GetComponent<CanvasGroup>();
originalPosition = myTransform.position;
}
void Update()
{
}
public void OnBeginDrag(PointerEventData eventData)
{
canvasGroup.blocksRaycasts = false;//让event trigger忽略自身,这样才可以让event trigger检测到它下面一层的对象,如包裹或物品格子等
lastEnter = eventData.pointerEnter;
lastEnterNormalColor = lastEnter.GetComponent<Image>().color;
originalPosition = myTransform.position;//拖拽前记录起始位置
//originalPosition = eventData.pointerEnter.transform.position;//拖拽前记录起始位置
gameObject.transform.SetAsLastSibling();//保证当前操作的对象能够优先渲染,即不会被其它对象遮挡住
}
public void OnDrag(PointerEventData eventData)
{
Vector3 globalMousePos;
if (RectTransformUtility.ScreenPointToWorldPointInRectangle(myRectTransform, eventData.position, eventData.pressEventCamera, out globalMousePos))
{
myRectTransform.position = globalMousePos;
}
GameObject curEnter = eventData.pointerEnter;
bool inItemGrid = EnterItemGrid(curEnter);
if (inItemGrid)
{
Image img = curEnter.GetComponent<Image>();
lastEnter.GetComponent<Image>().color = lastEnterNormalColor;
if (lastEnter != curEnter)
{
lastEnter.GetComponent<Image>().color = lastEnterNormalColor;
lastEnter = curEnter;//记录当前物品格子以供下一帧调用
}
//当前格子设置高亮
img.color = highLightColor;
}
}
public void OnEndDrag(PointerEventData eventData)
{
GameObject curEnter = eventData.pointerEnter;
//拖拽到的空区域中(如包裹外),恢复原位
if (curEnter == null)
{
myTransform.position = originalPosition;
}
else
{
//移动至物品格子上
if (curEnter.name == "ItemGrid")
{
myTransform.position = curEnter.transform.position;
originalPosition = myTransform.position;
curEnter.GetComponent<Image>().color = lastEnterNormalColor;//当前格子恢复正常颜色
}
else
{
//移动至包裹中的其它物品上
if (curEnter.name == eventData.pointerDrag.name && curEnter != eventData.pointerDrag)
{
Vector3 targetPostion = curEnter.transform.position;
//curEnter.transform.position = originalPosition;
curEnter.transform.position = new Vector3(originalPosition.x,originalPosition.y,0);
myTransform.position = targetPostion;
originalPosition = myTransform.position;
}
else//拖拽至其它对象上面(包裹上的其它区域)
{
//myTransform.position = originalPosition;
myTransform.position = curEnter.transform.position;
}
}
}
lastEnter.GetComponent<Image>().color = lastEnterNormalColor;//上一帧的格子恢复正常颜色
canvasGroup.blocksRaycasts = true;//确保event trigger下次能检测到当前对象
}
// 判断鼠标指针是否指向包裹中的物品格子
// <param name="go">鼠标指向的对象</param>
bool EnterItemGrid(GameObject go)
{
if (go == null)
{
return false;
}
return go.name == "ItemGrid";
}
}
代码分析及遇到的问题
代码将物品的拖动过程分为三部分,分别是开始拖动,拖动中,释放。并设置了几个变量,分别记录了物品之前的位置,物品格子的颜色(正常/高亮)。在拖动开始时,先记录下物品的位置,使得物品拖到无效位置可以返回。在物品跟随鼠标移动的过程中,鼠标一般是不会检测到它所经过的格子区域的,因为鼠标视线被你的物品挡住了。为了解决这个问题,代码作者给出了解决方案。
这其中的关键就在于一个组件CanvasGroup,其里面有一个属性blocksRaycasts,将它的值设置为false就可以让鼠标透过物品,看到物品下面的格子了。
因此,需要先为每个装备物品添加Canvas group组件:
在拖动时设置blocksRaycasts = false,使其可以忽略物品并检测到格子。然后就是在移动过程中,若经过了格子区域(装备栏、背包)则会将当前格子区域设置为高亮,在释放装备时,若位置是空位置,则装备会返回原位,若位置是其他装备的位置,则会交换二者位置,若位置是背包中的空位置,则会将装备放入空位置中。
在第三种情况,即“释放装备的位置是背包中的空位置,则会将装备放入空位置中”,一开始遇到了一点问题,这部分的代码如下:
myTransform.position = curEnter.transform.position;
按理说,装备的位置是会到鼠标所指对象的位置,即装备应该会被放入空位置中,但是实际结果是装备放入了背包的正中间,经过分析,这是因为当背包栏为空时,鼠标就会指向整个背包,而不是背包中的某个栏位,这时松开鼠标,装备会回到整个背包的位置,因此装备放入了背包的正中间。为了解决这个问题,我将Item panel的位置移到远处并通过手动调整16个装备image子对象的坐标,将其位置设回原处。这就是刚才Item panel的x轴为800的原因,这是为了避免当背包有空位置时,鼠标会指向整个Item panel。通过这个调整,现在若释放装备的位置是背包中的空位置,则会将装备放入空位置中,因为没有了Item panel的遮挡,此时鼠标指向的是背包中的栏位。
而刚才调整了两个panel的位置,其x轴都为800,为什么还要调整Bag panel的x轴呢?这是因为二者panel位置不同则panel随鼠标摆动的幅度会不同。因此只有设置panel相同,摆动的幅度才会一致。
人物模型和背景
创建一个空对象用来放人物、背景图、主相机。该相机用来渲染人物和背景。结构如下:
为人物添加动画。
截图