Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question finding nearest armature bone

Discussion in 'Scripting' started by Bannanaking3, Aug 21, 2023.

  1. Bannanaking3

    Bannanaking3

    Joined:
    Jun 21, 2022
    Posts:
    109
    Alright, this is a longer one, buckle up.

    I have an enemy, animated with an armature. when I shoot this enemy I want blood to appear on him.
    in order to do this, I am using a decal. for the decal to stick to the enemy properly, I need to stick it to specific bones on the armature.

    and herein lies the problem: I am horribly inexperienced with code.

    I know what I need my code to do, but I'm not good enough at searching documentation to figure out how to do it, or to know the best method.

    I need a script that can generate a list of bones in the armature at start, and I believe this is done by iterating through the children of the armature, although there might be other ways to do this. That might be too complex because the bones aren't all parented directly to the armature but to each other with only two or three bones directly parented to the armature. I then need to, when the enemy is hit with a raycast, find the nearest bone to the hit.point of the raycast, and parent my decal to it, I think by iterating through the bones and checking the distance to each and choosing the smallest distance.

    I think there is a way to check within a certain area of something for a component, so I could maybe add a blank script to the bones and then do that check. I'm not sure.

    does anyone have any ideas?
     
  2. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,326
    Yep, there's actually an easy way to do this:
    Code (csharp):
    1.  
    2. void ListAddTransformRecursive (List<Transform> list, Transform startingTransform)
    3. {
    4.      list.Add(startingTransform);
    5.      foreach (Transform child in startingTransform)
    6.      {
    7.         ListAddTransformRecursive(list, child);
    8.      }
    9. }
    10.  
    All you need to do is call the method once with an empty list and your root bone and it will initiate a cascade of function calls which will eventually add all of the transforms to the list.

    you can simply use
    https://docs.unity3d.com/ScriptReference/Vector3.Distance.html
    and find the smallest distance.
     
    wideeyenow_unity likes this.
  3. wideeyenow_unity

    wideeyenow_unity

    Joined:
    Oct 7, 2020
    Posts:
    728
    Well each bone is a technical "GameObject", so they can be "found" in a sense. However OverlapSphere() will only return colliders, and the bones in a model are just standard objects with transforms(no colliders).

    So I can think of some ways of doing this, including adding a collider(small sphere/box) to each bone object, or make a list of all the bones transforms within parent script and check by distance of "Ray.hit".
     
  4. wideeyenow_unity

    wideeyenow_unity

    Joined:
    Oct 7, 2020
    Posts:
    728
    But as I mentioned in the other post, it would be harder to setup initially, but easy to code later, would be the separating each mesh section in blender, then using a simplified version of that mesh as a collider itself, so then rayhit would just simply hit and place the blood spot where it is.

    So I guess it all depends on which way you want to go, and where you want to put more effort(model/code editing)
     
  5. Bannanaking3

    Bannanaking3

    Joined:
    Jun 21, 2022
    Posts:
    109
    yeah. I'm looking into it.

    It seems to be(seems being the operative word) working so far, but finding the closest bone seems to not be working.

    this is currently my code for that, but it doesn't seem to be correctly finding the closest bone. how do check the bone once it's cycled through all the bones?

    Code (CSharp):
    1.  public void FindNearest(Vector3 point)
    2.     {
    3.         float smallDist = 10;
    4.         GameObject closeBone;
    5.         foreach (Transform bone in bones)
    6.         {
    7. //checks distance per bone
    8.             float dist = Vector3.Distance(bone.position, point);
    9. //if its closer than the previous it updates the bone
    10.             if (dist <= smallDist)
    11.             {
    12.                 dist = smallDist;
    13.                 closeBone = bone.gameObject;
    14.             }
    15.         }
    16.  
    17.         Debug.Log(closeBone);
    18.     }
     
  6. wideeyenow_unity

    wideeyenow_unity

    Joined:
    Oct 7, 2020
    Posts:
    728
    You had smallDist backwards, but this might be more what you're looking for:
    Code (CSharp):
    1. public GameObject FindNearest(Vector3 point)
    2. {
    3.     float smallDist = 10;
    4.     int index;
    5.     for (int i = 0; i < bones.Count; i++)
    6.     {
    7.         float dist = Vector3.Distance(bone.transform.position, point);
    8.         if (dist < smallDist) { smallDist = dist; index = i; }
    9.     }
    10.     return bones[index];
    11. }
     
  7. wideeyenow_unity

    wideeyenow_unity

    Joined:
    Oct 7, 2020
    Posts:
    728
    oof, just noticed you wanted transform, not gameObject, my bad, just swap those out
     
  8. Bannanaking3

    Bannanaking3

    Joined:
    Jun 21, 2022
    Posts:
    109
    Ok I just re-tested that code and it is technically working but it's returning the same bone regardless of where I hit.

    which is fundamentally wrong.
     
  9. wideeyenow_unity

    wideeyenow_unity

    Joined:
    Oct 7, 2020
    Posts:
    728
    Again my bad, I type before I think, lol:
    Code (CSharp):
    1. float dist = Vector3.Distance(bone[i].position, point);
    forgot to add the index parameter for the list
     
  10. wideeyenow_unity

    wideeyenow_unity

    Joined:
    Oct 7, 2020
    Posts:
    728
    Should be this:
    Code (CSharp):
    1. public Transform FindNearest(Vector3 point)
    2. {
    3.     float smallDist = 10;
    4.     int index;
    5.     for (int i = 0; i < bones.Count; i++)
    6.     {
    7.         float dist = Vector3.Distance(bone[i].position, point);
    8.         if (dist < smallDist) { smallDist = dist; index = i; }
    9.     }
    10.     return bones[index];
    11. }
     
  11. Bannanaking3

    Bannanaking3

    Joined:
    Jun 21, 2022
    Posts:
    109
    ah, I see. my previous response was sent before I reloaded the page, so I didn't see your replies. sorry if it was a little callous sounding.
     
    wideeyenow_unity likes this.
  12. Bannanaking3

    Bannanaking3

    Joined:
    Jun 21, 2022
    Posts:
    109
    Oh my GOD it finally works. You all are saints among men
     
  13. Bannanaking3

    Bannanaking3

    Joined:
    Jun 21, 2022
    Posts:
    109
    I'll post final scripts for everything once I work out a kink with the size of the decal
     
  14. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,326
    Are you sure? Put something like this inside your loop:
    Code (csharp):
    1.  
    2. Debug.Log("Point: "+point+" | "+bone.name+": "+bone.position);
    3.  
    That way you can see everything.
     
  15. Bannanaking3

    Bannanaking3

    Joined:
    Jun 21, 2022
    Posts:
    109
    alright, here are the scripts I used. I chopped them down a bit because my shoot controller is a little bit of a hacked together mess. Let me know if there are any issues or questions, I'm happy to help however I can.
    FYI, I commented the crap out of them for any newer coders (such as myself, to some extent) so they can understand it a bit easier, since this is pretty long.

    Enjoy!


    VVShooting ControllerVV
    (there are some variables you may have to declare on your own if you use this whole chunk. the important part is clearly labeled, and you should be able to use that pretty much anywhere else. if you'd like to use this specific controller, it is a somewhat modified version of the one from the brackets shooting tutorial on YouTube. Look up Brackeys Shooting tutorial on YouTube and you should find it pretty quickly)
    Code (CSharp):
    1.   RaycastHit hit;
    2.    
    3.             if (Physics.Raycast(fpsCam.transform.position, fpsCam.transform.forward, out hit, range, 9))
    4.             {
    5.                 //checks for the health script. insert yours here
    6.                 EnemyHealth health = hit.transform.GetComponent<EnemyHealth>();
    7.                 if (health != null)
    8.                 {
    9.                     //damage function. your's might be different
    10.                     health.TakeDamage(damage);
    11.  
    12.                     //particles
    13.                     GameObject i = Instantiate(impact, hit.point , Quaternion.LookRotation(hit.normal));
    14.  
    15.                     Destroy(i, 1);
    16.  
    17.                     //*****IMPORTANT PART***** the first line instantiates the decal. if you use something other than a decal ignore the second and third lines.
    18.                     //note that the transform(second argument) is the armature bone's ("B")  "find nearest" function.
    19.                     //I have it referenced through the enemy health script because It's easier than finding multiple components across complex children/parent relationships
    20.                     GameObject m_DecalProjectorObject = Instantiate(blood, health.B.FindNearest(hit.point));
    21.                     DecalProjector m_DecalProjectorComponent = m_DecalProjectorObject.GetComponent<DecalProjector>();
    22.                     m_DecalProjectorComponent.material = new Material(m_DecalProjectorComponent.material);
    23.                 }
    24.  
    25.                 if (hit.rigidbody != null)
    26.                 {
    27.                     hit.rigidbody.isKinematic = false;
    28.                     hit.rigidbody.AddForce(-hit.normal * impactForce);
    29.                 }
    30.             }
    VV Armature ScriptVV
    (You should be able to slap this on any old armature and have it work, although you'll need to reference the FindNearest bit in another script.)
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Rendering.HighDefinition;
    5. using static UnityEditor.Progress;
    6.  
    7. public class bone : MonoBehaviour
    8. {
    9.     //This is the armature script. I have it on the "armature" object that is created by default when you import an armature.
    10.     //I'm not sure if that's any different when using import methods other than what I used.
    11.     public List<Transform> bones;
    12.     public GameObject blood;
    13.     int index;
    14.  
    15.  
    16.     // Start is called before the first frame update
    17.     void Start()
    18.     {
    19.         //creates a list of bones at start
    20.         ListAddTransformRecursive(bones, transform);
    21.     }
    22.  
    23.     void ListAddTransformRecursive(List<Transform> list, Transform startingTransform)
    24.     {
    25.         list.Add(startingTransform);
    26.         foreach (Transform child in startingTransform)
    27.         {
    28.             ListAddTransformRecursive(list, child);
    29.         }
    30.     }
    31.  
    32.  
    33.     //*****IMPORTANT PART*****
    34.     //this is what is referenced in the shooting script.
    35.     //it checks the previously created list of bones to determine the closest to the raycast point from earlier.
    36.     //everytime it cycles through it checks if it is a smaller distance than the previous bone
    37.     //if it is, it sets "index" (see variable declarations above) to the current bone
    38.     //I moved index up above because unity was throwing an unassigned variable error when it was in this function. oh well.
    39.     public Transform FindNearest(Vector3 point)
    40.     {
    41.         //large default distance to check against
    42.         float smallDist = 10;
    43.         for (int i = 0; i < bones.Count; i++)
    44.         {
    45.             float dist = Vector3.Distance(bones[i].transform.position, point);
    46.             if (dist < smallDist) { smallDist = dist; index = i; }
    47.         }
    48.         return bones[index];
    49.  
    50.     }
    51.  
    52.     }
    53.  
    Thank you all so much for your help. again, let me know if you have any questions, I'd be happy to help as much as I can.
     
  16. Bannanaking3

    Bannanaking3

    Joined:
    Jun 21, 2022
    Posts:
    109
    It absolutely was, but that was my fault. I'm not sure what the issue was but wideeyenow_unity's code fixed it immediately so I'm not worried about it
     
  17. wideeyenow_unity

    wideeyenow_unity

    Joined:
    Oct 7, 2020
    Posts:
    728
    yeah his original issue was
    Code (CSharp):
    1. if (dist <= smallDist)
    2.    dist = smallDist;
    3. // should have been
    4. if (dist <= smallDist)
    5.    smallDist = dist;
    I don't know why I always jump for a for loop over a foreach loop, I'm pretty sure they run at the same speed. But my suggestion just made more sense in head(which isn't saying much) :rolleyes:
     
  18. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    1,834
    Maybe I am missing something, but didn't you just re-implement
    Transform[] bones = topBone.GetComponentsInChildren<Transform>();
    ?
     
  19. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    1,834
    Almost all searches for the single nearest object are going to follow this basic pattern.

    Code (CSharp):
    1. Thing FindClosestThing(Vector3 position)
    2. {
    3.     Thing best = null;
    4.     float closestSq = float.PositiveInfinity;
    5.  
    6.     foreach (Thing thing in SomeIteratorOfThings())
    7.     {
    8.         if (!CheapAppropriateThingTest(thing))
    9.             continue;
    10.  
    11.         Vector3 delta = (thing.transform.position - position);
    12.         float dSq = delta.sqrMagnitude;
    13.         if (dSq >= closestSq)
    14.             continue;
    15.  
    16.         if (!ComplicatedAppropriateThingTest(thing))
    17.             continue;
    18.  
    19.         best = thing;
    20.         closestSq = dSq;
    21.     }
    22.  
    23.     return best;
    24. }
    The CheapAppropriateThingTest() would be for trivial checks like which team the character belongs to, if it is on a valid layer, or if a field is null.

    The ComplicatedAppropriateThingTest() would be for more cumbersome checks like whether it has a specific component on it, if it's the child of a known object, or has a tag that is known to a list of valid tags. It could also use the dSq calculation to see if the object is within a maximum range or not.

    You want to order your appropriate tests so that the most likely exclusion or cheapest exclusion rules are checked first.
     
  20. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,326
    Oh, certainly. Sometimes I forget that transform is just a component too.