之前完成了一个背包以及添加物品,但是物品并不具备“物品”所该有的功能,除开每个游戏所需的特定的效果之外,也缺乏基本的移动拖拽功能等,这篇我们完善一下背包系统。先回顾一下之前的代码,这里我直接贴出来。
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class GridManager : MonoBehaviour {
public List<GameObject> grid;//格子列表,用来存储所有个物品格子
public GameObject item;//物品对象,把prefab添加到这里
void Update () {
if(Input.GetKeyDown(KeyCode.K)){
AddItem(item);//调用添加物品的方法
}
}
public void AddItem(GameObject _item){
//查找每个格子,寻找空格子的物体。
for(int i=0;i<grid.Count;i++){
//这里我们通过查找名字来寻找物体,判断格子内是否有物品存在
//但这样就只能查找命名为该名字的物体,实际中我们可以通过tag或者其他属性来判断
bool isHaving= grid[i].transform.FindChild("Sword(Clone)");
//如果当前格子有物品存在
if(isHaving)
continue;
else if(!isHaving){
//当前的空格子
Transform _i=grid[i].gameObject.GetComponent<RectTransform>();
//创建一个物品对象
GameObject go=(GameObject)Instantiate(_item);
//把创建的物品对象添加到格子下
go.transform.SetParent(_i);
//调整物品的位置位于格子中间
go.transform.localPosition=Vector3.zero;
break;
}
}
}
这段代码实现了根据按顺序给每个格子添加物品,如果我们的格子里面有物品,则会跳过,查看下一个格子是否能存放物品。注意,我们暂时没有写背包格子满载的情况,也没有判断物品是否能够叠加的功能。
那么,当一个背包里的东西杂乱无章的时候,我们就需要对其进行整理。这里我们先添加一个函数。
//在开头添加一个存储列表
public List<GameObject> c_grid;//备用列表,用于存储需要整理位置的物品
//整理物品函数
public void ClearUpGrid(){
for (int i = 0; i < grid.Count; i++)
{
bool isHaving= grid[i].transform.FindChild("Sword(Clone)");
if(isHaving){
GameObject go=grid[i].transform.FindChild("Sword(Clone)").gameObject;
c_grid.Add(go);//将当前格子内的物品存储到后备列表里面
if(i==grid.Count-1)//当所有格子内的物品都存放到备用列表中后
grid.Clear();//清空当前的背包
}
}
for (int j = 0; j < c_grid.Count; j++)
{
//对于每个存储到后备列表中的物品,我们依次添加到格子列表中
GameObject go=c_grid[j];
go.transform.SetParent(grid[j].transform);
//调整物品的位置位于格子中间
go.transform.localPosition=Vector3.zero;
//如果所有的物品都添加到了物品格子列表中,则清空后备列表
if(j==c_grid.Count)
c_grid.Clear();
}
}
我们再给Update函数添加一句来调用整理背包的函数
if(Input.GetKeyDown(KeyCode.Space)){
ClearUpGrid();
}
分析一下
1、查询每个背包格子,如果有物品存在,则将该物品复制一份到备用列表中。
2、当查询到最后一个背包格子的时候,则清空当前背包格子内的物品
3、查询每个后备列表中的物品,依次添加到背包格子列表中。
4、当后备列表中所有物品都添加完成了,则清空后备列表。
这里也需要注意一下!!我们发现代码中再一次对整个背包列表进行了查询,与之前添加物品函数一样,总共我们就查询了两次,其实我们也可以当背包格子中有物品存在的时候,就将其添加到后备列表中存储,整理的时候直接清空格子再添加。但是当我们背包过大,物品过多的时候,这样子不一定好,因为这样每个物品就占了2个资源,如果我们按照这里的函数来执行的话,就只会在整理的时候占用资源。我们在做背包的时候,需要考虑如何安排这样的代码。这里提供这样的一个方法来参考。
另外,我们这里所有的物品都是一个,所以不存在类别关系,如果要对物品进行分类优先排序,我们需要对每个物品添加类别属性,通过类别属性来自定义优先级,在排序的时候进行优先排序。
背包整理的功能有了,接下来是对每个物品的处理。
首先,我们确定几项要点:
1、物品能够拖动
2、物品拖动后的层级关系
3、物品拖放的位置
4、拖动失败的处理
其实这是一个很简单的问题,我们事先给gridList添加了一个空物体GridManager作为父物体,先给我们的物品添加一个脚本(该脚本偷懒用的别人的)
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections;
public class ItemDrag : MonoBehaviour, IPointerDownHandler,IPointerUpHandler,IDragHandler {
// 鼠标起点
private Vector2 originalLocalPointerPosition;
// 面板起点
private Vector3 originalPanelLocalPosition;
// 当前面板
private RectTransform panelRectTransform;
// 父节点,这个最好是UI父节点,因为它的矩形大小刚好是屏幕大小
public RectTransform parentRectTransform;
//格子列表
private GameObject gridManager;//在gridlist之上添加的一个新的空物体作为父节点
private GameObject originalGrid;//记录物品拖动前的位置
private static int siblingIndex = 0;
void Awake () {
panelRectTransform = transform as RectTransform;
parentRectTransform = GameObject.FindGameObjectWithTag("ScrollRect").GetComponent<RectTransform>() as RectTransform;
gridManager=GameObject.Find("GridManager");
}
// 鼠标按下
public void OnPointerDown (PointerEventData data) {
//记录物品当前所在的格子信息
originalGrid=panelRectTransform.parent.gameObject;
//将物品放置在gridManager下,并设置层级,保证物品显示在整个背包层面之上
panelRectTransform.SetParent(gridManager.transform);
siblingIndex++; //层级管理
panelRectTransform.transform.SetSiblingIndex(siblingIndex);
// 记录当前面板起点
originalPanelLocalPosition = panelRectTransform.localPosition;
// 通过屏幕中的鼠标点,获取在父节点中的鼠标点
// parentRectTransform:父节点
// data.position:当前鼠标位置
// data.pressEventCamera:当前事件的摄像机
// originalLocalPointerPosition:获取当前鼠标起点
RectTransformUtility.ScreenPointToLocalPointInRectangle (parentRectTransform, data.position, data.pressEventCamera, out originalLocalPointerPosition);
}
public void OnPointerUp(PointerEventData data){
// transform.SetParent(gridlist.transform);
RaycastHit2D hit = Physics2D.Raycast(Input.mousePosition,-Vector2.up);
if (hit.collider != null) { //如果射线检测到的gameobject为grid,就把当前物品放在grid节点下
if(hit.collider.gameObject.tag=="Grid"&&hit.collider.gameObject.transform.FindChild("Sword(Clone)")==null)
transform.parent=hit.transform;
else
{
//如果不是格子或没有检测到物体,则将物品放回到原来的格子内
transform.parent=originalGrid.transform;
}
}
else
{
transform.parent=originalGrid.transform;
}
//重置物品位置
transform.localPosition=Vector3.zero;
}
// 拖动
public void OnDrag (PointerEventData data) {
if (panelRectTransform == null || parentRectTransform == null){
return;
}
Vector2 localPointerPosition;
// 获取本地鼠标位置
if (RectTransformUtility.ScreenPointToLocalPointInRectangle (parentRectTransform, data.position, data.pressEventCamera, out localPointerPosition)) {
// 移动位置 = 本地鼠标当前位置 - 本地鼠标起点位置
Vector3 offsetToOriginal = localPointerPosition - originalLocalPointerPosition;
// 当前物品位置 = 物品起点 + 移动位置
panelRectTransform.localPosition = originalPanelLocalPosition + offsetToOriginal;
}
// ClampToWindow ();
}
本段代码是借用他人的代码做修改来使用的。我们注意一下代码。
首先,在脚本开头我们添加了这样一句
using UnityEngine.EventSystems;
这是引用ugui的响应事件,同时也在MonoBehaviour后添加了三个接口。
同时提一下,因为C#不支持多继承,但我们可以通过实现接口的方法来做类似多继承的效果。
public class ItemDrag : MonoBehaviour, IPointerDownHandler,
IPointerUpHandler,IDragHandler
添加后会发现报错,提示我们没有实现接口。这三个接口名分别对应以下三个函数,分别当鼠标按下时,鼠标松开时,以及物品拖动时。
public void OnPointerDown (PointerEventData data) { }
public void OnPointerUp (PointerEventData data) { }
public void OnDrag(PointerEventData data) {
详细的功能在代码里注释有了。
另外关于RectTransformUtility.ScreenPointToLocalPointInRectangle,雨松的博客里也有讲过,是将一个坐标转换的问题,将鼠标的坐标转换为UI的坐标。这段代码呢可以去官网查询一下api了解一下,不仅是背包,包括血条名字的显示,飙血字效果等都会用到的相关方法。
再次注意,光是这样,我们是检测不到碰撞的,通过射线碰撞需要对每个物品格子添加Collider来检测,选中物品列表中的每个grid,添加BoxCollider2D组件,在Scene下调节Collider的大小。现在可以检测了。
别忘了给格子添加Tag为Grid。
运行一下,已经完成对背包的整理和对物品的拖放了。
如果对本篇内容有什么问题或者意见或不对的地方,请及时指出,欢迎交流~
网友评论
这个是什么错
出现在抬起鼠标时 OnPointerUp()的 if(hit.collider.gameObject.tag=="Grid"&&hit.collider.gameObject.transform.FindChild("Sword(Clone)")==null) 里
就是这句transform.parent=hit.transform;
现在拖动完放开的时候 位置老是出错