[Unity] 내배캠 최종프로젝트 : 루시퍼 서바이벌 23일차,
오늘은 어제 하던 RewardManager를 이어서 작업했다.
일단 며칠간 집에서 작업을 할수없어 맥북을 들고 나와서 작업중인데 정말 맥은 쓸때마다 적응이 안된다, 계속 단축키도 햇갈리고 창 잘못 내려서 어디갓지 하고있고 당황의 연속이었다.............
어제 내가 마무리할때 오늘 크게 해야할 것을 두가지로 정했는데
1. foreach를 사용하기 보다 다른 방법을 찾아서 리팩토링 할것,
2. 상호작용한 자원의 프리펩 색을 변경할것
근데 문제가 생겼다.
작업하고 백업하는 과정에서 코드가 섞였는지 뭐가 이상하다.. 확률도 안맞고 상호작용 횟수 체크를 위한 bool값도 기본 false로 되어있는게 한번 상호작용 하는순간 모든 프리펩이 true가 되는 마법도 있었고 뭔지도 모를 에러들도 뜨더라.
- 일단 뭔지도 모를 에러들은 깃에서 받아오는 과정에서 에셋 문제(추가, 제거 등)로 인한듯 하며 다시한번 패치받은 뒤로는 뜨지 않았다.
- 확률의 경우 데이터가 빠져있어서 이상하게 된거였다. 전부터 종종 ScriptableObject를 이용해서 Inspector에 넣어두고 깃에서 파일을 받아오거나 유니티를 껏다키면 사라지는 경우가 종종 생기더라.. 내가 저장을 안헀나ㅎㅎ..
내가 코드 작성할때 확률이 100이라는 가정 하에 진행되게 해두어서 이런식으로 에러메세지가 계속 뜨고 이로인해 자원파밍이 안되더라..
이부분은 다시 넣어주었더니 정상적으로 작동했다.
그리고 이제 문제의 RewardManager.. 뒤죽박죽이 되어버리고 뭔가 수정하기는 간단하지만 어차피 foreach도 없애고 여러가지로 리팩토링 예정이었으니 그냥 방법을 좀 바꿔보자 싶었다.
처음엔 FarmingDataSO를 리스트로 들고있고 각 데이터를 rcode라는 고유 문자열 키로 구분해서 딕셔너리에 저장, 이용하여 데이터를 빠르게 찾아오고 보상을 뽑을땐 foreach로 하나하나 확인하면서 결과를 골랏는데 foreach로 인한 성능저하가 우려되어 방식을 바꾸기로 했었으니 다른방식으로 진행해주었다.
public class RewardManager : Singleton<RewardManager>
{
protected override bool IsPersistent => false;
[SerializeField] private List<FarmingDataSO> farmingDataList;
// 누적 확률 테이블, 정렬된 리스트 사용
private List<(FarmingDataSO data, float cumulativeProbability)> probabilityTable;
// 확률 테이블 세팅 (초기화)
private new void Awake()
{
probabilityTable = new List<(FarmingDataSO, float)>();
float total = 0f;
// 인덱스 기반으로 리스트 안에 들어있는 데이터를 순회
for (int i = 0; i < farmingDataList.Count; i++)
{
var data = farmingDataList[i]; // 현재 인덱스 i에 해당하는 데이터를 꺼냄
if (data == null) // data가 null일 경우를 위해 인덱스 기반으로
{
continue; // null을 누적 확률 계산에 넣으면 오류발생으로 인해 건너뛰기
}
total += data.probability; // total에 지금까지 더한 확률을 저장, total의 역할
probabilityTable.Add((data, total)); // 누적된 total과 data를 튜플로 엮어서 probablitiyTable에 추가
}
}
/// <summary>
/// 자원채집 함수
/// </summary>
public void TryGatherResource()
{
var result = GetRandomFarmingResult();
// 추후 이쪽엔 UI부분이나 다른 내용 넣기, 현재는 확인용으로 Debug.Log 사용
Debug.Log($"결과: {result.displayName}, 보상: {result.gain}");
GiveReward(result.gain); // 실 보상
}
/// <summary>
/// 자원 보상 랜덤보상
/// </summary>
/// <returns></returns>
private FarmingDataSO GetRandomFarmingResult()
{
// 0부터 100까지의 랜덤한 float 값을 뽑기 (확률 비교용)
float rand = UnityEngine.Random.Range(0f, 100f);
int left = 0;
int right = probabilityTable.Count - 1;
// 이진탐색 이용, left가 right보다 작거나 같을 동안 반복하면서 중간값을 기준으로 탐색, rand가 속한 구간을 찾기
while (left <= right)
{
int mid = (left + right) / 2;
float currentProb = probabilityTable[mid].cumulativeProbability;
// 랜덤 숫자가 누적 확률보다 작거나 같을경우
if (rand <= currentProb)
{
// 재확인, 첫 항목이거나 이전 구간보다 rand가 큰 경우
if (mid == 0 || rand > probabilityTable[mid - 1].cumulativeProbability)
{
return probabilityTable[mid].data; // 반환
}
right = mid - 1; // 왼쪽구간 재탐색
}
else
{
left = mid + 1; // rand가 더 크므로 오른쪽구간 재탐색
}
}
// 어떤 항목도 선택되지 않았을 경우 방지, 리턴
return probabilityTable[^1].data;
}
/// <summary>
/// 보상지급
/// </summary>
/// <param name="amount"></param>
private void GiveReward(int amount)
{
// 보상 처리
}
}
바뀐 구조는 rcode는 쓰지않고 리스트에 FarmingDataSO를 직접 넣고 리스트에 누적 확률값을 같이 저장, 결과를 찾을땐 리스트를 이진 탐색으로 빠르게 조회한다.
먼저 게임이 시작되거나 해당 매니저가 생성되면 Awake()에서 각각 보상에 누적 확률을 계산해서 저장해두고 리스트를 만들어두고
보상을 뽑는 함수에선 0~100사이의 랜덤 숫자를 하나 뽑고 이 숫자가 어디 누적 확률 구간에 해당하는지를 이진 탐색으로 빠르게 찾아내고 찾은 보상 데이터를 기반으로 실제 보상을 지급한다.
기존구조는 유연하지만 복잡했고 foreach는 최악의 경우 100번을 다 돌아야 하는 경우가 있을 수 있었으며, 바뀐 구조는 단순하고 빠르며 단순한 보상을 주는 기능이기 때문에 이쪽이 작성하고 보니 훨씬 괜찮아보였다.
그리고 자원 채집 횟수를 위한 bool값은 프리펩에 직접 들어가있는 ResourceTile쪽으로 이동해주었고, 색 변경 역시 TileManager가 아닌 이쪽에서 불값에 의해 변경되게 진행하려고 한다.
public class ResourceTile : BaseInteractable
{
[SerializeField] private bool isGathered = false; // 이미 채취했는지 여부
public override void Interact(PlayerEntity player)
{
if (isGathered)
{
Debug.Log("이미 채취한 자원입니다.");
return;
}
GatherTile();
}
public void GatherTile()
{
isGathered = true; // 채취 상태로 변경
RewardManager.Instance.TryGatherResource();
if (resourceRenderer != null)
{
// Material 색 변경
}
}
}
이렇게 하니 RewardManager에서 복잡하게 하지 않아도 간단하게 한번만 채취할수 있게 된다, 이렇게 하면 해야할 일 1번은 끝.
다만 true인 상태에서 색을 변경해주고싶었는데, renderer를 이용해 간단하게 색을 바꿀수있는 구조가 아닌 쉐이더+마테리얼을 프리펩마다 각자 갖고있어 이부분에 대해서는 어떤식으로 해야할까 하던 중 조언을 받고 두 방식 중 하나를 선택해서 결정하려고 한다.
- 마테리얼 내 BaseMap의 이미지를 변경하여 어두운 색으로 바꿔주기
- 프리펩 안의 마테리얼 배열에 있는 마테리얼들의 베이스 값을 코드로 낮춰주기
이 두가지는 하루 더 고민해보고 맞는 방식으로 진행하고자 한다. 그래서 결국 2번은 진행하지 못했다...
이제 색 바꾸는것과 RewardManager의 GiveReward()부분 작성하면 마무리!
GiveReward()의 경우 다른 매니저에 구현되어있는 재화를 연결해주어 보상을 지급해주는 식으로 작성하면 된다.
이렇게 하면 어느정도 코드 작성은 마무리 될듯 하다..! 조금만 더 파이팅
1. 추가로 오늘 튜터님과 모의면접을 진행했는데 값/참조 타입과 힙/스택 영역에 대해 공부할 필요가 있었다고 느꼈으며 이부분은 내가 공부하던 부분이었기에 주말동안 공부하여 어느정도 숙지하고자 한다.
- 값/참조타입에 대해선 어느정도 기억은 나는데 힙/스택영역은 어떤식으로 대답해야할지 모르겠어서 어.... 만 했다. 머쓱했다ㅎㅎ..
2. 코드 작성할 때 클래스를 정하는 기준에 대해서 팀원분께 조언을 좀 받았다. 목적기반 / 기능기반에 대한 내용이었는데 덕분에 어디에 어떤 재화가 들어가야할지 감을 좀 잡게되었다. 이부분은 나중에 따로 정리해서 올릴 예정
- 그래서 이제 GiveReward() 작성이 가능해진것이다,, 우선순위는 아니었기에 다행인..가?