实现背包GUI 中我们已经完成了全部的背包显示功能下面需要实现怎么拖拽了,对于拖拽实际上是能够离开 godot 实现的,但是这样太不方便了,需要通过一层层接口转换,可维护性是提高了,但是作为游戏这种快速迭代的项目并不是那么合理,所以这里我们继续使用较为耦合的方式实现。

拖拽的数据存储

首先在拖拽的时候我们的鼠标只有一个,所以需要考虑怎么储存拖拽数据,我的考虑是直接存 BagName、拖拽的起点 x 起点 y随后 记录 BagItem 的数据即可。

public class DragBagItem  
{  
    public BagItem Item;  
    public int X;  
    public int Y;  
    public string BagName;  
}

然后我们同之前 controller 一样,需要考虑 drag 是怎样交互的。

对接 BagController

为了方便记录和获取数据,这里就不定义 interface 了,可以选择直接将 BagController 耦合的方式传入, 随后让 BagController 直接 new 他即可:

public class BagDragManager  
{  
    private readonly BagController _bagController;  
    // 后面肯定是需要获取 Bags 的,给个 getter
    private Dictionary<string, Bag> Bags => _bagController.Bags;  
    // CurrentDragBagItem 一般外部也是需要获取的,公开暴露即可
    public DragBagItem CurrentDragBagItem;
    // 前文提到的 MouseSlot 我们先定义好名字未来肯定需要使用的
    private MouseSlot _mouseSlot;
    private const string MOUSE_SLOT_SCENE = "<path to MouseSlot>"
  
    public BagDragManager(BagController bagController)  
    {        
	    _bagController = bagController;  
    }
}
 
// BagController 补充
public class BagController  
{
	// ...
	public readonly BagDragManager DragManager;
	private BagController()  
	{  
	    DragManager = new BagDragManager(this);  
	}
	// ...
}

随后我们需要思考 Bag 是怎么怎样工作的。

流程上 伪代码 一般是这样的:

1. 点击 item slot
2. 触发 开始拖拽
3. 拖拽中 -> mouse slot 渲染 drag item 并跟随着鼠标运动
	1. 拖拽中点击了右键,则向 item slot 添加一个(需要判断是否可以添加)
4. 再次点击页面
	1. 点击了 item slot -> 触发合并、交换位置、或者放置在空白(也就是交换位置)
	2. 点击了空白位置 -> 触发取消操作

所以我们需要从 item slot 开始绑定事件,在此之前可以先把交换的逻辑现都写了,交换逻辑本身和页面无关。

SetDragMove

开始拖拽时是需要创建 _mouseSlot 的这里的话,我们简单判断一下 mouseSlot 有没有被挂在到全局,没有就挂载过去即可:

public void SetDragMove(string bagName, BagItem itemInfo, int x, int y)  
{  
    if (_mouseSlot is null)  
    {        _mouseSlot = GD.Load<PackedScene>(MOUSE_SLOT_SCENE)  
            .Instantiate<MouseSlot>();  
    }
    if (Engine.GetMainLoop() is SceneTree sceneTree && _mouseSlot.GetParent() != sceneTree.Root)  
	{  
	    sceneTree.Root.AddChild(_mouseSlot);  
	}
	// 然后置空 bag 数据、随后设置到 CurrentDragBagItem 上
	if (Bags.TryGetValue(bagName, out var bag))  
	{  
	    bag.Items[x, y] = new BagItem();  
	    CurrentDragBagItem = new DragBagItem  
	    {  
	        BagName = bagName,  
	        Item = itemInfo.Clone(),  
	        X = x,  
	        Y = y  
	    };  
	}
}

AddToSlot

添加到 slot .的逻辑也比较简单如果是空的,说明直接设置过去就可以了,如果是不同的物品则不操作,如果是相同的,加 count 数量。

public void AddToSlot(DragBagItem dragBagItem, string bagName, int x, int y, int count)  
{  
    if (Bags.TryGetValue(bagName, out var bag))  
    {        
	    if (bag.Items[x, y].IsEmpty)  
        {            // 空的情况下创建一个  
            bag.Items[x, y] = dragBagItem.Item.Clone();  
            bag.Items[x, y].Count = count;  
            return;  
        }  
        if (bag.Items[x, y].Name != dragBagItem.Item.Name)  
        {            // 不同物品, 保底操作直接返回  
            return;  
        }  
        // 非空的情况下则添加指定数量
        bag.Items[x, y].Count += count;  
    }
}

MoveToSlot

也就是最终点击完成交换什么的操作,这里逻辑稍稍多一点点。

  
public void MoveToSlot(DragBagItem dragBagItem, string bagName, int x, int y)  
{  
	// 这里不做任何合法性校验,是否可以添加由调用方决定。
    if (Bags.TryGetValue(bagName, out var bag))  
    {        
	    // 这种情况下都是需要修改的可以不 clone 了
	    var currentItem = bag.Items[x, y];  
	    // 如果目标是空的,直接设置过去
        if (currentItem.IsEmpty)  
        {            
	        currentItem = dragBagItem.Item.Clone();  
        }        
        else if (currentItem.Name == dragBagItem.Item.Name)  
        {            
	        // 如果相同 name 的,直接加指定数量,如果超过了,则加入最多可以加入的
	        var maxCount = currentItem.Resource.MaxCount;  
            var newCount = currentItem.Count + dragBagItem.Item.Count;  
            if (newCount <= maxCount)  
            {                
	            currentItem.Count = newCount;  
            }            
            else  
            {  
                var countToAdd = maxCount - currentItem.Count;  
                currentItem.Count = maxCount;  
                dragBagItem.Item.Count -= countToAdd;  
            }        
		}  
        bag.Items[x, y] = currentItem;  
    }
}
 

MoveBackToSlot

点击了其他地方挪回去.

public void MoveBackToSlot(DragBagItem dragBagItem)  
{  
    if (Bags.TryGetValue(dragBagItem.BagName, out var bag))  
    {        
	    bag.Items[dragBagItem.X, dragBagItem.Y] = dragBagItem.Item;  
    }
}

现在我们实现了主要逻辑,需要去 item slot 中绑定了。

实现 ItemSlot 的 OnInput 事件

使用 gui_input 添加 OnInput

Pasted image 20240517004414

public void OnInput(InputEvent @event)  
{  
    var dragManager = BagController.Instance.DragManager;  
    var dragBagItem = dragManager.CurrentDragBagItem;  
    if (@event.IsActionPressed("mouse_left_down"))  
    {        
	    if (dragBagItem is null)  
        {            
	        if (!Item.IsEmpty)  
            {                
	            // 没有拖拽东西,且目标物品不是空的,才能开始拖物品  
                dragManager.SetDragMove(_bagName, Item, _x, _y);  
            }        
		}        
		else  
        {  
            // 有拖拽的东西,说明,完成点击,交换物品  
            dragManager.MoveToSlot(dragBagItem, _bagName, _x, _y);  
            // 清除拖拽  
            dragManager.CurrentDragBagItem = null;  
        }    
	}    
	else if (@event.IsActionPressed("mouse_right_down"))  
    {        
	    // 左键点击
	    if (dragBagItem is not null)  
        {            
	        // 空,或者 有相同物品可以叠加  
            if (Item.IsEmpty || (Item.Name == dragBagItem.Item.Name && Item.Resource.MaxCount >= 1 + Item.Count))  
            {                
	            // 放置一个物品到格子里面  
                dragManager.AddToSlot(dragBagItem, _bagName, _x, _y, 1);  
                // 放置以后 dragItem 减去一个  
                dragBagItem.Item.Count -= 1;  
                if (dragBagItem.Item.IsEmpty)  
                {                    
	                dragManager.CurrentDragBagItem = null;  
                }            
			}        
		}    
	}
}

实现鼠标的显示

上面我们已经定义的鼠标的显示,下面我们需要制作一个 mouse 拖拽的 item 来显示内容

dragNode 我是直接复制的 ItemSlot 不多赘述了。

代码上有一些小小的不同:

public partial class MouseSlot : Control  
{  
    [Export] public Label CountLabel;  
    [Export] public TextureRect Icon;
    [Export] public Control Container;  
    private static DragBagItem DragItem => BagController.Instance.DragManager.CurrentDragBagItem;  
    private bool StartDragging => DragItem is not null;  
  
    private void ClearRender()  
    {        
	    Icon.Texture = null;  
        CountLabel.Text = "";  
    }  
    public override void _Process(double delta)  
    {        
	    Container.Visible = StartDragging;  
        if (!StartDragging)  
        {            
	        ClearRender();  
            return;  
        }  
        var item = BagController.Instance.DragManager.CurrentDragBagItem.Item;  
        CountLabel.Text = item.Count.ToString();  
        Icon.Texture = item.Resource.Texture;  
        // 跟着鼠标移动
        var mousePosition = GetGlobalMousePosition();  
        GlobalPosition = mousePosition;  
    }  
    public override void _UnhandledInput(InputEvent @event)  
    {        
	    // 如果点击了左键,因为是 _UnhandledInput 所以是取消操作,挪动回去
	    if (@event.IsActionPressed("mouse_left_down"))  
        {            
	        var dragManager = BagController.Instance.DragManager;  
            var dragBagItem = dragManager.CurrentDragBagItem;  
            if (dragBagItem is not null)  
            {                
	            dragManager.MoveBackToSlot(dragBagItem);  
                dragManager.CurrentDragBagItem = null;  
            }        
		}    
	}
}

本文标题:实现背包拖拽

永久链接:https://iceprosurface.com/godot/bag-system/drag/

作者授权:本文由 icepro 原创编译并授权刊载发布。

版权声明:本文使用「署名-非商业性使用-相同方式共享 4.0 国际」创作共享协议,转载或使用请遵守署名协议。

查看源码: