【unity实战】Unity中基于瓦片的网格库存系统——类似《逃离塔科夫》的库存系统(上)

简介: 【unity实战】Unity中基于瓦片的网格库存系统——类似《逃离塔科夫》的库存系统

最终效果


前言

在这一集中我将使用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;
        }
    }
}

运行看是否正常

目录
相关文章
|
2天前
|
存储 JSON 关系型数据库
【unity实战】制作unity数据保存和加载系统——大型游戏存储的最优解
【unity实战】制作unity数据保存和加载系统——大型游戏存储的最优解
11 2
|
2天前
|
人工智能 定位技术 图形学
【unity实战】制作敌人的AI,使用有限状态机、继承和抽象类多态 定义不同状态的敌人行为
【unity实战】制作敌人的AI,使用有限状态机、继承和抽象类多态 定义不同状态的敌人行为
43 1
|
2天前
|
图形学
【unity实战】Unity中基于瓦片的网格库存系统——类似《逃离塔科夫》的库存系统(下)
【unity实战】Unity中基于瓦片的网格库存系统——类似《逃离塔科夫》的库存系统
7 0
|
2天前
|
图形学
【制作100个unity游戏之29】使用unity复刻经典游戏《愤怒的小鸟》(完结,附带项目源码)(上)
【制作100个unity游戏之29】使用unity复刻经典游戏《愤怒的小鸟》(完结,附带项目源码)
10 2
|
2天前
|
图形学
【制作100个unity游戏之27】使用unity复刻经典游戏《植物大战僵尸》,制作属于自己的植物大战僵尸随机版和杂交版3(附带项目源码)
【制作100个unity游戏之27】使用unity复刻经典游戏《植物大战僵尸》,制作属于自己的植物大战僵尸随机版和杂交版3(附带项目源码)
13 2
|
2天前
|
图形学
【制作100个unity游戏之27】使用unity复刻经典游戏《植物大战僵尸》,制作属于自己的植物大战僵尸随机版和杂交版2(附带项目源码)
【制作100个unity游戏之27】使用unity复刻经典游戏《植物大战僵尸》,制作属于自己的植物大战僵尸随机版和杂交版2(附带项目源码)
8 1
|
2天前
|
存储 JSON 图形学
【unity实战】制作unity数据保存和加载系统——小型游戏存储的最优解
【unity实战】制作unity数据保存和加载系统——小型游戏存储的最优解
6 0
|
2天前
|
图形学
【制作100个unity游戏之29】使用unity复刻经典游戏《愤怒的小鸟》(完结,附带项目源码)(下)
【制作100个unity游戏之29】使用unity复刻经典游戏《愤怒的小鸟》(完结,附带项目源码)(下)
8 0
|
2天前
|
存储 JSON 关系型数据库
【制作100个unity游戏之27】使用unity复刻经典游戏《植物大战僵尸》,制作属于自己的植物大战僵尸随机版和杂交版13(完结,附带项目源码)
【制作100个unity游戏之27】使用unity复刻经典游戏《植物大战僵尸》,制作属于自己的植物大战僵尸随机版和杂交版13(完结,附带项目源码)
9 0
|
2天前
|
图形学
【制作100个unity游戏之27】使用unity复刻经典游戏《植物大战僵尸》,制作属于自己的植物大战僵尸随机版和杂交版12(附带项目源码)
【制作100个unity游戏之27】使用unity复刻经典游戏《植物大战僵尸》,制作属于自己的植物大战僵尸随机版和杂交版12(附带项目源码)
8 0