{"id":3511,"date":"2015-03-14T16:06:06","date_gmt":"2015-03-14T15:06:06","guid":{"rendered":"http:\/\/t-machine.org\/?p=3511"},"modified":"2015-03-14T19:50:23","modified_gmt":"2015-03-14T18:50:23","slug":"fix-unity3ds-broken-onmousedown","status":"publish","type":"post","link":"http:\/\/new.t-machine.org\/index.php\/2015\/03\/14\/fix-unity3ds-broken-onmousedown\/","title":{"rendered":"Fix #Unity3d&#8217;s broken OnMouseDown"},"content":{"rendered":"<p>This one has annoyed me for years: Unity&#8217;s OnMouseDown has never really worked. (NB: it works perfectly if you make a scene with a single cube; but that is not a game. It breaks completely as soon as you add multiple objects, colliders, etc to a scene)<\/p>\n<p>The main reason to use a 3rd party engine is to avoid re-inventing the wheel; it&#8217;s tragic that Unity can&#8217;t even do mouse-input out of the box. Let&#8217;s fix it!<\/p>\n<p><!--more--><\/p>\n<h2>The problem<\/h2>\n<p>OnMouseDown is &#8220;supposed to&#8221; be invoked on all the Components of a GameObject whenever the player moves the mouse over that GameObject on screen, and clicks.<\/p>\n<p>In reality:<\/p>\n<ol>\n<li>OnMouseDown requires the physics engine. WTF? Yeah, I know: bad design decision right there (*)\n<li>OnMouseDown conflicts with the physics engine: you cannot have mouse-clicks without having physics objects. Workaround: you use-up some of your limited layers + tags to &#8220;isolate&#8221; the physics for clicks from physics for reality\n<li>OnMouseDown <strong>randomly decides which GameObject to invoke<\/strong> &#8212; this is the Killer Bug. (**)\n<li>OnMouseDown will <strong>only tell ONE GameObject that the mouse has been clicked<\/strong>. If there were multiple objects under the mouse, a RANDOM selection of them will be ignored. &#8212; this is the other Killer Bug\n<\/ol>\n<p>In addition, there&#8217;s some obvious stuff that OnMouseDown needs in order to be useful. Without these, I&#8217;ve generally found it about as useful as a chocolate teapot:<\/p>\n<ol>\n<li>If the &#8220;hit&#8221; object is marked as a trigger (i.e. it&#8217;s a NON PHYSICAL collider), then mouse clicks ought to &#8220;pass through&#8221; it to the objects behind. Unity ought to send events to BOTH objects\n<li>Optionally, the &#8220;hit&#8221; object should be able to say &#8220;I am consuming this mouse click&#8221;. This has been standard behaviour for API&#8217;s \/ UI&#8217;s for decades. It&#8217;s disappointing that Unity doesn&#8217;t do it.\n<\/ol>\n<p><em>(**) &#8211; Educated guess: this is because Physics.RayCast() is so badly named\/designed, and explicitly returns hits in &#8220;random&#8221; order. This is perhaps so counter-intuitive that even Unity&#8217;s internal programmers were confused by it<\/em><\/p>\n<p><em>(*) &#8211; my guess is that OnMouseDown was <strong>never intended to be used by game developers<\/strong>. Rather, it was a quick hack that someone thought &#8220;hey, I could abuse our physics engine, and get a simplistic mouse handler for free! Let&#8217;s try it!&#8221;. I appreciate the hack; it&#8217;s a nice idea. But it&#8217;s out of place as the sole implementation for mouse-handling in a production game-engine!<\/em><\/p>\n<h2>The solution<\/h2>\n<p>If you&#8217;re not used to fixing Unity, here&#8217;s the high-level overview:<\/p>\n<ol>\n<li>Implement a Scene-wide hack that runs on Update (because we have to check this every frame!)\n<li>Use a manual Physics.RayCast from the mouse pointer\n<li>Check <strong>everything<\/strong> along the ray\n<li>Check <strong>in order, starting from the nearest hit<\/strong>\n<li>Allow each successive hit to <strong>optionally consume<\/strong> the hit\n<li>Deliver to multiple objects\n<li>For each object, the source code should be the same as OnMouseDown: i.e. you simply write a method with the right name, and everything is automatic\n<li>Using Reflection, we look for the magic method name on each object\n<li>When something is hit, <strong>don&#8217;t just check the object, check it&#8217;s parent and grandparents<\/strong>\n<ul>\n<li>Why? Because <strong>in Unity, many bugs require you to &#8220;parent&#8221; one object inside another in order to fix them. Unity doesn&#8217;t fix the bugs because this workaround is &#8220;known&#8221; and &#8220;recommended&#8221;<\/strong>. So &#8230; we have to assume that our script that handles OnMouseDown <em>might not be<\/em> on the same object that has the Collider that receives the hit<\/li>\n<\/ul>\n<\/ol>\n<p>N.B.:<\/p>\n<blockquote><p>\nThis should not be implemented using Physics; as noted at the start, that&#8217;s a terrible idea. But since Unity has <strong>zero support<\/strong> for using rendering-without-physics, this is the only reasonably cheap way to write the code. Writing a full solution would require adding the complete &#8220;rendering-feedback API&#8221; that Unity is missing. I didn&#8217;t have time for that.\n<\/p><\/blockquote>\n<h3>The Code<\/h3>\n<p>You can do this better. If you&#8217;d like this done better (easier to re-use, import to projects, etc) let me know and I&#8217;ll do an Asset Store package at some point. At the moment, this is a fast, quick, hack that works &#8220;good enough&#8221; for small projects.<\/p>\n<p><span style=\"color: red\">Sampsa Lehtonen <a href=\"https:\/\/twitter.com\/snlehton\/status\/576771713599447040\">pointed out on twitter<\/a> that this has a stupid bug: if a gameobject has multiple components that ALL want the event, only the first one receives it. That was not intended. I recommend fixing it<\/span><\/p>\n<p>[csharp]<br \/>\nusing UnityEngine;<br \/>\nusing System.Collections;<br \/>\nusing System.Collections.Generic;<br \/>\nusing System.Reflection;<\/p>\n<p>\/** Unity OnMouseDown has never worked properly, except in trivial scenes with only 1\/few objects<\/p>\n<p> c.f. *\/<br \/>\npublic class FixUnityBrokenMouseDown : MonoBehaviour<br \/>\n{<\/p>\n<p>\t\tvoid Update ()<br \/>\n\t\t{<br \/>\n\t\t\t\tif (Input.GetMouseButtonDown (0))<br \/>\n\t\t\t\t{<br \/>\n\t\t\t\t\t\tRay mouseRay = Camera.main.ScreenPointToRay (Input.mousePosition);<\/p>\n<p>\t\t\t\t\t\tList&lt;RaycastHit&gt; orderedHits = new List&lt;RaycastHit&gt; (Physics.RaycastAll (mouseRay));<br \/>\n\t\t\t\t\t\torderedHits.Sort ((h1,h2) =&gt; h1.distance.CompareTo (h2.distance));<\/p>\n<p>\t\t\t\t\t\tbool hasBeenConsumed = false;<br \/>\n\t\t\t\t\t\tforeach (RaycastHit hit in orderedHits)<br \/>\n\t\t\t\t\t\t{<br \/>\n\t\t\t\t\t\t\t\tif (hasBeenConsumed)<br \/>\n\t\t\t\t\t\t\t\t{<br \/>\n\t\t\t\t\t\t\t\t\t\tbreak;<br \/>\n\t\t\t\t\t\t\t\t}<\/p>\n<p>\t\t\t\tComponentWithMethod target = ComponentThatCanReceiveMethod (hit.collider.gameObject, &quot;FixedOnMouseDown&quot;);<\/p>\n<p>\t\t\t\t\t\t\t\tif (target.component != null)<br \/>\n\t\t\t\t\t\t\t\t{<br \/>\n\t\t\t\t\t\t\t\t\t\tobject result = target.method.Invoke (target.component, null);<br \/>\n\t\t\t\t\t\t\t\t\t\tbool didConsume = (result == null)? ( true \/** assume consumption if not specified *\/ ) : (bool)result;<\/p>\n<p>\t\t\t\t\t\t\t\t\t\tif (!didConsume)<br \/>\n\t\t\t\t\t\t\t\t\t\t{<br \/>\n\t\t\t\t\t\t\t\t\t\t\t\tcontinue;<br \/>\n\t\t\t\t\t\t\t\t\t\t} else<br \/>\n\t\t\t\t\t\t\t\t\t\t{<br \/>\n\t\t\t\t\t\t\t\t\t\t\t\thasBeenConsumed = true;<br \/>\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;<br \/>\n\t\t\t\t\t\t\t\t\t\t}<br \/>\n\t\t\t\t\t\t\t\t}<br \/>\n\t\t\t\t\t\t}<br \/>\n\t\t\t\t}<br \/>\n\t\t}<\/p>\n<p>\t\tstruct ComponentWithMethod<br \/>\n\t\t{<br \/>\n\t\t\t\tpublic Component component;<br \/>\n\t\t\t\tpublic MethodInfo method;<br \/>\n\t\t}<\/p>\n<p>\t\tprivate ComponentWithMethod ComponentThatCanReceiveMethod (GameObject go, string methodName)<br \/>\n\t\t{<br \/>\n\t\t\t\tforeach (Component subComponent in go.GetComponents ( typeof(Component)))<br \/>\n\t\t\t\t{<br \/>\n\t\t\t\t\t\tMethodInfo info = subComponent.GetType ().GetMethod ( methodName, BindingFlags.Public | BindingFlags.Instance);<br \/>\n\t\t\t\t\t\tif (info != null)<br \/>\n\t\t\t\t\t\t{<br \/>\n\t\t\t\t\t\t\t\tComponentWithMethod result = new ComponentWithMethod ();<br \/>\n\t\t\t\t\t\t\t\tresult.component = subComponent;<br \/>\n\t\t\t\t\t\t\t\tresult.method = info;<br \/>\n\t\t\t\t\t\t\t\treturn result;<br \/>\n\t\t\t\t\t\t}<br \/>\n\t\t\t\t}<\/p>\n<p>\t\t\/**<br \/>\n\t\tdidn&#8217;t find aything on this object or its components.<\/p>\n<p>\t\tSo &#8230; check again on parent object. Keep going till you find a match or fail<br \/>\n\t\t*\/<br \/>\n\t\t\t\tif (go.transform.parent != null)<br \/>\n\t\t\t\t{<br \/>\n\t\t\t\t\t\treturn ComponentThatCanReceiveMethod (go.transform.parent.gameObject, methodName);<br \/>\n\t\t\t\t}<\/p>\n<p>\t\t\t\treturn new ComponentWithMethod();<br \/>\n\t\t}<br \/>\n}<br \/>\n[\/csharp]<\/p>\n<h3>Example Usage<\/h3>\n<p>You can do it the simplest way:<\/p>\n<p>[csharp]<br \/>\npublic class MyComponent : MonoBehaviour<br \/>\n{<br \/>\n\tpublic void FixedOnMouseDown()<br \/>\n\t{<br \/>\n\t\t\/\/ works exactly like OnMouseDown.<br \/>\n\t\t\/\/<br \/>\n\t\t\/\/ Except: it actually works.<br \/>\n\t}<br \/>\n}<br \/>\n[\/csharp]<\/p>\n<p>Alternatively, you can make the method return a bool. If &#8220;true&#8221; (the default) it will consume the event, and nothing else will receive the mouse click.<\/p>\n<p>If &#8220;false&#8221; &#8230; the click will be passed on to the next thing along the raycast. This is very useful for transparent objects.<\/p>\n<p>It&#8217;s also useful for objects that might have other context for whether they should &#8220;accept&#8221; the mouse-click (e.g. &#8220;if the player is too far away from the treasure chest, ignore the click&#8221;)<\/p>\n<p>[csharp]<br \/>\npublic class MyComponent : MonoBehaviour<br \/>\n{<br \/>\n\tpublic bool FixedOnMouseDown()<br \/>\n\t{<br \/>\n\t\t\/\/ Return &quot;false&quot; to allow other classes to see the mouse-click too<br \/>\n\t\t\/\/<br \/>\n\t\t\/\/ Except: it actually works.<\/p>\n<p>\t\tif( collider.isTrigger ) \/\/ e.g. use the collider&#8217;s isTrigger state!<br \/>\n\t\t\treturn false;<br \/>\n\t\telse<br \/>\n\t\t\treturn true;<br \/>\n\t}<br \/>\n}<br \/>\n[\/csharp]<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This one has annoyed me for years: Unity&#8217;s OnMouseDown has never really worked. (NB: it works perfectly if you make a scene with a single cube; but that is not a game. It breaks completely as soon as you add multiple objects, colliders, etc to a scene) The main reason to use a 3rd party [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[20,67,69],"tags":[],"_links":{"self":[{"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/posts\/3511"}],"collection":[{"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/comments?post=3511"}],"version-history":[{"count":6,"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/posts\/3511\/revisions"}],"predecessor-version":[{"id":3517,"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/posts\/3511\/revisions\/3517"}],"wp:attachment":[{"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/media?parent=3511"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/categories?post=3511"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/tags?post=3511"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}