
RootBase 클래스 수정
RootBase 클래스에 꽃 오브젝트 배치를 위한 새로운 로직 추가.
ActivateNextPlantObject 메서드에서 미리 배치된 오브젝트를 활성화하거나, 배열이 비어 있으면 터레인에 새로운 식물을 배치하도록 수정.
RootDataSO 클래스 수정
RootDataSO 클래스에 새로운 필드 추가: centerPosition, unlockConditionText, requiredOfflineRewardSkillLevel.
중심 위치와 해금 조건 텍스트를 스크립터블 오브젝트에서 관리하도록 설정.
RootBase 클래스와 OfflineRewardAmountSkill 클래스 간의 상호작용
RootBase 클래스에 OfflineRewardAmountSkill 객체를 참조하는 필드를 추가.
CheckUnlockCondition 메서드에서 오프라인 보상 스킬 레벨을 확인하여 해금 조건을 만족하는지 검사.
using System.Collections.Generic;
using System.Numerics;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using Quaternion = UnityEngine.Quaternion;
using Vector3 = UnityEngine.Vector3;
public class RootBase : MonoBehaviour, IRoot
{
// 기존 필드
public Terrain terrain;
public GameObject flowerPrefab;
public float brushSize = 1f;
public int flowersPerPosition = 10;
public GameObject[] prePlacedFlowers;
protected List<Vector3> flowerPositions;
private int currentFlowerIndex = 0;
private int currentPrePlacedFlowerIndex = 0;
public int rootLevel = 0;
public BigInteger baseLifeGeneration = 1;
public BigInteger initialUpgradeCost = 20;
public BigInteger unlockCost = 0;
public BigInteger upgradeLifeCost;
public TextMeshProUGUI rootLevelText;
public TextMeshProUGUI generationRateText;
public TextMeshProUGUI rootUpgradeCostText;
public Image lockImage;
public TextMeshProUGUI lockText;
public bool isUnlocked = false;
public int unlockThreshold = 5;
private int requiredOfflineRewardSkillLevel = 1;
public event System.Action OnGenerationRateChanged;
protected CameraTransition cameraTransition;
private BigInteger currentMultiplier;
private Coroutine boostCoroutine;
public RootDataSO rootDataSO;
public OfflineRewardAmountSkill offlineRewardAmountSkill;
protected virtual void Start()
{
flowerPositions = new List<Vector3>();
if (rootDataSO != null)
{
unlockThreshold = rootDataSO.unlockThreshold;
baseLifeGeneration = BigInteger.Parse(rootDataSO.baseLifeGenerationString);
unlockCost = BigInteger.Parse(rootDataSO.unlockCostString);
requiredOfflineRewardSkillLevel = rootDataSO.requiredOfflineRewardSkillLevel;
}
CalculateFlowerPositions();
OnGenerationRateChanged += UpdateUI;
cameraTransition = FindObjectOfType<CameraTransition>();
currentMultiplier = 1;
LifeManager.Instance.RegisterRoot(this);
UpdateUI();
}
protected virtual void GenerateLife()
{
if (!isUnlocked || rootLevel == 0) return;
BigInteger generatedLife = GetTotalLifeGeneration();
InvokeLifeGenerated(generatedLife);
}
protected void InvokeLifeGenerated(BigInteger amount)
{
//OnLifeGenerated?.Invoke(amount);
}
public BigInteger CalculateUpgradeCost()
{
if (rootLevel == 0)
{
return unlockCost;
}
else
{
return unlockCost * BigInteger.Pow(120, rootLevel) / BigInteger.Pow(100, rootLevel);
}
}
public virtual void UpgradeLifeGeneration()
{
if (!isUnlocked) return;
rootLevel++;
if (rootLevel == 1 || rootLevel % 25 == 0)
{
ActivateNextPlantObject();
if (rootLevel % 25 == 0)
{
baseLifeGeneration *= 2;
}
}
upgradeLifeCost = CalculateUpgradeCost();
OnGenerationRateChanged?.Invoke();
AutoObjectManager.Instance.CalculateTotalAutoGeneration();
UpdateUI();
}
private void ActivateNextPlantObject()
{
if (currentPrePlacedFlowerIndex < prePlacedFlowers.Length)
{
prePlacedFlowers[currentPrePlacedFlowerIndex].SetActive(true);
currentPrePlacedFlowerIndex++;
}
else
{
PlaceFlowers(flowerPositions, flowerPrefab, ref currentFlowerIndex);
}
}
public virtual void CalculateFlowerPositions()
{
Vector3 terrainSize = terrain.terrainData.size;
Vector3 terrainPosition = terrain.transform.position;
Vector3 terrainCenter = GetRootCenterPosition();
Debug.Log($"{this.GetType().Name} 중심 위치: {terrainCenter}");
float innerRadius = brushSize / 4;
float outerRadius = brushSize / 2;
int numPositions = 8;
for (int i = 0; i < numPositions; i++)
{
float angle = Mathf.Deg2Rad * (360f / numPositions * i);
float x = terrainCenter.x + innerRadius * Mathf.Cos(angle);
float z = terrainCenter.z + innerRadius * Mathf.Sin(angle);
flowerPositions.Add(new Vector3(x, terrainPosition.y, z));
}
for (int i = 0; i < numPositions; i++)
{
float angle = Mathf.Deg2Rad * (360f / numPositions * i);
float x = terrainCenter.x + outerRadius * Mathf.Cos(angle);
float z = terrainCenter.z + outerRadius * Mathf.Sin(angle);
flowerPositions.Add(new Vector3(x, terrainPosition.y, z));
}
}
protected virtual Vector3 GetRootCenterPosition()
{
if (rootDataSO != null)
{
return rootDataSO.centerPosition;
}
return new Vector3(-7.3f, -0.66f, -5);
}
void PlaceFlowers(List<Vector3> flowerPositions, GameObject flowerPrefab, ref int currentFlowerIndex)
{
if (currentFlowerIndex >= flowerPositions.Count)
{
currentFlowerIndex = 0;
}
Vector3 flowerPosition = flowerPositions[currentFlowerIndex];
for (int i = 0; i < flowersPerPosition; i++)
{
float angle = Random.Range(0f, 2f * Mathf.PI);
float radius = Random.Range(0f, brushSize / 6);
Vector3 randomOffset = new Vector3(radius * Mathf.Cos(angle), 0, radius * Mathf.Sin(angle));
Vector3 finalPosition = flowerPosition + randomOffset;
float y = terrain.SampleHeight(finalPosition) + terrain.transform.position.y;
finalPosition.y = y;
GameObject newFlower = Instantiate(flowerPrefab, finalPosition, Quaternion.identity);
newFlower.transform.parent = terrain.transform;
Vector3 terrainNormal = terrain.terrainData.GetInterpolatedNormal(
(finalPosition.x - terrain.transform.position.x) / terrain.terrainData.size.x,
(finalPosition.z - terrain.transform.position.z) / terrain.terrainData.size.z
);
newFlower.transform.up = terrainNormal;
newFlower.isStatic = true;
foreach (var renderer in newFlower.GetComponentsInChildren<Renderer>())
{
if (renderer.sharedMaterial != null)
{
renderer.sharedMaterial.enableInstancing = true;
}
}
}
currentFlowerIndex++;
}
public virtual void UpdateUI()
{
UpdateRootLevelUI(rootLevel, upgradeLifeCost);
UpdateGenerationRateUI(GetTotalLifeGeneration());
UpdateUnlockUI();
}
public virtual void ApplyIncreaseRate(BigInteger rate)
{
baseLifeGeneration = baseLifeGeneration * (1 + rate);
OnGenerationRateChanged?.Invoke();
UpdateUI();
}
public virtual void UpdateRootLevelUI(int rootLevel, BigInteger upgradeCost)
{
if (rootLevelText != null)
{
rootLevelText.text = isUnlocked ? $"꽃 레벨: {rootLevel}" : $"꽃 레벨: 0";
}
if (rootUpgradeCostText != null)
{
rootUpgradeCostText.text = $"강화 비용: {BigIntegerUtils.FormatBigInteger(upgradeCost)} 물";
}
}
public virtual void UpdateGenerationRateUI(BigInteger generationRate)
{
if (generationRateText != null)
{
generationRateText.text = $"생산률: {BigIntegerUtils.FormatBigInteger(generationRate)} 물/초";
if (isUnlocked && rootLevel == 0)
{
BigInteger levelOneGenerationRate = baseLifeGeneration * BigInteger.Pow(103, 0) / BigInteger.Pow(100, 0);
generationRateText.text = $"생산률: {BigIntegerUtils.FormatBigInteger(generationRate)} 물/초 \n1레벨 업그레이드시 자동생산: {BigIntegerUtils.FormatBigInteger(levelOneGenerationRate)} 물/초";
}
if (!isUnlocked && rootLevel == 0)
{
BigInteger levelOneGenerationRate = baseLifeGeneration * BigInteger.Pow(103, 0) / BigInteger.Pow(100, 0);
generationRateText.text = $"생산률: {BigIntegerUtils.FormatBigInteger(generationRate)} 물/초 \n1레벨 업그레이드시 자동생산: {BigIntegerUtils.FormatBigInteger(levelOneGenerationRate)} 물/초";
}
}
}
public virtual void UpdateUnlockUI()
{
if (!isUnlocked)
{
if (lockText != null)
{
lockText.gameObject.SetActive(true);
if (rootDataSO != null)
{
lockText.text = rootDataSO.unlockConditionText.Replace("\\n", "\n");
}
else
{
lockText.text = $"잠금 해제 조건: 세계수 레벨 {unlockThreshold}\n꽃 해금 시 배치 가능 동물 수 + 5";
}
}
if (lockImage != null)
{
lockImage.gameObject.SetActive(true);
}
}
else
{
if (lockText != null)
{
lockText.gameObject.SetActive(false);
}
if (lockImage != null)
{
lockImage.gameObject.SetActive(false);
}
}
}
public virtual BigInteger GetTotalLifeGeneration()
{
if (!isUnlocked || rootLevel == 0) return 0;
BigInteger baseGeneration = baseLifeGeneration * BigInteger.Pow(103, rootLevel - 1) / BigInteger.Pow(100, rootLevel - 1);
BigInteger totalGeneration = baseGeneration * currentMultiplier;
return totalGeneration;
}
public void Unlock()
{
isUnlocked = true;
upgradeLifeCost = CalculateUpgradeCost();
OnGenerationRateChanged?.Invoke();
DataManager.Instance.animalGenerateData.AddMaxAnimalCount();
UpdateUI();
Debug.Log("Unlocked successfully.");
}
private void CheckUnlockCondition()
{
if (!isUnlocked && DataManager.Instance.touchData != null
&& DataManager.Instance.touchData.touchIncreaseLevel >= unlockThreshold)
{
Unlock();
}
if (!isUnlocked && offlineRewardAmountSkill != null
&& offlineRewardAmountSkill.currentLevel >= requiredOfflineRewardSkillLevel)
{
Unlock();
}
}
public void ApplyTemporaryBoost(BigInteger multiplier, float duration)
{
if (boostCoroutine != null)
{
StopCoroutine(boostCoroutine);
}
boostCoroutine = StartCoroutine(TemporaryBoost(multiplier, duration));
}
private IEnumerator TemporaryBoost(BigInteger multiplier, float duration)
{
currentMultiplier = multiplier;
OnGenerationRateChanged?.Invoke();
UpdateUI();
yield return new WaitForSeconds(duration);
currentMultiplier = 1;
OnGenerationRateChanged?.Invoke();
UpdateUI();
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "RootDataSO", menuName = "RootData/Default", order = 0)]
public class RootDataSO : ScriptableObject
{
public int unlockThreshold;
public string baseLifeGenerationString;
public string unlockCostString;
public Vector3 centerPosition;
public string unlockConditionText;
public int requiredOfflineRewardSkillLevel; // 추가된 필드
}
문제 발생: Root1 ~ Root10이 겹쳐서 생성되는 문제 발생.
해결 과정:
CalculateFlowerPositions 메서드에서 중심 위치를 GetRootCenterPosition을 통해 가져오도록 수정.
RootDataSO에 centerPosition 필드를 추가하여 각 Root 객체가 고유한 중심 위치를 가질 수 있도록 함.
각 Root 객체에 RootDataSO 인스턴스를 할당하고 중심 위치를 설정.
문제 발생: 잠금 해제 조건 텍스트가 한 줄로 표시되는 문제 발생.
해결 과정:
RootDataSO에 unlockConditionText 필드를 추가.
UpdateUnlockUI 메서드에서 텍스트를 rootDataSO.unlockConditionText.Replace("\n", "\n")로 설정하여 줄바꿈이 적용되도록 수정.
문제 발생: 오프라인 보상 스킬 레벨 조건에 따른 해금 기능 추가 요청.
해결 과정:
RootBase 클래스에 OfflineRewardAmountSkill 객체를 참조하는 필드 추가.
CheckUnlockCondition 메서드에서 오프라인 보상 스킬의 레벨을 확인하여 해금 조건을 만족하는지 검사.
RootDataSO에 requiredOfflineRewardSkillLevel 필드 추가하여 해금 조건 레벨을 관리하도록 설정.
각 RootDataSO 인스턴스에 필요한 해금 조건 레벨을 설정.
오늘 작업을 통해 각 Root 객체가 고유한 중심 위치를 가지도록 설정하고, 오프라인 보상 스킬 레벨에 따른 해금 조건을 적용했습니다. 이를 위해 RootBase 클래스와 RootDataSO 클래스에 새로운 필드를 추가하고, 해금 조건을 확인하는 로직을 수정했습니다.