- Home /
Tutorial dark overlay with cutouts
I'm trying to build a tutorial system that can darken the screen, except for one or more areas that I want to highlight to the user. Similar to this: https://imgur.com/gallery/ElHa1Im
I have a couple of requirements that make this non-trivial:
There can be more than one cutout area at a time
The cutout areas must follow any scaling or positioning of the UI elements they're attached to
Cutout areas shouldn't be simple rectangles, they must allow for rounded edges or circles
My current approach is to use a second camera that renders to a RenderTexture. That camera has a clear color of black with 50% alpha. A separate Screen Space Camera canvas renders to that camera. The cutouts are UI Images on that canvas, which are opaque where I want the cutout to be and are rendered on that camera with a blend mode of Blend Zero One, Zero OneMinusSrcAlpha
.
So my hierarchy looks like this:
- Main Camera
- Tutorial Camera (renders to RenderTexture)
- Tutorial Render Canvas (SSC, renders to TutorialCamera)
- Cutout Image
- Main Canvas (SSC, renders to Main Camera)
- Main UI Elements
- Tutorial Overlay (uses RenderTexture)
Where I'm struggling is how to align them with the game UI on the main canvas. It works fine when I copy/paste them in the Unity editor, but they lose all positioning when I try to Instantiate them via code. So what I'm looking for is either a way to reliably reproduce any positioning and scaling of the first canvas hierarchy onto the second canvas, or a simpler solution for what I'm trying to achieve here.
Update: I figured it out. It is a bit complicated though, so if anyone knows of a simpler solution, please enlighten me.
I setup the scene like above. The Tutorial RenderTexture Canvas needs to have the same CanvasScaler settings as the main canvas. I also add an additional Tutorial Display Canvas that renders to the main camera, but that's on a separate sorting layer to ensure it is drawn on top of all the main UI.
Then each of the cutouts is an Image plus a custom Script. The script Instantiates the cutout under the RenderTexture Canvas, and on each Update adapts the position and size like this:
var rt = cutout.GetComponent<RectTransform>();
var srcRT = GetComponent<RectTransform>();
rt.position = srcRT.position;
rt.SetSizeWithCurrentAnchors( RectTransform.Axis.Horizontal, srcRT.rect.size.x );
In addition to that, I also need another script that creates the RenderTexture at runtime, because it needs to have the same resolution as the main camera.
It's a bit hacky, and it doesn't work with masks, but other than that it seems to work.
For only one element, you can create dynamically a background image (black semitransparent) and put it behind the desired elemet, set dimentions to be huge enough to cover the rest of the screen (you can even scale it), put LayoutElement on background and toggle on ignore layout (to not break any Layout you may have)
That won't work because there are cases where I want to highlight only a part of an element, or a group of elements (e.g. a table where I want to highlight one row). Also there will be cases where I need to cut out two different regions of the screen.
you can go with secondary camera and render the portion of the screen (or all screen) to a texture, then apply those textures to dynamically created images in some new overlay canvas... too much work imo and may be not as performant as you might want(rendering textures is a part of Unity API and can't be offloaded to the background thread, so you may have some freezes).