在 实现背包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
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 国际」创作共享协议,转载或使用请遵守署名协议。