TIL(2024,07,12)최종 프로젝트 1차구현 하나씩 만들기

김보근·2024년 7월 12일

Unity

목록 보기
42/113
post-thumbnail

TIL (Today I Learned) - 2024년 7월 12일

오늘 한 작업

BubbleClickSkill 클래스 수정:

스킬 지속 시간 동안 큐가 비어 있더라도 계속해서 루프가 실행되도록 수정했습니다.
AddBubbleToQueue와 RemoveBubbleFromQueue 메서드를 통해 버블을 큐에 추가하고 제거하는 로직을 간단하게 수정했습니다.

HeartButton 클래스 수정:

TouchHeartBubble 메서드에서 버블이 비활성화될 때 큐에서 제거하고, 일정 시간 후에 다시 활성화될 때 큐에 추가되도록 수정했습니다.
Invoke 메서드를 사용하여 일정 시간 후에 ReactivateBubble 메서드를 호출하도록 했습니다.

Skill 클래스 수정:

해금과 업그레이드 기능을 포함하도록 수정했습니다.
UnlockSkill 메서드를 추가하여 해금 비용을 처리하고, 해금 후 스킬을 사용할 수 있도록 설정했습니다.
UpgradeSkill 메서드를 수정하여 업그레이드 비용을 처리하고, 업그레이드 후 스킬 레벨을 증가시키도록 설정했습니다.
최대 레벨에 도달하면 업그레이드 버튼을 비활성화하고 "최대 레벨" 텍스트를 표시하도록 수정했습니다.

LifeBoostSkill 클래스 추가:

터치 증가량의 5000배에 해당하는 생명력을 즉시 획득하는 스킬을 추가했습니다.
스킬 해금 비용을 300 다이아로 설정하고, 레벨 당 업그레이드 비용을 설정했습니다.

RootBoostSkill 클래스 추가:

특정 기간 동안 루트 생산량을 500배로 증가시키는 스킬을 추가했습니다.
스킬 해금 비용과 업그레이드 비용을 설정했습니다.

Skill 클래스

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

public abstract class Skill : MonoBehaviour
{
    public float skillDuration; // 스킬 지속시간
    public float cooldownTime = 15.0f; // 기본 쿨타임
    public TextMeshProUGUI cooldownText; // 쿨타임을 표시할 텍스트
    public Image cooldownImage; // 회전할 이미지

    public Button skillButton; // 스킬 버튼
    public Button upgradeButton; // 해금/업그레이드 버튼

    protected bool onCooldown = false;
    protected float cooldownRemaining;

    public int currentLevel = 0; // 현재 스킬 레벨 (0 = 잠금 상태)
    public TextMeshProUGUI upgradeCostText; // 업그레이드 비용을 표시할 텍스트
    public TextMeshProUGUI currentLevelText; // 현재 스킬 레벨 텍스트
    public TextMeshProUGUI skillInfoText; // 현재 스킬 설명 텍스트
    public BigInteger unlockCost = 200; // 해금 비용

    protected virtual void Start()
    {
        // 각 스킬의 지속시간과 쿨타임은 서브 클래스에서 설정됩니다.
        UpdateUpgradeCostUI(); // 초기 업그레이드 비용 UI 설정
        UpdateUI(); // 스킬 설명과 레벨 UI 업데이트
        CheckUnlockStatus(); // 해금 상태 확인 및 UI 업데이트
    }

    private void Update()
    {
        if (onCooldown)
        {
            cooldownRemaining -= Time.deltaTime;
            if (cooldownRemaining < 0) cooldownRemaining = 0;

            UpdateCooldownUI(cooldownRemaining);
        }

        CheckUnlockStatus();
    }

    public void UnlockOrUpgradeSkill()
    {
        if (currentLevel == 0)
        {
            UnlockSkill();
        }
        else
        {
            UpgradeSkill();
        }
    }

    private void UnlockSkill()
    {
        if (DiamondManager.Instance.HasSufficientDiamond(unlockCost))
        {
            DiamondManager.Instance.DecreaseDiamond(unlockCost);
            currentLevel = 1;
            UpdateClickValues();
            UpdateUpgradeCostUI(); // 업그레이드 비용 UI 업데이트
            UpdateUI(); // UI 업데이트
            CheckUnlockStatus(); // 해금 후 상태 확인 및 UI 업데이트
        }
        else
        {
            Debug.Log("Not enough diamonds to unlock.");
        }
    }

    public abstract void ActivateSkill();

    protected IEnumerator SkillEffect()
    {
        onCooldown = true;
        cooldownRemaining = cooldownTime;

        yield return StartCoroutine(ApplySkillEffect());

        while (cooldownRemaining > 0)
        {
            yield return null;
        }

        onCooldown = false;
        UpdateCooldownUI(0);
    }

    public void UpdateCooldownUI(float remaining)
    {
        if (cooldownText != null)
        {
            if (remaining > 0)
            {
                int minutes = Mathf.FloorToInt(remaining / 60); // 분 계산
                int seconds = Mathf.CeilToInt(remaining % 60);  // 초 계산
                cooldownText.text = $"{minutes:D2}:{seconds:D2}"; // 분:초 형식으로 텍스트 설정
            }
            else
            {
                cooldownText.text = ""; // 00:00일 때는 텍스트를 빈 문자열로 설정
            }
        }

        if (cooldownImage != null)
        {
            float fillAmount = 1 - (remaining / cooldownTime);
            cooldownImage.fillAmount = fillAmount;
        }
    }

    protected abstract IEnumerator ApplySkillEffect();

    private void UpgradeSkill()
    {
        if (currentLevel >= 21)
        {
            Debug.Log("Maximum level reached.");
            return;
        }
        BigInteger upgradeCost = CalculateUpgradeCost(currentLevel);
        if (DiamondManager.Instance.HasSufficientDiamond(upgradeCost))
        {
            DiamondManager.Instance.DecreaseDiamond(upgradeCost);
            currentLevel++;
            UpdateClickValues(); // 필요 시 업데이트
            UpdateUpgradeCostUI(); // 업그레이드 비용 UI 업데이트
            UpdateUI();
        }
        else
        {
            Debug.Log("Not enough diamonds to upgrade.");
        }
    }

    public BigInteger CalculateUpgradeCost(int level)
    {
        if (level >= 1 && level <= 10)
        {
            return level * 10;
        }
        else if (level >= 11 && level <= 21)
        {
            return (level - 10) * 100;
        }
        else
        {
            return BigInteger.Zero; // 임의로 추가한 범위 외의 레벨은 업그레이드 불가
        }
    }

    public virtual void UpdateUI()
    {
        UpdateUpgradeCostUI();
        NowskillInfoUI();
        LevelUI();
    }

    protected void UpdateUpgradeCostUI()
    {
        if (upgradeCostText != null)
        {
            if (currentLevel >= 21)
            {
                upgradeCostText.text = "최대 레벨";
                upgradeButton.interactable = false;
            }
            else
            {
                BigInteger nextCost = currentLevel > 0 ? CalculateUpgradeCost(currentLevel) : unlockCost;
                upgradeCostText.text = currentLevel > 0
                    ? $"업그레이드 비용: {BigIntegerUtils.FormatBigInteger(nextCost)} 다이아"
                    : $"해금 비용: {BigIntegerUtils.FormatBigInteger(nextCost)} 다이아";
            }
        }
    }

    protected virtual void LevelUI()
    {
        if (currentLevelText != null)
        {
            currentLevelText.text = currentLevel > 0
                ? $"현재 스킬 레벨: {currentLevel}"
                : "스킬이 해금되지 않았습니다";
        }
    }

    protected virtual void NowskillInfoUI()
    {
        if (skillInfoText != null)
        {
            skillInfoText.text = currentLevel > 0
                ? $"현재 스킬 능력치: "
                : "스킬이 해금되지 않았습니다";
        }
    }

    protected virtual void UpdateClickValues() { }

    private void CheckUnlockStatus()
    {
        if (upgradeButton != null)
        {
            upgradeButton.interactable = currentLevel == 0
                ? DiamondManager.Instance.HasSufficientDiamond(unlockCost)
                : currentLevel < 21 && DiamondManager.Instance.HasSufficientDiamond(CalculateUpgradeCost(currentLevel));
        }

        if (skillButton != null)
        {
            skillButton.interactable = currentLevel > 0 && !onCooldown;
        }
    }
}

LifeBoostSkill 클래스

using System.Collections;
using UnityEngine;
using System.Numerics;

public class LifeBoostSkill : Skill
{
    public BigInteger skillMultiplier = 5000; // 스킬 배수

    private TouchData touchData;

    protected override void Start()
    {
        cooldownTime = 1800f; // 스킬 쿨타임 30분
        currentLevel = 0;
        unlockCost = 300; // 해금 비용

        // TouchData 컴포넌트를 찾아서 참조합니다.
        touchData = FindObjectOfType<TouchData>();

        // 초기 UI 설정
        UpdateCooldownUI(0);
        UpdateUI();
    }

    public override void ActivateSkill()
    {
        if (!onCooldown)
        {
            StartCoroutine(SkillEffect());
        }
    }

    protected override IEnumerator ApplySkillEffect()
    {
        // 터치 증가량의 5000배에 해당하는 생명력을 추가로 획득합니다.
        if (touchData != null)
        {
            BigInteger bonusLife = touchData.touchIncreaseAmount * skillMultiplier;
            LifeManager.Instance.IncreaseWater(bonusLife);
        }

        yield return null; // 즉시 완료
    }

    protected override void LevelUI()
    {
        currentLevelText.text = currentLevel > 0
            ? $"즉시 획득 스킬 레벨: {currentLevel}"
            : "즉시 획득 스킬이 해금되지 않았습니다";
    }
    protected override void NowskillInfoUI()
    {
        skillInfoText.text = currentLevel > 0
            ? $"현재 즉시 획득 생명력: {skillMultiplier}"
            : "스킬이 해금되지 않았습니다";
    }
}

RootBoostSkill 클래스

using System.Collections;
using UnityEngine;
using System.Numerics;

public class RootBoostSkill : Skill
{
    public BigInteger boostMultiplier = 500; // 부스트 배수
    public float boostDuration = 300f; // 부스트 지속 시간 (5분)

    private IRoot[] roots;

    protected override void Start()
    {
        cooldownTime = 1800f; // 스킬 쿨타임 30분
        currentLevel = 0;
        unlockCost = 200; // 해금 비용

        // IRoot 컴포넌트를 찾아서 참조합니다.
        roots = FindObjectsOfType<RootBase>();

        // 초기 UI 설정
        UpdateCooldownUI(0);
        UpdateUI();
    }

    public override void ActivateSkill()
    {
        if (!onCooldown && currentLevel > 0)
        {
            StartCoroutine(SkillEffect());
        }
    }

    protected override IEnumerator ApplySkillEffect()
    {
        // 5분간 모든 루트의 생산량을 500배로 증가
        foreach (var root in roots)
        {
            root.ApplyTemporaryBoost(boostMultiplier, boostDuration);
        }

        yield return new WaitForSeconds(boostDuration); // 부스트 지속 시간 동안 대기
    }

    protected override void LevelUI()
    {
        currentLevelText.text = currentLevel > 0
            ? $"루트 부스트 스킬 레벨: {currentLevel}"
            : "루트 부스트 스킬이 해금되지 않았습니다";
    }

    protected override void NowskillInfoUI()
    {
        skillInfoText.text = currentLevel > 0
            ? $"현재 부스트 배수: {boostMultiplier}배 \n 부스트 지속 시간: {boostDuration}초"
            : "스킬이 해금되지 않았습니다";
    }
}

트러블슈팅

문제 1: 스킬 지속 시간 동안 큐가 비었을 때 스킬이 조기에 종료되는 문제
원인: 스킬 지속 시간 동안 큐가 비었을 때 스킬이 조기에 종료되는 문제가 발생했습니다.

해결 방법: ApplySkillEffect 메서드에서 큐가 비었을 때도 스킬 지속 시간 동안 루프가 계속 실행되도록 수정했습니다.

문제 2: 버블이 재활성화될 때 큐에 올바르게 추가되지 않는 문제
원인: 버블이 재활성화될 때 AddBubbleToQueue 메서드가 제대로 호출되지 않아 큐에 추가되지 않는 문제가 발생했습니다.

해결 방법: ReactivateBubble 메서드에서 버블을 재활성화할 때 AddBubbleToQueue 메서드를 올바르게 호출하여 큐에 추가되도록 수정했습니다.

문제 3: 큐에서 버블을 제거할 때 임시 큐를 사용하여 복잡성이 증가한 문제
원인: RemoveBubbleFromQueue 메서드에서 임시 큐를 사용하여 버블을 제거하는 과정이 복잡하여 버그가 발생할 가능성이 있었습니다.

해결 방법: 임시 큐를 사용하지 않고 간단하게 큐에서 버블을 제거하는 방법으로 수정했습니다.

BubbleClickSkill 클래스

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BubbleClickSkill : Skill
{
    public float clickInterval = 0.5f; // 자동 클릭 간격
    public float baseClickIntervalIncrease = 1; // 클릭 시간 증가량
    public int baseClickRateIncrease = 1; // 클릭 횟수 증가량

    private Queue<GameObject> bubbleQueue = new Queue<GameObject>();

    protected override void Start()
    {
        skillDuration = 300f; // 스킬 지속 시간
        cooldownTime = 1800f; // 스킬 쿨타임 30분
        currentLevel = 0;

        // 초기 UI 설정
        UpdateUI();
        UpdateCooldownUI(0);

        // 현재 활성화된 모든 버블을 큐에 추가
        GameObject[] bubbles = GameObject.FindGameObjectsWithTag("Bubble");
        foreach (GameObject bubble in bubbles)
        {
            AddBubbleToQueue(bubble);
        }
    }

    public void AddBubbleToQueue(GameObject bubble)
    {
        bubbleQueue.Enqueue(bubble);
    }

    public void RemoveBubbleFromQueue(GameObject bubble)
    {
        Queue<GameObject> tempQueue = new Queue<GameObject>();
        while (bubbleQueue.Count > 0)
        {
            GameObject currentBubble = bubbleQueue.Dequeue();
            if (currentBubble != bubble)
            {
                tempQueue.Enqueue(currentBubble);
            }
        }
        bubbleQueue = tempQueue;
    }

    public override void ActivateSkill()
    {
        if (!onCooldown && currentLevel > 0) // 해금된 경우에만 스킬 사용 가능
        {
            // 현재 활성화된 모든 버블을 큐에 추가
            GameObject[] bubbles = GameObject.FindGameObjectsWithTag("Bubble");
            foreach (GameObject bubble in bubbles)
            {
                AddBubbleToQueue(bubble);
            }
            StartCoroutine(SkillEffect());
        }
    }

    protected override IEnumerator ApplySkillEffect()
    {
        float elapsedTime = 0f;

        while (elapsedTime < skillDuration)
        {
            if (bubbleQueue.Count > 0)
            {
                ClickNextBubble();
            }

            yield return new WaitForSeconds(clickInterval);
            elapsedTime += clickInterval;
        }

        bubbleQueue.Clear(); // 스킬 종료 후 큐를 비웁니다.
    }

    private void ClickNextBubble()
    {
        if (bubbleQueue.Count > 0)
        {
            GameObject bubble = bubbleQueue.Dequeue();
            var heartButton = bubble.GetComponent<HeartButton>();
            if (heartButton != null)
            {
                heartButton.TouchHeartBubble();
            }
        }
    }

    protected override void UpdateClickValues()
    {
        clickInterval = Mathf.Max(0.1f, clickInterval - baseClickIntervalIncrease * currentLevel);
        baseClickRateIncrease = 1 + currentLevel; // 클릭 횟수 증가량은 레벨에 비례하여 증가
    }

    protected override void LevelUI()
    {
        currentLevelText.text = currentLevel > 0
            ? $"자동 클릭 스킬 레벨: {currentLevel}"
            : "자동 클릭 스킬이 해금되지 않았습니다";
    }

    protected override void NowskillInfoUI()
    {
        skillInfoText.text = currentLevel > 0
            ? $"현재 클릭 간격: {clickInterval}초 \n 클릭 횟수: {baseClickRateIncrease}"
            : "스킬이 해금되지 않았습니다";
    }
}

HeartButton 클래스

using System.Collections;
using UnityEngine;

public class HeartButton : MonoBehaviour
{
    // 클릭하면 생명력을 얻을 수 있는 버블
    Camera cam;
    private BubbleClickSkill bubbleClickSkill;

    private void Awake()
    {
        cam = Camera.main;
    }

    private void Start()
    {
        bubbleClickSkill = FindObjectOfType<BubbleClickSkill>();
    }

    private void Update()
    {
        LookCamera();
    }

    // 카메라를 보고 있도록 하지 않으면 버튼 자체가 회전해버림.
    private void LookCamera()
    {
        transform.LookAt(cam.transform);
        transform.rotation = Quaternion.Euler(transform.rotation.eulerAngles.x,
                                              transform.rotation.eulerAngles.y + 180, transform.rotation.eulerAngles.z);
    }

    public void TouchHeartBubble()
    {
        // 화면 터치 시 효과음 재생
        SoundManager.instance.PlaySFX(SoundManager.instance.sfxClips[0]);
        // 재화를 획득한다.
        LifeManager.Instance.IncreaseWater(LifeManager.Instance.touchData.touchIncreaseAmount);
        if (bubbleClickSkill != null)
        {
            bubbleClickSkill.RemoveBubbleFromQueue(gameObject);
        }
        // 사라진다.
        gameObject.SetActive(false);
        // 일정 시간 후 다시 활성화
        Invoke("ReactivateBubble", 5f); // 5초 후 다시 활성화
    }

    private void ReactivateBubble()
    {
        gameObject.SetActive(true);
        if (bubbleClickSkill != null)
        {
            bubbleClickSkill.AddBubbleToQueue(gameObject);
        }
    }
}

결론

오늘은 BubbleClickSkill과 HeartButton 클래스의 버그를 수정하여 스킬이 정상적으로 작동하도록 개선하고, 새로운 스킬인 LifeBoostSkill과 RootBoostSkill을 추가했습니다. 특히 트러블슈팅 과정에서 스킬 지속 시간 동안 큐가 비어도 스킬이 조기에 종료되지 않도록 수정하고, 버블이 재활성화될 때 큐에 올바르게 추가되지 않는 문제를 해결했습니다. 이러한 작업을 통해 스킬 시스템을 보다 견고하게 만들 수 있었습니다.

profile
게임개발자꿈나무

0개의 댓글