Search code examples
c#unity-game-engineraycasting3d-model

Unity3d Select Object nearest to click no colliders


I have a Mixamo Model that I'm using for development. This model is fully rigged with a skeleton. I'm trying to create a function that will allow me to select a piece of the skeleton with out having a collider.

This Current Script only works with a collider,

public void RegisterSelectControls()
{
    if (Input.GetMouseButtonDown(0))
    {
        var hit = new RaycastHit();
        var ray = Camera.main.ScreenPointToRay(Input.mousePosition);

        if (Physics.Raycast(ray, out hit, 100.0f))
        {
            var objectHitName = hit.collider.transform.name;
            Debug.Log(objectHitName);
        }
    }
}

Is there a way to detect which part of the skeleton was click, or the nearest part of the skeleton to the click?

I do not want to add Colliders to every body part manually this would be too excessive and redundant, and if I choose to use a different model I would have to modify the box colliders, I would like to do this with out specifying a box collider

Edit

I cannot add colliders to the models, in the future I intend to support allowing a user to upload a model, in which it needs to naturally be able to select parts of the spine to interact with, without the user setting boudaries for the components of the spine


Solution

  • For anyone else looking how to do this it's actually semi simple. Throw this function in an update loop and have the 'SpineRoot' be equal to the root node of all of the objects you want to check.

    public void RegisterSelectControls()
    {
        if (Input.GetMouseButtonDown(0))
        {
            var clickLocation = Input.mousePosition;
    
            Transform closestBone = null;
    
            this.MinimumRecursiveBoneDistance(clickLocation, SpineRoot, out closestBone);
    
            if(null != closestBone)
            {
                Debug.Log(closestBone.gameObject.name);
            }
        }
    }
    
    public float MinimumRecursiveBoneDistance(Vector3 clickLocation, Transform root, out Transform closestBone)
    {
        float minimumDistance = float.MaxValue;
        closestBone = null;
        foreach (Transform bone in root)
        {
    
            var boneScreenPoint = Camera.main.WorldToScreenPoint(bone.position);
            var distance = Math.Abs(Vector3.Distance(clickLocation, boneScreenPoint));
    
            if (distance < minimumDistance)
            {
                minimumDistance = distance;
                closestBone = bone;
            }
    
            if (bone.childCount > 0)
            {
                Transform closestChildBone;
                var minimumChildDistance = this.MinimumRecursiveBoneDistance(clickLocation, bone, out closestChildBone);
                if(minimumChildDistance < minimumDistance)
                {
                    minimumDistance = minimumChildDistance;
                    closestBone = closestChildBone;
                }
            }
    
        }
    
        return minimumDistance;
    }
    

    This code can use some refactoring, this was just a proof of concept. This will work for purely nearest. However this can yield a potential bug, If a clicks on an object, and the center of that object is further away from the click that the center of another object you will select the other object.