Skip to main content

The Drag Ghost

·761 words·4 mins

I uploaded a little prototype level to Itch.io. My first public release! …such as it is.

Some notes to self and feedback from a couple friends who play tested for me:

  • I should make this playable in the browser.
  • The puzzle was tricky but doable.
  • Didn’t realize at first that mirrors could be rotated.
  • Unclear which objects can be dragged and which can’t.
  • Dragging and rotating is quite finicky.
    • Maybe this puzzle in particular the solution requires a level of precision that’s not fun.

I reinstalled Godot using the non-C# version so that exporting to Web works. And it does! Now my Itch.io post is playable right in the browser. https://nielmclaren.itch.io/raysandblocks01

A note to my future self: ZIP all the files for the web release and upload the archive instead of uploading files one at a time. The latter doesn’t work at all.

Over the weekend, I played the cozy packing game Pack and thought about other UIs for dragging and dropping blocks (or luggage, as it were). The physics I have right now is cool but it makes the game less fun for what it’s trying to do.

So to that end I’m gonna prototype a different interaction, one that’s been tried and tested and which I think works here:

  1. Mouse press the block you want to move.
  2. A translucent clone of the object is created and follows the mouse. The old block stays in place.
  3. When the block is in a position where it cannot be dropped, it gets a red tint. Releasing the mouse here removes the drag clone and leaves the block where it is.
  4. When the block is released in a valid position, the old block takes the place of the drag clone which then disappears.

K, I’ve got it working, now I just want to move some of the logic from the Main scene to the Block. Things like, what colour the ghostly block should be when it’s colliding or how translucent the ghost should be. That all belongs in Block.

I also moved the logic out of the Main scene into a separate DragAndDrop class.

I had an issue with the revert Tween. When the player releases a block in an invalid position, I have an animation where the ghost blocks slides back toward the original. However, if the animation is still running but the player clicks another block to start dragging it, problems occur. Two things happen:

  • The new drag and drop ghost is removed even though the player hasn’t released it.
  • The previous drag and drop ghost, though invisible, hangs around in the scene tree.

Fortunately with Godot 4 currying is a thing. I create a function that takes the node to be removed at the end of the tween and have it return yet another function that does the actual removing. I pass that function to the tween finished signal and it’s good to go.

Here’s what I mean:

# This is the Node I want to remove when the Tween is finished.
var drag_ghost: CollisionObject2D

var tween: Tween = ...

var remove_ghost: Callable = func(ghost: CollisionObject2D) -> Callable:
	return func() -> void:
		ghost.queue_free()

# Connect the inner function to the `finished` signal.
tween.finished.connect(remove_ghost.call(drag_ghost))

Now snapping.

I don’t think I’ll need snapping for any nodes that I haven’t already extended. Thanks to this SO answer I got it working pretty quickly.

var cell_size: Vector2 = Vector2(32, 32)

func _enter_tree() -> void:
	set_notify_transform(true)

func _notification(what: int) -> void:
	if what == NOTIFICATION_TRANSFORM_CHANGED:
		position = snapped(position, cell_size)

But it seems like it might be relatively easy to get the same snapping to work in the editor, too..? I haven’t done anything with Godot plugins yet (and wasn’t expecting to, but).

That’s kinda crazy how easy it is to setup. I got snapping working in the editor in a few minutes. I wonder if it’s processing the rest of the script as well, though..? How much of my Block class is running in the editor and how much only at runtime?

Looks like _ready() is but _input_event() is not.

The Godot documentation on Running code in the editor seems pretty good here. Gives good examples of why you would want to do so, very practical ones.

Ooh, in-editor configuration warnings will be useful, too. I was thinking I might want that for the drag and drop class. Make sure the draggable nodes implement the necessary methods.

func _get_configuration_warnings():
	var warnings = []

	if title == "":
		warnings.append("Please set `title` to a non-empty value.")

	# Returning an empty array means "no warning".
	return warnings
	```