Unity3D仿星露谷物语开发20之道具简介弹出窗
1、目标
当鼠标悬停在栏的某个道具上面时(没有点击道具),会自动弹出道具的简介的说明框。
2、思路
1)制作一个预制件
2)当鼠标悬停在某个slot上时,填充预制件的数据
3、自动布局ContentSizeFitter组件
布局相关的多个组件说明参考:「Unity3D」自动布局LayoutElement、ContentSizeFitter、AspectRatioFitter、GridLayoutGroup_unity layout element-CSDN博客
(1)什么是ContentSizeFitter组件?
它是Unity UGUI中的一个组件,用于自动调整UI元素的大小,以适应其内容的大小变化。
它可以根据内容的大小自动调整UI元素的宽度和高度,确保内容不会被截断或溢出。
(2)工作原理
生效的时候,如果尺寸变动,会根据pivot来改变伸缩方向,即:pivot在中心就是四圈伸展或收缩,pivot在左上就是向右下伸或收缩,以此类推。
4、制作预制件
在Hierarchy -> PersistentScene -> UI -> MainGameUICanvas 下创建新的空物体命名为InventoryTextBox。
(1)添加ContentSizeFitter组件
给这个对象添加ContentSizeFitter组件。
设置Vertical Fit属性为Preferred Size,会根据元素设置这个文本框的垂直大小。
像图像和文本框这样的元素可以设置一个默认的带大小,然后根据内容进行扩展。
(2)添加Vertical Layout Group组件
设置Spacing为-4,使得元素间有轻微的重叠。
Control Child Size的Width和Height都勾选,子元素的Layout Element的才能其作用。
整体效果如下:
(3)添加子元素Image对象
给InventoryTextBox添加Image对象,并且重命名为TextBoxImage1。
Soure Image选择“textBox”。
(4)添加Vertical Layout Group组件
其属性设置如下:
整体效果如下:
(5)添加Text对象
在TextBoxImage1下添加UI -> Text MeshPro对象 并重命名为Text1。
其属性设置如下:
复制Text1得到Text2,其属性设置如下:
复制Text2得到Text3,其属性设置如下:
(6)复制TextBoxImage1得到TextBoxImage2
修改Padding -> Top值为10。
并将其下的Text1~3的Font Size设置为6,Vertex Color设置为black。
整体效果如下:
然后清空所有6个Text对象中的Text Input,默认情况下没有文本输出。
最后将InventoryTextBox对象拖到Assets -> Prefabs -> UI目录下:
然后再删除Hierarchy中的InventoryTextBox对象。
5、创建UIInventoryTextBox脚本
在Assets -> Scripts -> UI -> UIInventory目录下,创建UIInventoryTextBox脚本。
其代码如下:
using TMPro;
using UnityEngine;
public class UIInventoryTextBox : MonoBehaviour
{
[SerializeField] private TextMeshProUGUI textMeshTop1 = null;
[SerializeField] private TextMeshProUGUI textMeshTop2 = null;
[SerializeField] private TextMeshProUGUI textMeshTop3 = null;
[SerializeField] private TextMeshProUGUI textMeshBottom1 = null;
[SerializeField] private TextMeshProUGUI textMeshBottom2 = null;
[SerializeField] private TextMeshProUGUI textMeshBottom3 = null;
public void SetTextboxText(string textTop1, string textTop2, string textTop3, string textBottom1, string textBottom2, string textBottom3)
{
textMeshTop1.text = textTop1;
textMeshTop2.text = textTop2;
textMeshTop3.text = textTop3;
textMeshBottom1.text = textBottom1;
textMeshBottom2.text = textBottom2;
textMeshBottom3.text = textBottom3;
}
}
打开InventoryTextBox的预制体,然后添加UIInventoryTextBox组件。
同时填充6个属性值。
6、优化Settings.cs脚本
设置工具的description描述信息。
添加如下代码:
// Tools
public const string HoeingTool = "Hoe";
public const string ChoppingTool = "Axe";
public const string BreakingTool = "Pickaxe";
public const string ReapingTool = "Scythe";
public const string WateringTool = "Watering Can";
public const string CollectingTool = "Basket";
7、优化InventoryManager.cs脚本
增加获取道具类型描述的功能。
/// <summary>
/// Get the item type description for an item type - returns the item type description as a string for a given ItemType
/// </summary>
/// <param name="itemType"></param>
/// <returns></returns>
public string GetItemTypeDescription(ItemType itemType)
{
string itemTypeDescription;
switch (itemType)
{
case ItemType.Breaking_tool:
itemTypeDescription = Settings.BreakingTool;
break;
case ItemType.Chopping_tool:
itemTypeDescription = Settings.ChoppingTool;
break;
case ItemType.Hoeing_tool:
itemTypeDescription = Settings.HoeingTool;
break;
case ItemType.Reaping_tool:
itemTypeDescription = Settings.ReapingTool;
break;
case ItemType.Watering_tool:
itemTypeDescription = Settings.WateringTool;
break;
case ItemType.Collecting_tool:
itemTypeDescription = Settings.CollectingTool;
break;
default:
itemTypeDescription = itemType.ToString();
break;
}
return itemTypeDescription;
}
8、优化UIInventoryBar.cs脚本
增加属性,用于记录Bar对应的弹出框。
[HideInInspector] public GameObject inventoryTextBoxGameobject;
其完整代码如下:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UIInventoryBar : MonoBehaviour
{
[SerializeField] private Sprite blank16x16sprite = null; // 插槽默认图案
[SerializeField] private UIInventorySlot[] inventorySlot = null; // 所有插槽集合
public GameObject inventoryBarDraggedItem;
[HideInInspector] public GameObject inventoryTextBoxGameobject;
private RectTransform rectTransform;
private bool _isInventoryBarPositionBottom = true;
public bool IsInventoryBarPositionBottom { get { return _isInventoryBarPositionBottom;} set { _isInventoryBarPositionBottom = value; } }
private void Awake()
{
rectTransform = GetComponent<RectTransform>();
}
private void OnDisable()
{
EventHandler.InventoryUpdatedEvent -= InventoryUpdated;
}
private void InventoryUpdated(InventoryLocation inventoryLocation, List<InventoryItem> inventoryList)
{
if (inventoryLocation == InventoryLocation.player)
{
ClearInventorySlots();
if (inventorySlot.Length > 0 && inventoryList.Count > 0)
{
for (int i = 0; i < inventorySlot.Length; i++)
{
if (i < inventoryList.Count)
{
int itemCode = inventoryList[i].itemCode;
ItemDetails itemDetails = InventoryManager.Instance.GetItemDetails(itemCode);
if (itemDetails != null)
{
// add images and details to inventory item slot
inventorySlot[i].inventorySlotImage.sprite = itemDetails.itemSprite;
inventorySlot[i].textMeshProUGUI.text = inventoryList[i].itemQuantity.ToString();
inventorySlot[i].itemDetails = itemDetails;
inventorySlot[i].itemQuantity = inventoryList[i].itemQuantity;
}
}
else
{
break;
}
}
}
}
}
private void ClearInventorySlots()
{
if(inventorySlot.Length > 0)
{
// loop through inventory slots and update with blank sprite
for(int i = 0; i < inventorySlot.Length; i++)
{
inventorySlot[i].inventorySlotImage.sprite = blank16x16sprite;
inventorySlot[i].textMeshProUGUI.text = "";
inventorySlot[i].itemDetails = null;
inventorySlot[i].itemQuantity = 0;
}
}
}
private void OnEnable()
{
EventHandler.InventoryUpdatedEvent += InventoryUpdated;
}
private void Update()
{
// Switch inventory bar position depending on player position
SwitchInventoryBarPosition();
}
private void SwitchInventoryBarPosition()
{
Vector3 playerViewportPosition = Player.Instance.GetPlayerViewportPosition();
if (playerViewportPosition.y > 0.3f && IsInventoryBarPositionBottom == false)
{
rectTransform.pivot = new Vector2(0.5f, 0f);
rectTransform.anchorMin = new Vector2(0.5f, 0f);
rectTransform.anchorMax = new Vector2(0.5f, 0f);
rectTransform.anchoredPosition = new Vector2(0f, 2.5f);
IsInventoryBarPositionBottom = true;
}
else if (playerViewportPosition.y <= 0.3f && IsInventoryBarPositionBottom == true)
{
rectTransform.pivot = new Vector2(0.5f, 1f);
rectTransform.anchorMin = new Vector2(0.5f, 1f);
rectTransform.anchorMax = new Vector2(0.5f, 1f);
rectTransform.anchoredPosition = new Vector2(0f, -2.5f);
IsInventoryBarPositionBottom = false;
}
}
}
9、优化UIInventorySlot.cs脚本
添加属性并进行初始化:
private Canvas parentCanvas;
[SerializeField] private GameObject inventoryTextBoxPrefab = null;
private void Awake()
{
parentCanvas = GetComponentInParent<Canvas>();
}
给UIInventorySlot类继承IPointerEnterHandler, IPointerExitHandler的接口,并实现这两个接口:
public void OnPointerEnter(PointerEventData eventData)
{
// Populate text box with item details
if(itemQuantity != 0)
{
// Instantiate inventory text box
inventoryBar.inventoryTextBoxGameobject = Instantiate(inventoryTextBoxPrefab, transform.position, Quaternion.identity);
inventoryBar.inventoryTextBoxGameobject.transform.SetParent(parentCanvas.transform, false);
UIInventoryTextBox inventoryTextBox = inventoryBar.inventoryTextBoxGameobject.GetComponent<UIInventoryTextBox>();
// Set item type description
string itemTypeDescription = InventoryManager.Instance.GetItemTypeDescription(itemDetails.itemType);
// Populate text box
inventoryTextBox.SetTextboxText(itemDetails.itemDescription, itemTypeDescription, "", itemDetails.itemLongDescription, "", "");
// Set text box position according to inventory bar position
if (inventoryBar.IsInventoryBarPositionBottom)
{
inventoryBar.inventoryTextBoxGameobject.GetComponent<RectTransform>().pivot = new Vector2(0.5f, 0f);
inventoryBar.inventoryTextBoxGameobject.transform.position = new Vector3(transform.position.x, transform.position.y + 50f, transform.position.z);
}
else
{
inventoryBar.inventoryTextBoxGameobject.GetComponent<RectTransform>().pivot = new Vector2(0.5f, 1f);
inventoryBar.inventoryTextBoxGameobject.transform.position = new Vector3(transform.position.x, transform.position.y - 50f, transform.position.z);
}
}
}
public void OnPointerExit(PointerEventData eventData)
{
DestroyInventoryTextBox();
}
private void DestroyInventoryTextBox()
{
if (inventoryBar.inventoryTextBoxGameobject != null)
{
Destroy(inventoryBar.inventoryTextBoxGameobject);
}
}
当在库存栏中交互两个道具的位置时,此时鼠标会经过Slot,此时我们不希望显示描述窗口。
if (eventData.pointerCurrentRaycast.gameObject != null && eventData.pointerCurrentRaycast.gameObject.GetComponent<UIInventorySlot>() != null)
{
// Destroy inventory text box
DestroyInventoryTextBox();
}
完整代码如下:
using UnityEngine;
using TMPro;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System;
public class UIInventorySlot : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, IPointerEnterHandler, IPointerExitHandler
{
private Camera mainCamera;
private Transform parentItem; // 场景中的物体父类
private GameObject draggedItem; // 被拖动的物体
private Canvas parentCanvas;
public Image inventorySlotHighlight;
public Image inventorySlotImage;
public TextMeshProUGUI textMeshProUGUI;
[SerializeField] private UIInventoryBar inventoryBar = null;
[SerializeField] private GameObject itemPrefab = null;
[SerializeField] private int slotNumber = 0; // 插槽的序列号
[SerializeField] private GameObject inventoryTextBoxPrefab = null;
[HideInInspector] public ItemDetails itemDetails;
[HideInInspector] public int itemQuantity;
private void Awake()
{
parentCanvas = GetComponentInParent<Canvas>();
}
private void Start()
{
mainCamera = Camera.main;
parentItem = GameObject.FindGameObjectWithTag(Tags.ItemsParentTransform).transform;
}
public void OnBeginDrag(PointerEventData eventData)
{
if(itemDetails != null)
{
// Disable keyboard input
Player.Instance.DisablePlayerInputAndResetMovement();
// Instatiate gameobject as dragged item
draggedItem = Instantiate(inventoryBar.inventoryBarDraggedItem, inventoryBar.transform);
// Get image for dragged item
Image draggedItemImage = draggedItem.GetComponentInChildren<Image>();
draggedItemImage.sprite = inventorySlotImage.sprite;
}
}
public void OnDrag(PointerEventData eventData)
{
// move game object as dragged item
if(!draggedItem != null)
{
draggedItem.transform.position = Input.mousePosition;
}
}
public void OnEndDrag(PointerEventData eventData)
{
// Destroy game object as dragged item
if (draggedItem != null)
{
Destroy(draggedItem);
// if drag ends over inventory bar, get item drag is over and swap then
if (eventData.pointerCurrentRaycast.gameObject != null && eventData.pointerCurrentRaycast.gameObject.GetComponent<UIInventorySlot>() != null)
{
// get the slot number where the drag ended
int toSlotNumber = eventData.pointerCurrentRaycast.gameObject.GetComponent<UIInventorySlot>().slotNumber;
// Swap inventory items in inventory list
InventoryManager.Instance.SwapInventoryItems(InventoryLocation.player, slotNumber, toSlotNumber);
// Destroy inventory text box
DestroyInventoryTextBox();
}
else
{
// else attemp to drop the item if it can be dropped
if (itemDetails.canBeDropped)
{
DropSelectedItemAtMousePosition();
}
}
// Enable player input
Player.Instance.EnablePlayerInput();
}
}
/// <summary>
/// Drops the item(if selected) at the current mouse position. called by the DropItem event
/// </summary>
private void DropSelectedItemAtMousePosition()
{
if(itemDetails != null)
{
Vector3 worldPosition = mainCamera.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, -mainCamera.transform.position.z));
// Create item from prefab at mouse position
GameObject itemGameObject = Instantiate(itemPrefab, worldPosition, Quaternion.identity, parentItem);
Item item = itemGameObject.GetComponent<Item>();
item.ItemCode = itemDetails.itemCode;
// Remove item from player's inventory
InventoryManager.Instance.RemoveItem(InventoryLocation.player, item.ItemCode);
}
}
public void OnPointerEnter(PointerEventData eventData)
{
// Populate text box with item details
if(itemQuantity != 0)
{
// Instantiate inventory text box
inventoryBar.inventoryTextBoxGameobject = Instantiate(inventoryTextBoxPrefab, transform.position, Quaternion.identity);
inventoryBar.inventoryTextBoxGameobject.transform.SetParent(parentCanvas.transform, false);
UIInventoryTextBox inventoryTextBox = inventoryBar.inventoryTextBoxGameobject.GetComponent<UIInventoryTextBox>();
// Set item type description
string itemTypeDescription = InventoryManager.Instance.GetItemTypeDescription(itemDetails.itemType);
// Populate text box
inventoryTextBox.SetTextboxText(itemDetails.itemDescription, itemTypeDescription, "", itemDetails.itemLongDescription, "", "");
// Set text box position according to inventory bar position
if (inventoryBar.IsInventoryBarPositionBottom)
{
inventoryBar.inventoryTextBoxGameobject.GetComponent<RectTransform>().pivot = new Vector2(0.5f, 0f);
inventoryBar.inventoryTextBoxGameobject.transform.position = new Vector3(transform.position.x, transform.position.y + 50f, transform.position.z);
}
else
{
inventoryBar.inventoryTextBoxGameobject.GetComponent<RectTransform>().pivot = new Vector2(0.5f, 1f);
inventoryBar.inventoryTextBoxGameobject.transform.position = new Vector3(transform.position.x, transform.position.y - 50f, transform.position.z);
}
}
}
public void OnPointerExit(PointerEventData eventData)
{
DestroyInventoryTextBox();
}
private void DestroyInventoryTextBox()
{
if (inventoryBar.inventoryTextBoxGameobject != null)
{
Destroy(inventoryBar.inventoryTextBoxGameobject);
}
}
}
10、UIInventorySlot0~11添加InventoryTextBox属性
最终效果如下: