I can give you my example of how I’ve been doing things, after trying to organize things as best as possible I’ve come to this sort of structure -
Player,Enemy, Combat, Ability, AbilityProjectile, EnemyAI
Player.cs contains generic control of the player, movement, controls, etc, with references to combat and other components
Combat.cs is a class that any entity that can possibly be involved in combat uses, containing health, damage, other stats, as well as functions like ‘useAttack(str/int)/useAbility(str/int)’, ‘addDamage(int)’, ‘isAlive()’, etc. Combat also contains a list of abilities that can be spells or any type of attacks used by the player or enemies or items
Ability.cs contains definition of abilities/skills, and has stuff like ‘abilityType’, ‘abilityPower’, ‘abilityDuration’, ‘cooldownRemaining, cooldownMax’ etc, and contains CoRoutines useAbility() that will use the ability
AbilityProjectile.cs is generated/instantiated any time an ability is used (whether its actually melee or projectile), and its collision is used to detect if an attack hits or not, upon collision ‘applyAbilityEffects(enemyCombatCs,baseAbility)’ is called upon the collided object’s script
Enemy.cs contains generic control of the enemy, references to combat.cs and enemyAI.cs and everything is updated from update()
enemyAI.cs contains AI parameters (aggressiveness, patrolling, passive, patrol radius/paths, etc.) and functions updateAI that will update the enemy’s movement and attack based on its current AI states/commands that are continuously updated, but updateAI is called thru enemy.cs’s reference to this AIclass
This system is not perfect but it gives a general idea of how things can work