当前位置: 首页 > news >正文

Unity打造塔科夫式网格背包系统

目录

最终效果

前言

图片配置

获取格子坐标

动态控制背包大小

添加物品

移动物品

物品跟随鼠标

创建物品的容器,定义不同物品

修改物品尺寸

修复物品放置位置问题

按物品尺寸占用对应大小的格子

判断物品是否超出边界范围

物品放置重叠,交换物品

放置加入点偏移量

突出显示我们选中的物品

优化

多个背包

自动入库物品

旋转物品

修改旋转高亮背景和占位也跟着旋转

选中拖拽物品排序问题

最终效果


最终效果

image

前言

在这一集中Unity制作基于瓦片的网格库存系统。 就像在《逃离塔科夫》、《暗黑破坏神》或《流放之路》等游戏中一样。

图片配置

配置图片为重复

image

修改UI画布适配

image

新增UI图片,类型改为平铺,默认图片是256的,太大了,所以我们选择缩小4倍,每单位像素为4,同时注意修改轴心在左上角

image

获取格子坐标

public class ItemGrid : MonoBehaviour
{// 格子尺寸常量const float tileSizeWidth = 64f;  // 256/4const float tileSizeHeight = 64f; // 256/4Vector2 positionOnTheGrid;Vector2Int tileGridPosition;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;// 转换为网格坐标,考虑Canvas缩放tileGridPosition.x = (int)(positionOnTheGrid.x / (tileSizeWidth * canvas.scaleFactor));tileGridPosition.y = (int)(positionOnTheGrid.y / (tileSizeHeight * canvas.scaleFactor));return tileGridPosition;}
}

挂载脚本

image

效果,点击格子打印位置

image

动态控制背包大小

#修改ItemGrid[SerializeField] private int gridWidth = 10;
[SerializeField] private int gridHeight = 10;private void Start()
{rectTransform = GetComponent<RectTransform>();canvas = FindObjectOfType<Canvas>();InitializeGrid(gridWidth, gridHeight);
}private void InitializeGrid(int width, int height)
{Vector2 gridSize = new Vector2(width * tileSizeWidth, height * tileSizeHeight);rectTransform.sizeDelta = gridSize;
}

配置

image

效果

image

添加物品

配置物品预制体。修改尺寸和去掉光线投射目标

image

新增Item脚本,挂载在物品上

public class Item : MonoBehaviour {}

image

动态添加测试物品,修改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;
}

配置

image

运行效果

image

移动物品

修改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;}
}

挂载

image

image

效果

image

物品跟随鼠标

修改InventoryControllerprivate void Update()
{//物品跟随鼠标if(selectedItem) selectedItem.transform.position = Input.mousePosition;//...
}

效果

image

创建物品的容器,定义不同物品

新增ItemData

[CreateAssetMenu]
public class ItemData : ScriptableObject
{public int width = 1;public int height = 1;public Sprite itemIcon;
}

配置物品

image

修改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]);
}

配置

image

效果,按Q生成不同物品

image

修改物品尺寸

修改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;
}

效果

image

修复物品放置位置问题

修改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;
}

效果

image

按物品尺寸占用对应大小的格子

修改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;}}
}

运行看是否正常

image

判断物品是否超出边界范围

修改ItemGrid

//按格子坐标添加物品
public bool PlaceItem(Item item, int posX, int posY)
{//判断物品是否超出边界if (BoundryCheck(posX, posY, item.itemData.width, item.itemData.height) == false) return false;//...return true;
}
//判断物品是否超出边界
bool BoundryCheck(int posX, int posY, int width, int height)
{if (PositionCheck(posX, posY) == false) return false;posX += width - 1;posY += height - 1;if (PositionCheck(posX, posY) == false) return false;return true;
}
//判断格子坐标是否超出
bool PositionCheck(int posX, int posY)
{if (posX < 0 || posY < 0) return false;if (posX >= gridSizeWidth || posY >= gridSizeHeight) return false;return true;
}

修改InventoryController

private void Update()
{//...if (Input.GetMouseButtonDown(0)){Vector2Int tileGridPosition = selectedItemGrid.GetTileGridPosition(Input.mousePosition);if (selectedItem == null){//选中物品selectedItem = selectedItemGrid.PickUpItem(tileGridPosition.x, tileGridPosition.y);}else{// 移动物品PlaceItem(tileGridPosition);} }
}//移动物品
void PlaceItem(Vector2Int tileGridPosition){bool complete = selectedItemGrid.PlaceItem(selectedItem, tileGridPosition.x, tileGridPosition.y);if(complete) selectedItem = null;
}

效果

物品放置重叠,交换物品

修改InventoryController

Item overlapItem;//重叠物品
//移动物品
void PlaceItem(Vector2Int tileGridPosition){bool complete = selectedItemGrid.PlaceItem(selectedItem, tileGridPosition.x, tileGridPosition.y, ref overlapItem);if(complete) {selectedItem = null;//如果存在重叠物品if(overlapItem != null) {selectedItem = overlapItem;overlapItem = null;}}
}

修改ItemGrid

//按格子坐标添加物品
public bool PlaceItem(Item item, int posX, int posY, ref Item overlapItem)
{//判断物品是否超出边界if (BoundryCheck(posX, posY, item.itemData.width, item.itemData.height) == false) return false;//检查指定位置和范围内是否存在重叠物品,有多个重叠物品退出if (OverlapCheck(posX, posY, item.itemData.width, item.itemData.height, ref overlapItem) == false) return false;if(overlapItem) CleanGridReference(overlapItem);//...
}
//检查指定位置和范围内是否存在重叠物品,并overlapItem返回重叠物品,,有多个重叠物品返回false
private bool OverlapCheck(int posX, int posY, int width, int height, ref Item overlapItem)
{for (int x = 0; x < width; x++){for (int y = 0; y < height; y++){// 如果当前位置有物品if (itemSlot[posX + x, posY + y] != null){// 如果 overlapItem 还未被赋值(第一次找到重叠物品)if (overlapItem == null){overlapItem = itemSlot[posX + x, posY + y];}else{// 如果发现范围有多个重叠物品if (overlapItem != itemSlot[posX + x, posY + y]){overlapItem = null;return false;}}}}}// 如果所有被检查的位置都有相同的重叠物品,则返回 truereturn true;
}

效果

放置加入点偏移量

修改InventoryController放置时加入点偏移量,让放置效果更好

private void Update()
{//TODO: 方便测试,动态随机添加物品if (Input.GetKeyDown(KeyCode.Q)){CreateRandomItem();}//物品跟随鼠标if (selectedItem) selectedItem.transform.position = Input.mousePosition;if (selectedItemGrid == null) return;if (Input.GetMouseButtonDown(0)){LeftMouseButtonPress();}
}
//点击操作
private void LeftMouseButtonPress()
{Vector2 position = Input.mousePosition;if (selectedItem != null){position.x -= (selectedItem.itemData.width - 1) * ItemGrid.tileSizeWidth / 2;position.y += (selectedItem.itemData.height - 1) * ItemGrid.tileSizeHeight / 2;}Vector2Int tileGridPosition = selectedItemGrid.GetTileGridPosition(position);if (selectedItem == null){//选中物品selectedItem = selectedItemGrid.PickUpItem(tileGridPosition.x, tileGridPosition.y);}else{// 移动物品PlaceItem(tileGridPosition);}
}

效果

image

突出显示我们选中的物品

修改ItemGrid

//按格子坐标转化为UI坐标位置
public Vector2 CalculatePositionOnGrid(Item item, int posX, int posY)
{Vector2 position = new Vector2();position.x = posX * tileSizeWidth + tileSizeWidth * item.itemData.width / 2;position.y = -(posY * tileSizeHeight + tileSizeHeight * item.itemData.height / 2);return position;
}
//按格子坐标获取物品
internal Item GetItem(int x, int y)
{return itemSlot[x, y];
}

新增InventoryHighlight,控制高亮背景显示

//控制高亮背景显示
public class InventoryHighlight : MonoBehaviour
{[SerializeField] RectTransform highlighter;// 设置高亮框大小public void SetSize(Item targetItem){Vector2 size = new Vector2();size.x = targetItem.itemData.width * ItemGrid.tileSizeWidth;size.y = targetItem.itemData.height * ItemGrid.tileSizeHeight;highlighter.sizeDelta = size;}// 设置高亮框位置public void SetPosition(ItemGrid targetGrid, Item targetItem){Vector2 pos = targetGrid.CalculatePositionOnGrid(targetItem, targetItem.onGridPositionX, targetItem.onGridPositionY);highlighter.localPosition = pos;}//显示隐藏public void Show(bool b){highlighter.gameObject.SetActive(b);        }//设置高亮背景父级public void SetParent(ItemGrid targetGrid){highlighter.SetParent(targetGrid.GetComponent<RectTransform>());}//设置高亮框位置public void SetPosition(ItemGrid targetGrid, Item targetItem, int posX, int posY){Vector2 pos = targetGrid.CalculatePositionOnGrid(targetItem, posX, posY);highlighter.localPosition = pos;}
}

修改InventoryController

InventoryHighlight inventoryHighlight;
Item itemToHighlight;//高亮显示物品
private void Start()
{canvas = FindObjectOfType<Canvas>();inventoryHighlight = GetComponent<InventoryHighlight>();
}
private void Update()
{//TODO: 方便测试,动态随机添加物品if (Input.GetKeyDown(KeyCode.Q)){CreateRandomItem();}//物品跟随鼠标if (selectedItem) selectedItem.transform.position = Input.mousePosition;if (selectedItemGrid == null){inventoryHighlight.Show(false);return;}if (Input.GetMouseButtonDown(0)){// 获取当前鼠标位置在网格中的格子坐标,并打印到控制台Debug.Log(selectedItemGrid.GetTileGridPosition(Input.mousePosition));LeftMouseButtonPress();}//高亮显示HandleHighlight();
}
//点击操作,选中物品
private void LeftMouseButtonPress()
{Vector2Int tileGridPosition = GetTileGridPosition();if (selectedItem == null){//选中物品selectedItem = selectedItemGrid.PickUpItem(tileGridPosition.x, tileGridPosition.y);}else{// 移动物品PlaceItem(tileGridPosition);}
}
//鼠标坐标转化为格子坐标
private Vector2Int GetTileGridPosition()
{Vector2 position = Input.mousePosition;if (selectedItem != null){position.x -= (selectedItem.itemData.width - 1) * ItemGrid.tileSizeWidth / 2;position.y += (selectedItem.itemData.height - 1) * ItemGrid.tileSizeHeight / 2;}Vector2Int tileGridPosition = selectedItemGrid.GetTileGridPosition(position);return tileGridPosition;
}
//高亮显示
private void HandleHighlight()
{Vector2Int positionOnGrid = GetTileGridPosition();if (selectedItem == null){itemToHighlight = selectedItemGrid.GetItem(positionOnGrid.x, positionOnGrid.y);if (itemToHighlight != null){inventoryHighlight.Show(true);inventoryHighlight.SetSize(itemToHighlight);inventoryHighlight.SetParent(selectedItemGrid);inventoryHighlight.SetPosition(selectedItemGrid, itemToHighlight);}else{inventoryHighlight.Show(false);}}else{inventoryHighlight.Show(selectedItemGrid.BoundryCheck(positionOnGrid.x,positionOnGrid.y,selectedItem.itemData.width,selectedItem.itemData.height));//防止显示跨界inventoryHighlight.SetSize(selectedItem);inventoryHighlight.SetParent(selectedItemGrid);inventoryHighlight.SetPosition(selectedItemGrid, selectedItem, positionOnGrid.x, positionOnGrid.y);}
}

新增高亮背景

image

挂载配置

image

效果

image

优化

修改InventoryController,节约不必要的计算

Vector2Int oldPosition;
//高亮显示
private void HandleHighlight()
{Vector2Int positionOnGrid = GetTileGridPosition();//节约没必要的计算if(oldPosition == positionOnGrid) return;oldPosition = positionOnGrid;//...
}

最好为光线投射添加一些填充,Raycast Padding区域

image

多个背包

只要复制背包,修改尺寸即可

image

效果

image

自动入库物品

修改ItemGrid

//按格子坐标添加物品
public bool PlaceItem(Item item, int posX, int posY, ref Item overlapItem)
{//判断物品是否超出边界if (BoundryCheck(posX, posY, item.itemData.width, item.itemData.height) == false) return false;//检查指定位置和范围内是否存在重叠物品,有多个重叠物品退出if (OverlapCheck(posX, posY, item.itemData.width, item.itemData.height, ref overlapItem) == false) return false;if (overlapItem) CleanGridReference(overlapItem);PlaceItem(item, posX, posY);return true;
}
//按格子坐标添加物品
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 position = CalculatePositionOnGrid(item, posX, posY);item.transform.localPosition = position;
}
// 检查指定位置是否有足够的空间来放置物品
private bool CheckAvailableSpace(int posX, int posY, int width, int height)
{for (int x = 0; x < width; x++){for (int y = 0; y < height; y++){if (itemSlot[posX + x, posY + y] != null){return false; // 如果当前位置已经有物品,则返回false}}}return true; // 如果所有位置都空闲,则返回true
}
// 在网格中找到适合放置物品的位置Data
public Vector2Int? FindSpaceForObject(ItemData itemData)
{int height = gridSizeHeight - itemData.height + 1;int width = gridSizeWidth - itemData.width + 1;for (int y = 0; y < height; y++){for (int x = 0; x < width; x++){if (CheckAvailableSpace(x, y, itemData.width, itemData.height) == true){return new Vector2Int(x, y); // 返回找到的空闲位置}}}return null; // 如果没有找到合适的位置,则返回null
}

修改InventoryController

//TODO:方便测试,随机入库物品
if (Input.GetKeyDown(KeyCode.W))
{InsertRandomItem();
}
//随机入库物品
private void InsertRandomItem()
{if(selectedItemGrid == null) return;int index = UnityEngine.Random.Range(0, items.Count);// 在网格中找到适合放置物品的位置Vector2Int? posOnGrid = selectedItemGrid.FindSpaceForObject(items[index]);if (posOnGrid == null) return;Item item = Instantiate(itemPrefab).GetComponent<Item>();item.transform.SetParent(canvas.transform, false);item.Set(items[index]);// 将物品放置到网格中的指定位置selectedItemGrid.PlaceItem(item, posOnGrid.Value.x, posOnGrid.Value.y);
}

效果

image

旋转物品

修改Item

public bool rotated = false;
//旋转物品
public void Rotate()
{rotated = !rotated;transform.rotation = Quaternion.Euler(0, 0, rotated == true ? 90f : 0f);
}
修改InventoryController//旋转物品
if (Input.GetKeyDown(KeyCode.R))
{RotateItem();
}
//旋转物品
void RotateItem(){if (selectedItem == null) return;selectedItem.Rotate();
}

效果

image

修改旋转高亮背景和占位也跟着旋转

修改Item

public int WIDTH{get{if(rotated == false){return itemData.width;}return itemData.height;}
}
public int HEIGHT{get{if(rotated == false){return itemData.height;}return itemData.width;}
}

修改InventoryController、ItemGrid和InventoryHighlight中的代码:

将:

item.itemData.width

改为:

item.WIDTH

将:

item.itemData.height

改为:

item.HEIGHT

技巧提示:可以先临时注释掉ItemData中的宽高属性定义,这样编译器会报错提示所有需要修改的位置,修改完成后再恢复注释。

image

效果

image

选中拖拽物品排序问题

修改InventoryController,大概就是添加selectedItem.transform.SetAsLastSibling();保证选中对象排最后,及排序最靠前

//点击操作,选中物品
private void LeftMouseButtonPress()
{Vector2Int tileGridPosition = GetTileGridPosition();if (selectedItem == null){//选中物品selectedItem = selectedItemGrid.PickUpItem(tileGridPosition.x, tileGridPosition.y);selectedItem.transform.SetAsLastSibling();}else{// 移动物品PlaceItem(tileGridPosition);}
}//移动物品
void PlaceItem(Vector2Int tileGridPosition)
{bool complete = selectedItemGrid.PlaceItem(selectedItem, tileGridPosition.x, tileGridPosition.y, ref overlapItem);if (complete){selectedItem = null;//如果存在重叠物品if (overlapItem != null){selectedItem = overlapItem;overlapItem = null;selectedItem.transform.SetAsLastSibling();}}
}
//随机添加物品
private void CreateRandomItem()
{if (selectedItem) return; Item item = Instantiate(itemPrefab).GetComponent<Item>();selectedItem = item;selectedItem.transform.SetParent(canvas.transform, false);selectedItem.transform.SetAsLastSibling();int index = UnityEngine.Random.Range(0, items.Count);item.Set(items[index]);
}

效果

image

最终效果

image

http://www.lryc.cn/news/612233.html

相关文章:

  • Enhancing Long Video Question Answering with Scene-Localized Frame Grouping
  • 根据经纬度(从nc格式环境数据文件中)提取环境因子
  • 基于Hadoop的股票大数据分析可视化及多模型的股票预测研究与实现
  • 2025年测绘程序设计模拟赛一--地形图图幅编号及图廓点经纬度计算
  • DAY32打卡
  • golang的map
  • 哈尔滨云前沿-关于物理服务器
  • 关于 idea 里 properties 文件的中文乱码问题
  • get请求中文字符参数乱码问题
  • 软件定义汽车 --- 电子电气架构的驱动
  • Vue Vant使用
  • AI大语言模型如何重塑软件开发与测试流程
  • 初识神经网络01——认识PyTorch
  • 需求EAV模型的优化与思考
  • PCL 平面特征点提取
  • 一、Istio基础学习
  • Next.js 服务器组件与客户端组件:区别解析
  • [FOC电机控制]-高速刹车机制
  • 滑动窗口相关题目
  • C++ 运算符重载:避免隐式类型转换的艺术
  • 利用DeepSeek编写go语言按行排序程序
  • DAY 37 早停策略和模型权重的保存
  • 线程互斥与同步
  • 周鸿祎:AI 时代安全智能体,能否重塑数字安全格局?
  • 一个AI硬件项目经理的PMP实战笔记
  • OpenObserve非sql模式 query editor 中 xx like ‘|’报错如何处理
  • 芯片封装(DIP、SOP、QFP、QFN、BGA、LGA、PGA)
  • 从零开始的云计算生活——第三十八天,避坑落井,Docker容器模块
  • Spring Data MongoDB 教程:用 @Query 快速实现字段查询
  • 模型学习系列之精度