프로젝트/프로젝트 A

#003 Skill (with. UI & Cooldown)

효따 2025. 8. 10. 02:17

안녕하세요.

 

이번 글에서는 스킬에 쿨다운을 다루어 보려고 합니다.

 

생각보다 작업 시간이 꽤 소모가 되어서 스킬 동작 구현은 아직 작업하지 못했습니다.😥

 

또한 아직 UI 작업을 할 단계는 아닌 것 같아서, 임시로 Canvas와 Image를 생성해 주어 작업을 했습니다.

 

 

먼저 canvas의 인스펙터창 설정입니다.

 

화면 크기의 변화에 따라 UI가 조정되게 Scale Mode > Scale With Screen Size로 해주었고

기준이 되는 해상도로 UI 작업한 해상도로 입력되게 Reference Resolution은 1920 x 1080으로 해주었습니다.

또한 가로 / 세로 비율에 맞게 줄이도록 Match는 0.5로 설정을 해주었습니다.

 

다음은 간단한 UI 구조를 만들었습니다.

Skill01 : 관리하기 위한 부모 오브젝트입니다.

ImageBackground : 스킬 이미지의 뒷 배경입니다.

ImageSkill : 스킬 이미지입니다.

Text (TMP) Cooldown : 쿨다운 표시를 나타낼 텍스트입니다.

(생성된 오브젝트 이름을 그대로 사용하고 뒤에 이름을 붙이는 것은 팀원과 작업 도중 각 오브젝트 이름을 쉽게 찾기 위한 방법으로 정한 것이니 큰 의미는 없습니다.)

 


 

다음은 코드입니다.

먼저 "SkillController"라는 이름의 클래스를 만들었습니다.

using TMPro;
using UnityEngine;
using UnityEngine.UI;
using System;
using System.Collections.Generic;

public class SkillController : MonoBehaviour
{
    [Serializable]
    public class Skill
    {
        public Image skillImage;
        public TextMeshProUGUI skillText;
        public KeyCode skillKeyCode;
        public float cooldown;

        [HideInInspector] public bool isCoolingDown = false;
        [HideInInspector] public float currentCooldown = 0f;

        public Action onUseSkill;
    }

    [SerializeField] private List<Skill> skills;

    void Start()
    {
        foreach (var skill in skills)
        {
            if (skill.skillImage != null)
                skill.skillImage.fillAmount = 0f;
            if (skill.skillText != null)
                skill.skillText.text = "";
        }

        if (skills.Count > 0)
            skills[0].onUseSkill = Skill1Effect;
        if (skills.Count > 1)
            skills[1].onUseSkill = Skill2Effect;
    }

    void Update()
    {
        foreach (var skill in skills)
        {
            HandleSkillInput(skill);
            HandleSkillCooldown(skill);
        }
    }

    private void HandleSkillInput(Skill skill)
    {
        if (Input.GetKeyDown(skill.skillKeyCode) && !skill.isCoolingDown)
        {
            skill.isCoolingDown = true;
            skill.currentCooldown = skill.cooldown;

            skill.onUseSkill?.Invoke();
        }
    }

    private void HandleSkillCooldown(Skill skill)
    {
        if (skill.isCoolingDown)
        {
            skill.currentCooldown -= Time.deltaTime;

            if (skill.currentCooldown <= 0f)
            {
                skill.isCoolingDown = false;
                skill.currentCooldown = 0f;

                if (skill.skillImage != null)
                    skill.skillImage.fillAmount = 0f;
                if (skill.skillText != null)
                    skill.skillText.text = "";
            }
            else
            {
                if (skill.skillImage != null)
                    skill.skillImage.fillAmount = skill.currentCooldown / skill.cooldown;
                if (skill.skillText != null)
                    skill.skillText.text = Mathf.Ceil(skill.currentCooldown).ToString();
            }
        }
    }

    private void Skill1Effect()
    {
        Debug.Log("스킬 1 발동!");
    }
    private void Skill2Effect()
    {
        Debug.Log("스킬 2 발동!");
    }
}

 

 

1.Skill이라는 클래스를 정의해 주었고 각 스킬마다 관리할 스킬 이미지, 쿨다운 텍스트, 실행할 키, 쿨다운을 선언해 주었으며,

[HideInInspector]를 인스펙터에 노출될 필요가 없는 쿨다운 플래그, 현재 쿨다운 시간에 붙여주었고

 Action으로 OnUseSkill을 선언해 주어 함수를 담게 해 주었습니다.

 

마지막으로 [Serializable]을 선언해 주어 인스펙터에서 Skill 객체를 리스트 안에 편집할 수 있게 해 주었습니다.

[Serializable]
    public class Skill
    {
        public Image skillImage;
        public TextMeshProUGUI skillText;
        public KeyCode skillKeyCode;
        public float cooldown;

        [HideInInspector] public bool isCoolingDown = false;
        [HideInInspector] public float currentCooldown = 0f;

        public Action onUseSkill;
    }

 

 

2. Skill 클래스를 담을 리스트를 만들어 주었습니다.

    [SerializeField] private List<Skill> skills;

 

 

3. 게임 시작 시 스킬 관련된 모든 요소를 초기화해 주었으며 

각 스킬에 고유한 함수를 할당해 주었습니다.

    void Start()
    {
        foreach (var skill in skills)
        {
            if (skill.skillImage != null)
                skill.skillImage.fillAmount = 0f;
            if (skill.skillText != null)
                skill.skillText.text = "";
        }

        if (skills.Count > 0)
            skills[0].onUseSkill = Skill1Effect;
        if (skills.Count > 1)
            skills[1].onUseSkill = Skill2Effect;
    }

 

 

4. 매 프레임마다 모든 스킬에 대한 키 입력 처리와 쿨다운 처리를 반복해 주었습니다.

    void Update()
    {
        foreach (var skill in skills)
        {
            HandleSkillInput(skill);
            HandleSkillCooldown(skill);
        }
    }

 

 

5. 해당 키가 눌렸는지 체크와 쿨다운 중이 아니면 쿨다운 상태를 활성화하는 함수를 만들어 주었습니다.

    private void HandleSkillInput(Skill skill)
    {
        if (Input.GetKeyDown(skill.skillKeyCode) && !skill.isCoolingDown)
        {
            skill.isCoolingDown = true;
            skill.currentCooldown = skill.cooldown;

            skill.onUseSkill?.Invoke();
        }
    }

 

 

6.   isCoolingDown 함수로 쿨다운 여부를 판단해 이미지의 foillAmount에 남은 비율과 text에 남은 시간을 보여주는 함수를 만들었습니다.

    private void HandleSkillInput(Skill skill)
    {
        if (Input.GetKeyDown(skill.skillKeyCode) && !skill.isCoolingDown)
        {
            skill.isCoolingDown = true;
            skill.currentCooldown = skill.cooldown;

            skill.onUseSkill?.Invoke();
        }
    }

 

 

7. 마지막으로 스킬 발동 시(Invoke > Action) 실행될 함수입니다.

    private void Skill1Effect()
    {
        Debug.Log("스킬 1 발동!");
    }
    private void Skill2Effect()
    {
        Debug.Log("스킬 2 발동!");
    }

 

SkillController 스크립트는 Player 오브젝트에 붙여서 사용할 계획입니다.

 


 

 

감사합니다.


 

키 코드는 Q와 W입니다.

 

+ 카메라 줌 인/아웃을 할 때 코드상 FOV 기준이 30으로 되어있고, 인스펙터 메인카메라는 FOV가 50으로 설정되어 있어서

맞추는 디버깅 작업을 해주었습니다.😁

 

'프로젝트 > 프로젝트 A' 카테고리의 다른 글

#006 Click (with. Object Pooling)  (4) 2025.08.11
#005 Skill Motion 2~4  (1) 2025.08.10
#004 Skill Motion 1 (with. UI)  (0) 2025.08.10
#002 Camera (with. cinemachine)  (3) 2025.08.10
#001 Movement (with. NaviMeshAgent, Animator)  (3) 2025.08.09