UI panel without Image component as Raycast target? It is possible?
At the moment I'm using UI panel WITH Image component. Image is invisible. Is it possible to remove image component but mark this object as Raycast target?
Answer by Statement · Nov 01, 2015 at 03:09 PM
So the GraphicRaycaster work on Graphic objects (see EventSystem, Raycasters).
Image is a MaskableGraphic which is a Graphic, so this is why Image acts a raycast blocker.
Theres Canvas Group (scripting reference) which according to the docs sound like it would do what you ask, but I was not able to get click events without a Graphic attached. For reference, here's the script I used to test if it worked, perhaps I did something wrong etc. It didnt appear to get input without a Graphic anyway. As soon as I add an image etc, it works.
using UnityEngine;
using UnityEngine.EventSystems;
public class PrintOnPointerClick : MonoBehaviour, IPointerClickHandler
{
void IPointerClickHandler.OnPointerClick(PointerEventData eventData)
{
print("Clicked " + name);
}
}
Either you have to use any Component that subclasses from Graphic (such as Image, or create your own), or you have to provide a new Raycaster, such as a Physics2DRaycaster or a custom raycaster that extend from BaseRaycaster.
Unfortunately, setting alpha to zero will still cause the quad to be drawn, causing alpha blending which could impact performance on mobile devices. The method I used to try this out was to slide alpha all the way to zero and then use a material which doesn't use the alpha channel, but output a solid color instead. Therefore we can assume that the quad will be rendered, unless the system has a default mode for standard (blank) materials with zero alpha.
However for the GraphicRaycaster to block, the bare minimum is to have a Graphic, and graphics will be rendered. If you subclass Graphic, you'll notice it'll draw a quad on the transform so there seems to be no way out without rendering something, using GraphicRaycaster alone...
Raycasters, then...
Any custom built Raycasters will automatically work with Unitys EventSystem component. All you have to do is to implement the abstract method and property to get going, and possibly override any of the few virtual members. You can also take a look at Unity 5.2 UI open source code to see how the systems interact with each other. If you use a secondary Raycaster that detects your object, then you don't need a Graphic to get the pointer click or block the ray.
public abstract Camera eventCamera { get; }
public abstract void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList);
I remember a UT dev mentioning that alpha 0 images don't get drawn but still catch events.
Please clarify what do you mean by "alpha 0 images" as setting Alpha value to "0" for "Color" property of "Image" component still shows draw call in "Frame Debug"? Unity version is 5.3.0f4.
I can't (and won't be bothered to verify) remember which version I personally saw that graphics which had alpha 0 texture on them still were drawn. I replaced the materials shader with one that outputs a solid color to verify that it was happening.
I'm encountering this exact same issue now as well. What I've done is created a class that extends Graphic which overrides the UpdateGeometry function, and then just left it completely blank. It should then receive raycasts without incurring the additional draw call.
"Should"? Does it, or does it not? I would assume you you'd get another draw call because Unity wouldn't know if alpha was used to make the entire quad transparent, depending on shader used.
... and over 4 years later... :-)
Good idea! I tried it and it does actually reduce the draw calls! However, the same draw call reduction can now also be achieved by setting the image color alpha to 0 and enabling "Cull Transparent $$anonymous$$esh" on the CanvasRenderer. If I remember well, CanvasRenderer did not expose any components in the Inspector when you wrote this, so it was an excellent idea, thanks!
Answer by slippdouglas · Mar 20, 2016 at 02:32 AM
After reading over @Statement's incredibly helpful answer, I decided to try making a non-drawing Graphic
subclass. The result was surprisingly simple, and in my testing has worked without problems (descendant-GO components still layout and draw as expected).
The NonDrawingGraphic
class:
using UnityEngine;
using UnityEngine.UI;
/// A concrete subclass of the Unity UI `Graphic` class that just skips drawing.
/// Useful for providing a raycast target without actually drawing anything.
public class NonDrawingGraphic : Graphic
{
public override void SetMaterialDirty() { return; }
public override void SetVerticesDirty() { return; }
/// Probably not necessary since the chain of calls `Rebuild()`->`UpdateGeometry()`->`DoMeshGeneration()`->`OnPopulateMesh()` won't happen; so here really just as a fail-safe.
protected override void OnPopulateMesh(VertexHelper vh) {
vh.Clear();
return;
}
}
An optional NonDrawingGraphicEditor
class, put in an Editor/
folder in your project, will make sure Unity doesn't show the “Color” and “Material” fields in the Inspector, which we'd rather not see since they don't actually affect anything.
using UnityEngine;
using UnityEditor;
using UnityEditor.UI;
[CanEditMultipleObjects, CustomEditor(typeof(NonDrawingGraphic), false)]
public class NonDrawingGraphicEditor : GraphicEditor
{
public override void OnInspectorGUI ()
{
base.serializedObject.Update();
EditorGUILayout.PropertyField(base.m_Script, new GUILayoutOption[0]);
// skipping AppearanceControlsGUI
base.RaycastControlsGUI();
base.serializedObject.ApplyModifiedProperties();
}
}
Usage couldn't be simpler:
Looks like this no need more with comment of @victorbisaev
@U_$$anonymous$$u_Shu If I'm understanding you correctly, then yes, @victorbisaev's answer does too solve this problem. However, one of the most important things I've learned working in the game industry is that rule #1 is to never, ever, ever, ever have the machine do more work than it has to, unless saving the machine work will take your time away from implementing more important things. I've now solved the problem with a simple solution that doesn't incur performance costs of the Text
component's internal logic; therefore there's almost no reason not to use NonDrawingGraphic
.
@slippdouglas I wish we could somehow combine your answer and @Statement 's answer into a super answer. His for being informative and helpful in understanding the underlying issue. And yours for giving a clean script implementation for doing what's needed. Thanks to both of you for both your answers.
@Ben BearFish: Thanks. ;-) No need; if you like both his & $$anonymous$$e then just upvote both. There's no reason why there can't be multiple good answers to a question here on Unity Answers.
O$$anonymous$$G! this is a gem. Rewarded 500 reputation!
Additionally, If you need a Raycast target that is not a rectangle, you can implement bool Raycast(Vector2 sp, Camera eventCamera) method from Graphic.
Answer by victorbisaev · Dec 10, 2015 at 04:51 PM
Finally this works well: create "Text" uGUI component inside Canvas, clear its "Text" value and set "Raycast Target" to "true". No extra draw call but clicks are intercepted. I'm not the author for the solution, I've just seen it somewhere in the Internet. Thank you, the author!
I just figured this out myself as well. But decided to look around if there is a cleaner solution. I'm in the process of optimizing and reducing drawcalls and I confirm that blank text component is not causing any drawcall but still catching input. This is the simplest solution so far...
I can confirm this was the cleanest and simplest solution.
Answer by petersvp · Jun 18, 2020 at 06:31 PM
This is the only thing you actually need!
using UnityEngine.UI;
public class RaycastTarget : Graphic
{
public override void SetMaterialDirty() { return; }
public override void SetVerticesDirty() { return; }
}
@guneyozsan At least in Overdraw mode it is invisible if compared to an Image. Even with Color.alpha != 0.
Answer by Yiming075 · Apr 18 at 10:47 AM
Tested in Unity 2020.3.18f1. The image with alpha set to 0 will not be drawn.