22.02.15 업적 창 구현하기
2022. 2. 15. 19:02ㆍUnity3D/수업 내용
업적 창은 지난 번 포스팅했던 상점 구현과 비슷하다.
데이터를 불러오고, UI에 표기하는 것.
[도전 과제]
- 보상 수령이 가능한 업적이 맨 위에, 진행 중인 업적이 가운데에, 모두 완료한 업적은 맨 밑에 나열되도록 할 것이다.
- 업적 1단계를 완료하고 보상을 받은 후, 보상이 늘어난 다음 단계의 업적 달성 조건을 보여준다.
- 업적 달성 정도에 따라 별 개수가 달라지고, 버튼 모양 또한 달라지게 한다.
- 업적 리스트가 실시간으로 갱신된다.
[해결 과정]
- Achievement Info 클래스를 만들어서 게임의 모든 업적 진행 상황을 담았다. 이 인포 클래스를 참조해서 업적이 진행 중인지, 완료된 상태인지, 보상 수령이 가능한 상태인지 판별한다.
- 업적 창에서 업적의 진행 상태에 따라서 위에 둘 것인지, 밑에 둘 것인지를 정하기 위해 상태별로 리스트를 만들었다. 완료된 업적의 인덱스는 리스트에 들어가서 나중에 슬롯을 생성할 때 가장 마지막에 생성하도록 한다.
- 버튼의 Onclick.AddListener로 대리자 함수를 실행하게 했다. 대리자에는 업적 창을 새로 갱신하는 코드가 들어있어서, 버튼을 눌러 보상을 받고난 다음에는, 그 업적 슬롯이 밑으로 내려가도록 했다.
실행 결과
소스 코드
public class GameInfo
{
public int score;
public int heart;
public int gold;
public int gem;
public HeroInfo hero;
public List<ItemInfo> inventory;
public List<StageInfo> stageInfos;
public List<AchievementInfo> achievementBook;
//for be referenced by achievement's slider.
public int collectedGoldSum;
public int killedMonsterSum;
public void InitGameInfo()
{
hero = new HeroInfo();
hero.Init();
inventory = new List<ItemInfo>();
InitInventory();
stageInfos = new List<StageInfo>();
achievementBook = new List<AchievementInfo>();
InitAchievementBook();
}
void InitAchievementBook()
{
Dictionary<int, AchievementData> dict = DataManager.GetInstance().GetDictAchievementData();
foreach(int data in dict.Keys)
{
achievementBook.Add(new AchievementInfo(data, 0, false));
}
}
}
public class AchievementInfo
{
public int id;
public int star;
public bool isFull;
public AchievementInfo(int id, int star, bool isFull)
{
this.id = id;
this.star = star;
this.isFull = isFull;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UIMissionControl : MonoBehaviour
{
[SerializeField] GameObject goPopUp;
[SerializeField] GameObject grid;
[SerializeField] GameObject slotPrefab;
List<int> finishAchiIndex = new List<int>();
List<int> undergoingAchiIndex = new List<int>();
List<int> isFullAchiIndex = new List<int>();
List<GameObject> genedSlots = new List<GameObject>();
public void BtnTest()
{
DataManager.GetInstance().AddUpGold(500);
DataManager.GetInstance().AddUpKilledMonster(1000);
LoadAchiSlot();
}
public void ClosePopUp()
{
goPopUp.SetActive(false);
}
public void OpenPopUp()
{
LoadAchiSlot();
goPopUp.SetActive(true);
}
void LoadAchiSlot()
{
//업적 창을 초기화하기 위해서
foreach(GameObject go in genedSlots)
{
Destroy(go);
}
genedSlots.RemoveRange(0, genedSlots.Count);
finishAchiIndex.RemoveRange(0, finishAchiIndex.Count);
isFullAchiIndex.RemoveRange(0, isFullAchiIndex.Count);
undergoingAchiIndex.RemoveRange(0, undergoingAchiIndex.Count);
//~업적 창을 초기화하기 위해서
Dictionary<int, AchievementData> dict = DataManager.GetInstance().GetDictAchievementData();
foreach(AchievementData data in dict.Values)
{
AchievementInfo info = DataManager.GetInstance().GetAchiInfo(data.id);
AchievementProgressData data2 = DataManager.GetInstance().GetAchiProgressData(data.progressId + (int)Mathf.Clamp(info.star, 0, 4));
//업적을 달성한 횟수(== 별 갯수)에 따라서
//업적의 다음 단계를 순차적으로 보여주게 한다.
//몬스터 100마리를 잡으세요인 조건일 때,
//정보를 보고 100마리를 잡았다면 보상수령이 가능한 상태로 만든다(== 'isFull = true')
if((float)DataManager.GetInstance().GetSliderReference(data.typeIndex) / (float)data2.numForPercentage >= 1f)
{
info.isFull = true;
}
//위에서부터 '완료한 상태', '보상수령이 가능한 상태', '진행 중'으로 구분하고,
//각각의 리스트가 구분된 인덱스를 가져간다.
if (info.star >= 5)
{
finishAchiIndex.Add(info.id);
}
else if (info.isFull)
{
isFullAchiIndex.Add(info.id);
}
else if (!info.isFull)
{
undergoingAchiIndex.Add(info.id);
}
}
C#
//각각의 리스트가 담고 있는 인덱스를 차례대로 꺼내어 업적 창에 순서대로 진열한다.
foreach(int index in isFullAchiIndex)
{
GameObject go = Instantiate(slotPrefab, grid.transform);
UIAchiSlotControl slot = go.GetComponent<UIAchiSlotControl>();
slot.InitSlot(index);
//이 구문이 업적 슬롯에 실제로 정보를 부여하는 코드이다.
//인덱스(업적id)에 따라 업적 슬롯에 해당 업적에 대한 정보를 채워넣는다.
slot.whenPressBtnClaim = LoadAchiSlot;
//버튼을 누를때마다 업적창을 갱신하기 위해 대리자에 함수를 넣어둔다.
genedSlots.Add(go);
//업적창을 초기화할때 사용하기 위해 담아둔다.
}
foreach(int index in undergoingAchiIndex)
{
GameObject go = Instantiate(slotPrefab, grid.transform);
UIAchiSlotControl slot = go.GetComponent<UIAchiSlotControl>();
slot.InitSlot(index);
slot.whenPressBtnClaim = LoadAchiSlot;
genedSlots.Add(go);
}
foreach (int index in finishAchiIndex)
{
GameObject go = Instantiate(slotPrefab, grid.transform);
UIAchiSlotControl slot = go.GetComponent<UIAchiSlotControl>();
slot.InitSlot(index);
slot.whenPressBtnClaim = LoadAchiSlot;
genedSlots.Add(go);
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.U2D;
public class UIAchiSlotControl : MonoBehaviour
{
int id;
[SerializeField] Image icon;
[SerializeField] Slider slider;
[SerializeField] Text nameText;
[SerializeField] Image budgetIcon;
[SerializeField] Text rewardText;
public GameObject[] BtnClaim;
[SerializeField] GameObject[] star;
[SerializeField] SpriteAtlas atlas;
public System.Action whenPressBtnClaim;
private void Start()
{
BtnClaim[1].GetComponent<Button>().onClick.AddListener(InitBtnClaim);
}
public void InitSlot(int id)
{
this.id = id;
LoadSlotInfo();
}
void InitBtnClaim()
{
AchievementData data = DataManager.GetInstance().GetAchievementData(id);
AchievementInfo info = DataManager.GetInstance().GetAchiInfo(data.id);
AchievementProgressData data2 = DataManager.GetInstance().GetAchiProgressData(data.progressId + (int)Mathf.Clamp(info.star, 0, 4));
BudgetData data3 = DataManager.GetInstance().GetBudgetData(data.budgetId);
info.isFull = false;
info.star++;
if (info.star >= 5)
{
info.star = 5;
this.slider.value = 1f;
}
else
{
this.slider.value = 0f;
}
DataManager.GetInstance().PressBtnClaim(data.budgetId, data2.rewardText);
whenPressBtnClaim();
}
public void LoadSlotInfo()
{
AchievementData data = DataManager.GetInstance().GetAchievementData(id);
AchievementInfo info = DataManager.GetInstance().GetAchiInfo(data.id);
AchievementProgressData data2 = DataManager.GetInstance().GetAchiProgressData(data.progressId + (int)Mathf.Clamp(info.star, 0, 4));
BudgetData data3 = DataManager.GetInstance().GetBudgetData(data.budgetId);
this.icon.sprite = atlas.GetSprite(data.spriteName);
this.slider.value = (float)DataManager.GetInstance().GetSliderReference(data.typeIndex) / (float)data2.numForPercentage;
this.nameText.text = data2.name;
this.budgetIcon.sprite = atlas.GetSprite(data3.spriteName);
this.rewardText.text = data3.textColor + data2.rewardText + "</color>";
ChooseClaimBtnFrame(info, data, data2);
FillStar(info);
}
void ChooseClaimBtnFrame(AchievementInfo info, AchievementData data, AchievementProgressData data2)
{
foreach (GameObject go in BtnClaim)
{
go.SetActive(false);
}
if (info.star >= 5)
{
BtnClaim[2].SetActive(true);
}
else if(info.isFull)
{
BtnClaim[1].SetActive(true);
} else if (!info.isFull)
{
BtnClaim[0].SetActive(true);
}
}
void FillStar(AchievementInfo info)
{
for(int s = 0; s < info.star; s++)
{
star[s].SetActive(true);
}
}
}
[피드백]
※ 업적이 여러 단계로 되어있을 경우 메인테이블에서 세분된 테이블로 한 번 더 넘어가주는 게 좋음.
시작 id의 위치를 간단하게 알 수 있었다.
※ UI 디자인부터 마치고 나서 스크립트 작업을 했을 때 UI의 구조가 한 눈에 들어와서 코드를 설계하는 게 수월했다.
※ 서로 다른 테이블에 있는 데이터를 참조할 때 참조하는 id를 주의깊게 살피자. 인덱스오류가 자주 발생하는 부분이다.
※ 리스트는 사용하기 직전에 new를 쓰면 정상적으로 동작하지 않는다. new와 사용하는 구문 사이에 시간 차를 조금 둬야 한다.
※ 버튼의 AddListener에 들어가는 함수는 들어갈 당시의 상태와 동일하게 작동한다. 버튼을 누르는 때마다 상태가 달라져야 한다면 누를 때마다 참조를 새로 구해야 한다.
'Unity3D > 수업 내용' 카테고리의 다른 글
22.02.18 애니메이션 이벤트, HUD 텍스트 표기하기 (0) | 2022.02.20 |
---|---|
22.02.14 Shop 메뉴 구현하기 (0) | 2022.02.14 |
22.02.11 UI 기능 추가 (페이지, 해금, 재화 표시) (0) | 2022.02.11 |
22.02.04 JSON 데이터를 UI에 바인딩하기 (0) | 2022.02.05 |
22.02.03 Canvas와 UI 배우기 (0) | 2022.02.03 |