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

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

【unity实战】Unity中基于瓦片的网格库存系统——类似《逃离塔科夫》的库存系统(上):https://developer.aliyun.com/article/1553558

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

修改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;
                    }
                }
            }
        }
    }
    // 如果所有被检查的位置都有相同的重叠物品,则返回 true
    return 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);
    }
}

效果

突出显示我们选中的物品

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

新增高亮背景

挂载配置

效果

优化

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

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

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

参考:Unity 显示Raycast Padding区域

多个背包

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

效果

自动入库物品

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

效果

旋转物品

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

效果

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

修改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宽高注释,这时代码会报错,对应修改位置即可,然后再还原回去

效果

选中拖拽物品排序问题

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

效果

最终效果

源码

整理好了我会放上来

目录
相关文章
|
3月前
|
开发者 图形学 Java
揭秘Unity物理引擎核心技术:从刚体动力学到关节连接,全方位教你如何在虚拟世界中重现真实物理现象——含实战代码示例与详细解析
【8月更文挑战第31天】Unity物理引擎对于游戏开发至关重要,它能够模拟真实的物理效果,如刚体运动、碰撞检测及关节连接等。通过Rigidbody和Collider组件,开发者可以轻松实现物体间的互动与碰撞。本文通过具体代码示例介绍了如何使用Unity物理引擎实现物体运动、施加力、使用关节连接以及模拟弹簧效果等功能,帮助开发者提升游戏的真实感与沉浸感。
78 1
|
2月前
|
图形学 C++ C#
Unity插件开发全攻略:从零起步教你用C++扩展游戏功能,解锁Unity新玩法的详细步骤与实战技巧大公开
【8月更文挑战第31天】Unity 是一款功能强大的游戏开发引擎,支持多平台发布并拥有丰富的插件生态系统。本文介绍 Unity 插件开发基础,帮助读者从零开始编写自定义插件以扩展其功能。插件通常用 C++ 编写,通过 Mono C# 运行时调用,需在不同平台上编译。文中详细讲解了开发环境搭建、简单插件编写及在 Unity 中调用的方法,包括创建 C# 封装脚本和处理跨平台问题,助力开发者提升游戏开发效率。
200 0
|
2月前
|
图形学 开发者 UED
Unity游戏开发必备技巧:深度解析事件系统运用之道,从生命周期回调到自定义事件,打造高效逻辑与流畅交互的全方位指南
【8月更文挑战第31天】在游戏开发中,事件系统是连接游戏逻辑与用户交互的关键。Unity提供了多种机制处理事件,如MonoBehaviour生命周期回调、事件系统组件及自定义事件。本文介绍如何有效利用这些机制,包括创建自定义事件和使用Unity内置事件系统提升游戏体验。通过合理安排代码执行时机,如在Awake、Start等方法中初始化组件,以及使用委托和事件处理复杂逻辑,可以使游戏更加高效且逻辑清晰。掌握这些技巧有助于开发者更好地应对游戏开发挑战。
120 0
|
3月前
|
开发者 图形学 API
从零起步,深度揭秘:运用Unity引擎及网络编程技术,一步步搭建属于你的实时多人在线对战游戏平台——详尽指南与实战代码解析,带你轻松掌握网络化游戏开发的核心要领与最佳实践路径
【8月更文挑战第31天】构建实时多人对战平台是技术与创意的结合。本文使用成熟的Unity游戏开发引擎,从零开始指导读者搭建简单的实时对战平台。内容涵盖网络架构设计、Unity网络API应用及客户端与服务器通信。首先,创建新项目并选择适合多人游戏的模板,使用推荐的网络传输层。接着,定义基本玩法,如2D多人射击游戏,创建角色预制件并添加Rigidbody2D组件。然后,引入网络身份组件以同步对象状态。通过示例代码展示玩家控制逻辑,包括移动和发射子弹功能。最后,设置服务器端逻辑,处理客户端连接和断开。本文帮助读者掌握构建Unity多人对战平台的核心知识,为进一步开发打下基础。
120 0
|
3月前
|
开发者 图形学 C#
揭秘游戏沉浸感的秘密武器:深度解析Unity中的音频设计技巧,从背景音乐到动态音效,全面提升你的游戏氛围艺术——附实战代码示例与应用场景指导
【8月更文挑战第31天】音频设计在游戏开发中至关重要,不仅能增强沉浸感,还能传递信息,构建氛围。Unity作为跨平台游戏引擎,提供了丰富的音频处理功能,助力开发者轻松实现复杂音效。本文将探讨如何利用Unity的音频设计提升游戏氛围,并通过具体示例代码展示实现过程。例如,在恐怖游戏中,阴森的背景音乐和突然的脚步声能增加紧张感;在休闲游戏中,轻快的旋律则让玩家感到愉悦。
84 0
|
3月前
|
图形学 C# 开发者
Unity粒子系统全解析:从基础设置到高级编程技巧,教你轻松玩转绚丽多彩的视觉特效,打造震撼游戏画面的终极指南
【8月更文挑战第31天】粒子系统是Unity引擎的强大功能,可创建动态视觉效果,如火焰、爆炸等。本文介绍如何在Unity中使用粒子系统,并提供示例代码。首先创建粒子系统,然后调整Emission、Shape、Color over Lifetime等模块参数,实现所需效果。此外,还可通过C#脚本实现更复杂的粒子效果,增强游戏视觉冲击力和沉浸感。
176 0
|
3月前
|
开发者 图形学 前端开发
绝招放送:彻底解锁Unity UI系统奥秘,五大步骤教你如何缔造令人惊叹的沉浸式游戏体验,从Canvas到动画,一步一个脚印走向大师级UI设计
【8月更文挑战第31天】随着游戏开发技术的进步,UI成为提升游戏体验的关键。本文探讨如何利用Unity的UI系统创建美观且功能丰富的界面,包括Canvas、UI元素及Event System的使用,并通过具体示例代码展示按钮点击事件及淡入淡出动画的实现过程,助力开发者打造沉浸式的游戏体验。
95 0
|
3月前
|
图形学
Unity动画☀️Unity动画系统Bug集合
Unity动画☀️Unity动画系统Bug集合
|
3月前
|
图形学 C#
超实用!深度解析Unity引擎,手把手教你从零开始构建精美的2D平面冒险游戏,涵盖资源导入、角色控制与动画、碰撞检测等核心技巧,打造沉浸式游戏体验完全指南
【8月更文挑战第31天】本文是 Unity 2D 游戏开发的全面指南,手把手教你从零开始构建精美的平面冒险游戏。首先,通过 Unity Hub 创建 2D 项目并导入游戏资源。接着,编写 `PlayerController` 脚本来实现角色移动,并添加动画以增强视觉效果。最后,通过 Collider 2D 组件实现碰撞检测等游戏机制。每一步均展示 Unity 在 2D 游戏开发中的强大功能。
172 6
|
2月前
|
测试技术 C# 图形学
掌握Unity调试与测试的终极指南:从内置调试工具到自动化测试框架,全方位保障游戏品质不踩坑,打造流畅游戏体验的必备技能大揭秘!
【9月更文挑战第1天】在开发游戏时,Unity 引擎让创意变为现实。但软件开发中难免遇到 Bug,若不解决,将严重影响用户体验。调试与测试成为确保游戏质量的最后一道防线。本文介绍如何利用 Unity 的调试工具高效排查问题,并通过 Profiler 分析性能瓶颈。此外,Unity Test Framework 支持自动化测试,提高开发效率。结合单元测试与集成测试,确保游戏逻辑正确无误。对于在线游戏,还需进行压力测试以验证服务器稳定性。总之,调试与测试贯穿游戏开发全流程,确保最终作品既好玩又稳定。
98 4