Summary of methods for unity to detect surrounding objects

explain

There are many detection methods, and the key is to analyze when and what method is the most appropriate (with good effect and high performance)

1. Collision detection

(1) Conditions for collision detection:
Both have collision components, the moving object has rigid body components, and at least one collider is attached with a non dynamic rigid body.
(2) Collision detection method:
Just do what you want to do in these methods. These methods will be called automatically in the life cycle of the unit script, so we don't have to worry about it.

  • OnCollisionEnter is called when the current collader / rigidbody starts to encounter another rigidbody / collader.
void OnCollisionEnter(Collision collision)
  • Whenever the collader / rigidbody encounters another rigidbody / collader, OnCollisionStay will be called at each frame. Generally speaking, when a collider or rigid body encounters another rigid body or Collider, OnCollisionStay will be called at each frame until they leave without contact.
void OnCollisionStay(Collision collisionInfo)
  • OnCollisionExit is called when the current collider/rigidbody stops colliding with another rigidbody/collider.
void OnCollisionExit(Collision collisionInfo)

The parameters in these methods are the Collision that collides with the current collider. What the trigger detects is the collider that collides with the current collider. The Collision has Collider and rigidbody attributes, and there is a crucial attribute in the Collision: contacts, Collision point collection, type ContactPoint []. The ContactPoint class also has many common attributes and methods. Go to the document!!! We can get the first Collision point from contacts[0] Point, do some things there, such as the spark of Collision... Or by contacts[0] Normal gets the normal of the contact surface (the Collision direction is obtained).

2. Trigger detection

Trigger is an object with collider component and Is Trigger is checked. It has no collision effect and is only used to detect collision (intersection).
(1) Trigger condition:
Both have colliders, and at least one tick Trigger.
(2) Trigger method:
Just do what you want to do in these methods. These methods will be called automatically in the life cycle of the unit script, so we don't have to worry about it.

  • OnTriggerEnter is called when the collider enters the trigger.
void OnTriggerEnter(Collider other)
  • OnTriggerStay is called almost every frame whenever the collider enters the trigger from.
    Note: the OnTriggerStay function is based on a physical timer, so it may not run every frame.
    In other words, OnTriggerStay is at every time Fixeddeltatime runs on the time node, not time Run on the time node of deltatime
void OnTriggerStay(Collider other)
  • OnTriggerExit is called when the collider stops touching the trigger. That is, OnTriggerExit is called when the collider leaves the trigger.
void OnTriggerExit(Collider other)

Note that the parameter here is the Collider in contact with the current Collider.

3. Radiographic testing

The first two methods need us to have a good understanding of the physical engine in some detailed places to be more realistic. Setting the properties of rigid bodies has always been my most disgusting thing, so I would rather use the character controller than configure rigid bodies and colliders for the model myself (of course, this is only for characters, and it is very convenient to use the first two methods for terrain (dead objects).
Then sometimes we have to abandon the first two methods, although their event response methods are very fragrant. For example, fast-moving objects (such as bullets) may pass through within one frame, while the first two methods are detected once a frame. It is easy for objects to pass through but cannot be detected. This is that we must do ray detection for fast-moving objects.

(1) Classes / structures required for radiographic testing:

There are many ready-made properties and methods for us to do these things conveniently.

i.RaycastHit ray projection collision information:

Important attributes:

  • Collider hit the collider.
  • Distance the distance from the origin of the ray to the point of contact.
  • Normal ray touches the normal of the surface.
  • Point in the world coordinate space, the ray touches the contact point of the collider.

ii.Ray

  • Origin the origin of the ray.
  • Direction the direction of the ray.
  • Ray(Vector3 origin, Vector3 direction); Constructor.

(2) Many radiographic testing methods in Physics are used:

There are many different forms of radiographic testing in Physics. Here are some common examples:
Note: there will be many overloads in the following methods, but most of them are some differences in functionality and expression. See the official documents to select the appropriate overloads according to needs.

i. Line projection: applicable to directional detection with detection distance

public static bool Linecast(Vector3 start, Vector3 end, out RaycastHit hitInfo, int layerMask = DefaultRaycastLayers);
If the collision position between tmask and infolayer is true, it is used to return the information of intersection with any collision layer.

ii. Ray casting: drop a ray that can collide with all colliders in the scene and return the details of the collision.

public static bool Raycast(Vector3 origin, Vector3 direction, out RaycastHit hitInfo, float maxDistance = Mathf.Infinity, int layerMask = DefaultRaycastLayers, QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal);
parameter
origin is the world coordinate, the starting point of the ray.
Direction ray direction
hitInfo will contain more information about hitting the collider.
distance the length of the ray.
layermask select the projected layer mask.
queryTriggerInteraction specifies whether the query encounters a trigger.

iii. ray casting list: cast a ray in the scene and return all collisions. Note that sequence is not guaranteed.

public static RaycastHit[] RaycastAll(Ray ray, float maxDistance = Mathf.Infinity, int layerMask = DefaultRaycastLayers, QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal);
parameter
origin the starting point of the ray.
Direction the direction of the ray.
maxDistance is the maximum distance allowed to project from the ray.
layermask select the projected layer mask.
queryTriggerInteraction specifies whether the query encounters a trigger.

iv. spherical rays: all impactors returned to or in contact with the ball.

I often use it to detect all kinds of objects around me. For example, the object hit by casting skills, and the player is found in the enemy patrol area
public static Collider[] OverlapSphere(Vector3 position, float radius, int layerMask = AllLayers, QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal);
parameter
position the center of the sphere.
radius of the sphere.
layerMask selects the projected layer mask.
queryTriggerInteraction specifies whether the query encounters a trigger.

v. Spherical projection list

The difference between this and the above is that this returns all collision information scanned by the sphere. More powerful.
public static RaycastHit[] SphereCastAll(Vector3 origin, float radius, Vector3 direction, float maxDistance = Mathf.Infinity, int layerMask = DefaultRaycastLayers);
origin is the spherical center point at the beginning of the scan.
radius of the sphere.
Direction spherical scanning direction
distance the length of the scan.
layerMask scans only the colliders of the specified layer.

(3) Example:

Launch the bullet to see whether it hits the game object and what game object it hits. In fact, it has nothing to do with the bullet. We only need to do radiographic testing on the muzzle and choose the appropriate overload method according to different needs. Here we simply test: here are the bullets in the project (the starting position is the muzzle position)

/// <summary>
///Bullet, defining the common behavior of bullets
/// </summary>
public class Bullet : MonoBehaviour
{
    //Calculate the target point (using the ray detection mentioned earlier)
    protected RaycastHit hit;
    //Attack distance
    public float attackMaxDistance = 200f;
    //Which layers
    public LayerMask mask;
    //Target point
    private Vector3 target;
    //Moving speed
    public float moveSpeed = 200;
    //aggressivity
    [HideInInspector]//Hide in compiler
    public float atk;
    /// <summary>
    ///Calculate target point
    /// </summary>
    private void CalculateTargetPoint()
    {
        if(Physics.Raycast(transform.position, transform.forward,out hit,attackMaxDistance, mask))
        {
            target = hit.point;
        }
        else
        {
            target = transform.position + transform.forward * attackMaxDistance;
        }
    }
    private void Awake()
    {
        CalculateTargetPoint();
    }
    //move
    private void Update()
    {
        Movement();
        if ((transform.position - target).sqrMagnitude < 0.1f)
        {
            Destroy(this.gameObject);
            GenerateContactEffect();
        }
    }
    private void Movement()
    {
        transform.position = Vector3.MoveTowards(transform.position, target, moveSpeed * Time.deltaTime);
    }
    //Reach the target point: destroy and create relevant special effects (according to the notes of the target point (the information in radiographic testing includes collider component collider.tag))
    //Create relevant special effects (according to the notes of the target point (the information in radiographic testing includes collider component collider.tag))
    private void GenerateContactEffect()
    {
        if (hit.collider == null)//If you don't hit something, it won't work
        {
            return;
        }
        //switch (hit.collider.tag)
        //{
        //    case "":

        //        break;
        //}
        //Read through code when there are many resources (the resources must be placed in the Resources folder) [it will be replaced by the object pool in the future]
        GameObject prefabGo = Resources.Load<GameObject>("ContactEffects/Effects"+hit.collider.tag);
        if (prefabGo)
            //Create an asset (it should face the normal of the target point and move up a little in the direction of the normal)
            Instantiate(prefabGo, target+hit.normal*0.01f, Quaternion.LookRotation(hit.normal));
    }
}

4. Use code (such as label)

Many times, we have various requirements for the detection of surrounding objects. For example, in the attack selection, we only want to select the little monster within the attack range and closest to us, with a blood volume greater than 0 and the most gold coins... So it is not easy to use rays, and the ray consumption performance is much higher than that of finding the type of game object we want to select through the tag, so we need to write our own code. This is mainly reflected by the examples in the project:
eg: attack selection method of skill

namespace ARPGDemo.Skills
{
    /// <summary>
    ///Circular attack selection class: select the enemy in the circular area as the attack target
    /// </summary>
    public class CircleAttackSelector: IAttackSelector
    {
        /// <summary>
        ///Select target method
        /// </summary>
        ///< param name = "skilldata" > skill object. The target selection depends on it < / param >
        ///< param name = "transform" > reference point for selection (usually the protagonist himself (skill owner) < / param >
        /// <returns></returns>
        public GameObject[] SelectTarget(SkillData skillData, Transform ownerTransform)
        {
            //1. Find by ray: find in this way when the object has no tag mark
            //2. With tag tag, high performance can be found through tag
            //Here, use the ray search (the attack distance is the radius) and tag in the sector
            var colliders = Physics.OverlapSphere(ownerTransform.position, skillData.attackDistance);
            if (colliders == null || colliders.Length == 0)
            {
                return null;
            }
            //Find all objects marked XX (in attackTargetTags)
            //And alive (HP > 0)
            var allCanBeSelected = ArrayHelper.FindAll(colliders, 
                c => (Array.IndexOf(skillData.attackTargetTags, c.tag) >= 0) 
                && (c.gameObject.GetComponent<CharacterStatus>().HP > 0));
            if (allCanBeSelected == null || allCanBeSelected.Count == 0)
            {
                return null;
            }
            //Not at all. The list has a ToArray method
            //Because the parameters of the array helper class are arrays, and the return type of tmp from FindAll is List, it should be converted to array
            //Collider[] allCanBeSelected = new Collider[tmp.Count];
            //for (int i = 0; i < tmp.Count; i++)
            //{
            //    allCanBeSelected[i] = tmp[i];

            //}
            //Determine the return of single or multiple targets according to the type of skill attack
            switch (skillData.attackType)
            {
                case SkillAttackType.Group://All attacks are selected
                    return ArrayHelper.Select(allCanBeSelected.ToArray(), a => a.gameObject);
                    break;
                case SkillAttackType.Single://Choose the nearest one for a single attack
                    var collider = ArrayHelper.Min(allCanBeSelected.ToArray(), 
                        a => Vector3.Distance(ownerTransform.position, a.transform.position));
                    return new GameObject[] { collider.gameObject };
                    break;
            }
            return null;
        }

    }
}

Sector selection is limited by multiple angles:

			//Find another one within the range
            //And alive (HP > 0)
            //And have an angle
            var allCanBeSelected = allTargets.FindAll(
                a => Vector3.Distance(ownerTransform.position, a.transform.position) <= skillData.attackDistance
                && a.GetComponent<CharacterStatus>().HP > 0
                && TransformHelper.RangeFind(a.transform, ownerTransform, skillData.attackAngle));

This is a lambda expression, which is very convenient, but it mainly uses code to reflect the idea of detection.

5. Character controller

Character controllers make it easy for you to move under collision without dealing with rigid bodies.
This is my favorite, because he doesn't need to configure complex rigid body attributes and colliders for the character, but can detect the objects colliding with it through void oncontrolercolliderhit (controllercolliderhit hit).
ControllerColliderHit also has important attributes such as Collider, controller, normal, point and rigidbody to help us obtain the information of the collider. See the official manual for details.

Tags: Unity

Posted by warptwist on Wed, 18 May 2022 16:32:34 +0300