
视频链接:使用unity实现的3D RPG游戏_网络游戏热门视频 (bilibili.com)
目录
导入素材设置画布布局
使用 Grid Layout Group 进行网格化控制
保存 Slot Holder 为 Prefab
下载两个素材:


然后导入设置画布:


然后给图片设定长宽,注意到被拉伸后设置的不好看:

进入原图的sprite editor:
调整其border,这样其就只会有中心被拉伸而边框不拉伸了。

点击apply,回到原panel中:
image type选择slice前后对比:


创建title和字、以及按钮:

接下来是关键,添加格子的办法:
先添加一个panel,锚点居中调整大小:

阿尔法值设为0
然后添加这个便于布局:

然后为其设定大小、空隙和初始:


限定纵向列数为5
效果如图:
接下来在格子中间实现图片:
添加图片,选择覆盖全屏的锚点,然后设定边距

效果:

随便选中一个图片可看到效果:

然后添加字体并设定大小。
然后将其设为预制体:

然后复制30个:

创建快捷栏 Action Bar 的按钮 UI
创建人物信息面板 UI
添加画布,设定位置,然后添加这个layout Group

然后将item slot拖出来也作为预制体:

然后将item slot加到action button里去,将其设为预制体,添加脚本,和挂接组件

然后将其放进去actionbar的container脚本里:

修改下图片使其美观:

然后再创建一个画布如下:

上面那一格拿来做武器。
在此基础上创建一个武器图片:
再给那个武器图片添加一个item slot


添加武器和盾牌:

创建信息:



这是人物身上的装备的地方
剑弄进来放在地上并导入材质

然后将剑作为预制体并添加刚体碰撞体。

然后创建一个SO:
[CreateAssetMenu(fileName ="New Item",menuName ="Inventory/Item Data")]
public class ItemData_SO : ScriptableObject
{
}
并创建文件:


此处需要将地图上的武器和玩家的武器区分开来,因为玩家身上的武器没有刚体组件,所以需要两个预制件。

public enum ItemType { Useable,Weapon,Armor}
[CreateAssetMenu(fileName ="New Item",menuName ="Inventory/Item Data")]
public class ItemData_SO : ScriptableObject
{
public ItemType itemType;
public string itemName;
public Sprite itemIcon;
public int itemAmount;//这个物品有多少数量
[TextArea]
public string description = "";
public bool stackable;//是否可堆叠
[Header("Weapon")]
public GameObject WeaponPrefab;
}
设定剑信息

接下来实现碰撞到世界地图上的剑然后装备。


public class ItemPickUp : MonoBehaviour
{
public ItemData_SO itemData;
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
//TODO:添加物品到背包
//装备武器
Destroy(gameObject);
}
}
}
为实现给武器一个掉落到地面上的效果,添加重力和capsule collider:

为使得剑在人物身上时可以正常,调位置:
由于我们希望装备武器后,武器攻击力也会改变,所以换武器的操作在characterStats完成。
设定位置:


然后即可实现触碰武器就装备上了。
接下来实现人物的数据由武器的数据决定。
先在物品信息处添加武器信息。

添加武器数据

修改数据的函数:(在攻击数据的SO里进行修改)

装备武器时修改属性

内容:
创建 InventoryData_SO 作为不同背包的数据库
创建单独的 class InventoryItem 用来记录背包里的物品和现有数量
添加 AddItem 方法将物品信息传递到 List 列表当中
修改 ItemPickUp 实现碰撞物体 拾取到背包里
创建背包的SO
[CreateAssetMenu(fileName ="New Inventory",menuName ="Inventory/Inventory Data")]
public class InventoryData_SO : ScriptableObject
{
}
再创建一个文件夹:

创建一个背包的inventory data,这个背包想要的无非就是一个列表,就是之前的itemData,但是如果创建一系列的itemData没有办法给它计数,因为此处的itemAmount指的是创建的物品本身的数量(比如打怪掉落苹果,掉落三个)

如果直接把这个数据放到data里面用的话,就无法使用这个数据的相加或者相减了,所以此处为它单独创建一个列表里的元素,单独创建一个class。
实现如下:
public class InventoryData_SO : ScriptableObject
{
public List<InventoryItem> items = new List<InventoryItem>();
}
[System.Serializable]
public class InventoryItem
{
public ItemData_SO itemData;
public int amount;
}
为使得类里面的变量能看见,添加Serializable使其序列化。
效果:

堆叠物品的代码:
[CreateAssetMenu(fileName ="New Inventory",menuName ="Inventory/Inventory Data")]
public class InventoryData_SO : ScriptableObject
{
public List<InventoryItem> items = new List<InventoryItem>();
public void AddItem(ItemData_SO newItemData,int amount)
{
//对于添加物体,如果是可以堆叠的,只需要在背包中找到同类型的,改变数量,如果不能堆叠,就新建一个格子
bool found = false;//是否找到同类物品
//
if (newItemData.stackable)
{
foreach(var item in items)
{
if (item.itemData == newItemData)
{
item.amount += amount;
found = true;
break;
}
}
}
//上面用foreach是为了找相同的物品,下面使用for循环是为了找到最近的空格
//如果捡到一个物体并且背包中不可堆叠只需要找到一个空格子放入即可
for(int i = 0; i < items.Count; i++)
{
if (items[i].itemData == null && !found)
{
items[i].itemData = newItemData;
items[i].amount = amount;
break;
}
}
}
}
这条添加物品的命令在哪里执行呢?我们创建了数据,接下来需要数据去管理,创建代码挂到inventory canvas管理。
添加背包数据:


拾取武器时不再是直接装备而是放入背包:

效果:

除此之外,有一点需要注意一下,

在itemData_SO中,其自带一个amount信息,是这个物品初始就具有的数量,比如打怪一次掉落三个苹果。这时候这三个苹果就是一个整体。
由于有些物品是可堆叠的,对于可堆叠的物品就需要有一个数量,意思是背包中,这种类型的物品一共有多少个。
所以需要物品信息在此基础上,设定一个背包内的物品信息,将物品的数量和背包中物品的数量区分开:

并将其作为一个新的类
梳理逻辑并创建代码 ContainerUI / SlotHolder / ItemUI
修改 SlotHolder 的 Prefab 添加空物体 ItemSlot
逐层完成代码实现背包里正确显示物品图片和数量
创建三个脚本


为了实现代码的逻辑,调整下背包里物体的逻辑,背包里的格子的图片仅仅作为图片,改名为image,创建一个空物体用来挂接脚本,叫做item slot,并且把image赋值进去

此处实现将图片和数字更新进去

注意到初始时image为空,所以需要激活,为空时则关闭。

别忘了将image修改成它的子物体:

然后在此处写这个脚本

然后挂接上脚本;

Item UI用子物体去赋值,它会自动获得子物体的item ui这个脚本:

在item ui创建两个变量:

对不同情况进行分类,然后获取数据库中对应序号的物品,然后对其进行更新:

在父级物体上创建一个SlotHolder的容器:

然后在inspector窗口中赋值。

然后书写UI更新的代码:

再在总的管理器上添加一个获取的代码:

然后赋值:

接下来实现通过InventoryManager来对背包里的物品进行更新:
初始时更新一次:

捡到物品时再更新一次:

别忘了给剑赋予图片

运行后发现数字不在正中间,解决办法:
因为之前多添加了一个空物体作为item slot,我们将这个item slot布局为覆盖全部,然后留下5个空余:

将image设置为0:

然后拾取地上的剑即可看到剑出现在背包:

人物去捡剑时也会出现

此处介绍一下从上到下的逻辑:

InventoryCanvas包含全部的背包系统,包括背包,人物属性和快捷栏。
InventoryBag包含背包的标题、关闭键和背包里的格子容器(InventoryContainer)。
格子容器(InventoryContainer)包含所有的子物体格子,代码上体现在:
并且由它执行子物体格子的刷新

子物体SlotHolder的赋值通过在inspector窗口中进行:并且格子的顺序和ContainerUI中的List列表里的顺序是一致的。

在SlotHolder中需要实现的代码如下:
SlotHolder是格子(由holder也可以理解),是不可移动的,是和容器里的List一一对应的。
而ItemSlot可以理解为格子里的物品,是可以移动,放到其他的格子里去的,并且里面的物品数量也可以改变。
但是其实ItemSlot里是item物品的UI信息,即图片和数量,并不是真正的物品。
真正的物品信息是放在这里的:

然后
ItemData_SO长这样:
实际例子比如剑:

在inspector窗口中为ContainerUI里的东西拖拽赋值后,接下来需要为格子里的每个物品UI进行序号上的赋值:

并且还要让格子里进行物品UI上的更新,即:slotHolders[i].UpdateItem();
对于每个物品的UI信息,我们需要建立三个变量,获取的是Inventory管理器中的三个InvetoryData的SO物体:

然后我们在itemUI的父级物体SlotHolder中根据类型来为其赋值:

这样的话,除了通过父物体去调用子物体,还实现了通过子物体获取父物体的方法。
上面这样给每个itemUI都赋予了不同种类的inventory栏,然后就可以通过该栏去获取该栏中与UI的Index对应的物品信息:
var item = itemUI.Bag.items[itemUI.Index];//找到数据库中对应序号的对应物品
获取了该物品信息后,就可以进行更新了:
itemUI.SetupItemUI(item.itemData, item.amount);
根据item信息更新itemUI的方法如下:

总结一下就是说,我们实际上获取物品或者移动物品,事实上是先直接修改背包里的数据库的物品信息,修改完毕后,我们让背包中的每个物品栏里的UI去通过背包里的数据库获取对应index的物品信息,然后让物品栏中的UI信息更新为数据库中对应序号的物品的图片信息和数量。
创建 DragItem 实现拖拽功能
介绍 EventSystems 里的接口
实现拖拽跟随鼠标指针
创建 DragData 用来记录每一个 UI 物品原始数据
给信息栏里的containerUI也添加:

public class InventoryManager : Singleton<InventoryManager>
{
[Header("Inventory Data")]
//使用类似之前那样的模板,游戏新开始时复制一份的操作
//TODO:最后添加模板用于保存数据
public InventoryData_SO inventoryData;
public InventoryData_SO actionData;
public InventoryData_SO equipmentData;
[Header("Containers")]
public ContainerUI inventoryUI;
public ContainerUI actionUI;
public ContainerUI equipmentUI;
private void Start()
{
inventoryUI.RefreshUI();
}
}



接下来实现拖拽功能:
为ItemSlot预制体添加该功能。

想要实现拖拽的效果实现这几个接口即可
(交换数据往往需要一个第三方的东西变量来存储然后进行交换)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
[RequireComponent(typeof(ItemUI))]
public class DragItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
ItemUI currentItemUI;
SlotHolder currentHolder;
SlotHolder targetHolder;
void Awake()
{
currentItemUI = GetComponent<ItemUI>();
currentHolder = GetComponentInParent<SlotHolder>();
}
public void OnBeginDrag(PointerEventData eventData)
{
//记录原始数据
}
public void OnDrag(PointerEventData eventData)
{
//跟随鼠标位置移动
transform.position = eventData.position;
}
public void OnEndDrag(PointerEventData eventData)
{
//放下物品 交换数据
}
}
然后即可实现拖拽移动物体,但是物体会被挡在背包里的格子之下。
解决方法:创建一个画布,当物体移动时,将该物体放到该画布上。

dragItem:

接下来需要用第三方的数据去保存那个holder,当拽完了之后需要还原到之前的位置
在InventoryManager设置:
并声明该变量:

然后在拖拽时候修改:

使用 EventSystem 判断指针位置
利用 RectTransformUtility.RectangleContainsScreenPoint 判断鼠标位置是否包含在每一个格子范围内
交换图片的同时要交换真是的数据列表排序
调整 RectTransform 的 offset 确保图片在正确位置显示
参考代码手册:
此处要用到event system

需要要到event system的这个变量:

我们需要判断被拖拽的物品是否在三个栏里面
书写函数:
public bool CheckInInventoryUI(Vector3 position)//此处这个位置是要传输进来的位置
{
for(int i = 0; i < inventoryUI.slotHolders.Length; i++)
{
RectTransform t = inventoryUI.slotHolders[i].transform as RectTransform;//强制类型转换
if (RectTransformUtility.RectangleContainsScreenPoint(t, position))//判断当前位置是否物品栏里
{
return true;
}
}
return false;
}
使用如下:
public void OnEndDrag(PointerEventData eventData)
{
//放下物品 交换数据
if (EventSystem.current.IsPointerOverGameObject())//是否指向UI组件
{
if(InventoryManager.Instance.CheckInActionUI(eventData.position)
|| InventoryManager.Instance.CheckInInventoryUI(eventData.position)
|| InventoryManager.Instance.CheckInEquipmentUI(eventData.position))
//判断是否在三个栏里面的格子里
{
}
}
}
}
下面这个是判断是否在UI组件上

想要实现交换,在endDrag的函数里实现,首先看它是否指向UI组件,如果是的话看是否在设定的背包、快捷栏、人物信息里。
如果在的话,我们获取鼠标点击松开位置的该物品信息。
如果该物体和原来鼠标拖拽的物体一样且可堆叠,只需要增加数字即可。
如果不可堆叠,那就交换数据库中的物品信息,然后刷新UI,就实现了这个效果。
实现代码:
[RequireComponent(typeof(ItemUI))]
public class DragItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
ItemUI currentItemUI;
//
SlotHolder currentHolder;
SlotHolder targetHolder;
void Awake()
{
currentItemUI = GetComponent<ItemUI>();
currentHolder = GetComponentInParent<SlotHolder>();//原先的格子的信息
}
public void OnBeginDrag(PointerEventData eventData)
{
InventoryManager.Instance.currentDrag = new InventoryManager.DragData();
InventoryManager.Instance.currentDrag.orginalHolder = GetComponentInParent<SlotHolder>();
InventoryManager.Instance.currentDrag.originalParent = (RectTransform)transform.parent;
//记录原始数据
transform.SetParent(InventoryManager.Instance.dragCanvas.transform,true);
}
public void OnDrag(PointerEventData eventData)
{
//跟随鼠标位置移动
transform.position = eventData.position;
}
public void OnEndDrag(PointerEventData eventData)
{
//放下物品 交换数据
if (EventSystem.current.IsPointerOverGameObject())//是否指向UI组件
{
if(InventoryManager.Instance.CheckInActionUI(eventData.position)
|| InventoryManager.Instance.CheckInInventoryUI(eventData.position)
|| InventoryManager.Instance.CheckInEquipmentUI(eventData.position))
//判断是否在三个栏里面的格子里
{
if (eventData.pointerEnter.gameObject.GetComponent<SlotHolder>())
targetHolder = eventData.pointerEnter.gameObject.GetComponent<SlotHolder>();
else
targetHolder = eventData.pointerEnter.gameObject.GetComponentInParent<SlotHolder>();
//如果没找到,此时是因为被图片所挡住了,那么就获取其父类的component
Debug.Log(eventData.pointerEnter.gameObject);
//判断鼠标选中的物体是否有slot holder,
//由于例如食物不能放在武器栏里,所以需要对其做区分
switch (targetHolder.slotType)
{
case SlotType.BAG:
SwapItem();
break;
case SlotType.WEAPON:
break;
case SlotType.ARMOR:
break;
case SlotType.ACTION:
break;
}
currentHolder.UpdateItem();//交换完毕后需要更新数据
targetHolder.UpdateItem();
}
}
transform.SetParent(InventoryManager.Instance.currentDrag.originalParent);
}
public void SwapItem()
{
//targetHolder是鼠标指向的位置的格子。
//获取目标格子上面显示的UI,UI图片对应它身上属于哪个背包的哪一个序号的物品
var targetItem = targetHolder.itemUI.Bag.items[targetHolder.itemUI.Index];//实际上就是获取鼠标指向的物品item
var tempItem = currentHolder.itemUI.Bag.items[currentHolder.itemUI.Index];//点击前的鼠标的格子的信息
//如果是相同的物品就进行合并
bool isSameItem = tempItem.itemData == targetItem.itemData;
if (isSameItem && targetItem.itemData.stackable)//并且还要可堆叠才行
{
targetItem.amount += tempItem.amount;
tempItem.itemData = null;
tempItem.amount = 0;
}
else
{
currentHolder.itemUI.Bag.items[currentHolder.itemUI.Index] = targetItem;
//targetItem=tempItem;这样的写法不行因为targetItem只是我们获取的一个变量,应该直接用其本身去更换,即下一行的写法
targetHolder.itemUI.Bag.items[targetHolder.itemUI.Index] = tempItem;
}
}
}
然后接下来如果我们移动物体,如果正好移到中心方块则可以正常实现,如果没有移到中心方块则会出现下面这样的情况。
观察可得,此时物体卡在中间:

item slot的位置也发生了变化:

所以我们要保证它能回到间距5个单位的中心格子上。

注意这两个变量:

offsetMax就是
这个里面的right和top
然后在代码中新增这两项:

接下来即可实现拖拽。

DragItem 中添加物品属性和 SlotType 的匹配
对于非背包里的物品我们也可以交换但是要加以限制。

在 SlotHolder 中切换所属背包的数据库
当我们将Slot Holder放在不同的地方时,此时也要更改其所属的背包对应的数据库:

这样即可实现不同栏间的物品交换,并且数据库也会因此更新。
鼠标点击会使得人物移动的问题


换武器的方法
在characterStats中补充卸下武器的代码:
public void UnEquipWeapon()
{
if (weaponSlot.transform.childCount != 0)
{
for(int i = 0; i < weaponSlot.transform.childCount; i++)
{
Destroy(weaponSlot.transform.GetChild(i).gameObject);
}
}
}
卸下武器前就要还原成原来的攻击数据,因此需要有一个变量来记录初始时没武器的数据:

代码如下:
public void UnEquipWeapon()
{
if (weaponSlot.transform.childCount != 0)
{
for(int i = 0; i < weaponSlot.transform.childCount; i++)
{
Destroy(weaponSlot.transform.GetChild(i).gameObject);
}
}
attackData.ApplyWeanponData(baseAttackData);//卸下武器后还原之前的攻击力
//TODO:切换动画
}
public void ChangeWeapon(ItemData_SO weapon)
{
UnEquipWeapon();
EquipWeapon(weapon);
}
然后在SlotHolder更新一栏中更改武器:


创建大蘑菇 并添加必要的组件
创建 UseableItemData_SO 制作可使用物品的属性模版
利用 IPointerClickHandler 接口实现双击使用物品
在 CharacterStats 中添加 ApplyHealth 实现血量变化
参考代码手册:
IPointerClickHandler :点击跳转
创建一个蘑菇的变量:
创建useable类型的SO:
然后在itemData中加入这种类型:
创建useable类型的SO:
并赋值:(为useable Item赋值)
然后在场景中创建一个mushroom并添加这些组件:
之后即可实现拾取蘑菇并且在格子和快捷栏里自由移动。
接下来实现使用物品的功能:
先在itemUI中写一个根据对应UI获取对应物品的方法:
在characterStats中书写一个函数:
使用需要实现一个接口,
在SlotHolder里实现该接口
public void OnPointerClick(PointerEventData eventData)
{
if (eventData.clickCount % 2 == 0)//代表是双击的话
{
UseItem();
}
}
public void UseItem()
{
if (itemUI.GetItem().itemType == ItemType.Useable&&itemUI.Bag.items[itemUI.Index].amount>0)
{
GameManager.Instance.playerStats.ApplyHealth(itemUI.GetItem().useableData.healthPoint);
itemUI.Bag.items[itemUI.Index].amount -= 1;
}
UpdateItem();
}
别忘了要进行数量减一,更新UI的操作。
但是之后发现一个bug,当蘑菇用完时图片仍然存在:
解决方法:
设定一个相机让它观察player,并设定好各类信息:
创建texture然后放进去
然后将renderTexture给Stats里面创建的RawImage
记得将相机的设置放入prefab里面
然后可以显示:
接下来实现血量和伤害的显示:
然后在装备武器时进行信息的更新:
也可以在manager中更新:
随后即可实现血量和攻击的更新了:升级时也会改变
上面完成了武器的装载与拆卸,接下来实现不同武器的切换。
切换到不同武器时,对应的动画控制器也应该相应做出更改。
根据不同武器切换动画控制器
使用更多动画来创建 Animator override controller
ItemData_SO 中添加武器配套动画控制器
在 EquipWeapon 和 UnEquipment 中添加代码切换武器伴随动画
参考素材:
RPG Character Animation Pack Free :点击跳转
这两个作者的素材很好,有很多动作素材

下载这个只保留animation即可:

创建一个没武器的override controller:并导入动画

然后即可实现无武器的运行。
但是会有两个报错,是因为动画自带两个事件。删除即可。

我们希望给武器自己加上一个animation controller:


在此处更换装备。
初始时获取初始动画控制器:

卸武器时候还原:

这样就可以自由切换了。
有个问题在于人物的武器不能在属性中显示,将sword的预制体的layer设置为player即可。
创建一个image,调整大小:
然后设定为了能根据不同的设置出不同的大小,添加这个组件
添加这个:
给信息一栏也添加Fitter:
这样就可以实现UI的信息的多少会随着栏的信息而改变:
创建代码,实现控制:
鼠标的悬停希望实现的接口:
让SlotHolder实现这两个接口:
public void OnPointerEnter(PointerEventData eventData)
{
if (itemUI.GetItem())
{
InventoryManager.Instance.tooltip.SetupTooltip(itemUI.GetItem());
InventoryManager.Instance.tooltip.gameObject.SetActive(true);
}
}
public void OnPointerExit(PointerEventData eventData)
{
InventoryManager.Instance.tooltip.gameObject.SetActive(false);
}
这样就可以实现悬停时出现。
接下来希望悬停时出现的位置是鼠标的位置:
效果:
但是出现的位置是在鼠标中心,并且还会闪烁。
是因为锚点是出现在中心:
修改方法:
代码如下:
public class ItemToolTip : MonoBehaviour
{
public Text itemNameText;
public Text itemInfoText;
RectTransform rectTransform;
private void Awake()
{
rectTransform = GetComponent<RectTransform>();
}
public void SetupTooltip(ItemData_SO item)
{
itemNameText.text = item.itemName;
itemInfoText.text = item.description;
}
void OnEnable()
{
//开始时会闪烁是因为没有定位好坐标,开始时先更新一下即可避免这种效果
UpdatePosition();
}
private void Update()
{
UpdatePosition();
}
public void UpdatePosition()
{
Vector3 mousePos = Input.mousePosition;
rectTransform.position = mousePos;
Vector3[] corners = new Vector3[4];
rectTransform.GetWorldCorners(corners);
float width = corners[3].x - corners[0].x;
float height = corners[1].y - corners[0].y;
if (mousePos.y < height)
rectTransform.position = mousePos + Vector3.up * height * 0.6f;
else if (Screen.width - mousePos.x > width)
rectTransform.position = mousePos + Vector3.right * width * 0.6f;
else
rectTransform.position = mousePos + Vector3.left * width * 0.6f;
}
}
还有个问题在于当你背包关闭时,信息仍然有显示,解决方法:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LootSpawner : MonoBehaviour
{
[System.Serializable]
public class LootItem
{
public GameObject item;
[Range(0, 1)]
public float weight;
}
public LootItem[] lootItems;
public void Spawnloot()
{
float currentValue = Random.value;
for(int i = 0; i < lootItems.Length; i++)
{
if (currentValue <= lootItems[i].weight)
{
GameObject obj = Instantiate(lootItems[i].item);
obj.transform.position = transform.position + Vector3.up * 2;
}
}
}
}
给怪物添加以上代码
当敌人死亡时调用掉落:
这样即可实现掉落
创建模板
保存加载数据:
开始时加载数据:
然后在inventory manager的inspector中修改:
如果我们需要在第二个场景中也使用背包系统,就将其作为预制体在第二个场景中载入:
并且记得添加eventSystem、
这样即可实现场景切换时保存的效果。
实现拖拽面板
给两个背包面板添加这个代码:
public class DragPanel : MonoBehaviour,IDragHandler
{
RectTransform rectTransform;
void Awake()
{
rectTransform = GetComponent<RectTransform>();
}
public void OnDrag(PointerEventData eventData)
{
rectTransform.anchoredPosition += eventData.delta;//让其锚点的位置的改变量和鼠标的改变量相同即可
}
}
即可实现拖拽。
但是有一点问题在于如果鼠标处于背包的边边部分进行拖拽时,此时面板的位移增量就比鼠标的位移增量要大了。
是因为面板中这个组件的存在:
方法:
但是移动背包时会被另外一个背包挡住:
观察可发现是因为顺序在下的被后渲染,后渲染的就会显示在前方
使用这个方法将其放在索引的最下方。
但是有一点,不能将其放在drag canvas索引的下方。要保证drag canvas始终在下方:
所以需要使用获取索引的方法。
将其索引设置在2即可。
这样就不会被遮挡。
但是此处又出现了一个新的问题,问题在于将物品拖拽放回原处的时候,背包里就看不见了。
解决方法在此处添加:
效果:
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
电脑0x0000001A蓝屏错误怎么U盘重装系统教学分享。有用户电脑开机之后遇到了系统蓝屏的情况。系统蓝屏问题很多时候都是系统bug,只有通过重装系统来进行解决。那么蓝屏问题如何通过U盘重装新系统来解决呢?来看看以下的详细操作方法教学吧。 准备工作: 1、U盘一个(尽量使用8G以上的U盘)。 2、一台正常联网可使用的电脑。 3、ghost或ISO系统镜像文件(Win10系统下载_Win10专业版_windows10正式版下载-系统之家)。 4、在本页面下载U盘启动盘制作工具:系统之家U盘启动工具。 U盘启动盘制作步骤: 注意:制作期间,U盘会被格式化,因此U盘中的重要文件请注
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------
本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01 客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02 数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit
目录1.AdmobSDK下载地址2.将下载好的unityPackagesdk导入到unity里编辑 3.解析依赖到项目中
Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO