SkillManager.cs

public class SkillManager : MonoBehaviour
{
    [Header("SO")]
    [SerializeField] private SkillEventChannelSO skillEventChannel;
    [SerializeField] private PlayerEquipSkillSO equipSkillSO;

    [Header("Folder")]
    [SerializeField] private Transform skillFolder;

    [Header("Pool")]
    [SerializeField] private SkillPool skillPool;

    private SkillQueue skillQueue = new();
    private bool isSkillSpawnable = false;
    private float spawnTimer = 0;
    private const float skillTerm = 1f;

    private void OnEnable()
    {
        skillEventChannel.OnFinishSkillEvent += SkillRelease;
    }

    private void OnDisable()
    {
        skillEventChannel.OnFinishSkillEvent -= SkillRelease;
    }

    private void Start() => Setup();
    private void Setup()
    {
        for (int i = 0; i < equipSkillSO.GetEquipCount(); i++)
        {
            SkillBookSO skill = equipSkillSO.equipSkill[i];
            skillPool.InitPool(skill.skillName, skill.skillPrefab);
        }
    }

		// 스킬 스폰 요청
    private void SkillSpawn(SkillBookSO skillBook, Vector3 skillPosition)
    {
        SkillController skill = skillPool.Get(skillBook.skillName);
        skill.Setup(skillBook);
        skill.transform.SetParent(skillFolder);
        skill.transform.position = skillPosition;

        // 스킬 큐가 비어있다면
        if (skillQueue.IsEmptyQueue() && isSkillSpawnable == true)
        {
            InitSpawnTimer();
            skill.Play();
        }
        else
            skillQueue.Push(skill);
    }

		// 스킬 릴리스
    private void SkillRelease(SkillBookSO skillBook, SkillController skill) => skillPool.Release(skillBook.skillName, skill);

    private void ExecuteSkillQueue()
    {
        // 스킬 큐가 존재한다면
        if (!skillQueue.IsEmptyQueue())
        {
            InitSpawnTimer();
            SkillController nextSkill = skillQueue.Pop();
            nextSkill.Play();
        }
    }

		// Auto
    private void InitSpawnTimer() => spawnTimer = 0;
    
    private void Update()
    {
        spawnTimer += Time.deltaTime;
        isSkillSpawnable = spawnTimer >= skillTerm ? true : false;
        if (isSkillSpawnable)
            ExecuteSkillQueue();
    }
}

SkillEventChannelSO.cs

[CreateAssetMenu(menuName = "MySO/EventChannel/SkillEventChannerlSO", fileName = "SkillEventChannerlSO")]
public class SkillEventChannelSO : ScriptableObject
{
    public Action<SkillBookSO> OnSpawnSkillEvent;
    public Action<SkillBookSO, SkillController> OnFinishSkillEvent;

    public void SpawnSkill(SkillBookSO skillBook) => OnSpawnSkillEvent?.Invoke(skillBook);
    public void FinishSkill(SkillBookSO skillBook, SkillController skill) => OnFinishSkillEvent?.Invoke(skillBook, skill);
}

PlayerEquipSkillSO.cs

[CreateAssetMenu(menuName = "MySO/Data/Player/PlayerEquipSkillSO", fileName = "PlayerEquipSkillSO")]
public class PlayerEquipSkillSO : ScriptableObject
{
    [field: SerializeField] public List<SkillBookSO> equipSkill = new();

    public int GetEquipCount() => equipSkill.Count;
    // 액티브만 추가 가능하도록 체크가 필요
}

SkillBookSO.cs

[CreateAssetMenu(menuName = "MySO/Data/Book/SkillBook", fileName = "SkillBook")]
public class SkillBookSO : ScriptableObject, ILevelUpValue<float>, IPercentValue<float>
{
    [field: SerializeField] public int index { get; set; }
    [field: SerializeField] public string code { get; set; }
    [field: SerializeField] public string skillName { get; set; }
    [field: SerializeField] public ESkillTree skillTree { get; set; }
    [field: SerializeField] public ESkillType skillType { get; set; }
    [field: SerializeField] public int grade { get; set; }      // 차수
    [field: SerializeField] public string explain { get; set; }
    [field: SerializeField] public int entityCount { get; set; }
    [field: SerializeField] public int hitCount { get; set; }
    [field: SerializeField] public float duration { get; set; }
    [field: SerializeField] public float coolTime { get; set; }
    [field: SerializeField] public int needPoint { get; set; }
    [field: SerializeField] public bool multiAttack { get; set; }
    [field: SerializeField] public float start { get; set; }
    [field: SerializeField] public float raise { get; set; }
    [field: SerializeField] public int maxLevel { get; set; }

    [field: SerializeField] public GameObject skillPrefab { get; set; }

    public float GetValue(int levelIndex)
    {
        levelIndex = levelIndex > maxLevel ? maxLevel : levelIndex;
        return start + raise * levelIndex;
    }

    public string GetPercent(int level) => $"{GetValue(level) * 100f}%";
}

SkillQueue.cs