A game with a card-based minigame and an animation-heavy UI.
Describe the problem or limitation you are having in your project
In my project, I am implementing a simple Blackjack minigame. Cards are dealt from the deck to the player's hand, which is a HBoxContainer.
My goals are:
Smoothly animate the card from the deck to the player's hand
Smoothly animate the cards already in the player's hand so that they shift properly whenever a new card is added.
Apply other animations to each card, for example, scaling up and down whenever the mouse cursor enters or leaves.
The issues with these goals are:
As long as the control is inside a container, we have no control over it. Containers generally reset the position, scale and size of their children after a sort. This means that, while we can animate a Control, that is unwanted behavior by the engine and any changes made to it will be short-lived.
The control must be inside a container for it to be laid out correctly. We can't know where the control will end up, short of calculating its final position by hand.
We can change the way a container acts before and after it places its children (using NOTIFICATION_PRE_SORT and NOTIFICATION_SORT), but we have no way to override the placement mechanism itself, short of writing a new container from scratch.
These issues with containers and animation are a sticking point among the community. See #7053 and #7425 and a bunch of different threads on Reddit and the forums asking for help on this.
Describe the feature / enhancement and how it helps to overcome the problem or limitation
Allow the user to override/customize the rect-fitting logic by addition of a virtual method Container._fit_child_in_rect(Control, Rect).
The solution of both previous use-cases is trivial: a HBoxContainer subclass can be created, which implements _fit_child_in_rect to tween its children to their final position instead of setting it directly.
Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams
The way containers work currently can be summarized as follows:
A sort is queued.
A new NOTIFICATION_SORT_CHILDREN is fired.
The container's _notification method iterates over children, calculates the rects making up its layout, and calls fit_child_in_rect(Control, Rect2) on each child.
fit_child_in_rect calculates the final rect of the control in accordance with its size flags and applies it directly.
My proposal is to:
Move the layouting logic from fit_child_in_rect to a new function called get_fitted_rect(Control, Rect2) which returns the calculated Rect2without applying it.
Modify fit_child_in_rect so that it checks for a _fit_child_in_rect(Control, Rect2) implementation. If it doesn't exist, it calls get_fitted_rect and applies it directly as it does now. If it exists, it forwards its arguments to it. It could even work like Object._set(), where the default behavior is applied even if the method has been implemented, if the implementation returns false.
The user-defined _fit_child_in_rect can implement any custom behavior and even use get_fitted_rect as its starting point.
It would probably be necessary to implement a warning to the user if they attempt to change size flags, minimum size or visibility of the Control in _fit_child_in_rect, as that could result in cascading sorts.
I imagine these changes would be retrocompatible and not break any existing code.
If this enhancement will not be used often, can it be worked around with a few lines of script?
Most of the behavior of this proposal can be simulated by storing the properties we're interested in during PRE_SORT, and restoring them afterwards. As an example, if I wanted to smoothly animate the position of cards in my HBoxContainer:
extends HBoxContainer
var pre_sort_positions := {}
func _notification(what):
if what == NOTIFICATION_PRE_SORT_CHILDREN:
pre_sort_positions.clear()
for c in get_children():
if !(c is Control): continue
pre_sort_positions[c] = c.position
elif what == NOTIFICATION_SORT_CHILDREN:
for c in get_children():
if !(c is Control): continue
var final_position = c.position
create_tween().tween_property(c, "position", final_position, 0.2).from(pre_sort_positions[c])
This setup is prone to user error and can fail for any number of random reasons. Other workarounds exist (such as using proxy nodes or wrapping controls in dummy containers so child nodes can animate freely within) but they're equally overcomplicated, janky, and don't work in all situations.
Is there a reason why this should be core and not an add-on in the asset library?
Describe the project you are working on
A game with a card-based minigame and an animation-heavy UI.
Describe the problem or limitation you are having in your project
In my project, I am implementing a simple Blackjack minigame. Cards are dealt from the deck to the player's hand, which is a HBoxContainer.
My goals are:
The issues with these goals are:
These issues with containers and animation are a sticking point among the community. See #7053 and #7425 and a bunch of different threads on Reddit and the forums asking for help on this.
Describe the feature / enhancement and how it helps to overcome the problem or limitation
Allow the user to override/customize the rect-fitting logic by addition of a virtual method
Container._fit_child_in_rect(Control, Rect)
.The solution of both previous use-cases is trivial: a HBoxContainer subclass can be created, which implements
_fit_child_in_rect
to tween its children to their final position instead of setting it directly.Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams
The way containers work currently can be summarized as follows:
NOTIFICATION_SORT_CHILDREN
is fired._notification
method iterates over children, calculates the rects making up its layout, and callsfit_child_in_rect(Control, Rect2)
on each child.fit_child_in_rect
calculates the final rect of the control in accordance with its size flags and applies it directly.My proposal is to:
fit_child_in_rect
to a new function calledget_fitted_rect(Control, Rect2)
which returns the calculatedRect2
without applying it.fit_child_in_rect
so that it checks for a_fit_child_in_rect(Control, Rect2)
implementation. If it doesn't exist, it callsget_fitted_rect
and applies it directly as it does now. If it exists, it forwards its arguments to it. It could even work likeObject._set()
, where the default behavior is applied even if the method has been implemented, if the implementation returnsfalse
._fit_child_in_rect
can implement any custom behavior and even useget_fitted_rect
as its starting point._fit_child_in_rect
, as that could result in cascading sorts.I imagine these changes would be retrocompatible and not break any existing code.
If this enhancement will not be used often, can it be worked around with a few lines of script?
Most of the behavior of this proposal can be simulated by storing the properties we're interested in during PRE_SORT, and restoring them afterwards. As an example, if I wanted to smoothly animate the position of cards in my HBoxContainer:
This setup is prone to user error and can fail for any number of random reasons. Other workarounds exist (such as using proxy nodes or wrapping controls in dummy containers so child nodes can animate freely within) but they're equally overcomplicated, janky, and don't work in all situations.
Is there a reason why this should be core and not an add-on in the asset library?
This is core Container functionality.