Here's a look at how I code through an NPC class I wrote for my 2024 gamejam submission 'Dancing Mania'. Players need to perform a combo to recruit NPCs who proceed to circle and dance around the player. Two classes inherit from this; aggressive attackers who chase the player and special NPCs who have dialogue. 
public class scr_npc : MonoBehaviour
    {
        //Variables for performing combos
        [SerializeField] protected int comboIndex;
        Combo comboVal;
        public SpriteRenderer icon;
    
        //Designate the movement modes of the NPC
        public enum Movement
        {
            Following,
            Dying,
            None
        }
        private Movement currentMovement = Movement.None;
    
        //Movement variables, a random value is selected at random intervals
        [SerializeField, Range(0.1f, 0.25f)]
        public float maxSpeed = 0.2f;
        [SerializeField, Range(0.01f, 0.1f)]
        protected float minSpeed = 0.05f;
        [SerializeField] protected float maxVar; //Variable variance!
        protected (float, float) var;
        protected float currentSpeed;
        protected Vector3 target; //Point the NPC is moving towards
        int depth;
    
        //References to other objects
        protected GameObject p;
        protected scr_player player;
        protected Transform player_pos;
    
        //References to other components on the NPC
        public SpriteRenderer sprite;
        public SpriteRenderer shadow;
    
        //Variables used in animating the sprite's bobbing animation
        public Transform iconTrans;
        public Transform spriteTrans;
        protected float aniSpeed;
        [SerializeField] protected float pointToReach;
        protected float baseX;
        protected float baseY;
        protected int upOrDown = 1;
    
        //Variables used in animating the sprite's frames
        [SerializeField] float maxAniSpeed;
        protected int time = 0;
        public Animator animator;
    
        //Getters and setters
        public int Cv { get => comboIndex; set => comboIndex = value; }
        public Movement CurrentMovement { get => currentMovement; set => currentMovement = value; }
    
        void Start()
        {
            GenerateRecruitCombo();
            InitialiseVariables();
        }
    
        //Generate a combo needed to recruit this npc within the range of possible combos 
        //the player will have unlocked at this point
        private void GenerateRecruitCombo()
        {
            if (comboIndex >= 0)
            {
                comboIndex = Random.Range(0, Cv);
                comboVal = db_combo.findCombo(comboIndex);
                icon.sprite = comboVal.Image;
    
                //Chance to flip the sprite around
                if (Random.Range(0, 2) == 1)
                {
                    transform.localScale = new Vector2(-1, 1);
                    iconTrans.localScale = new Vector2(-1, 1);
                }
            }
        }
    
        private void InitialiseVariables()
        {
            //Get player variables
            p = GameObject.FindWithTag("player");
            player = p.GetComponent<scr_player>();
            player_pos = p.GetComponent<Transform>();
    
            //Only the chaser npc needs this
            if (GetType() == typeof(scr_chaser_npc))
            {
                AIDestinationSetter dest = GetComponent<AIDestinationSetter>();
    
                if (dest != null)
                {
                    dest.target = player_pos;
                }
            }
    
            //Initial variables
            baseX = spriteTrans.localPosition.x;
            baseY = spriteTrans.localPosition.y;
            aniSpeed = maxAniSpeed;
        }
    
        void Update()
        {
            SetMovement();
            SetLayerOrder();
        }
    
        private void SetMovement()
        {
            switch (CurrentMovement)
            {
                case Movement.Dying: break;
                case Movement.Following:
                    target = new Vector3(player_pos.position.x + var.Item1, player_pos.position.y + var.Item2, 0);
    
                    if (target == transform.position)
                    {
                        UpdateMovement();
                    }
                    else
                    {
                        transform.position = Vector2.MoveTowards(transform.position, target, currentSpeed);
                    }
    
                    BobSprite();
                    break;
                case Movement.None:
                    BobSprite();
                    break;
            }
        }
    
        //Update the order in layer
        private void SetLayerOrder()
        {
            depth = Mathf.RoundToInt(-shadow.transform.position.y * 100f);
            sprite.sortingOrder = depth;
            shadow.sortingOrder = depth;
        }
    
        //Update the movement by providing a random speed and a random direction
        public void UpdateMovement()
        {
            currentSpeed = Random.Range(minSpeed, maxSpeed);
            float inc = player.CurrentIncrease;
    
            var = (Random.Range(-maxVar * inc, maxVar * inc), Random.Range(-maxVar * inc, maxVar * inc));
        }
    
        //Triggered after being recruited
        public void BeginFollowing()
        {
            CurrentMovement = Movement.Following;
            Destroy(icon);
            UpdateMovement();
    
            //Only special NPCs have a dance animation
            if (animator != null)
            {
                animator.enabled = true;
            }
        }
    
        //Only performs code for the chaser npc inherited class
        protected virtual void BobSprite() { }
    
        //Collision properties
        //Triggers when this npc enters the players aura, allowing them to be recruited 
        public void OnTriggerEnter2D(Collider2D collision)
        {
            if ((collision.isTrigger) && (CurrentMovement == Movement.None))
            {
                player.addToNPCList(this);
            }
        }
    
        //Triggers when this npc exits the players aura
        public void OnTriggerExit2D(Collider2D collision)
        {
            if ((collision.isTrigger) && (CurrentMovement == Movement.None))
            {
                player.removeFromNPCList(this);
            }
        }
    }