2
\$\begingroup\$

I'm working on a 2D implementation of Free Cell in Unity 4.6.1, and I'm having trouble getting my click-detection code working consistently. The code below should be called every time a card is clicked (they all have BoxCollider2D components) but it doesn't always happen.

// Handle Clicks on Card
void OnMouseUpAsButton()
{
    print ("test");

    int cardLayerMask = LayerMask.GetMask("Cards");

    RaycastHit2D[] hit = Physics2D.RaycastAll(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero, Mathf.Infinity, cardLayerMask);

    print ("Hit length: " + hit.Length.ToString());

    if (hit.Length > 0)
    {
        print ("Hit");

    }
}

Even the "Test" print statement doesn't get called consistently, so it's not a problem with the raycast. If it manages to print the test, the raycast always works.

Each time the game runs, the cards get dealt onto the playing field randomly into stacks. On each run, some subset of the cards don't call the above code. The number of cards that work correctly varies, as does their position on the field. It's not consistent with any particular card.

I'm wondering if maybe there's an issue with the BoxCollider2D instances overlapping. The other colliders in the scene, like the one on the "table", always trigger OnMouseUpAsButton() successfully.

I need to be able to register clicks on every card, so I need to find a way to make this work.

Have any of you run into a similar issue before? If not, is there a way for me to work around this? Maybe a different way to detect the clicks on the cards?

Here's a screenshot of an example card from the inspector. Aside from transform position & script property values, all of them are identical (from a prefab):

Screenshot of Inspector showing Card components, including BoxCollider2D

Update w/ Solution from Accepted Answer

Using @Byte56's solution, I was able to take care of this pretty easily. I've included sample code below.

Note that to get the sample to work, the "top" object has to have the highest transform.position.z out of all the objects the raycast hits. I also put all the possible hit objects into their own layers so I can trigger different behavior based on what type of object is clicked.

// Handle clicks
    if (Input.GetMouseButtonDown(0))
    {
        // Cast a ray, get everything it hits
        RaycastHit2D[] hit = Physics2D.RaycastAll(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero, Mathf.Infinity);

        if (hit.Length > 0)
        {
            // Grab the top hit GameObject
            GameObject lastHitObject = hit[hit.Length - 1].collider.gameObject;

            // Determine layer of last hit object, take next steps as appropriate
            switch (LayerMask.LayerToName(lastHitObject.layer))
            {
            case "Cards":
                print ("Hit card!");
                break;
            case "Table":
                print ("Hit Table!");
                break;
            case "Cells":
                print ("Hit cell!");
                break;
            }
        }
    }
\$\endgroup\$
  • \$\begingroup\$ I'm not sure if this fires automatically somehow from an event, but do you have code somewhere that checks for clicks that's in the fixedupdate loop instead of the update loop? \$\endgroup\$ – ssb Feb 11 '15 at 13:13
  • \$\begingroup\$ OnMouseUpAsButton() fires automatically when a collider is clicked and the button is released over the same collider it was pressed on. It's not something you have to check: docs.unity3d.com/ScriptReference/… \$\endgroup\$ – Ben Wyatt Feb 11 '15 at 13:14
4
\$\begingroup\$

It's hard to know exactly what's happening without hands on but:

  1. OnMouseUpAsButton is only ever fired after Unity has performed its own successful ray cast (this happens in the background, during each frame so that Unity knows which objects to call the various OnMouse???? methods on).
  2. OnMouseUp() is called when releasing the mouse button over a collider. OnMouseUpAsButton() is called when releasing the mouse button over the same collider that you started the click on. If your colliders are stacked in the same 2D plane, it's entirely possible the ray cast is hitting different objects on the down and the up. This would explain the "sometimes" part of your problem, as your code only triggers when it happens to get both the down and the up.

I recently answered another question here that describes how to handle mouse input using the OnMouse??? methods.

I might also recommend not using those methods and just having an input handler script that inside it's Update method is just watching for mouse clicks and casts its own ray to choose which object has been clicked. This will not only give you more control over which object is actually selected (perhaps getting all hit objects and choosing the one you've marked as "on top"), but it'll put all of your input handling in one place (making it easier than spreading out all the OnMouse???? events for each card object).

\$\endgroup\$
  • \$\begingroup\$ Thanks. I've upvoted now, and I'll accept once I've had a chance to verify your solution worked (though I'm pretty certain it will). \$\endgroup\$ – Ben Wyatt Feb 11 '15 at 14:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.