My Widgets Don't Tick Properly in 4.9 Anymore...

I have hit an issue with User Widgets in UE4.9, all of this stuff was working before I updated from 4.8.

For certain objects like Vehicles and Powerups, I have a 2D Widget on screen that follows it’s parent object around in the viewport. If the object’s position can’t be deprojected to the screen, it set’s it’s position to be outside the screen bounds. This is a workaround for Widgets not Ticking when they’re hidden, and also because I can’t just set the size of the widget to be zero. By the way, being able to Tick widgets when they’re hidden would be a REALLY handy per-widget option to have!

Unfortunately, whether this is due to the new invalidation panel system or whatever, they now only tick once then intermediately disappear. They still exist in the game world because I can debug them, but they only tick once then they stop. I’ve tried checking ‘Is Volatile’ to prevent the new invalidation panel thing but it doesn’t have any effect. I can’t find anyway to actually force the Widgets to Tick at the moment, so what’s the option I’m missing? For reference, here’s the code I’m using to make this work. By the way, the function NEVER goes into the first If Statement, so it’s not killing itself off. I checked that already.

GameObject is an ActorComponent that the Widget is assigned to track. OwnerPC is my custom PlayerController, cached for easy reference later.



void UBZGame_GOCWidget::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
{
	Super::NativeTick(MyGeometry, InDeltaTime);

	/* Remove Self if the GOC isn't Valid */
	if (!GameObject.IsValid() || !GameObject->IsAlive())
	{
		GameObject = NULL;
		RemoveFromParent();
		SetVisibility(ESlateVisibility::Hidden);
	}
	else if (GameObject->GetOwner() == OwnerPC->GetPawn())
	{
		SetPositionInViewport(FVector2D(0.f, 0.f) - GetDesiredSize());
	}
	else if (GameObject->bTimedUIVisibility || GameObject->bForcedUIVisibility || GameObject->bIsBeingTargetted)
	{
		/* Set Location In Viewport */
		FVector2D ScreenLoc;
		if (OwnerPC->ProjectWorldLocationToScreen(GameObject->GetOwnerLocation(), ScreenLoc))
		{
			SetPositionInViewport(ScreenLoc - (GetDesiredSize() / 2.0f));

			/* Distance */
			if (GetOwningPlayerPawn() && GameObject.IsValid())
			{
				DistanceToObject = FText::FromString(FString::FromInt(FMath::RoundToInt(FVector(GameObject->GetOwnerLocation() - GetOwningPlayerPawn()->GetActorLocation()).Size() / 100.0f)) + TEXT(" M"));
			}
			else
			{
				DistanceToObject = FText::FromString(TEXT("0 M"));
			}

			/* Health Colour */
			HealthColour = FLinearColor::Red + GameObject->GetCurrHealthRatio() * (FLinearColor::Green - FLinearColor::Red);

			/* Overall Colour */
			TeamAffiliation = BZGameState->GetTeamAffiliation(BZPlayerState->GetTeamNum_Inline(), GameObject->GetTeam());
			TeamColour = TeamAffiliation == EAffiliation::EA_Neutral ? FLinearColor::White : (TeamAffiliation == EAffiliation::EA_Friendly ? FLinearColor::Green : (TeamAffiliation == EAffiliation::EA_Ally ? FLinearColor::Blue : FLinearColor::Red));

			/* Radius */
			ObjectRadius = UBZGameplayStatics::GetObjectScreenRadius(GameObject->GetOwner(), OwnerPC);
		}
		else
		{
			SetPositionInViewport(FVector2D(0.f, 0.f) - GetDesiredSize());
		}
	}
	else
	{
		SetPositionInViewport(FVector2D(0.f, 0.f) - GetDesiredSize());
	}
}


If I tab-out the ‘SetPositionInViewport’ code that places it outside the viewport, it stops ticking. But, I kind of have to do this because there’s no other way to hide it and then allow it to manage when it should be on-screen again…

Do you not use the “WidgetComponent”? It’s experimental (in 4.8) but it works fine for me and it’s ideal for your purpose I think.

I avoided 3D / In-World Widgets since these are easier for me to work with, and I need to be able to deproject them for things like on-screen arrows etc when they are off-screen plus a bunch of other things like only showing for certain players etc, modifying colours for each player etc.

The problem seems to be because of the new Cached Widget / Invalidation stuff that was added in 4.9, which doesn’t seem to be working correctly. If I try to set the widget to be in a location outside of the viewport, it gets nuked or it’s desired size gets set to zero and it stops ticking anymore. If I don’t set that location, it seems fine (but obviously, I have loads of widgets in the wrong place on screen).


So I tried adding ‘InvalidateLayoutAndValidity()’ just after Super::NativeTick in my custom widget, and also checking ‘Is Volatile’ in the editor. It had no effect UNTIL I used the Widget-Reflector to select the Widget (while in Invalidation Debugging mode), and then closed the Widget Reflector. After that, everything behaved as it should. That indicates a problem somewhere, since why on Earth would opening the Reflector fix it? Can’t do that in a packaged game either.

Are you using the Invalidation panel? The volatility stuff only matters if you’re inside an invalidation panel.

That already exists, just use SetTimer to get a callback when you’d like. We will not be ticking invisible widgets as it would require traversing the hierarchy for invisible widgets and ticking them, totally negating the reason we put it in for a performance win.

Your problem doesn’t have anything to do with the invalidation system, it has everything to do with how SConstraintCanvas’s OnPaint works. Which no longer paints widgets completely outside its clip rect, because why do a ton of work for things you can’t see. So not painting them does not tick them either.

Any tips on how to work around that ?
I also had exactly the same problem with enemy health bars over their heads. Once of screen, they never reappear again, because they never start tick again.

  1. Change SConstraintCanvas.
  2. Use SetTimer to always get a “Tick”.
  3. Change your approach so that the ‘Indicator Manager’ widget manages and updates widgets keeping them in view…etc. That is how the Widget Component does it. Best performant way really. Having each widget manage itself adds more overhead, and doesn’t let you easily sort them, which the Widget Component’s SWorldWidgetScreenLayer takes care of.

Well I got confused by the invalidation debugging thing, since when I use the widget reflector and select one of the widgets (while in invalidation debug mode) - they all show up. If I close the Widget Reflector while i still have one selected, they then all start working just as they did previously, which led me to the invalidation thing.

I honestly don’t know if I’m using it or not, I thought it was just an on-by-default thing. So you’re saying that SConstraintCanvas has had some changes in 4.9 to stop drawing off-screen widgets? The Timer thing could work but the issue is that they need to tick every frame regardless to ensure they draw in the correct place, otherwise they’ll jitter around the viewport. Plus as you say, performance might be pretty **** (which it probably is now, I just haven’t got to the stage where it’s an issue yet).

What it sounds like I should do then, is create a widget that’s basically just a full-screen canvas panel, add my ‘object tracker’ widgets as children to it, and ‘update’ all of them by calling some kind of ‘Update’ function on each one via the main canvas widget?

Yeah Option 3 is your best bet, having the central indicator layer manage all over the head things. Which solves loads of problems, like, cuts overhead, proper ordering based on distance is achievable. All kinds of things :slight_smile:

I probably wouldn’t have an Update function. I’d probably have some base indicator widget class and one of the members would be, MyActor, or something. And the indicator widget would just loop over all widgets in the canvas and position them correctly. Then if they happen to be visible that frame, they’ll be Ticked, and do any extra work if needed.

Or just put that code in your update functions and call those if that’s easier to transition for now. Sorry for the change, felt innocuous at the time and we’re on a mission to make Slate run much faster right now. Trying to keep changes minimal, but if a widget was doing something bad for performance without really a reason for doing it, those kinds of things are being eliminated where possible, along with a million other improvements and features (like the invalidation panel).

No worries, I know how it is :stuck_out_tongue: It’ll all benefit in the long run

I’ll switch over to that system for now, which realistically sounds easier to manage anyway. I’ll likely re-write my Radar system too since right now, all the little ‘pings’ and ‘blips’ Tick themselves too. Is it generally cheaper then to have one master object “tick” a bunch of children, rather than have lot’s of UWidgets and / or UUserWidgets ticking themselves? I guess if one widget manages what should and shouldn’t be ticked it’s gonna be cheaper than lots of widgets working out if they shouldn’t be ticked.