Tutorial

Unreal Engine UMG - How to add a dynamic radius to a widget component

If you are just starting off with UI design in Unreal Engine, or (like me) you are used to dedicated UX/UI prototyping software like Protopie, working with widgets inside UE4 can be frustrating. The biggest difference to conventional digital prototyping tools is probably the absence of a vector graphics editor. Widgets in Unreal Engine pretty much rely on premade UI assets in the form of PNG files. Thus, dynamically updating colors, shadows, or radii can become tricky. Luckily there are some workarounds. In this step by step tutorial I would like to introduce a solution on how to implement a dynamic radius for images inside the UMG designer in Unreal Engine.

The Task

Lets say you are working on an application where you portray images in a preview window. During preview the images should be displayed with round corners. However, when you click on full screen button, the corners will disappear in a smooth way.

Step I - The Basic Setup

We will create a new widget (I called mine "WGD_RadiusComponent"). Afterwards we add a background image with a plane color as well as a button to the canvas. We also add the image to the canvas that we would like to use inside our gallery app. But instead of just placing it straight on the viewport, we gonna wrap it inside a Retainer Box component. Apart of the image-child, a retainer box can house an effect material that utilizes a texture. The texture inside the material is then replaced by the image we wrapped into the retainer box.

Top-left: UMG canvas setup, Top-right: Retainer-Box setup, Bottom: simple scale animation

We also set up a simple animation of the image (or rather the retainer box that houses the image) scaling up once you click the AppIconButton that we added. The retainer box is using an effect material that in our case is solely drawing a texture. Note that the texture parameter name inside the retainer pox details panels needs to match the name of the texture parameter inside our effect material. (NOTE: The Material Domain of the Effect Material needs to be set to "User Interface")

RetainerBox Material. Texture is made a perameter and named accordingly

The result of this first step is an image that scales up, once we hit the app icon button. Nothing too fancy and something we could have achieved in a simpler fashion for sure.

Step II - Lets add some Radii

Okay, so far so good. Now let us add the radius to the image. To do so, we need to create another effect material for our retainer box. One that masks the corners of our image to our liking.

First of all: Don't forget to set the Material Blend mode from "opaque" to "masked" to access the Opacity Mask input. Let's try to break down the material graph. The main difference to the prior material is the "GenerateRoundRect" node that we input into the Opacity Mask. However, the issue with this node is that it doesn't take into account the image ratio of our texture. Thus, to have a coherent radius on all four corners, we need to multiply the texture coordinate node with a normalized 2D vector, where X is the width of our image and Y the height. That happens in the top left corner. For the box dimension we subtract the corner radius from the UV Coordinate vector to give it the extra space of the radius that is cut away in the next step. The corner radius itself is clamped between 0 and half of the smaller side of our image as that is the maximum radius we can apply without having one corner overlap with the other. To be able to set a pixel value for our radius we divide the radius parameter by the pixel width of our image before putting it into the clamp node. Lastly the center will be in the middle point of the UV coordinates so we multiply them with a 0.5/0.5 vector.

The result is a material instance with a radius parameter that we can now add to our retainer box.

Step III - Changing the radius dynamically

Okay, now we get to our last task. We want to transition from preview to fullscreen mode. First of, we need to access our material parameter and to do so we need to create a dynamic material instance once our widget is constructed.

After we created the dynamic material instance we stored it in a variable and set it to be the effect material within our retainer box. If you are familiar with blueprints, you might think "alright, now all we need is a set scalar parameter node and a timeline and we are good to go", however, we don't have access to timelines in widget blueprints. So, instead we need to get another workaround and use a timer instead of a timeline.

To adjust the radius frame by frame during the animation we store the initial radius and a copy of it (current radius) that we will subtract from. We also need the iteration interval which we set by dividing the animation duration by the frame rate (which we need to set manually unfortunately. You can do that in the top bar of the animation timeline window in the widget designer). We then set a looping timer that triggers every animation frame and store its handler insider avariable to clear it later on.

The iteration loop is pretty straight forward as well. we check if the radius should be adjusted and iterate the timer (set timer handle) we then adjust the radius by feeding it the current radius variable where we subtracted the radius difference per frame from. Once our timer handle has reached the end of the animation, the flag is set to false and the handle clears.

As a result, our mask radius updates smoothly during the scaling animation.

Next Steps

What I am desperately missing is a way to work with drop shadows in Unreal Engine. Thus, I will try to add dynamic drop shadows to a widget blueprint next. Let's see how far I get :)

Read more