Hello, we updated Unity on the latest LTS and we have a very strange behaviour with the EventSystem.
Here’s our hieraarchy:
GameObject → Script with IPointer Enter/Exit Handler
child Image 1 : raycast on
child Image (GameObject off) 2 : raycast on
child TextMeshPro : raycast on
In 2021.2.7f1 : script receive the OnPointerEnter() callback. We enable Image 2 GO. Nothing happens until cursor leaves the GameObject: expected behaviour.
In 2021.3.1f1 : script receive the OnPointerEnter() callback. We enable Image 2 GO. Next frame, the EventSystem calls OnPointerExit() on the script. But the cursor didn’t move.
Can at least someone from Unity confirm if this is the nex expected behaiour?
This would be in total contradiction with the previous bug tracker report,but, well…
Recently got surprised by this trying do some animated buttons. Not wanting to dig deeper into this as a quick workaround I disabled raycast target for child elements. This is fine if child elements are fully within bounds of parent, but would be more problematic if child popped out of the parent bounds.
This post encouraged me to do a bit more experiements. It seems that enter/exit behavior is somewhat inconsistent even when child isn’t dynamically enabled or disabled. My testcase consists two square images one parented to first so that you can easily mouse over them in any order.
O → outside all images
P → mouse inside parent
P+C → mouse in the part where parent and child overlaps
C → mouse in the child part
a.1) event listeners on parent, start with parent
*) O → nothing (as expected)
*) P → “enter Parent”
*) P+C → “exit Parent”
*) C → nothing
*) O → nothing
a.2) event listeners on parent, start with child
*) O → nothing (as expected)
*) C → “enter Parent” differs from previous case !
*) P+C → nothing
*) P → “enter Parent” entering parent twice !
*) O → “exit Parent”, “exit Parent” exiting parent twice !
If the child is considered as part of parent mouse area (which would be reasonable) then In case a.1) I would expect “exit Parent” to happen during C->O transition not P->P+C. If child isn’t part of parent mouse area there shouldn’t be “enter Parent” during O->C transition. Entering same object twice without any exits, and afterwards exiting twice seems wrong.
If there is event handler on parent and child I get following.
b.1) event listener on parent and child, start with parent
*) O → nothing (as expected)
*) P → “enter Parent”
*) P+C → “exit Parent”, “enter Child”
*) C → nothing
*) O → “exit Child”
b.2) event listener on parent and child, start with child
*) O → nothing (as expected)
*) C → “enter Child”, “enter Parent”
*) P+C → nothing
*) P → “exit Child”, “enter Parent” entering parent second time !
*) O → “exit Parent”, “exit Parent” exiting parent twice !
I also tried a few cases where child is hidden by default and enabled when hovering parent, all of them resulting in child flickering which matches with “exit parent” behavior during P->C transition observed before. Tested both by enabling using GameObject.SetActive(true)
and Image.enabled = true
Tests where done using Unity 2021.3.2, but It probably also applies to 2021.3.1, since I had to apply workaround for animated buttons there.
This also ruins “highlighted” behavior for simple button with color tint transition and child image representing custom icon . Similar thing might happen with text but didn’t check that yet, because I wanted to keep my test project lean without extra packages and files (that includes textmesh pro).
In theory some of the cases broken by these changes could be handled by looking at those flags (builtin unity components don’t do it), but there are also case where it doesn’t seem to be possible. Code seems to be assuming that child is strictly smaller than parent, and between exiting from child and exiting combined shape cursor will always reenter the parent. That is not the case if child is partially outside parent, and maybe even if child is on edge or near edge and you are moving cursor quickly.
Hello, we are aware of the issues this “fix” is bringing. The new fix makes it possible to have whichever behavior you wish - for example, highlight the parent or not when hovering a child element. However, we see now that a lot of people depended on the previous behavior and for them to keep it that way they need to change their project (disable raycast target) and/or add some code.
We are looking for a new solution that will have the new flexibility, but will fallback on the old behavior by default.
Thank you for your feedback, it’s always appreciated. We will keep you updated once the new solution is ready.
@RoxanneUnity What about the case where PointerExit{fullyExited=true} is never called? Once I learned about fullyExited and reenter I assumed that ignoring PointerEnter events with reenter=true and exit events with fullyExited=false would produce the old behavior, it almost does but there are some exceptions.
If you do transitions O->C->P->C->O there is a pointer exit event for parent with fullyExited=true during C->O transition (this makes sense). But if you enter the object parent first O->P->C->O there is no pointer exit event with fullyExited=true during C->O transition.
For enter/exit events to be usable I would expect it to have these two properties:
Enter{reenter=false} should be fully symmetrical with Exit{fullyExited=true}, this means whenever moving between two areas produce Enter{reenter=false} event, opposite movement should produce Exit[fullyExited=true} event
If moving between pair of areas produce an enter or exit event, it should do it consistently regardless of mouse movement sequence that was done before
Enter{reenter=true} is symetrical with Exit{fullyExited=false}, this seems to be working fine at the moment
Currently the new version don’t have the first two properties property which seems to be more of a bug in the way reenter/fullyExited flags are implemented.
One more property to consider is whether PointerEnter=+1, PointerExit=-1, events add up to 0. This may seem like desirable property but it has a couple of problems. The rules can be slightly fudged depending on how you count internal transitions.
Currently when exiting from parent there can be 2 exit events with fullyExited=true. This partially makes sense since there where 2 enter events (one with reenter and one without), but at the same time it’s slightly confusing since the two exit events are identical and there is no way to tell that 2 exit events correspond to two different kind of enter events.
Number of internal transitions isn’t guaranteed to be even number, this makes it problematic to balance out enter and exit events. Currently it’s somewhat “solved” by having no exit transition for C->O when doing O–(enter)->P–(exit)->C->O . Although lack of exit event during C->O transition in may opinion isn’t a worthwhile tradeoffs for the sake of balancing events.
A couple of different approaches to maintain balanced enter and exit events:
Accept the fact that number of internal transitions can be either even or odd and ignore them when keeping balance. Count only Exit{fullyExited=true} and Enter{reenter=false}
When generating Exit{fullyExited=false} also generate a Enter{pointerEnter=child} event. Any code which is only interested in events for parent but not children, needs to handle it anyway during O->C transition for O->C->P->O. This would allow doing Exit{fullyExited=true} later while keeping enter and exit events somewhat balanced. One downside is that events for O->P->C->O and O->C->P->O aren’t exactly symetrical, unless you also move one of the 2 exit events for O->C->P->O from P->O to C->P.
Completely different aproach whould be rethinking the labeling of “reenter” and “fullyExited” flags. And instead having single boolean for “root/combined shape” . It would make the process of filtering relevant events much easier. Single boolean regardless of enter or exit events instead of current new aproach requiring to check 2 booleans + object property (fullyExited, reenter, pointerEnter). It also makes slightly clearer what the expected events for sequence of mouse movements should be. O->P->P+C->C->O should generate Enter{root=false}, Enter{root=true}, Exit{root=true}, Exit{root = false} . And when doing mouse movement in opposite direction O->C->P+C->P->O, Enter{root=false}, Enter{root=true}, Exit{root=true}, Exit{root=false}. Only somewhat unclear part is whether Exit{root=true} should happen during P->P+C transition or during P+C->C, both have their usecases.
Any update or timeline on this? We are depending on this logic working correctly and as karliss pointed out, exiting the entire object from the child does not trigger a full exit = true.