最终效果
前言
在这一集中我将使用Unity制作基于瓦片的网格库存系统。 就像在《逃离塔科夫》、《暗黑破坏神》或《流放之路》等游戏中一样。
素材下载
https://assetstore.unity.com/packages/2d/gui/icons/gui-parts-159068
图片配置
配置图片为重复
不懂UI画布适配查看:【Unity小技巧】最简单的UI设置适配方案
修改UI画布适配
新增UI图片,类型改为平铺,默认图片是256的,太大了,所以我们选择缩小4倍,每单位像素为4,同时注意修改轴心在左上角
获取格子坐标
新增ItemGrid代码
public class ItemGrid : MonoBehaviour { // 定义每个格子的宽度和高度 const float tileSizeWidth = 256 / 4; const float tileSizeHeight = 256 / 4; // 计算在格子中的位置 Vector2 positionOnTheGrid = new Vector2(); Vector2Int tileGridPosition = new Vector2Int(); RectTransform rectTransform; Canvas canvas; private void Start() { rectTransform = GetComponent<RectTransform>(); canvas = FindObjectOfType<Canvas>(); } private void Update() { if (Input.GetMouseButtonDown(0)) { // 获取当前鼠标位置在网格中的格子坐标,并打印到控制台 Debug.Log(GetTileGridPosition(Input.mousePosition)); } } // 根据鼠标位置计算在格子中的位置 public Vector2Int GetTileGridPosition(Vector2 mousePosition) { // 计算鼠标位置相对于 RectTransform 的偏移量 positionOnTheGrid.x = mousePosition.x - rectTransform.position.x; positionOnTheGrid.y = rectTransform.position.y - mousePosition.y; // 将偏移量转换为网格位置 // 这里假设 tileSizeWidth 和 tileSizeHeight 是单个瓦片的宽度和高度 // canvas.scaleFactor 是 Canvas 的缩放因子(通常用于 UI 适配不同分辨率) tileGridPosition.x = (int)(positionOnTheGrid.x / tileSizeWidth / canvas.scaleFactor); tileGridPosition.y = (int)(positionOnTheGrid.y / tileSizeHeight / canvas.scaleFactor); // 返回计算出的网格位置 return tileGridPosition; } }
挂载脚本
效果,点击格子打印位置
动态控制背包大小
修改ItemGrid
[SerializeField] int gridSizeWidth = 10; [SerializeField] int gridSizeHeight = 10; private void Start() { rectTransform = GetComponent<RectTransform>(); canvas = FindObjectOfType<Canvas>(); Init(gridSizeWidth, gridSizeHeight); } void Init(int width, int height){ Vector2 size = new Vector2(width * tileSizeWidth, height * tileSizeHeight); rectTransform.sizeDelta = size; }
配置
效果
添加物品
配置物品预制体。修改尺寸和去掉光线投射目标
新增Item脚本,挂载在物品上
public class Item : MonoBehaviour { }
动态添加测试物品,修改ItemGrid
Item[,] itemSlot;//存储物品位置信息 private void Start() { itemSlot= new Item[gridSizeWidth, gridSizeHeight]; rectTransform = GetComponent<RectTransform>(); canvas = FindObjectOfType<Canvas>(); Init(gridSizeWidth, gridSizeHeight); //动态添加测试物品 Item item = Instantiate(itemPrefab).GetComponent<Item>(); PlaceItem(item, 0, 0); item = Instantiate(itemPrefab).GetComponent<Item>(); PlaceItem(item, 3, 2); item = Instantiate(itemPrefab).GetComponent<Item>(); PlaceItem(item, 2, 4); } //按格子坐标添加物品 public void PlaceItem(Item item, int posX, int posY){ itemSlot[posX, posY] = item; item.transform.SetParent(transform, false); Vector2 positon = new Vector2(); positon.x = posX * tileSizeWidth + tileSizeWidth / 2; positon.y = -(posY * tileSizeHeight + tileSizeHeight / 2); item.transform.localPosition = positon; }
配置
运行效果
移动物品
修改ItemGrid,按格子坐标获取物品
//按格子坐标获取物品 public Item PickUpItem(int x, int y){ Item toReturn = itemSlot[x, y]; itemSlot[x, y] = null; return toReturn; }
新增InventoryController,实现物品交互功能
public class InventoryController : MonoBehaviour { public ItemGrid selectedItemGrid;//操作的背包 Item selectedItem;//选中物品 private void Update() { if (selectedItemGrid == null) return; if (Input.GetMouseButtonDown(0)) { // 获取当前鼠标位置在网格中的格子坐标,并打印到控制台 Debug.Log(selectedItemGrid.GetTileGridPosition(Input.mousePosition)); //获取物品 Vector2Int tileGridPosition = selectedItemGrid.GetTileGridPosition(Input.mousePosition); if(selectedItem == null){ selectedItem = selectedItemGrid.PickUpItem(tileGridPosition.x, tileGridPosition.y); }else{ selectedItemGrid.PlaceItem(selectedItem, tileGridPosition.x, tileGridPosition.y); selectedItem = null; } } } }
新增GridInteract,动态赋值背包数据
[RequireComponent(typeof(ItemGrid))] public class GridInteract : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler { private InventoryController inventoryController; private ItemGrid itemGrid; private void Awake() { inventoryController = FindObjectOfType<InventoryController>(); itemGrid = GetComponent<ItemGrid>(); } // 鼠标进入触发 public void OnPointerEnter(PointerEventData eventData) { inventoryController.selectedItemGrid = itemGrid; } // 鼠标退出触发 public void OnPointerExit(PointerEventData eventData) { inventoryController.selectedItemGrid = null; } }
挂载
效果
物品跟随鼠标
修改InventoryController
private void Update() { //物品跟随鼠标 if(selectedItem) selectedItem.transform.position = Input.mousePosition; //... }
效果
创建物品的容器,定义不同物品
新增ItemData
[CreateAssetMenu] public class ItemData : ScriptableObject { public int width = 1; public int height = 1; public Sprite itemIcon; }
配置物品
修改Item
public class Item : MonoBehaviour { public ItemData itemData; public void Set(ItemData itemData){ this.itemData = itemData; GetComponent<Image>().sprite = itemData.itemIcon; } }
修改InventoryController
[SerializeField] List<ItemData> items; [SerializeField] GameObject itemPrefab; Canvas canvas; private void Start() { canvas = FindObjectOfType<Canvas>(); } private void Update() { //TODO: 方便测试,动态随机添加物品 if (Input.GetKeyDown(KeyCode.Q)) { CreateRandomItem(); } //... } //随机添加物品 private void CreateRandomItem() { if (selectedItem) return; Item item = Instantiate(itemPrefab).GetComponent<Item>(); selectedItem = item; selectedItem.transform.SetParent(canvas.transform, false); int index = UnityEngine.Random.Range(0, items.Count); item.Set(items[index]); }
配置
效果,按Q生成不同物品
修改物品尺寸
修改Item
public void Set(ItemData itemData){ this.itemData = itemData; GetComponent<Image>().sprite = itemData.itemIcon; //修改物品尺寸 Vector2 size = new Vector2(); size.x = itemData.width * ItemGrid.tileSizeWidth; size.y = itemData.height * ItemGrid.tileSizeHeight; GetComponent<RectTransform>().sizeDelta = size; }
效果
修复物品放置位置问题
修改ItemGrid
//按格子坐标添加物品 public void PlaceItem(Item item, int posX, int posY){ itemSlot[posX, posY] = item; item.transform.SetParent(transform, false); Vector2 positon = new Vector2(); positon.x = posX * tileSizeWidth + tileSizeWidth * item.itemData.width / 2; positon.y = -(posY * tileSizeHeight + tileSizeHeight * item.itemData.height / 2); item.transform.localPosition = positon; }
效果
按物品尺寸占用对应大小的格子
修改ItemGrid
//按格子坐标添加物品 public void PlaceItem(Item item, int posX, int posY) { item.transform.SetParent(transform, false); // 按物品尺寸占用对应大小的格子 for (int ix = 0; ix < item.itemData.width; ix++){ for (int iy = 0; iy < item.itemData.height; iy++){ itemSlot[posX + ix, posY + iy] = item; } } item.onGridPositionX = posX; item.onGridPositionY = posY; Vector2 positon = new Vector2(); positon.x = posX * tileSizeWidth + tileSizeWidth * item.itemData.width / 2; positon.y = -(posY * tileSizeHeight + tileSizeHeight * item.itemData.height / 2); item.transform.localPosition = positon; } //按格子坐标获取物品 public Item PickUpItem(int x, int y) { Item toReturn = itemSlot[x, y]; if(toReturn == null) return null; CleanGridReference(toReturn); return toReturn; } //按物品尺寸取消对应大小的格子的占用 void CleanGridReference(Item item){ for (int ix = 0; ix < item.itemData.width; ix++) { for (int iy = 0; iy < item.itemData.height; iy++) { itemSlot[item.onGridPositionX + ix, item.onGridPositionY + iy] = null; } } }
运行看是否正常