【Unity】实现 可拖拽+格子位置互换 的背包系统
真的特别激动,花了两个小时的学习,终于学会了背包格子拖拽的效果,这个效果不仅仅用于背包,还有很多游戏的功能和效果可以基于这里面所包含的技术来实现,非常值得学习!!!
在这里特别感谢M_Studio老师的课程中提供的方法和思路。
特别提醒,在学习该效果之前,请务必预先学习C#的接口以及Unity协程的相关知识!!!
一、UI框架搭建
为了方便演示,这里我只做了一个非常非常简单的UI框架。
1.背包格子容器制作
在这里的话我只使用了一个Panel,然后拉伸成一个较宽的矩形做成容器。
并且给这个Panel添加Horizontal Layout Group组件(主要是为了方便给容器中的格子调整位置,按照你的需求来添加合适的就好。)
2.创建预制体
这里我在Panel中添加了一个Image对象作为格子,改名为item(随便调整一下颜色,看得清楚一些)
然后你需要给这个Image对象添加CanvasGroup组件(后续使用的是射线检测,会用到这个组件)
3.完成预制体的制作
将做好的对象拖放到文件夹当中,预制体制作完成!
4.放置预制体
在容器中多放几个预制体,比如像这样
二、编写脚本
1.创建item的控制脚本
新建一个脚本(我就取名叫做“UIDrag.cs”),然后将这个脚本拖拽给item预制体,这个脚本将控制item预制体实现拖拽效果。
2.创建变量
这里我们需要创建四个变量,用于获取一些必要的组件和保存一些必要的数据。
using System.Collections;
using UnityEngine;
using UnityEngine.EventSystems;
public class UIDrag : MonoBehaviour
{
public Vector2 originalPosition; //初位置
public Vector2 offsetPostion; //拖动物体时候的位置差
private Transform parentTransform; //父级物体的Transform
private CanvasGroup canvasGroup;
private void Awake() {
canvasGroup = GetComponent();
parentTransform = transform.parent;
originalPosition = transform.position; //获得初位置的值
}
}
3.实现接口
我们需要实现三个Unity提供的接口,分别是 IBeginDragHandler, IDragHandler,和IEndDragHandler。
这里我续写代码:
using System.Collections;
using UnityEngine;
using UnityEngine.EventSystems;
public class UIDrag : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
public Vector2 originalPosition; //初位置
public Vector2 offsetPostion; //拖动物体时候的位置差
private Transform parentTransform; //父级物体的Transform
private CanvasGroup canvasGroup;
private void Awake() {
canvasGroup = GetComponent();
parentTransform = transform.parent;
originalPosition = transform.position; //获得初位置的值
}
//这下面是实现的接口的方法
public void OnBeginDrag(PointerEventData eventData)
{
}
public void OnDrag(PointerEventData eventData)
{
}
public void OnEndDrag(PointerEventData eventData)
{
}
}
4.开始拖拽
在这里我们需要在OnBeginDrag中实现以下几个逻辑
将Item的父级设置为与父级同级(为了暂时的脱离父级容器,也就是那个Panel)
获得鼠标与Item的位置差(为了拖拽的时候不会有鬼畜的抖动)
被拖拽的物体禁用射线阻挡(方便射线射向底部的item)
这里我们继续续写代码:
using System.Collections;
using UnityEngine;
using UnityEngine.EventSystems;
public class UIDrag : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
public Vector2 originalPosition; //初位置
public Vector2 offsetPostion; //拖动物体时候的位置差
private Transform parentTransform; //父级物体的Transform
private CanvasGroup canvasGroup;
private void Awake() {
canvasGroup = GetComponent();
parentTransform = transform.parent;
originalPosition = transform.position; //获得初位置的值
}
//这下面是实现的接口的方法
public void OnBeginDrag(PointerEventData eventData)
{
//将Item的父级设置为与父级同级
transform.SetParent(parentTransform.parent);
//获得鼠标与Item的位置差
offsetPostion = (Vector2)transform.position - eventData.position;
//被拖拽的物体禁用射线阻挡,以方便鼠标发射的射线射向底部的UI
canvasGroup.blocksRaycasts = false;
}
public void OnDrag(PointerEventData eventData)
{
}
public void OnEndDrag(PointerEventData eventData)
{
}
}
5.拖拽中
这里我们需要在OnDrag方法中实现以下逻辑
- 让item跟随鼠标移动
这里继续续写代码:
using System.Collections;
using UnityEngine;
using UnityEngine.EventSystems;
public class UIDrag : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
public Vector2 originalPosition; //初位置
public Vector2 offsetPostion; //拖动物体时候的位置差
private Transform parentTransform; //父级物体的Transform
private CanvasGroup canvasGroup;
private void Awake() {
canvasGroup = GetComponent();
parentTransform = transform.parent;
originalPosition = transform.position; //获得初位置的值
}
//这下面是实现的接口的方法
public void OnBeginDrag(PointerEventData eventData)
{
//将Item的父级设置为与父级同级
transform.SetParent(parentTransform.parent);
//获得鼠标与Item的位置差
offsetPostion = (Vector2)transform.position - eventData.position;
//被拖拽的物体禁用射线阻挡,以方便鼠标发射的射线射向底部的UI
canvasGroup.blocksRaycasts = false;
}
public void OnDrag(PointerEventData eventData)
{
//拖动改变位置
transform.position = eventData.position+offsetPostion;
}
public void OnEndDrag(PointerEventData eventData)
{
}
}
6.结束拖拽(重点)
这一步至关重要!!!因为要完成的逻辑特别的多!!!
我们要实现以下几个功能:
恢复被拖拽的Item所处的父级
检测当前射线所射向哪个对象,并进行空值处理
通过射线获得被拖拽的item位于哪个item之上
两个item交换位置
重新设置两个item的初始位置
这里继续续写代码:
using System.Collections;
using UnityEngine;
using UnityEngine.EventSystems;
public class UIDrag : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
public Vector2 originalPosition; //初位置
public Vector2 offsetPostion; //拖动物体时候的位置差
private Transform parentTransform; //父级物体的Transform
private CanvasGroup canvasGroup;
private void Awake() {
canvasGroup = GetComponent();
parentTransform = transform.parent;
originalPosition = transform.position; //获得初位置的值
}
//这下面是实现的接口的方法
public void OnBeginDrag(PointerEventData eventData)
{
//将Item的父级设置为与父级同级
transform.SetParent(parentTransform.parent);
//获得鼠标与Item的位置差
offsetPostion = (Vector2)transform.position - eventData.position;
//被拖拽的物体禁用射线阻挡,以方便鼠标发射的射线射向底部的UI
canvasGroup.blocksRaycasts = false;
}
public void OnDrag(PointerEventData eventData)
{
//拖动改变位置
transform.position = eventData.position+offsetPostion;
}
public void OnEndDrag(PointerEventData eventData)
{
//结束拖动后,恢复Item所处的层级
transform.SetParent(parentTransform);
//如果不在背包上面或者射线检测到的是空值
if(eventData.pointerCurrentRaycast.gameObject==null||
!eventData.pointerCurrentRaycast.gameObject.CompareTag("Item"))
{
//允许射线阻挡
canvasGroup.blocksRaycasts = true;
//为本Item设置位置
SetPosition(originalPosition);
}
//又如果检测到被拖拽的对象处于另一个item之上
else if(eventData.pointerCurrentRaycast.gameObject.CompareTag("Item"))
{
//获得目标对象
var target = eventData.pointerCurrentRaycast.gameObject;
//预存储目标对象的原始位置
Vector2 newPosition = target.GetComponent().originalPosition;
//设置目标Item的位置为自己的初始位置
target.GetComponent().SetPosition(originalPosition);
//设置目标对象的新的原始位置
target.GetComponent().originalPosition = originalPosition;
//设置本item的初始位置为目标Item的原初始位置
originalPosition = newPosition;
//允许射线阻挡
canvasGroup.blocksRaycasts = true;
//为本Item设置位置
SetPosition(originalPosition);
}
}
//设置Item位置
public void SetPosition(Vector2 position)
{
//启动协程
StartCoroutine(Move(position));
}
///
/// 协程:平滑移动
///
/// 要移动到的位置
///
private IEnumerator Move(Vector2 position)
{
float elapsed = 0f,duration = 0.3f;
while(elapsed<duration)
{
transform.position = Vector2.Lerp(transform.position,position,elapsed/duration);
elapsed+=Time.deltaTime;
yield return null;
}
transform.position = position;
}
}
这样就大功告成啦!
我这里为了做视觉区分,改了一下颜色,现在来预览一下效果: