发布于 2026-01-06 7 阅读
0

Godot 4.x 控制节点界面中的拖放功能:全局拖放

Godot 4.x 中的拖放功能

控制节点接口

使用它

全局拖放

我当时在开发一个库存系统,因此必须让玩家在无聊的时候可以玩“库存俄罗斯方块”游戏。我之前在不同的平台上做过拖放功能,知道其中存在一些潜在的特殊情况和问题,会耗费我大量的时间和精力。为了找到适用于 Godot 的拖放解决方案,我搜索了一下,结果发现搜索引擎提供的几乎都是一些老旧的、手动实现的方案。

但我最终还是找到了 Godot 内置的解决方案,而我一直努力避免的就是钻研 DIY/NIH 之类的无底洞。太好了!
下面我快速讲解一下,这样你就不用自己摸索了:

控制节点接口

Control节点有 3 个虚拟私有函数,您可以重写这些函数,从而启用拖放功能:

# Control that can be dragged from
func _get_drag_data(at_position:Vector2)->Variant
# Control that can be dragged to
func _can_drop_data(at_position:Vector2, data:Variant)->bool
func _drop_data(at_position:Vector2, data:Variant)->void
Enter fullscreen mode Exit fullscreen mode

文档:_get_drag_data_can_drop_data_drop_data

拖放机制也是按这个顺序工作的:

  1. _get_drag_data- 返回可以从当前位置拖动的数据Control。在我的例子中,如果一个槽位中有物品,那么我将返回该物品,否则返回空值null
  2. _can_drop_dataControl该方法会持续在鼠标位置下方调用,传递相对鼠标位置和data,并返回该物品是否data可以被接受。这里我会检查当前物品是否适合鼠标位置的物品栏网格,以及当前栏位是否与物品类型兼容(武器栏位用于武器,背包栏位用于背包,等等)。
  3. _drop_data- 最后一步调用与之前相同_can_drop_data,只是这里我们接受丢弃操作,并且应该处理从之前的容器中移除物品,并将其添加到当前容器Control(或您计划对物品执行的任何其他操作data)。

为了处理拖放操作的显示预览,我们使用
func set_drag_preview(control:Control)->void:(文档

不出所料,这将使用Control您传递的节点作为拖放的显示图标,将其添加到场景树中,并在拖动完成后将其销毁。

force_drag(data:Variant, preview:Control)->void文档还提供了一个函数,可以通过编程方式触发拖放操作。如果您是从事件_drop_data处理程序中调用此函数,请使用 ` call_deferredif` 语句在下一帧中调用它,因为当前的拖放操作仍在进行中。我通常在将项目拖放到已有项目的槽位时使用此函数,这样可以先对前一个项目触发拖放操作,从而加快切换速度。

使用它

它界面简洁,基本用法也很直接——创建一些数据,这些数据将从某个位置拖拽过来Control,检查这些数据,并可能将这些数据接收到一个Control可以拖拽到的位置。

我遇到的最后一个问题是如何检测用户是否结束了拖拽操作,但当前页面Control却不接受这种操作。这时我们可以回归Node基本方法,利用预览的生命周期Control!完整的生命周期如下所示:

  1. 用户点击和拖动,Godot 调用:_get_drag_data
  2. 我们称之为:set_drag_preview
  3. 用户拖动鼠标到屏幕上Control,Godot 调用:_can_drop_data
  4. 用户松开拖拽操作,结果_can_drop_data属实,Godot 调用:_drop_data
  5. preview_control.tree_exiting发出信号

为了简化这个生命周期,我使用了一个单独的对象来管理拖拽操作:

class_name ItemDrag

signal drag_completed(data:ItemDrag)

var source: Control = null
var destination: Control = null

var item: Item
var preview: Control

func _init(_source: Control, _item: Item, _preview: Control):
    self.source = _source
    self.item = _item
    self.preview = _preview
    self.preview.tree_exiting.connect(_on_tree_exiting)

func _on_tree_exiting()->void:
    drag_completed.emit(self)
Enter fullscreen mode Exit fullscreen mode

使用此类,其他函数的示例可以是:

func remove_item(item:Item)->void:
    inventory.remove_item(item)

func _get_drag_data(at_position:Vector2)->Variant:
    var item := inventory.item_at(at_position)
    if item == null: return null

    var drag_data = ItemDrag.new(self, item, _create_item_preview(item))
    set_drag_preview(drag_data.preview)

    return drag_data

func _can_drop_data(at_position:Vector2, data:Variant)->bool:
    if !data is ItemDrag: return false
    var drag_data := data as ItemDrag
    # Check if the item can fit in the inventory at this position
    return !inventory.intersects_at(drag_data.item, at_position)

func _drop_data(at_position:Vector2, data:Variant)->void:
    if !data is ItemDrag: return
    var drag_data := data as ItemDrag

    drag_data.destination = self
    if drag_data.source: drag_data.source.remove_item(drag_data.item)

    inventory.add_item_at(drag_data.item, at_position)
Enter fullscreen mode Exit fullscreen mode

某些功能可以通过ItemDrag类来强制执行,特别是赋予诸如添加/删除项目之类的sourcedestination强大的类型,以确保它们具有处理/添加/删除项目的接口。但定义这组函数是处理完整的拖放操作流程的良好开端。

全局拖放

除了上述Control基于 GUI 的方法之外,Godot 还在生命周期中提供了一些事件钩子,ViewportControl提供了查询任何拖放操作状态的函数:

# Viewport
func gui_is_dragging()->bool
func gui_get_drag_data()->Variant
func gui_is_drag_successful()->bool
# Control
func is_drag_successful()->bool
Enter fullscreen mode Exit fullscreen mode

前两个应该很明显——第一个可以查询是否在任何时候发生了拖放操作,第二个可以获取data我们在_get_drag_data回调中返回的相同内容Control(或者data包含的任何内容force_drag,或者其他方式,拖放操作是如何启动的)。

最后两个参数可以与func _notification(what:int)->void处理程序一起使用,以便在拖放操作开始或结束时接收全局通知,以及在拖放操作成功结束时接收通知:

func _notification(what:int)->void:
    if what == Node.NOTIFICATION_DRAG_BEGIN:
        # Drag data is available (populated by our _get_drag_data() function for example)
        var data = get_viewport().gui_get_drag_data()
        # Use the drag data
    if what == Node.NOTIFICATION_DRAG_END:
        # Drag data is no longer available and has been disposed already
        print("Drag ended. Success: ", get_viewport().gui_is_drag_successful())
Enter fullscreen mode Exit fullscreen mode

这些功能或许有用,但与拖放操作的上下文关系不大。某些用例可供相关的拖放接收者根据拖放数据突出显示自身。

有了这些工具,任何拖放场景都能轻松应对,希望这能帮您省去编写自定义拖放功能的麻烦。祝您在使用这些信息时一切顺利,取得成功!

P.

文章来源:https://dev.to/pdeveloper/godot-4x-drag-and-drop-5g13