3DRPG游戏开发核心功能09-背包UI系统
主要技术:UGUI,背包显示与功能,背包数据库,世界物品拾取与掉落,武器装备
背包基本UI
步骤
建立
UI-canvens
,子物体panel
,设置 panel 背景图片比例对素材UI需要进行缩放,选中素材本身,Inspector 窗口
Sprite Editor
绿色框线外是保持比例不缩放的,只对绿色框内进行缩放然后将Panel的ImageType设置为
Sliced
新建标题图片文本框,新建退出按钮
Set Native Size
恢复原始尺寸
新建背包内格子
格子网格化控制使用
Grid Layout Group
每个格子添加 图像(背包物品) 和 文本(数量)
最终效果
世界地图上的物品
人物收集到物品 添加到背包
地图上的物体 挂载
rigibody
和collider
,数据等1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public enum ItemType { Useable, Weapon, Armor }
[ ]
public class ItemData_SO : ScriptableObject
{
public ItemType itemType;
public string itemname;
public Sprite itemTcon;
public int itemAmount;
// 可以输入一段文字
[ ]
public string description = "";
// 是否可以堆叠
public bool stackable;
[ ]
// 拾取的武器
public GameObject weaponPrefab;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ItemPickUp : MonoBehaviour
{
public ItemData_SO itemdata;
// 物体被触发
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
//TODO: 物品添加到背包
//装备武器
// 捡起物体后,原本地面上的物体销毁
Destroy(gameObject);
}
}
}人物拾取后,由于原本地面的上的物体有重力会掉落,拾取后重新生成一个
修改指针和移动
装备武器
角色死亡 掉落武器 武器与地面有碰撞
生成武器与原始武器的角度位置需要一致
步骤
触碰武器,在player原位置生成一个prefab
Sword_eq
1
2
3
4
5
6
7
8
9
10
11
public void EquipWeapon(ItemData_SO weapon)
{
if (weapon.weaponPrefab != null)
// Instantiate重载方法可以直接生成在父物体下面
Instantiate(weapon.weaponPrefab, weaponSlot.parent);
//TODO:更新属性
attackData.ApplyWeaponData(weapon.weaponData);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class ItemPickUp : MonoBehaviour
{
public ItemData_SO itemdata;
// 物体被触发
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
//TODO: 物品添加到背包
//装备武器
// 想获取其他数据可以通过GameManager,装备武器
GameManager.Instance.playStats.EquipWeapon(itemdata);
// 捡起物体后,原本地面上的物体销毁
Destroy(gameObject);
}
}
}为武器加载攻击数值,
ItemData_SO
添加AttackData_SO weaponData
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void ApplyWeaponData(AttackData_SO weapon)
{
attackRange = weapon.attackRange;
skillRange = weapon.skillRange;
coolDown = weapon.coolDown;
minDange = weapon.minDange;
maxDamage = weapon.maxDamage;
criticalMultiplier = weapon.criticalMultiplier;
criticalChance = weapon.criticalChance;
}
背包数据库
背包中的物体放在一个列表中
创建一个包含物体名字和数量类的列表:创建单独的 class
InventoryItem
用来记录背包里的物品和现有数量1
2
3
4
5
6
7
8
9// 能够看见
[ ]
public class InventoryItem
{
// 该类数据的名字
public ItemData_SO itemData;
// 数量
public int amount;
}[System.Serializable]
效果在背包中添加物体,考虑到堆叠:创建
InventoryData_SO
作为不同背包的数据库,新建Inventory Data 数据 Bag
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
[ ]
public class InventoryDats_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;
}
}
}
// 不能堆叠 放到最近的一个空格
for (int i = 0; i < items.Count; i++)
{
// 背包为空 或 没有同类物体
if (items[i].itemData == null && !found)
{
items[i].itemData = newItemData;
items[i].amount = amount;
break;
}
}
}
}
[ ]
// 添加物品的类
public class InventoryItem
{
// 该类数据的名字
public ItemData_SO itemData;
// 数量
public int amount;
}使用管理器管理背包中的格子:挂载给背包UI
InventoryCanvas
,1
2
3
4
5
6
7
public class InventoryManager : Singleton<InventoryManager>
{
// TODO: 最后添加模板保存数据
[ ]
public InventoryDats_SO inventoryData;
}修改
ItemPickUp
实现碰撞物体 拾取到背包里1
2
InventoryManager.Instance.inventoryData.AddItem(itemdata, itemdata.itemAmount);
背包显示
显示整个背包:ContainerUI
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class ContainerUI : MonoBehaviour
{
public SlotHolder[] slotHolders;
public void RefreshUI()
{
for (int i = 0; i < slotHolders.Length; i++)
{
// Index 也是从 0 开始
slotHolders[i].itemUI.Index = i;
// 对里面的每一个格子调用更新物品
slotHolders[i].UpdateItem();
}
}
}显示每个格子:SlotHolder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31// 不同的格子类型
public enum SlotType { BAG, WEAPON, ARMOR, ACTION}
public class SlotHolder : MonoBehaviour
{
public SlotType slotType;
public ItemUI itemUI;
public void UpdateItem()
{
// 枚举在切换类型时最好对应Switch
switch (slotType)
{
case SlotType.BAG:
// inventoryData 传给 itemUI 的 Bag
itemUI.Bag = InventoryManager.Instance.inventoryData;
break;
case SlotType.WEAPON:
break;
case SlotType.ARMOR:
break;
case SlotType.ACTION:
break;
}
// 匹配数据库中物体
// UI拿到 背包,不同的itemUI拿到不同的数据库
// 找到inventoryData list items里面格子index数量同样的物品
var item = itemUI.Bag.items[itemUI.Index];
// 给到UI
itemUI.SetItemUI(item.itemData, item.amount);
}
}控制背包内物体的文本和图像:ItemUI
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public class ItemUI : MonoBehaviour
{
// 图标 数量
public Image icon = null;
public Text amount = null;
public InventoryDats_SO Bag { get; set; }
public int Index { get; set; } = -1;
public void SetItemUI(ItemData_SO item, int itemAmount)
{
if (item != null)
{
// item 的图标和数量 传给 当前背包格子
icon.sprite = item.itemTcon;
amount.text = itemAmount.ToString();
// 开启这个格子的图标和数量
icon.gameObject.SetActive(true);
}
else
icon.gameObject.SetActive(false);
}
}
!!!! 记得设置image按比例缩放
设置快捷栏和信息面板的UI
- 设置下面的物品栏同背包:新建 ImageUI 设置里面的格子 每个格子挂载 ItemSlot
- 设置左侧属性栏同背包
拖拽物品
- 建三个类型的UI和数据库
- Bag 背包
- Action 下方工具栏
- Equipment 身上装备
- 拖拽的脚本
DragItem
挂载到能拖拽的UI上,也就是ItemSlot
- 拖拽接口
using UnityEngine.EventSystems
IBeginDragHandler
IDragHandler
IEndDragHandler
- 直接拖拽时,会受到UI之间层级的影响,新建一个Cavens覆盖在他们上面
- 创建 DragData 用来记录每一个 UI 物品原始数据
- 放下时还在 新建的Cavens里面,要改,
交换物品
通过射线
判断位置
拖拽物品到位置position 判断position是在 inventoryUI.slotHolders 的哪一个格子里或者不在
Rectransform
EventSystem.current.IsPointerOverGameObjet()
:返回true
范围下是UI,反之不是RectransformUtility.RectangleContainsScreenPoint
:是否包含给定摄像机观察到的屏幕点
- 是否在格子里
- 切换数据
使用 EventSystem 判断指针位置
1
2
3
4
5
6
7
8
9
10
11
12
public void OnEndDrag(PointerEventData eventData)
{
// 放下物品 交换数据
// 是否指向UI物品
if (EventSystem.current.IsPointerOverGameObject())
{
// 判断是否在格子内
// 执行 SwapItem()
}
// 刷新
}利用 RectTransformUtility.RectangleContainsScreenPoint 判断鼠标位置是否包含在每一个格子范围内
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 拖拽物品到位置position
// 判断position是在 inventoryUI.slotHolders 的哪一个格子里或者不在
// 检测30个背包格子
public bool CheckInventoryUI(Vector3 position)
{
for (int i = 0; i < inventoryUI.slotHolders.Length; i++)
{
RectTransform t = (RectTransform)inventoryUI.slotHolders[i].transform;
if (RectTransformUtility.RectangleContainsScreenPoint(t, position))
return true;
}
return false;
}
// 检测6个工具栏
public bool CheckInActionUI(Vector3 position)
{
for (int i = 0; i < actionUI.slotHolders.Length; i++)
{
RectTransform t = (RectTransform)actionUI.slotHolders[i].transform;
if (RectTransformUtility.RectangleContainsScreenPoint(t, position))
return true;
}
return false;
}
// 检测2个装备栏
public bool CheckInEquipmentUI(Vector3 position)
{
for (int i = 0; i < equipmentUI.slotHolders.Length; i++)
{
RectTransform t = (RectTransform)equipmentUI.slotHolders[i].transform;
if (RectTransformUtility.RectangleContainsScreenPoint(t, position))
return true;
}
return false;
}交换图片的同时要交换真是的数据列表排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void SwapItem()
{
var targetItem = targetHolder.itemUI.Bag.items[targetHolder.itemUI.Index];
// 背包里真实数据
var tempItem = currentHolder.itemUI.Bag.items[currentHolder.itemUI.Index];
// 是否相同物体
bool isSameItem = tempItem.itemData == targetItem.itemData;
if (isSameItem && targetItem.itemData.stackable)
{
// 相同可堆叠就加上
targetItem.amount += tempItem.amount;
// 移动的那个物体归0
tempItem.itemData = null;
tempItem.amount = 0;
}
else
{
currentHolder.itemUI.Bag.items[currentHolder.itemUI.Index] = targetItem;
targetHolder.itemUI.Bag.items[targetHolder.itemUI.Index] = tempItem;
}
}调整 RectTransform 的 offset 确保图片在正确位置显示
1
2
3
4
5
RectTransform t = (RectTransform)transform;
// 放到格子中间
t.offsetMax = -Vector2.one * 5;
t.offsetMin = Vector2.one * 5;DragItem
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97// 没有的时候自动挂载
[ ]
public class DragItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
ItemUI currentItemUI;
// 拖动的格子
SlotHolder currentHolder;
// 目标格子
SlotHolder targetHolder;
void Awake()
{
currentItemUI = GetComponent<ItemUI>();
// DragItem挂载在ItemSlot上,格子是他的父级
currentHolder = GetComponentInParent<SlotHolder>();
}
// 开始拖拽
public void OnBeginDrag(PointerEventData eventData)
{
// 记录原始信息:最终没有拖走要回到原始位置
InventoryManager.Instance.currentDrag = new InventoryManager.DragData();
InventoryManager.Instance.currentDrag.originalHolder = GetComponentInParent<SlotHolder>();
InventoryManager.Instance.currentDrag.originalParent = (RectTransform)transform.parent;
// 解决遮挡问题:当前格子的父级设置为新的Canvas在顶层
transform.SetParent(InventoryManager.Instance.dragCanvas.transform, true);
}
// 拖拽过程
public void OnDrag(PointerEventData eventData)
{
// 跟随鼠标位置
transform.position = eventData.position;
}
// 结束拖拽
public void OnEndDrag(PointerEventData eventData)
{
// 放下物品 交换数据
// 是否指向UI物品,然后判断是否在格子范围内
if (EventSystem.current.IsPointerOverGameObject())
{
// 三种类型的格子都可
if (InventoryManager.Instance.CheckInventoryUI(eventData.position) ||
InventoryManager.Instance.CheckInActionUI(eventData.position) ||
InventoryManager.Instance.CheckInEquipmentUI(eventData.position))
{
// 判断点击的是不是 SlotHolder 类型,空格子能直接拿到SlotHolder
if (eventData.pointerEnter.gameObject.GetComponent<SlotHolder>())
targetHolder = eventData.pointerEnter.gameObject.GetComponent<SlotHolder>();
// 非空格子 拿到的是 image,需要拿到其父级格子
else
targetHolder = eventData.pointerEnter.gameObject.GetComponentInParent<SlotHolder>();
// 目标格子类型
switch(targetHolder.slotType)
{
case SlotType.BAG:
SwapItem();
break;
case SlotType.ACTION:
break;
case SlotType.WEAPON:
break;
case SlotType.ARMOR:
break;
}
currentHolder.UpdateItem();
targetHolder.UpdateItem();
}
}
transform.SetParent(InventoryManager.Instance.currentDrag.originalParent);
RectTransform t = (RectTransform)transform;
// 放到格子中间
t.offsetMax = -Vector2.one * 5;
t.offsetMin = Vector2.one * 5;
}
public void SwapItem()
{
var targetItem = targetHolder.itemUI.Bag.items[targetHolder.itemUI.Index];
// 背包里真实数据
var tempItem = currentHolder.itemUI.Bag.items[currentHolder.itemUI.Index];
// 是否相同物体
bool isSameItem = tempItem.itemData == targetItem.itemData;
if (isSameItem && targetItem.itemData.stackable)
{
// 相同可堆叠就加上
targetItem.amount += tempItem.amount;
// 移动的那个物体归0
tempItem.itemData = null;
tempItem.amount = 0;
}
else
{
currentHolder.itemUI.Bag.items[currentHolder.itemUI.Index] = targetItem;
targetHolder.itemUI.Bag.items[targetHolder.itemUI.Index] = tempItem;
}
}
}
切换武器
在 MouseManager 添加函数方法 避免 UI 互动时影响人物控制
- 判定UI,对UI不使用鼠标控制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bool InteractWithUI()
{
// 碰到UI
if (EventSystem.current != null && EventSystem.current.IsPointerOverGameObject())
return true;
else
return false;
}
private void Update()
{
SetCursorTexture();
if (InteractWithUI()) return;
MouseControl();
}在 CharacterStats 中加入 ChangeWeapon 的方法
- 有武器先卸除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//切换武器
public void ChangeWeapon(ItemData_SO weapon)
{
UnEquipment();
EquipWeapon(weapon);
}
public void EquipWeapon(ItemData_SO weapon)
{
if (weapon.weaponPrefab != null)
// Instantiate重载方法可以直接生成在父物体下面
Instantiate(weapon.weaponPrefab, weaponSlot.parent);
//TODO:更新属性
attackData.ApplyWeaponData(weapon.weaponData);
}
//卸下武器
public void UnEquipment()
{
// 有其他武器
if (weaponSlot.transform.childCount != 0)
for (int i = 0; i < weaponSlot.transform.childCount; i++)
Destroy(weaponSlot.transform.GetChild(i).gameObject);
attackData.ApplyWeaponData(baseAttackData);
// TODO:切换动画
}在 SlotHolder 中切换所属背包的数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
switch (slotType)
{
case SlotType.BAG:
// inventoryData 传给 itemUI 的 Bag
itemUI.Bag = InventoryManager.Instance.inventoryData;
break;
case SlotType.WEAPON:
itemUI.Bag = InventoryManager.Instance.equipmentData;
// 装备武器 切换武器
if (itemUI.Bag.items[itemUI.Index].itemData != null)
GameManager.Instance.playStats.ChangeWeapon(itemUI.Bag.items[itemUI.Index].itemData);
else
GameManager.Instance.playStats.UnEquipment();
break;
case SlotType.ARMOR:
itemUI.Bag = InventoryManager.Instance.equipmentData;
break;
case SlotType.ACTION:
itemUI.Bag = InventoryManager.Instance.actionData;
break;
}
DragItem 中添加物品属性和 SlotType 的匹配
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 目标格子类型,相同类型才能放入对应格子
switch(targetHolder.slotType)
{
case SlotType.BAG:
SwapItem();
break;
// 武器格子的类型只能放武器
case SlotType.WEAPON:
if (currentItemUI.Bag.items[currentItemUI.Index].itemData.itemType == ItemType.Weapon)
SwapItem();
break;
case SlotType.ARMOR:
if (currentItemUI.Bag.items[currentItemUI.Index].itemData.itemType == ItemType.Armor)
SwapItem();
break;
// ACTION类型格子可以放 Useable 物品
case SlotType.ACTION:
if (currentItemUI.Bag.items[currentItemUI.Index].itemData.itemType == ItemType.Useable)
SwapItem();
break;
}
currentHolder.UpdateItem();
targetHolder.UpdateItem();
}不同的格子类型:public enum SlotType { BAG, WEAPON, ARMOR, ACTION}
不同的Item类型:public enum ItemType { Useable, Weapon, Armor }
可使用物品
建立可使用物品的ItemData 数据,添加useable item data
蘑菇的设置和剑类似,UseableItemData_SO 制作可使用物品的属性模版
吃蘑菇效果涨10点血
1
2
3
4
5
6
7
8
9
// 吃蘑菇血量修改
public void ApplyHealh(int amount)
{
if (CurrentHealth + amount <= MaxHealth)
CurrentHealth += amount;
else
CurrentHealth = MaxHealth;
}鼠标点击实现效果:利用
IPointerClickHandler
接口实现双击使用物品1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void OnPointerClick(PointerEventData eventData)
{
// 鼠标双击:取余防止三击判断
if (eventData.clickCount % 2 == 0&& itemUI.Bag.items[itemUI.Index].amount > 0)
{
// 使用物品
UseItem();
}
}
// 使用物品功能
public void UseItem()
{
if (itemUI.GetItem() != null)
{
if (itemUI.GetItem().itemType == ItemType.Useable && itemUI.Bag.items[itemUI.Index].amount > 0)
{
GameManager.Instance.playStats.ApplyHealh(itemUI.GetItem().useableItemData.healthPoint);
itemUI.Bag.items[itemUI.Index].amount -= 1;
}
}
// 使用之后物品数量-1 刷新
UpdateItem();
}背包物品为0时设置
1
2
3
4
5
6
7
8
9
10
11
public void SetItemUI(ItemData_SO item, int itemAmount)
{
// 物品数量为0时
if (itemAmount == 0)
{
Bag.items[Index].itemData = null;
icon.gameObject.SetActive(false);
return;
}
}
按键控制UI
添加实现 CloseBtn 关闭当前 UI 面板
添加按键控制打开关闭 UI 面板
创建 ActionButton 脚本设置独立按键使用快捷栏物品
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 按下按键实现对应方法
public class ActionBtn : MonoBehaviour
{
// 显示所有按键
public KeyCode actionKey;
private SlotHolder currentSlotHolder;
void Awake()
{
currentSlotHolder = GetComponent<SlotHolder>();
}
void Update()
{
// 按下对应按键 使用对应格子的物体
if (Input.GetKeyDown(actionKey) && currentSlotHolder.itemUI.GetItem())
currentSlotHolder.UseItem();
}
}
显示Player相关信息
UI上实时看到玩家模型:创建 RawImage 和 独立 Camera 实现映射 Player 的模型
在属性界面新建一个
RayImage
,设置好尺寸Image组件的图片必须是Sprite,而RawImage可以是任何类型的Texture。
Image组件功能更多,代码更复杂。
创建保存模型材质的 Render Text:
project creat -> Render Text
,尺寸为以上尺寸为当前Player新建一个子摄像机
给RawImage添加以上纹理,最终效果(预览显示武器需要武器layer设置为player,因为渲染的是player层)
获得 Text 的面板信息
创建函数方法实时更新面板信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 属性面板属性值
[ ]
public Text healthText;
public Text attackText;
// 更新属性面板函数
public void UpdataStatsData(int health, int min, int max)
{
// 当前生命值
healthText.text = health.ToString();
// 最小攻击力到最大攻击力
attackText.text = min + "-" + max;
}
动画切换控制
- 没拿武器一套动画 拿武器一套动画
使用更多动画来创建 Animator override controller
以拿武器为模板创建 Animator override controller,修改无武器攻击动画
以原模板为攻击动画
ItemData_SO 中添加武器配套动画控制器
1
2
public AnimatorOverrideController weaponAnimator;在 EquipWeapon 和 UnEquipment 中添加代码切换武器伴随动画
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public void EquipWeapon(ItemData_SO weapon)
{
if (weapon.weaponPrefab != null)
// Instantiate重载方法可以直接生成在父物体下面
Instantiate(weapon.weaponPrefab, weaponSlot);
//TODO:更新属性
attackData.ApplyWeaponData(weapon.weaponData);
// 切换武器同时切换另一套动画:runtimeAnimatorController当前控制器
GetComponent<Animator>().runtimeAnimatorController = weapon.weaponAnimator;
}
//卸下武器
public void UnEquipment()
{
// 有其他武器
if (weaponSlot.transform.childCount != 0)
{
for (int i = 0; i < weaponSlot.transform.childCount; i++)
Destroy(weaponSlot.transform.GetChild(i).gameObject);
}
attackData.ApplyWeaponData(baseAttackData);
// TODO:切换动画,还原原始动画
GetComponent<Animator>().runtimeAnimatorController = baseAnimator;
}
Item Tooltip 物品信息显示栏
创建 UI
创建Image,下面两个text,一个名字一个内容
1
2
3
4
5
6
7
8
9
10
11
12
13
public class ItemTooltip : MonoBehaviour
{
public Text itemNameText;
public Text itemInfoText;
// 设置物品信息
public void SetupTooltip(ItemData_SO item)
{
itemNameText.text = item.itemname;
itemInfoText.text = item.description;
}
}学习 Content size fitter 的用法实现自动拉伸缩放,Vertical Layout Group 文件排布
利用 IPointEnterHandler 接口实现鼠标悬停显示 Tooltip
- 悬停进入:
IPointerEnterHandler
1
2
3
4
5
6
7
8
9
10
11
12
// 鼠标悬停显示:悬停进入
public void OnPointerEnter(PointerEventData eventData)
{
if (itemUI.GetItem())
{
// 传入该格子的物体(ItemData_SO)
InventoryManager.Instance.tooltip.SetupTooltip(itemUI.GetItem());
// 显示UI
InventoryManager.Instance.tooltip.gameObject.SetActive(true);
}
}悬停离开退出:
IPointerExitHandler
1
2
3
4
5// 鼠标悬停离开:悬停退出
public void OnPointerExit(PointerEventData eventData)
{
InventoryManager.Instance.tooltip.gameObject.SetActive(false);
}
- 悬停进入:
RectTransform.GetWorldCorners
概念说明及使用方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 先更新位置防治鼠标闪烁
void OnEnable()
{
UpdataPosition();
}
void Update()
{
UpdataPosition();
}
// 信息UI显示在鼠标位置
public void UpdataPosition()
{
// 当前鼠标坐标
Vector3 mousePos = Input.mousePosition;
// 直接取UI位置为鼠标位置会发生遮挡
// UI位置为锚点中心,应该加锚点到边缘的高度
Vector3[] corners = new Vector3[4];
rectTransform.GetWorldCorners(corners);
// UI的宽和高
float width = corners[3].x - corners[0].x;
float heigh = corners[1].y - corners[0].y;
// UI显示位置与鼠标位置关系
// 物体在鼠标位置上方,显示在上方
if (mousePos.y < heigh)
rectTransform.position = mousePos + Vector3.up * heigh * 0.6f;
// 屏幕与鼠标的宽度大于ui宽度就显示在接近屏幕侧
else if (Screen.width - mousePos.x > width)
rectTransform.position = mousePos + Vector3.right * width * 0.6f;
// 否则相反
else
rectTransform.position = mousePos + Vector3.left * width * 0.6f;
}
void OnDisable()
{
InventoryManager.Instance.tooltip.gameObject.SetActive(false);
}
掉落物品
创建 LootItem 类 记录要掉落的物品和权重
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 随机掉落
public class LootSpander : MonoBehaviour
{
// 掉落物品类
[ ]
public class LootItem
{
public GameObject item;
// 权重 出现概率
[ ]
public float weight;
}
// 掉落物品数组
public LootItem[] lootItems;
}使用 [Range] 控制掉落权重的百分比
1
2[ ]
public float weight;实现多物品比较权重掉落
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 按掉落物品权重生成
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 * 2f;
break; // 保证只掉落一个
}
}
}敌人死亡调用
1
2
3
4
5
6
7
8
// 人物消失和游戏停止
void OnDisable()
{
// 死亡掉落物品
if (GetComponent<LootSpander>() && isDead)
GetComponent<LootSpander>().Spawnloot();
}
完成背包
添加背包的保存和读取方法
- 进入游戏拿到数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 单例模式 Awake 是 Override
protected override void Awake()
{
// 进入游戏保证拿到数据
base.Awake();
if (inventoryTemplate != null)
inventoryTemplate = Instantiate(inventoryTemplate);
if (actionTemplate != null)
actionTemplate = Instantiate(actionTemplate);
if (equipmentTemplate != null)
equipmentTemplate = Instantiate(equipmentTemplate);
}
// 用之前的存档方法:PlayerPrefs
// 保存背包数据
public void SaveDate()
{
SaveManager.Instance.Save(inventoryData, inventoryData.name);
SaveManager.Instance.Save(actionData, actionData.name);
SaveManager.Instance.Save(equipmentData, equipmentData.name);
}
// 读取背包数据
public void LoadData()
{
SaveManager.Instance.Load(inventoryData, inventoryData.name);
SaveManager.Instance.Load(actionData, actionData.name);
SaveManager.Instance.Load(equipmentData, equipmentData.name);
}转换场景
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 传送场景名字和终点tag(坐标)
IEnumerator Transition(string sceneName, TransitionDestination.DestinationTag destinationTag)
{
// 保存数据
SaveManager.Instance.SavePlayerData();
InventoryManager.Instance.SaveDate();
}
// 切换菜单场景
IEnumerator LoadLevel(string scene)
{
// 保存数据
SaveManager.Instance.SavePlayerData();
InventoryManager.Instance.SaveDate();
}添加Canves预制一定要加
EventSystem
添加 DragPanel 实现拖拽背包面板
- 挂载在属性和背包界面
鼠标拖拽界面任何位置都可以拖动:中心位置+偏移
匹配分辨率和位移差:
canvas.scaleFactor
画布缩放
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class DragPanel : MonoBehaviour, IDragHandler
{
RectTransform rectTransform;
Canvas canvas;
void Awake()
{
rectTransform = GetComponent<RectTransform>();
canvas = InventoryManager.Instance.GetComponent<Canvas>();
}
public void OnDrag(PointerEventData eventData)
{
// 鼠标拖拽界面任何位置都可以拖动:中心位置+偏移
// anchoredPosition 界面中心
// eventData.delta 每秒位移
rectTransform.anchoredPosition += eventData.delta / canvas.scaleFactor;
// 匹配分辨率和位移差: canvas.scaleFactor 画布缩放
}
}鼠标选中界面在前面: 利用调整 Hierarchy 排序的方法改变渲染前后层级,越往下的越在前
rectTransform.GetSiblingIndex()
索引排序
1
2
3
4
5
6
7
// 鼠标点下
public void OnPointerDown(PointerEventData eventData)
{
// 拖拽的设为第3个子物体,不影响DragItem
rectTransform.SetSiblingIndex(2);
}- 判断移动后的格子是否是原来格子