유니티 RPG - 44. 상점 구현
Intro
이번 포스팅에서는 상점을 만들고, 아이템을 구매하는것을 구현해보았습니다.
상점 UI
상점은 UI 틀을 만들고, 유니티의 Scroll View 를 이용하여 스크롤이 가능한 형태로 만들어보았습니다.
상점 NPC에 UI를 만들어 주었고, GUI는 아래와 같은 형태로 구상했습니다.
UI의 구성을 보면,
- StoreUI
- Header 영역 : 인벤토리, 프로필등과 같이 마우스로 드래그할 수 있는 부분입니다.
- UI를 비활성화 시킬 수 있는 버튼이 있습니다.
- Content 영역 : 플레이어에게 보여질 판매 아이템들이 들어갈 영역입니다.
- Scroll View 를 생성
- Viewport : 보여질 부분
- Content : 판매 아이템들이 위치할 부분
- Content 에 Vertical LayoutGroup 컴포넌트를 추가하고, 여러개의 아이템이 들어갈 수 있도록 했습니다.
- 각 아이템 슬롯에는 판매하는 아이템의 id, 아이콘, 아이템이름, 설명, 판매가격등의 정보가 들어갑니다.
- Content : 판매 아이템들이 위치할 부분
- ScrollBar Horizontal 는 필요가 없으므로 삭제하였습니다.
- Header 영역 : 인벤토리, 프로필등과 같이 마우스로 드래그할 수 있는 부분입니다.
StoreItemSlotUI 클래스
StoreItemSlotUI 클래스는 상점에서 판매할 각 아이템 슬롯들이 가질 아이템데이터를 관리합니다.
아이템은 ID 범위에따라 Portion(10001~19999), Armor(20001~29999), Weapon(30001~39999) 로 분류하였습니다.
using UnityEngine;
using UnityEngine.UI;
/*
StoreItemSlotUI
- 상점에서 판매하는 아이템들의 정보를 가짐
- 아이템 ID 및 ItemData
- 아이템 ID에 따라 아이템종류를 분류하고, 판매아이템들을 세팅
- 인벤토리와 연결하여 아이템을 구매하면 인벤토리에 아이템 생성
*/
public class StoreItemSlotUI : MonoBehaviour
{
public int itemId; // 아이템 ID
[SerializeField] private Image itemIcon; // 아이템 아이콘
[SerializeField] private Text itemNameText; // 아이템 이름
[SerializeField] private Text itemExplanation; // 아이템 설명
[SerializeField] private Text itemPrice; // 아이템 가격
[SerializeField] private Button perchaseBtn; // 구매 버튼
[SerializeField] private Inventory inventory; // 연결된 인벤토리
private ArmorItemData armorItemData;
private WeaponItemData weaponItemData;
private PortionItemData portionItemData;
private ItemData curItemData; // 현재 슬롯의 아이템 데이터
private int price; // 아이템 가격
private void Start()
{
GetItemData();
perchaseBtn.onClick.AddListener(PerchaseItem);
}
// 구매 버튼 이벤트
private void PerchaseItem()
{
if(DataManager.Instance.GetPlayerData().Gold > price)
{
inventory.AddItem(curItemData);
DataManager.Instance.GetPlayerData().UseGold(price);
}
else
{
Debug.Log("골드가 부족합니다.");
}
}
// 아이템 타입별 데이터 불러오기
private void GetItemData()
{
// 1. 포션아이템
if(itemId > 10000 && itemId < 20000)
{
portionItemData = DataManager.Instance.GetPortionDataById(itemId);
curItemData = portionItemData;
SetSlotData(portionItemData);
}
// 2. 방어구아이템
else if(itemId > 20000 && itemId < 30000)
{
armorItemData = DataManager.Instance.GetArmorDataById(itemId);
curItemData = armorItemData;
SetSlotData(armorItemData);
}
// 3. 무기아이템
else if(itemId > 30000 && itemId < 40000)
{
weaponItemData = DataManager.Instance.GetWeaponDataById(itemId);
curItemData = weaponItemData;
SetSlotData(weaponItemData);
}
}
// 아이템 슬롯 정보 채우기
private void SetSlotData(ItemData data)
{
// 1. 아이콘
ResourceManager.Instance.LoadIcon(data.ItemIcon, sprite =>
{
// 성공
if (sprite != null)
{
itemIcon.sprite = sprite;
}
else
{
Debug.Log($"Failed to load icon for item : {data.ItemIcon}");
}
});
// 2. 아이템 이름
itemNameText.text = data.ItemName;
// 3. 아이템 설명
itemExplanation.text = data.ItemExplanation;
// 4. 아이템 가격
itemPrice.text = data.ItemPrice.ToString() + "G";
price = data.ItemPrice;
}
}
- 플레이어가 충분한 골드를 가지고있다면, 골드를 차감하고 인벤토리에 아이템을 생성합니다.
NPC 수정
여러 NPC들을 구현하기위해 NPC가 가지는 공통적인 부분은 NPC 클래스에, 개별적인 부분은 NPC를 상속받은 클래스에 구현하도록 했습니다.
/*
NPC
- 플레이어와 상호작용(G키)
ㄴ BoxCollider
- DialogueManager 에서 이 NPC의 대화를 시작할 수 있도록 데이터를 넘김
*/
public class NPC : MonoBehaviour
{
[SerializeField] protected GameObject dialogueUI;
[HideInInspector]
public GameObject npcUI; // 개별 NPC의 UI
protected bool isPlayerInRange = false; // 플레이어가 범위안에 있는지 여부
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
isPlayerInRange = true;
}
}
private void OnTriggerExit(Collider other)
{
if(other.CompareTag("Player"))
{
isPlayerInRange = false;
}
}
// NPC UI 활성/비활성화
public void SetActiveNpcUI()
{
npcUI.SetActive(!npcUI.activeSelf);
DialogueManager.Instance.isReadyToTalk = !npcUI.activeSelf;
DialogueManager.Instance.npc = null;
}
}
- NPC 클래스는 이제 플레이어와의 상호작용 및 대화창 UI 활성 및 비활성화를 담당합니다.
- NPC 클래스를 상속받아 개별 NPC를 구현합니다.
Store 클래스
public class Store : NPC
{
[SerializeField] private DialogueDataSO dialogueData; // 상점 NPC 대화 데이터
[SerializeField] private GameObject storeUI; // 상점 UI
private void Start()
{
npcUI = storeUI;
}
private void Update()
{
// 대화 시작
if(isPlayerInRange && Input.GetKeyDown(KeyCode.G) && !dialogueUI.activeSelf)
{
DialogueManager.Instance.StartDialogue(dialogueData, this);
}
}
}
- Store 클래스는 상점 NPC로, 활성화 시킬 상점 UI와 상점 NPC의 대화데이터를 연결시켜주어야합니다.
- “G” 키 입력을 통해 상호작용을 할 수 있습니다.
DialogueManager
/*
DialogueManager
- 넘겨받은 대화 데이터를 가지고 대화를 시작
- NPC의 대화를 "--"를 기준으로 나누어 여러페이지에 걸쳐 출력되도록함
- 대화시 타이핑 효과
*/
public class DialogueManager : Singleton<DialogueManager>
{
[SerializeField] private GameObject dialogueUI; // 대화창 오브젝트
[SerializeField] private TMP_Text npcNameText; // NPC 이름 텍스트
[SerializeField] private TMP_Text dialogueText; // 대화 텍스트
private Queue<string> pages = new Queue<string>();
private bool isTypipng = false; // 대화 타이핑 효과
private float typingSpeed = 0.05f; // 타이핑 속도
[HideInInspector]
public NPC npc; // 현재 대상 NPC
[HideInInspector] // 대화하기 가능여부(중복대화방지)
public bool isReadyToTalk = true;
private void Update()
{
if(dialogueUI.activeSelf && npc != null && Input.GetKeyDown(KeyCode.G))
{
DisplayNextPage(npc);
}
}
// 대화시작
public void StartDialogue(DialogueDataSO dialogue, NPC npcData)
{
if (!isReadyToTalk)
return;
isReadyToTalk = false;
npc = npcData;
dialogueUI.SetActive(true); // 대화창 UI 활성화
npcNameText.text = dialogue.npcName;
pages.Clear(); // 이전 대화 내용 초기화
foreach(string sentence in dialogue.sentences)
{
SplitSentenceToPages(sentence);
}
DisplayNextPage(npc);
}
// 다음페이지 대화내용 출력
public void DisplayNextPage(NPC npc)
{
if (isTypipng) return;
// 더이상 출력할 내용이 없으면
if(pages.Count == 0)
{
EndDialogue(); // 대화창 비활성화
npc.SetActiveNpcUI(); // 현재 대화중인 NPC의 UI 출력
return;
}
string page = pages.Dequeue();
StopAllCoroutines();
StartCoroutine(TypePage(page));
}
// 기호 "--"를 기준으로 대화 페이지 나누기
private void SplitSentenceToPages(string sentence)
{
string[] pagesArray = sentence.Split(new string[] { "--" }, System.StringSplitOptions.RemoveEmptyEntries);
foreach (string page in pagesArray)
{
pages.Enqueue(page.Trim()); // 공백 제거 후 큐에 저장
}
}
// 타이핑 효과
private IEnumerator TypePage(string page)
{
isTypipng = true;
dialogueText.text = "";
foreach(char letter in page.ToCharArray())
{
dialogueText.text += letter;
yield return new WaitForSeconds(typingSpeed);
}
isTypipng = false;
}
// 대화창 비활성화
private void EndDialogue()
{
dialogueUI.SetActive(false);
}
}
- DialogueManager 클래스는 NPC에게 넘겨받은 대화데이터를 가지고 대화를 시작
- 대화내용을 특수기호 “–“를 기준으로 나누어 여러페이지에 걸쳐 출력되도록 하였습니다.
- 코루틴을 사용하여 대화가 출력될때 타이핑효과를 주었습니다.
기타 수정
아이템 데이터에
아이템의 설명(ItemExplanation)과 아이템 가격(ItemPrice)필드를 추가하였습니다.
그에 따른 Json, ItemData 등의 파일 수정이 필요합니다.
플레이어의 데이터에 골드가 추가되었습니다.
상점에서 아이템을 구매할 때 골드가 차감되도록 하기위한 UseGold 함수를 추가하였습니다.
// 골드 사용
public void UseGold(int amount)
{
// 골드가 -이면 0으로 표기
gold = (gold - amount) < 0 ? 0 : gold - amount;
}
댓글남기기