프로젝트/프로젝트 A

# 010 Player Mp (with. Runtime Performance Stats)

효따 2025. 8. 16. 05:53

안녕하세요.😀

 

이번 작업은 플레이어의 Mp에 관련된 작업을 해주었습니다.

 

Mp는 Skill을 사용할 때 소모가 됩니다.

 

우선 작업 전 화면에 동적인 느낌이 너무 없어서 심심해 보였는데요

 

그전에 미리 배치해 둔 UI 요소들 중

 

fps / ms / time을 간단히 구현해 봤습니다.


 

 


 

FpsDisplay 스크립트입니다.

Time.deltaTime - fpsTime으로 현재 프레임과 이전 평균의 차이를 나타내었으며

0.1f를 곱해줌으로써 10%만 반영해 차이의 폭을 보정해 주었습니다.

 

fps = 1초 / 1 프레임 시간으로

초당 프레임 수 (FPS)를 계산하며 Ceil() 함수를 사용해 소수점은 모두 올림 처리를 해주었습니다.

using TMPro;
using UnityEngine;

public class FpsDisplay : MonoBehaviour
{
    [SerializeField] private TextMeshProUGUI fpsText;
    private float fpsTime = 0f;

    public void FpsResult()
    {
        fpsTime += (Time.deltaTime - fpsTime) * 0.1f;
        float fps = 1.0f / fpsTime;
        fpsText.text = Mathf.Ceil(fps).ToString() + " FPS";
    }
}

 

 

MsDisplay() 스크립트입니다.

이번 프레임 시간의 평균값을 저장할 변수를 선언해 주었으며

FpsDisplay()와 동일한 필터를 적용시켜 주었습니다.

 

또한 1000을 곱해주어 (예: 0.016초 -> 16) 이 되게 해 주었으며

소수점 한 자리까지 표시하기 위해 ToString("F1")을 사용해 주었습니다.

using TMPro;
using UnityEngine;

public class MsDisplay : MonoBehaviour
{
    [SerializeField] private TextMeshProUGUI msText;
    private float msTime = 0f;

    public void MsResult()
    {
        msTime += (Time.deltaTime - msTime) * 0.1f;
        float ms = msTime * 1000;
        msText.text = ms.ToString("F1") + "ms";
    }
}

 

 

ElapsedTimeDisplay 스크립트입니다.

이전 프레임이 지난 시간초(Time.deltaTime)를 계속 더해주며 

분 (나누기) / 초 (몫)을 사용하여 계산하였습니다.

 

또한 {0:00}의 포맷 형식을 사용하여

두 자리까지 표시되게 해 주었습니다.

(7 -> 07)

using TMPro;
using UnityEngine;

public class ElapsedTimeDisplay : MonoBehaviour
{
    [SerializeField] private TextMeshProUGUI elapsedText;
    private float elapsedTime = 0f;

    public void ElapsedResult()
    {
        elapsedTime += Time.deltaTime;
        int minutes = Mathf.FloorToInt(elapsedTime / 60f);
        int seconds = Mathf.FloorToInt(elapsedTime % 60f);

        elapsedText.text = string.Format("{0:00}:{1:00}", minutes, seconds);
    }
}

 

 

 

Manager입니다.

특이사항으로는 매 시간 흐른 초를 나타내는 ElapsedResult()는 타이머 밖에서 호출을 하며

FPS와 MS는 너무 빠른 변화를 막기 위하여 0.5초의 제한이 걸려있는 timer 안에서 호출되게 해 주었습니다.

 

앞으로(?) 전체 흐름에 관련된 요소들은 Manager에서 호출이 될 예정입니다.

using UnityEngine;

public class Manager : MonoBehaviour
{
    [SerializeField] private FpsDisplay fpsDisplay;
    [SerializeField] private MsDisplay msDisplay;
    [SerializeField] private ElapsedTimeDisplay elapsedTimeDisplay;

    private float timer = 0f;
    private float timeLimit = 0.5f;

    void Update()
    {
        if (fpsDisplay != null && msDisplay != null && elapsedTimeDisplay != null)
        {
            // Elapsed Time
            elapsedTimeDisplay.ElapsedResult();
           
            timer += Time.deltaTime;
            if (timer >= timeLimit)
            {
                // FPS
                fpsDisplay.FpsResult();
                // MS
                msDisplay.MsResult();

                timer = 0f;
            }
        }
    }
}

 

 


 

 

1. Ui의 Slider를 사용하여 체력과 마나 Slider를 배치해 주었습니다.

 

 


 

 

2. 이전에 작성한 BillboardCanvas 스크립트를 Slider에 부착해 주었습니다.

using UnityEngine;

public class BillboardCanvas : MonoBehaviour
{
    void LateUpdate()
    {
        transform.LookAt(transform.position + Camera.main.transform.rotation * Vector3.forward,
                         Camera.main.transform.rotation * Vector3.up);
    }
}

 

 

UI 대해 검색을 해보다가 카메라 왜곡 현상에 대한 글을 보았는데

나중에 꽤 재미있는 연출을 할 수 있을 것 같아 보였습니다.

 

Lens Distortion - doc.unity3.com

다음은 공식 문서 링크입니다.

 

https://docs.unity3d.com/kr/Packages/com.unity.render-pipelines.universal@15.0/manual/Post-Processing-Lens-Distortion.html

 

렌즈 왜곡 | Universal RP | 15.0.4

렌즈 왜곡 렌즈 왜곡 효과는 최종 렌더링 이미지를 왜곡하여 실제 카메라 렌즈의 모양을 시뮬레이션합니다. 렌즈 왜곡 사용 Lens Distortion은 볼륨 프레임워크를 사용합니다. 따라서 Lens Distortion 프

docs.unity3d.com

 

 

 


 

 

3. PlayerMpBar 스크립트입니다.

using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class PlayerMpBar : MonoBehaviour
{
    [SerializeField] private PlayerStats playerStats;
    [SerializeField] private Slider playerMpBar;
    [SerializeField] private Slider playerWorldBottomMpBar;

    [Header("Skill")]
    [SerializeField] private Image skillQImage;
    [SerializeField] private Image skillWImage;
    [SerializeField] private Image skillEImage;
    [SerializeField] private Image skillRImage;

    [Header("Texts")]
    [SerializeField] private TextMeshProUGUI worldMaxMpText;
    [SerializeField] private TextMeshProUGUI worldMpText;
    [SerializeField] private TextMeshProUGUI skillQMpText;
    [SerializeField] private TextMeshProUGUI skillWMpText;
    [SerializeField] private TextMeshProUGUI skillEMpText;
    [SerializeField] private TextMeshProUGUI skillRMpText;


    void Update()
    {
        MpRegenRate();
        MpTextUpdate();
    }
    private void MpTextUpdate()
    {
        worldMaxMpText.text = playerStats.playerMaxMp.ToString("F1");
        worldMpText.text = "/" + playerStats.playerMp.ToString("F1");
        skillQMpText.text = playerStats.playerMpQConsumption.ToString();
        skillWMpText.text = playerStats.playerMpWConsumption.ToString();
        skillEMpText.text = playerStats.playerMpEConsumption.ToString();
        skillRMpText.text = playerStats.playerMpRConsumption.ToString();
    }
    private void MpRegenRate()
    {
        playerStats.playerMp += playerStats.playerManaRegenRate * Time.deltaTime;
        playerStats.playerMp = Mathf.Clamp(playerStats.playerMp, 0f, playerStats.playerMaxMp);


        MpBarUpdate(playerStats.playerMp, playerStats.playerMaxMp);
    }

    public void MpBarUpdate(float mp, float maxMp)
    {
        playerMpBar.value = mp / maxMp;
        playerWorldBottomMpBar.value = mp / maxMp;
    }
    public void MpBarLimit(byte skillNumber)
    {
        if (skillNumber == 0)
        {
            skillQImage.color = Color.blue;
        }
        if (skillNumber == 1)
        {
            skillWImage.color = Color.blue;
        }
        if (skillNumber == 2)
        {
            skillEImage.color = Color.blue;
        }
        if (skillNumber == 3)
        {
            skillRImage.color = Color.blue;
        }
    }
    public void MpBarLimitClear(byte skillNumber)
    {
        if (skillNumber == 0)
        {
            skillQImage.color = Color.white;
        }
        if (skillNumber == 1)
        {
            skillWImage.color = Color.white;
        }
        if (skillNumber == 2)
        {
            skillEImage.color = Color.white;
        }
        if (skillNumber == 3)
        {
            skillRImage.color = Color.white;
        }
    }
}

 

 

3.1 사용할 컴포넌트입니다.

playerStats : 플레이어의 기본 Stat들 이 담긴 스크립트입니다.

playerMpBar : 플레이어의 Mp Bar입니다.

playerWorldBottomMpBar : 하단에 Mp Bar입니다.

    [SerializeField] private PlayerStats playerStats;
    [SerializeField] private Slider playerMpBar;
    [SerializeField] private Slider playerWorldBottomMpBar;

 

 

3.2 Q, W, E, R 스킬 아이콘 이미지입니다.

마나 부족 시 컬러를 변경하기 위함입니다.

    [SerializeField] private Image skillQImage;
    [SerializeField] private Image skillWImage;
    [SerializeField] private Image skillEImage;
    [SerializeField] private Image skillRImage;

 

 

3.3 현재 마나, 최대 마나, 스킬별 마나 소모량을 나타내어줄 Text입니다.

    [SerializeField] private TextMeshProUGUI worldMaxMpText;
    [SerializeField] private TextMeshProUGUI worldMpText;
    [SerializeField] private TextMeshProUGUI skillQMpText;
    [SerializeField] private TextMeshProUGUI skillWMpText;
    [SerializeField] private TextMeshProUGUI skillEMpText;
    [SerializeField] private TextMeshProUGUI skillRMpText;

 

 


 

 

4. UI Text를 업데이트해 주는 MpTextupdate() 함수입니다.

F1 포맷은 소수점 한 자리까지 표시를 해줍니다.

예시) 현재 마나 : 45.0 / 100.0

    private void MpTextUpdate()
    {
        worldMaxMpText.text = playerStats.playerMaxMp.ToString("F1");
        worldMpText.text = "/" + playerStats.playerMp.ToString("F1");
        skillQMpText.text = playerStats.playerMpQConsumption.ToString();
        skillWMpText.text = playerStats.playerMpWConsumption.ToString();
        skillEMpText.text = playerStats.playerMpEConsumption.ToString();
        skillRMpText.text = playerStats.playerMpRConsumption.ToString();
    }

 

 


 

 

5. 마나 재생 처리 함수 MpRegenRate()입니다.

특이사항으로 마나가 0 이하로 내려가거나 최대치를 초과하지 않도록

Mathf.Clamp로 최솟값과 최댓값을 제한해 주었습니다.

    private void MpRegenRate()
    {
        playerStats.playerMp += playerStats.playerManaRegenRate * Time.deltaTime;
        playerStats.playerMp = Mathf.Clamp(playerStats.playerMp, 0f, playerStats.playerMaxMp);


        MpBarUpdate(playerStats.playerMp, playerStats.playerMaxMp);
    }

 

 


 

 

6. 현재 마나 / 최대 마나 비율을 계산해 슬라이더에 적용하는 함수입니다.

    public void MpBarUpdate(float mp, float maxMp)
    {
        playerMpBar.value = mp / maxMp;
        playerWorldBottomMpBar.value = mp / maxMp;
    }

 

 


 

 

7.  마나 부족시 상태 표시 / 상태 표시 제거입니다.

받아온 Image의 color를 Color.blue / white로 조정해 주었습니다.

    public void MpBarLimit(byte skillNumber)
    {
        if (skillNumber == 0)
        {
            skillQImage.color = Color.blue;
        }
        if (skillNumber == 1)
        {
            skillWImage.color = Color.blue;
        }
        if (skillNumber == 2)
        {
            skillEImage.color = Color.blue;
        }
        if (skillNumber == 3)
        {
            skillRImage.color = Color.blue;
        }
    }
    public void MpBarLimitClear(byte skillNumber)
    {
        if (skillNumber == 0)
        {
            skillQImage.color = Color.white;
        }
        if (skillNumber == 1)
        {
            skillWImage.color = Color.white;
        }
        if (skillNumber == 2)
        {
            skillEImage.color = Color.white;
        }
        if (skillNumber == 3)
        {
            skillRImage.color = Color.white;
        }
    }

 

 


 

1. PlayerStats에 추가된 변수입니다.

playerMaxMp / playerMp : 최대 마나 / 현재 마나

playerManaRegenRate : 마나 재생 값

playerMp(Q, W, E, R) Consumption : 스킬 마나 소모 값

using UnityEngine;

public class PlayerStats : MonoBehaviour
{
    [Header("Hp")]
    private float playerMaxHp = 100f;
    private float playerHp = 100f;

    [Header("Mp")]
    public float playerMaxMp = 100f;
    public float playerMp = 100f;
    public float playerManaRegenRate = 5.0f;
    public float playerMpQConsumption = 35f;
    public float playerMpWConsumption = 20f;
    public float playerMpEConsumption = 20f;
    public float playerMpRConsumption = 50f;

    [Header("Attack")]
    public float playerDamage = 10f;
    public float playerSkillQDamage = 2f;
    public float playerSkillWDamage = 0f;
    public float playerSkillRDamage = 4f;

    [Header("Setting Value")]
    public float playerAttackSpeed = 3f;

    public void TakeDamage(GameObject player, float damage)
    {
        playerHp -= damage;
        if (playerHp <= 0)
        {
            Destroy(this.gameObject);
        }
    }
}

 

 


 

 

PlayerSkillController에 마나 소모 로직을 추가해 주었습니다.

using TMPro;
using UnityEngine;
using UnityEngine.UI;
using System;
using System.Collections.Generic;
using UnityEngine.AI;
using System.Collections;
public class PlayerSkillController : 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 GameObject skillRangeQuadParent;

        public GameObject skillObject;
        [HideInInspector] public Vector3 skillStartPos;
        [HideInInspector] public bool skillIsMoving = false;
        public float skillMoveDistance = 5f;
        public float skillMoveSpeed = 2f;
        [HideInInspector] public Vector3 skillMoveDirection = Vector3.zero;
    }
    [SerializeField] private PlayerMpBar playerMpBar;
    [SerializeField] private PlayerStats playerStats;

    [SerializeField] private Transform playerTransform;
    [SerializeField] private Animator playerAnim;
    [SerializeField] private NavMeshAgent playerNav;
    [SerializeField] private List<Skill> skills;
    private Vector3? skillETargetPos = null;

    [SerializeField] private Skill fireObjectInpomation;

    public bool isSkill01 = false;
    public bool isSkill02 = false;
    public bool isSkill03 = false;
    public bool isSkill04 = false;
    void Start()
    {
        foreach (var skill in skills)
        {
            if (skill.skillImage != null)
                skill.skillImage.fillAmount = 0f;
            if (skill.skillText != null)
                skill.skillText.text = "";
            if (skill.skillRangeQuadParent != null)
                skill.skillRangeQuadParent.gameObject.SetActive(false);

            if (skill.skillObject != null)
            {
                // skill.skillStartPos = skill.skillObject.transform.position;
                skill.skillObject.SetActive(false);
            }
        }
    }

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

    private void HandleSkillInput(Skill skill)
    {
        if (skill == skills[0] && playerStats.playerMp - playerStats.playerMpQConsumption < 0f)
        {
            // print("Q 마나 부족");
            playerMpBar.MpBarLimit(0);
            if (skill.skillRangeQuadParent.activeSelf)
                skill.skillRangeQuadParent.gameObject.SetActive(false);
            return;
        }
        else if (skill == skills[0] && playerStats.playerMp - playerStats.playerMpQConsumption >= 0f)
        {
            playerMpBar.MpBarLimitClear(0);
        }

        if (skill == skills[1] && playerStats.playerMp - playerStats.playerMpWConsumption < 0f)
        {
            // print("W 마나 부족");
            playerMpBar.MpBarLimit(1);
            if (skill.skillRangeQuadParent.activeSelf)
                skill.skillRangeQuadParent.gameObject.SetActive(false);
            return;
        }
        else if (skill == skills[1] && playerStats.playerMp - playerStats.playerMpWConsumption >= 0f)
        {
            playerMpBar.MpBarLimitClear(1);
        }

        if (skill == skills[2] && playerStats.playerMp - playerStats.playerMpEConsumption < 0f)
        {
            // print("E 마나 부족");
            playerMpBar.MpBarLimit(2);
            if (skill.skillRangeQuadParent.activeSelf)
                skill.skillRangeQuadParent.gameObject.SetActive(false);
            return;
        }
        else if (skill == skills[2] && playerStats.playerMp - playerStats.playerMpEConsumption >= 0f)
        {
            playerMpBar.MpBarLimitClear(2);
        }

        if (skill == skills[3] && playerStats.playerMp - playerStats.playerMpRConsumption < 0f)
        {
            // print("R 마나 부족");
            playerMpBar.MpBarLimit(3);
            if (skill.skillRangeQuadParent.activeSelf)
                skill.skillRangeQuadParent.gameObject.SetActive(false);
            return;
        }
        else if (skill == skills[3] && playerStats.playerMp - playerStats.playerMpRConsumption >= 0f)
        {
            playerMpBar.MpBarLimitClear(3);
        }

        if (Input.GetKey(skill.skillKeyCode) && !skill.isCoolingDown)
        {
            if (!skill.skillRangeQuadParent.activeSelf)
                skill.skillRangeQuadParent.gameObject.SetActive(true);

            if (skill == skills[2])
            {
                Plane groundPlane = new Plane(Vector3.up, playerTransform.position);
                Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
                float enter;

                if (groundPlane.Raycast(ray, out enter))
                {
                    Vector3 hitPoint = ray.GetPoint(enter);

                    Vector3 playerPos = playerTransform.position;
                    playerPos.y = 0;

                    Vector3 targetPos = hitPoint;
                    targetPos.y = 0;

                    Vector3 dir = targetPos - playerPos;

                    // 최대 거리 제한
                    if (dir.magnitude > skill.skillMoveDistance)
                    {
                        dir = dir.normalized * skill.skillMoveDistance;
                        targetPos = playerPos + dir;
                    }

                    if (!skill.skillRangeQuadParent.activeSelf)
                        skill.skillRangeQuadParent.SetActive(true);

                    if (skill.skillObject != null)
                    {
                        skill.skillObject.SetActive(true);
                        skill.skillObject.transform.position = new Vector3(targetPos.x, playerTransform.position.y + 0.01f, targetPos.z);

                        skillETargetPos = skill.skillObject.transform.position;
                    }
                }
            }
            else if (skill == skills[0] || skill == skills[1] || skill == skills[3])
            {
                Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
                RaycastHit hit;

                if (Physics.Raycast(ray, out hit, Mathf.Infinity))
                {
                    Vector3 dir = hit.point - skill.skillRangeQuadParent.transform.position;
                    dir.y = 0;
                    skill.skillRangeQuadParent.transform.rotation = Quaternion.LookRotation(dir);
                }
            }
        }

        if (Input.GetKeyUp(skill.skillKeyCode) && !skill.isCoolingDown)
        {
            if (skill == skills[0])
            {
                playerStats.playerMp -= playerStats.playerMpQConsumption;
                // playerMpBar.MpBarUpdate(playerStats.playerMp, playerStats.playerMaxMp);
            }
            else if (skill == skills[1])
            {
                playerStats.playerMp -= playerStats.playerMpWConsumption;
                // playerMpBar.MpBarUpdate(playerStats.playerMp, playerStats.playerMaxMp);
            }
            else if (skill == skills[2])
            {
                playerStats.playerMp -= playerStats.playerMpEConsumption;
                // playerMpBar.MpBarUpdate(playerStats.playerMp, playerStats.playerMaxMp);
            }
            else if (skill == skills[3])
            {
                playerStats.playerMp -= playerStats.playerMpRConsumption;
                // playerMpBar.MpBarUpdate(playerStats.playerMp, playerStats.playerMaxMp);
            }

            skill.skillRangeQuadParent.gameObject.SetActive(false);
            skill.isCoolingDown = true;
            skill.currentCooldown = skill.cooldown;

            if (skill == skills[2])
            {
                if (skillETargetPos.HasValue)
                {
                    Vector3 teleportPos = skillETargetPos.Value;
                    teleportPos.y = playerTransform.position.y;

                    Vector3 lookDir = (teleportPos - playerTransform.position);
                    lookDir.y = 0;
                    if (lookDir.sqrMagnitude > 0.01f)
                    {
                        playerTransform.rotation = Quaternion.LookRotation(lookDir.normalized);
                    }

                    if (playerNav != null)
                    {
                        StartCoroutine(SkillEDelay(teleportPos));
                    }
                    else
                    {
                        playerTransform.position = teleportPos;
                    }
                    skillETargetPos = null;
                }

                if (skill.skillObject != null)
                    skill.skillObject.SetActive(false);
            }
            else if (skill == skills[0] || skill == skills[1] || skill == skills[3])
            {
                if (playerTransform != null)
                {
                    Vector3 lookDir = skill.skillRangeQuadParent.transform.forward;
                    lookDir.y = 0;
                    if (lookDir.sqrMagnitude > 0.01f)
                    {
                        playerTransform.rotation = Quaternion.LookRotation(lookDir);
                        skill.skillMoveDirection = lookDir.normalized;
                    }
                }
                if (skill.skillObject != null)
                {

                }
                if (skill != null)
                {
                    if (skill == skills[0] || skill == skills[1])
                    {
                        if (skill == skills[0])
                        {
                            playerAnim.SetBool("IsSkillQ", true);
                        }
                        else if (skill == skills[1])
                        {
                            playerAnim.SetBool("IsSkillW", true);
                        }
                        ObjectGen(skill);
                    }
                    else if (skill == skills[3])
                    {
                        if (skill == skills[3])
                        {
                            StartCoroutine(SkillRDelay(skill));
                        }
                    }
                }
            }
        }
    }
    private IEnumerator SkillEDelay(Vector3 pos)
    {
        isSkill03 = true;
        playerAnim.SetTrigger("IsSkillE");
        yield return new WaitForSeconds(0.5f);
        isSkill03 = false;
        playerNav.Warp(pos);        
    }
    private IEnumerator SkillRDelay(Skill skill)
    {
        isSkill04 = true;
        playerAnim.SetTrigger("IsSkillR");
        yield return new WaitForSeconds(1f);
        isSkill04 = false;
        ObjectGen(skill);
    }
    private void ObjectGen(Skill skill)
    {
        skill.skillObject.SetActive(true);
        skill.skillIsMoving = true;

        skill.skillObject.transform.position = playerTransform.position + playerTransform.TransformDirection(new Vector3(0f, 0.5f, 0.4f));

        skill.skillMoveDirection = playerTransform.forward.normalized;

        skill.skillStartPos = skill.skillObject.transform.position;

        skill.skillObject.transform.rotation = Quaternion.LookRotation(skill.skillMoveDirection);
    }

    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 HandleSkillMovement(Skill skill)
    {
        if (!skill.skillIsMoving || skill.skillObject == null)
            return;
        fireObjectInpomation = skill;
        skill.skillObject.transform.position += skill.skillMoveDirection * skill.skillMoveSpeed * Time.deltaTime;

        float traveledDist = Vector3.Distance(skill.skillObject.transform.position, skill.skillStartPos);

        if (traveledDist >= skill.skillMoveDistance)
        {            
            ResetSkillObject();
        }
    }

    public void ResetSkillObject()
    {
        if (fireObjectInpomation.skillObject == null)
            return;

        // skill.skillObject.transform.position = skill.skillStartPos;
        if (fireObjectInpomation.skillObject != null)
        {
            fireObjectInpomation.skillObject.SetActive(false);
        }
        fireObjectInpomation.skillIsMoving = false;
        playerAnim.SetBool("IsSkillQ", false);
        playerAnim.SetBool("IsSkillW", false);
        fireObjectInpomation = null;
    }
}

 

 


 

 

스킬을 사용할 때, (현재 플레이어의 마나 - 사용할 스킬의 마나 소모값) 이 0보다 작다면

스킬을 사용할 수 없는 것으로 판단하여 return을 해주었으며

 

스킬을 사용할 때, (현재 플레이어의 마나 - 사용할 스킬의 마나 소모값 이) 0 보다 크거나 같다면

playerMpBar.MpBarLimitClear()를 사용해 이미지의 컬러를 White로 바꿔 주었습니다.

    private void HandleSkillInput(Skill skill)
    {
        if (skill == skills[0] && playerStats.playerMp - playerStats.playerMpQConsumption < 0f)
        {
            // print("Q 마나 부족");
            playerMpBar.MpBarLimit(0);
            if (skill.skillRangeQuadParent.activeSelf)
                skill.skillRangeQuadParent.gameObject.SetActive(false);
            return;
        }
        else if (skill == skills[0] && playerStats.playerMp - playerStats.playerMpQConsumption >= 0f)
        {
            playerMpBar.MpBarLimitClear(0);
        }

        if (skill == skills[1] && playerStats.playerMp - playerStats.playerMpWConsumption < 0f)
        {
            // print("W 마나 부족");
            playerMpBar.MpBarLimit(1);
            if (skill.skillRangeQuadParent.activeSelf)
                skill.skillRangeQuadParent.gameObject.SetActive(false);
            return;
        }
        else if (skill == skills[1] && playerStats.playerMp - playerStats.playerMpWConsumption >= 0f)
        {
            playerMpBar.MpBarLimitClear(1);
        }

        if (skill == skills[2] && playerStats.playerMp - playerStats.playerMpEConsumption < 0f)
        {
            // print("E 마나 부족");
            playerMpBar.MpBarLimit(2);
            if (skill.skillRangeQuadParent.activeSelf)
                skill.skillRangeQuadParent.gameObject.SetActive(false);
            return;
        }
        else if (skill == skills[2] && playerStats.playerMp - playerStats.playerMpEConsumption >= 0f)
        {
            playerMpBar.MpBarLimitClear(2);
        }

        if (skill == skills[3] && playerStats.playerMp - playerStats.playerMpRConsumption < 0f)
        {
            // print("R 마나 부족");
            playerMpBar.MpBarLimit(3);
            if (skill.skillRangeQuadParent.activeSelf)
                skill.skillRangeQuadParent.gameObject.SetActive(false);
            return;
        }
        else if (skill == skills[3] && playerStats.playerMp - playerStats.playerMpRConsumption >= 0f)
        {
            playerMpBar.MpBarLimitClear(3);
        }

 

 


 

 

다음은 Input.GetKeyUp()이 되었을 때입니다.

Input.GetKeyUp()이 되었다면 위 로직에서 return 되지 않고 순조롭게

Input.GetKey() -> Input.GetKeyUp()를 넘어온 것이기에

현재 플레이어의 마나에서 사용한 스킬의 마나를 빼주었습니다.

        if (Input.GetKeyUp(skill.skillKeyCode) && !skill.isCoolingDown)
        {
            if (skill == skills[0])
            {
                playerStats.playerMp -= playerStats.playerMpQConsumption;
                // playerMpBar.MpBarUpdate(playerStats.playerMp, playerStats.playerMaxMp);
            }
            else if (skill == skills[1])
            {
                playerStats.playerMp -= playerStats.playerMpWConsumption;
                // playerMpBar.MpBarUpdate(playerStats.playerMp, playerStats.playerMaxMp);
            }
            else if (skill == skills[2])
            {
                playerStats.playerMp -= playerStats.playerMpEConsumption;
                // playerMpBar.MpBarUpdate(playerStats.playerMp, playerStats.playerMaxMp);
            }
            else if (skill == skills[3])
            {
                playerStats.playerMp -= playerStats.playerMpRConsumption;
                // playerMpBar.MpBarUpdate(playerStats.playerMp, playerStats.playerMaxMp);
            }

 

 


 

 

 

 

 

이번 작업은 플레이어 스킬과 마나바 UI를 연동하고, 마나 부족 처리 및 스킬별 제한을 구현하는 내용이었습니다.

예상보다 수정과 디버깅에 시간이 많이 소요되었는데, 특히 스킬별 마나 체크 로직과 UI 업데이트 과정에서 여러 예외 상황을 고려해야 했습니다.

결과물은 만족스럽지만, 구현 과정에서 많은 테스트와 코드 조정이 필요했던 점이 아쉽게 느껴집니다.

앞으로는 초기 설계 단계에서 스킬별 예외 처리와 UI 연동 로직을 좀 더 체계적으로 계획하면

디버깅 시간을 줄일 수 있을 것 같습니다.

 

분명 이것만 하고 자자라고 했던 게 평소 자던 시간이었는데 어느덧 시간이.. 😂

 


 

 

감사합니다. 😌

 

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

# 012 Player Exp (with. Get Gold)  (5) 2025.08.17
#011 Minimap  (3) 2025.08.16
# 009 Enumy Hp (With. Skill Attack)  (4) 2025.08.15
# 008 Attack (with. Animation)  (10) 2025.08.15
#007 Follow (with. Enumy)  (5) 2025.08.13