어제에 이어서
- 같은 아이템 데이터를 여러곳에 넣으면 다 참조하는 형식이라 여러곳이 장착/탈착으로 변경됨.
- 데이터 장착하면 원본 데이터의 bool값이 그대로 남는 현상
-> 데이터를 복제해서 사용하는 것으로 해결
복제해서 활용하는 게 더 권장되는 방법.
원본을 그대로 쓰는 건 데이터가 바뀌면 다른것도 다 바뀌기때문에 권장하지 않음
allSlots[0].item_Info = Instantiate(itemDatas[0]);
//깊은 복사 - 원본의 데이터를 가진 똑같은 애를 새로 만들어서 사용하는 것
//얕은 복사 - 원본을 그대로 복사해서 쓰는 거
_attackTxt.text = $"{GameManager.player.attack}
{(GameManager.player.equipAttack == 0 ? "" : $"+ {GameManager.player.equipAttack}")}";
상점에서 아이템을 구매하는 것 또한 Scriptabel Object를 활용하면 수월하다.
public void BuyItem(Item item)
{
var slots = Inventory.instance.allSlots;
var playerData = GameManager.player;
if (playerData.gold < item.price)
{
OpenWarningPopUp();
return;
}
for (int i = 0; i < slots.Length; i++)
{
if (slots[i].item_Info == null)
{
slots[i].item_Info = Instantiate(item);
break;
}
}
GameManager.player.gold -= item.price;
GameManager.instance.CallBackStatChangeEvent();
OpenConfirmPopUp();
}
구매할 수 있는 조건이 먼저 충족 되었는지 체크하고, 구매 가능하다면 인벤토리의 슬롯 중 빈 곳을 찾아 그 곳의 아이템 데이터에 구매한 아이템 데이터를 복제해서 넣어주고,
구매한 금액만큼 차감한다.
인벤토리를 한 번 Active해야 Slot들을 준비할 수 있다.
이 부분들을 수정하기 스크립트 위치를 옮기거나 해봤지만, 애초에 오브젝트가 비활성화 상태라면 해결되지 않는다.
그래서 인벤토리의 Awake에서 Slot들을 준비하고, Start에서 한번 껐다키는 것으로 준비할 시간을 만들어주었다.
경험치 획득과 레벨업에 관한 부분은 현재 몬스터를 잡는 기능이 없어서 GameManager에서 간단하게 구현해보았다.
public void ExpGain() //버튼 클릭 시 경험치를 얻을 수 있다.
{
player.curExp += 10;
if (player.curExp >= player.requireExp) //일정 조건이 만족되면 레벨업 함수 실행
{
LevelUp();
}
CallBackStatChangeEvent(); //스탯 변화에 따른 UI 업데이트
}
float GetPercent() //Image.Filled Amout를 위한 퍼센테이지값
{
return player.curExp / (float)player.requireExp;
}
void LevelUp()
{
player.level++;
player.curExp -= player.requireExp;
player.requireExp = (int)(player.requireExp*1.5f);
}
현재 구상으로는 한 번 레벨업 하면 감소되진 않기때문에
레벨 / 레벨별 필요 경험치 / 지금 나의 경험치
이렇게 구성해서 사용하고 있다.
하지만 레벨이 감소하는 구조라면 셋을 종합적으로 구성해서 나의 경험치의 Amout로 구성해야되지 않을까.
어제 과제를 만들다가 프리팹의 스크립트에는 외부의 데이터가 할당이 되지 않던 것을 보았다.
그래서 오늘도 만들 때 외부 데이터를 스크립트로 할당해주는 방법을 사용했었다.
transform.Find()를 많이 사용했는데, 이 함수는 하이어라키창 전체를 헤집기 때문에 좋은 방법이 아닐 듯 하여 튜터님께 질문드리러 갔다.
그래서 두 가지 방법을 알아왔다.
- Find로 찾는 것보다 GetChild(index)로 찾는게 안전하다. 비활성화 되었을 시 찾지 못하기도 하고, Find로 찾을 시 문자가 잘못 되어도 컴파일은 되기 때문에 오류를 찾기 쉽지 않다.
-> 경로를 직접 찾아서 넣어준다는 점이다. 예를들어 A라는 부모 오브젝트의 B,C 자식 오브젝트가 있다 생각하고, C를 가져오려 한다면 GetChild(1)을 사용해서 오브젝트를 가져온다는 것이다.transform.GetChild(0).GetChild(0).GetComponent<Image>(); //이런 느낌
- 프리팹 오브젝트 기준으로 자식에 있는 오브젝트들은 프리팹으로 할당해도 저장이 된다.
무슨 얘기냐면 프리팹의 외부 요소들은 할당되지 않지만, 내부의 요소들은 할당하면 그 상태로 저장된다는 이야기다.
결론 : Find는 피하는게 좋다. 뭐든간에 직접 지정하는 게 좋음.
일반적으로 버튼에 이벤트를 연결할 때는 컴포넌트에서 직접 할당했다.
하지만 지금처럼 프리팹 내부의 버튼에 연결하려는 경우 직접 할당은 할 수 없다.
그럴 때 AddListener를 활용할 수 있다.
shopslots.buyBtn.onClick.AddListener(() => BuyItem(shopslots.itemData));
//코루틴이나 매개변수 있는 것들
shopslots.buyBtn.onClick.AddListener(함수 이름);
위 스크립트에서는 상점의 아이템 판매 슬롯들을 세팅하는 함수를 포함하고 있다.
그 함수의 내용으로, 현재 스크립트에서 프리팹을 세팅할 때, 프리팹의 버튼에 접근해 현재 스크립트의 함수를 실행시키도록 연결시켜줄 수 있다는 것이다.
주의할 사항으로 AddListener가 delegate이기 때문에
shopslots.buyBtn.onClick.AddListener(BuyItem(shopslots.itemData));
이렇게 넣으면 안된다. 위처럼 넣을 시 BuyItem을 실행한 모양새가 되기 때문에
람다나 익명함수를 사용해 넣어주어야한다.
오늘 우수 TIL 선정 된 내용을 보다가 신기한 내용을 찾았다.
위처럼 DontDestoryOnLoad를 넣으면 사라지지도 않겠지만, 저건 굳이 싱글턴을 안해도 사라지지 않는다.
그래서 따로 싱글턴만 된 오브젝트의 데이터가 씬 전환 시 남아있을까? 라고 생각해서 실험한 결과, 당연하게도 메모리의 Data 영역에 저장되어 있으니 남아있더라.
'싱글턴 된 객체가 파괴되지 않는다'에서 객체가 Object가 아니라는 걸 뒤늦게 이해했다.
오브젝트가 사라져서 그렇지, 이런 방법을 이용한다면 지난번처럼 DontDestoryOnLoad로 씬을 넘기고 파괴하는 방법말고도 사용할 수 있다.
근데 생성/파괴가 리소스를 먹는다고 하지만, Data에 계속 남아있는 것보다 더 좋지 않을까싶다.