본문 바로가기
게임 개발

[Unity] 디펜스게임 개발 - 유닛 소환 막기 스킬

by chobbo 2024. 7. 30.

물 보스는 유닛 소환을 막는 스킬을 사용한다.

 

보스가 나타나기 전 UI

 

보스 나타난 후 UI

물보스는 일정 체력이 되었을 때 분열하므로 보스 체력을 보여주지 않도록 했다.

 

보스가 스킬을 사용하면 Boss 스킬이 체력바 아래에 생기고, 소환 버튼에 카운트다운 UI가 생긴다.

저 카운트다운 UI는 물 보스 스킬 사용시에만 사용하므로 동적생성으로 Instantiate 해주었다.

 

어려웠던 점 

물 보스는 분열할 때 자기 자신을 복제한다.

즉 스킬 중복 사용이나 동적 UI 중복 생성, 분열 보스는 다른 Init 로직을 사용하는 등의 예외처리가 굉장히 많이 필요했다.

 

BlockUnitSpawnSkill 스크립트

using System.Collections;
using UnityEngine;

public class BlockUnitSpawnSkill : CoolTimeSkill
{
    [Header("# Block Unit Spawn Skill")]
    [SerializeField] private int blockDuration;
    [SerializeField] private GameObject uiPrefab;

    [Header("# Block Unit Spawn Effect")]
    [SerializeField] private GameObject blockEffect;

    private Coroutine bossToUseSkillCoroutine;
    private Boss_Water waterBoss;

    private void Start()
    {
        waterBoss = boss as Boss_Water;
    }

    public override void UseSkill()
    {
        base.UseSkill();
        if (IsReadyToUse() && !isStun && !UIManager.Instance.UI_Interface.IsDisableSummonBtn)
        {
            isReadyToUseSkill = false;
            boss.SkillHandler.SetSkillInfo(
                ESkillType.CoolTime,
                0.7f, 
                boss.AnimationData.Skill2ParameterHash, 
                () => {
                    if (waterBoss.uI_BlockUnitSpawn != null)
                        waterBoss.uI_BlockUnitSpawn.SetActiveCountDownUI(true);
                        waterBoss.uI_BlockUnitSpawn.StartBlockUnitSpawn(blockDuration);

                    if (bossToUseSkillCoroutine != null)
                        StopCoroutine(bossToUseSkillCoroutine);
                    bossToUseSkillCoroutine= StartCoroutine(SetBossToUseSkill());}
                );
            boss.StateMachine.ChangeState(boss.StateMachine.SkillState);
        }
    }

    public override void EndSkill()
    {
        base.EndSkill();
        if (bossToUseSkillCoroutine != null)
            StopCoroutine(bossToUseSkillCoroutine);
        blockEffect.SetActive(false);

        UIManager.Instance.UI_Interface.UnBlockSummonBtn();
        UIManager.Instance.UI_Interface.DeActivateBossSkillIndicator();
        if (waterBoss.uI_BlockUnitSpawn!=null)
            waterBoss.uI_BlockUnitSpawn.DestroyUI();
    }

    private IEnumerator SetBossToUseSkill()
    {
        blockEffect.SetActive(true);
        yield return moveDelayWaitForSeconds;
        blockEffect.SetActive(false);
        boss.StateMachine.ChangeState(boss.StateMachine.MoveState);
        boss.SkillHandler.FinishSkillCast();
        StartCoroutine(SkillCoolTime());
    }
}

 

WaterBoss 스크립트

using System.Collections;
using UnityEngine;

public class Boss_Water : Boss
{
    [Header("# 분열 스킬 발동 체력 조건")]
    [SerializeField] private float splitPercentage;
    [field : Header("# 분열 스킬 최대 사용 가능 횟수")]
    [field : SerializeField] public int MaxSplitCount {  get; private set; }
    [field:Header("# 분열 스킬 현재 사용 가능 횟수")]
    [field: SerializeField] public int CurSplitCount { get; private set; }

    [SerializeField] private GameObject uiPrefab;
    public UI_BlockUnitSpawn uI_BlockUnitSpawn;
    private Vector3 originScale;

    protected override void Awake()
    {
        base.Awake();
        originScale = transform.localScale;
    }

    public override void EnemyInit(int key)
    {
        base.EnemyInit(key);
        PatternHandler.OnHealthChangePatternEvent += UseSplitSkill;
        CurSplitCount = MaxSplitCount;
        InitUI();
        transform.localScale = originScale;
        UIManager.Instance.UI_Interface.ActivateWBWarningTXT();
    }

    public override void DEV_EnemyInit(EnemyData data)
    {
        base.DEV_EnemyInit(data);
        PatternHandler.OnHealthChangePatternEvent += UseSplitSkill;
        CurSplitCount = MaxSplitCount;
        InitUI();
        transform.localScale = originScale;
        UIManager.Instance.UI_Interface.ActivateWBWarningTXT();
    }

    protected override void EnemyDie()
    {
        base.EnemyDie();
        PatternHandler.OnHealthChangePatternEvent -= UseSplitSkill;
        // 마지막 한 마리가 죽을 때 == 스테이지 끝날 때 DeActivate
        if(GameManager.Instance.Stage.SpawnEnemyList.Count ==1)
            UIManager.Instance.UI_Interface.DeActivateWBWarningTXT();
    }

    public void SplitBossInit(EnemyData enemyData, EnemyStat curData, int splitCount, UI_BlockUnitSpawn ui)
    {
        if(uI_BlockUnitSpawn!=null) 
            uI_BlockUnitSpawn = ui;

        EnemyData = enemyData;
        Stat = new EnemyStat(GetRandomSpeed(enemyData.MoveSpeed), curData.MaxHealth, enemyData.Toughness);

        StateMachine.ChangeState(StateMachine.MoveState);
        HealthSystem.InitHealthSystem(HealthSystem.GetCurHealth());
        HealthSystem.SetWayPoint(EffectWayPoint);
        AddHealthEvent();

        transform.localScale *= 0.7f;
        CurSplitCount = splitCount;
    }

    private void InitUI()
    {
        if (MaxSplitCount == CurSplitCount)
        {
            GameObject prefab = Instantiate(uiPrefab);
            uI_BlockUnitSpawn = prefab.GetComponent<UI_BlockUnitSpawn>();
            uI_BlockUnitSpawn.InitUI();
        }
    }

    public void Split()
    {
        CurSplitCount--;
    }

    public void UseSplitSkill(float healthPercentage)
    {
        if (healthPercentage <= splitPercentage && HealthSystem.GetCurHealth()!=0 && CurSplitCount > 0)
        {            
            SkillHandler.CallSkill(EBossSkill.SplitSkill);
        }
    }

    private float GetRandomSpeed(float baseSpeed)
    {
        return Random.Range(baseSpeed, baseSpeed + 3f);
    }
}

 

UI_BlockUnitSpawn 스크립트

using TMPro;
using UnityEngine;
using System.Collections;

public class UI_BlockUnitSpawn : MonoBehaviour
{
    [SerializeField] private TextMeshProUGUI countDownTXT;

    private Coroutine blockUnitSpawn;
    private readonly WaitForSeconds oneSeconds = new WaitForSeconds(1);

    public void InitUI()
    {
        SetActiveCountDownUI(false);
        transform.SetParent(UIManager.Instance.UI_Interface.countDownTransform, false);
    }

    public void StartBlockUnitSpawn(int duration)
    {
        if(blockUnitSpawn != null)
            StopCoroutine(blockUnitSpawn);

        blockUnitSpawn = StartCoroutine(BlockUnitSpawn(duration));
    }

    private IEnumerator BlockUnitSpawn(int duration)
    {
        UIManager.Instance.UI_Interface.BlockSummonBtn();
        UIManager.Instance.UI_Interface.ActivateBossSkillIndicator($"보스가 유닛 소환 버튼을 막습니다.");
        UpdateSummonCountDownTXT(duration);

        while (duration > 0)
        {
            yield return oneSeconds;
            duration--;
            UpdateSummonCountDownTXT(duration);
        }

        UIManager.Instance.UI_Interface.UnBlockSummonBtn();
        UIManager.Instance.UI_Interface.DeActivateBossSkillIndicator();
        SetActiveCountDownUI(false);
    }

    public void DestroyUI()
    {
        if (blockUnitSpawn != null)
            StopCoroutine(blockUnitSpawn);

        Destroy(gameObject);
    }

    public void SetActiveCountDownUI(bool b)
    {
        gameObject.SetActive(b);
    }

    private void UpdateSummonCountDownTXT(int second)
    {
        countDownTXT.text = second.ToString();
    }
}