I want to create a generic drag-and-drop component. During dragging, I’d like to create a mirrored version of the target object to follow the drag motion.
Is there a good way to achieve this mirror display? The target object might contain many child nodes.
The best solution I can think of is to use something like a SubViewport, setting the root node of the render target as the dragged object. This would allow me to easily create a mirrored display, which works perfectly without affecting logic. However, I’ve looked into SubViewport, Viewport, and Camera2D, and none of them seem to offer a similar approach.
Using target.duplicate() is not ideal because the target could be various types of objects, and duplicating it might affect the logic in certain situations. For example, if the drag component itself is inside the target object, it would be duplicated along with it, among other potential issues. Additionally, if the object structure is complex, frequent dragging could reduce efficiency.
I’ve also reviewed some drag-and-drop solutions made by others, but most are limited to specific objects and lack general applicability.
If anyone has a good suggestion, I’d greatly appreciate it.
Given that you find existing drag-and-drop solutions inapplicable, perhaps you can describe why this is the case. I’m not familiar with drag-and-drop solutions in Godot, and I am worried that any solution I create will lack the capabilities you are seeking.
Please describe further:
What this mirror display entails, and how it is related to drag-and-drop functionality
Why the solutions you’ve looked at are inapplicable – preferably on a case-by-case basis
I want to help but I can’t derive the full picture from what you’ve described.
Thank you very much for your willingness to help. Let me give a simple example: suppose I need to drag an item. This item may contain a background frame, an item icon, the item’s name, quantity, and so on. The item node might also have complex scripts and logic.
The usual approach is to pass the data needed for display to the drag component, which then creates an identical-looking item that follows the mouse. There are typically two methods for this:
The first is to directly call the original item’s .duplicate() function to make a complete copy. The downside of this approach is that everything in the original item is copied, including any complex scripts it might contain, and even the drag component itself. This is obviously wasteful. If the item has a complex node hierarchy and logic, frequent dragging that involves frequent creation and deletion of temporary objects may lead to performance issues and potentially trigger hidden bugs.
The second approach is to create a simpler duplicate scene that looks the same but doesn’t include the original item’s complex logic. Then, only the resource data corresponding to the item is sent to the drag component, which configures the appearance in the simplified duplicate scene based on the item’s resource. The downside of this method is that if I have many different types of objects to drag—such as items, skills, talents, cards, and so on—I would need to create a simplified duplicate scene for each type of item, which is also an inelegant solution.
So, I thought of a third method: creating something similar to a SubViewport and selecting the render target of the SubViewport to be the item being dragged. This would allow me to create a mirrored version of the item for dragging that looks identical to the original, without the need to create additional complex objects or risk any logical bugs. Of course, SubViewport can only render child nodes and can’t select specific render targets, so this is just an example. Another possible option could be something like a camera, but a camera might capture other surrounding objects besides the item. Alternatively, I could use a specific render layer to render only specific items, though this is less flexible.
Additional note: there’s also a solution where the original item itself is directly dragged without creating a temporary copy. However, this doesn’t suit my current game design. For instance, when dragging an item, I prefer that the original item remains semi-transparent in its original position, while a new mirrored version is created for dragging.
Now I see! I interpreted the term “mirrored” as “flipped” which is why I couldn’t quite understand the problem. You just want an identical copy of the source item. Thanks for describing the different approaches as well. This gives me a good foundation on which I can provide support.
For clarity, these are the accepted approaches you mentioned:
A one-to-one duplicate of the original; functionality and visuals.
A separate scene with the same layout as the original – no functionality.
A texture-based solution that utilizes a SubViewport to render a copy.
Approach #2 and #3 are similar as they both limit replication to the visuals of the item – the functionality of the source item is not duplicated.
Proposed solution
I actually believe that approach #1 is the best one. It’s simple and guarantees that the copy is identical to the source. The other two approaches (#2 and #3) have more overhead for you, the developer.
Overhead for approach #2
Overhead for approach #3
Since this approach suggests using a SubViewport to produce a visual duplicate, it would require dynamically wrapping the source element with the SubViewport. I imagine such an operation would result in margin differences and other layout problems, and that would be infuriating.
It’s possible to do this but it’s not something I would do.
However, you mention that approach #1 is wasteful. A potentially complex node hierarchy will produce performance loss as frequent drag-drop operations result in rapid node instantiation/destruction. Not good.
The good news is that this doesn’t have to be the case. The reasons are listed below.
Reason 1: You only need one copy
As far as I can tell, you would only ever need/want a single copy of an object – and it should only be visible when dragging. Therefore, you don’t need to constantly duplicate the source object – only once. The state and visibility of the duplicate should then be updated whenever the source is dragged or dropped.
Reason 2: Data and its representation should be separated
When building a user interface that is meant to represent data, it is important to not confuse the UI elements with the actual data. Beginners often allow their graphical components to store the data they represent (me included). This is a bad long-term solution as the object is no longer just a UI element, but a data container as well. You may have multiple UI elements that represent the same data. If that is the case, you will no longer have a true source for your data.
Similarly, a UI element should only ever contain UI functionality. If you have a sword item, the UI element representing this sword item should not have an attack() function, only functions that updates its visuals (e.g. show_item()).
In the context of your drag-and-drop problem, if you are unable to duplicate() your UI item in fear of duplicating “complex scripts” , there is something wrong with how your system functions. You should just be able to duplicate the source element.
I’m making a few assumptions of how your system works so please correct me if I’m wrong about anything.
I don’t yet have an example of what a drag-and-drop node (which you can attach to your items) might look like – I will need to do a little research first. I will try to have something for you tomorrow.
Let me know if there is anything else you want to discuss.
Thank you very much for your detailed answer. There may have been a misunderstanding regarding the complexity of the node hierarchy in Approach 1. In my current setup, data and UI are entirely separated. The complex node hierarchy and logic I mentioned earlier primarily relate to visual elements only. For example, my card nodes contain various effects for mouse enter, mouse leave, and interactions when the mouse hovers over different areas of the card. They also have distinct visual effects based on the card’s level, buffs, and include components like drag-and-drop and tooltips.
If I use duplicate() directly to create a one-to-one copy of the original object, the duplicated object would logically also execute the same gui_input logic. So, when the new object moves with the mouse, it could trigger unintended logic (although, in my current testing, it’s working surprisingly well without the expected bugs—I haven’t yet explored why this is). Additionally, the drag-and-drop node within the original object would also be copied, which could lead to potential logical deadlocks under certain conditions. Also, I don’t want the temporary drag-and-drop object to display tooltips, which would mean additional special handling after creating the temporary copy. Ideally, I’d like my drag-and-drop component to be completely universal so that any object with this component could support dragging without extra adjustments.
Regarding Approach 3, using something similar to SubViewport initially seemed promising. Unfortunately, SubViewport can only display child objects and doesn’t allow setting an alternative render target. After thinking it over last night, I also realized this approach wouldn’t suit my needs, as I want the original object to appear semi-transparent during dragging. Even if SubViewport could specify a render target, it wouldn’t allow the mirrored object to be opaque while the original is semi-transparent.
So, I’ve currently decided to go with Approach 2. While this approach is a bit more complex and sacrifices some versatility—requiring a custom temporary display scene for each draggable item—it seems to best meet my current needs in terms of performance, logical safety, and visual effect.