This is the code dump from my targeting system tutorial please check it out if you get the chance. It explains every line of code in extreme detail. I had to build a robust 3rd Person Targeting System for my current junior game which is Dark Souls like. I couldn’t find any useful tutorials on targeting systems so all of this code is from scratch completely. I did not use any code resources. I analyzed the targeting systems from Sekiro, Dark Souls 3, and Kingdom Hearts 2 in-game to figure out my goals.

/*******************************************************************/
/* \project: Spicy Dice
 * \author: Amir Azmi
 * \date: 11/13/2019
 * \brief: Locks onto the closest object when a key F is pressed and
 *         unlocks when F is pressed again, if the ai gets out of the 
 *         range of the camera collider, then targeting is broken free,
 *         if the camera gets out of range, then it also breaks free
 *         You can also cycle targets with the left and right arrow
 *         key within the m_Angle range
 *         
*/
/*******************************************************************/

using System.Linq; //Sorting for Orderby
using System.Collections.Generic;
using UnityEngine;
using Lovo3D.Input; //ExInput
using Lovo3D.Core;  //Player
using Lovo3D.Gameplay; //IAgent

public class TargetableOnCamera : MonoBehaviour
{
  public float m_Angle = 35.0f; //default for angle variable

  [HideInInspector] public GameObject m_ObjectClosestToCamera; //object your targeting
  [HideInInspector] public bool m_IsTargeting = false; //if targeting is true
  private readonly List<GameObject> m_CandidateTargets = new List<GameObject>(); //list of candidate game objects
  private bool m_TargetButton = false; //target button
  private bool m_AxisRight = false; //moving axis to the right
  private bool m_AxisLeft = false; //moving the axis to the left
  private int m_NumberOfTargetsWithinRange = 0; //number of targets within range
  private Animator m_Animator; //animator for palyer

  private void Start()
  {
    m_Animator = Player.Instance.GetComponent<Animator>();
  }

  private void Update()
  {
    //check if the targeting button has been pressed
    bool isDown = Input.GetKeyDown(KeyCode.F);

    //if candidate object happens to be null reset targeting
    if (m_ObjectClosestToCamera == null)
    {
      m_TargetButton = false;
    }

    //remove null objects in the list and decrement the counter
    // Could optimize through some onDelete event system, probably really not worth
    for (int i = m_CandidateTargets.Count - 1; i >= 0; --i)
    {
      if (m_CandidateTargets[i] == null || !m_CandidateTargets[i].activeInHierarchy)
      {
        m_CandidateTargets.RemoveAt(i);
        m_NumberOfTargetsWithinRange--;
      }
    }

    //id target button has been pressed and there are targets within the targeting radius
    if (isDown && m_NumberOfTargetsWithinRange > 0)
    {
      //if you want to sort by distance uncomment line below
      //List<GameObject> Sorted_List = m_CandidateTargets.OrderBy(go => (transform.position - go.transform.position).sqrMagnitude).ToList(); // sort objects in order as they enter

      //sorts objects by angle and stores it into the Sorted_List
      List<GameObject> Sorted_List = m_CandidateTargets.OrderBy(go =>
      {
        Vector3 target_direction = go.transform.position - Camera.main.transform.position; //get vector from camera to target
        var camera_forward = new Vector2(Camera.main.transform.forward.x, Camera.main.transform.forward.z); //convert camera forward direction into 2D vector
        var target_dir = new Vector2(target_direction.x, target_direction.z); //do the same with target direction
        float angle = Vector2.Angle(camera_forward, target_dir); //get the angle between the two vectors
        return angle;
      }).ToList(); //store the objects based off of the angle into the Sorted_List


      //copy objects into the main game_object list
      //remove objects that happen to die before selecting next target
      for (var i = 0; i < m_CandidateTargets.Count(); ++i)
      {
        m_CandidateTargets[i] = Sorted_List[i];

        if (!m_CandidateTargets[i].activeInHierarchy)
        {
          m_CandidateTargets.RemoveAt(i);
          m_NumberOfTargetsWithinRange--;
        }
      }

      GameObject old_object = m_ObjectClosestToCamera;  //current object to untarget

      UnTarget(old_object); //untarget old object shows to not show the indicator

      //Super cool thing to note,  "float angle = Vector2.Angle(camera_forward, target_dir);" sorts by abs(angle) aka unsigned so the first object you target is always the object you are most looking at
      m_ObjectClosestToCamera = m_CandidateTargets.First(); //set target as the target you are most looking at

      //target the new object
      if (old_object != m_ObjectClosestToCamera)
      {
        Target(m_ObjectClosestToCamera);  //show the indicator on the new object
      }

      m_TargetButton = !m_TargetButton; //this handles the unlocking / locking
    }

    //if I am targeting, there are candidate objects within my radius, and current target is not null and the object is alive aka in the scene
    if (m_TargetButton && m_NumberOfTargetsWithinRange > 0 && m_ObjectClosestToCamera != null && m_ObjectClosestToCamera.activeInHierarchy)
    {
      //gets the angle of the between the current game object and camera
      Vector3 target_direction = m_ObjectClosestToCamera.transform.position - Camera.main.transform.position;
      var camera_forward = new Vector2(Camera.main.transform.forward.x, Camera.main.transform.forward.z);
      var target_dir = new Vector2(target_direction.x, target_direction.z);
      float angle = Vector2.Angle(camera_forward, target_dir);

      //check if the object's angle is within the zone of targeting
      if (angle < Mathf.Abs(m_Angle))
      {
        m_IsTargeting = true; //set targeting to true

        if (Input.GetAxisRaw("Controller Right Stick X") > 0.0f || ExInput.GetButtonDown(UniversalCode.RightArrow)) //if the right stick was moved to the right
        {
          //List<GameObject> Sorted_List = m_CandidateTargets.OrderBy(go => (transform.position - go.transform.position).sqrMagnitude).ToList();

          if (m_AxisRight == false) //if axis initally was false
          {
            //sort objects in the list while targeting, yes you want to do this so if enemies move around it still keeps the list correct
            List<GameObject> Sorted_List = m_CandidateTargets.OrderBy(go =>
            {
              Vector3 target_dir_vec3 = go.transform.position - Camera.main.transform.position;
              var camera_forward_dir = new Vector2(Camera.main.transform.forward.x, Camera.main.transform.forward.z);
              var new_target_dir = new Vector2(target_dir_vec3.x, target_dir_vec3.z);
              float angle_from_sorted_list = Vector2.SignedAngle(camera_forward_dir, new_target_dir);
              return angle_from_sorted_list;
            }).ToList();

            //copy the objects from the sorted list into the main list
            for (var i = 0; i < m_CandidateTargets.Count(); ++i)
            {
              m_CandidateTargets[i] = Sorted_List[i];
            }

            //check if there is an object to the right
            if (m_CandidateTargets.IndexOf(m_ObjectClosestToCamera) - 1 >= 0)
            {
              //turn off current idicator
              UnTarget(m_ObjectClosestToCamera);

              //check its angle
              GameObject next_targeted_object = m_CandidateTargets[m_CandidateTargets.IndexOf(m_ObjectClosestToCamera) - 1];
              Vector3 target_direction_vec3 = next_targeted_object.transform.position - Camera.main.transform.position;
              var camera_forward_vec2 = new Vector2(Camera.main.transform.forward.x, Camera.main.transform.forward.z);
              var new_target_dir = new Vector2(target_direction_vec3.x, target_direction_vec3.z);
              float angle_between_vectors = Vector2.Angle(camera_forward_vec2, new_target_dir);

              //if the angle is within range of targeting angle
              if (angle_between_vectors < Mathf.Abs(m_Angle))
              {
                //set the new object as the target
                m_ObjectClosestToCamera = m_CandidateTargets[m_CandidateTargets.IndexOf(m_ObjectClosestToCamera) - 1];
              }

              //show the indicator of the target
              Target(m_ObjectClosestToCamera);

              //treat angle as button type
              m_AxisRight = !m_AxisRight;
            }
          }
        }
        else if (Input.GetAxisRaw("Controller Right Stick X") < 0.0f || ExInput.GetButtonDown(UniversalCode.LeftArrow))
        {
          //initially the leftAxis bool should be false for togglable logic       
          if (m_AxisLeft == false)
          {
            //sort objects by the SignedAngle  for the same reasons statef for right cycling
            List<GameObject> Sorted_List = m_CandidateTargets.OrderBy(go =>
            {
              //direction from  Camera to the target object indicated by go
              Vector3 targetDir = go.transform.position - Camera.main.transform.position;

              //convert Camera direction into 2D vector
              var cameraForward = new Vector2(Camera.main.transform.forward.x, Camera.main.transform.forward.z);
              //convert target direction into the 2D vector
              var new_target_dir = new Vector2(targetDir.x, targetDir.z);

              // calculate angle between the two vectors
              float angle_between_vectors = Vector2.SignedAngle(cameraForward, new_target_dir);
              return angle_between_vectors;
            }).ToList();

            //put the sorted list into the candidate list
            for (var i = 0; i < m_CandidateTargets.Count(); ++i)
            {
              m_CandidateTargets[i] = Sorted_List[i];
            }

            //notice here how I check if there is a next valid object in the list
            if (m_CandidateTargets.IndexOf(m_ObjectClosestToCamera) + 1 < m_CandidateTargets.Count)
            {
              //turn off indicator
              UnTarget(m_ObjectClosestToCamera);

              //store this next object into a gameObject 
              GameObject nextTargetedObject = m_CandidateTargets[m_CandidateTargets.IndexOf(m_ObjectClosestToCamera) + 1];

              //calcuate the angle to see if the next object is within defined targeting range
              Vector3 targeDir = nextTargetedObject.transform.position - Camera.main.transform.position;

              var cameraFor = new Vector2(Camera.main.transform.forward.x, Camera.main.transform.forward.z);

              var newtarget_dir = new Vector2(targeDir.x, targeDir.z);
              float anglex = Vector2.Angle(cameraFor, newtarget_dir);

              //if the next targeted object is a valid target
              if (anglex < Mathf.Abs(m_Angle))
              {
                // set the m_ObjectClosestToCamera as the next object in the list
                m_ObjectClosestToCamera = m_CandidateTargets[m_CandidateTargets.IndexOf(m_ObjectClosestToCamera) + 1];
              }

              //tell indicator what to do
              Target(m_ObjectClosestToCamera);

              //for togglable logic
              m_AxisLeft = !m_AxisLeft;
            }
          }
        }
        else
        {
          //once the axis is 0 it means the locking has been reset
          m_AxisRight = false;
          m_AxisLeft = false;
        }
      }
      else
      {
        //reset the target button
        m_TargetButton = false;
      }
    }
    else
    {
      //set targeting to false here
      m_IsTargeting = false;

      //if th object is not null here make it null and untarget
      if (m_ObjectClosestToCamera != null)
      {
        UnTarget(m_ObjectClosestToCamera);

        m_ObjectClosestToCamera = null;
      }
    }

    //Tell the ANIMATOR
    m_Animator.SetBool("TargetLocked", m_IsTargeting);
  }

  private void OnTriggerEnter(Collider other)
  {
    if (other.tag == "Targetable")
    {
      ++m_NumberOfTargetsWithinRange;
      m_CandidateTargets.Add(other.gameObject);
    }
  }

  private void OnTriggerExit(Collider other)
  {
    if (other.tag == "Targetable")
    {
      m_CandidateTargets.Remove(other.gameObject);
      --m_NumberOfTargetsWithinRange;
    }
  }

  #region Private

  private void Target(GameObject enemy)
  {
    if (enemy != null)
    {
      var agent = enemy.GetComponentInParent<IAgent>();

      if (agent != null)
      {
        agent.OnTargeted(GetComponent<IAgent>());
        agent.OnDie.AddListener(AgentDies);
      }
    }
  }

  private void UnTarget(GameObject enemy)
  {
    if (enemy != null)
    {
      var agent = enemy.GetComponentInParent<IAgent>();

      if (agent != null)
      {
        agent.OnUntargeted(GetComponent<IAgent>());
        agent.OnDie.RemoveListener(AgentDies);
      }
    }
  }

  private void AgentDies(IAgent agent)
  {
    m_CandidateTargets.Remove(agent.gameObject.gameObject);
    UnTarget(agent.gameObject);
    m_ObjectClosestToCamera = null;
  }

  #endregion
}