일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 10월
- C/C++
- c++
- 2023년
- 수학
- 단계별로 풀어보기
- 백준
- 2월
- 4월
- 5월
- 3월
- 개인 프로젝트 - 런앤건
- todolist
- 2024년
- 게임 엔진 공부
- 코딩 기초 트레이닝
- 2022년
- 유니티
- 2025년
- 기초
- 7월
- 1월
- 입문
- 골드메탈
- 개인 프로젝트
- 코딩 테스트
- 다이나믹 프로그래밍
- 프로그래머스
- 유니티 심화과정
- 자료 구조
- Today
- Total
기록 보관소
[Unity/유니티] 기초-탑다운 2D RPG: 서브메뉴와 저장기능 만들기[B26] 본문
개요
유니티 입문과 독학을 위해서 아래 링크의 골드메탈님의 영상들을 보며 진행 상황 사진 또는 캡처를 올리고 배웠던 점을 요약해서 적는다.
현재는 영상들을 보고 따라하고 배우는 것에 집중할 것이며, 영상을 모두 보고 따라한 후에는 개인 프로젝트를 설계하고 직접 만드는 것이 목표다.
https://youtube.com/playlist?list=PLO-mt5Iu5TeYI4dbYwWP8JqZMC9iuUIW2
유니티 강좌 기초 채널 Basic
유니티 개발을 처음 시작하시는 입문자 분들을 위한 기초 채널. [ 프로젝트 ] B00 ~ B12 (BE1) : 유니티 필수 기초 B13 ~ B19 (BE2) : 2D 플랫포머 B20 ~ B26 (BE3) : 2D 탑다운 대화형 RPG B27 ~ B37 (BE4) : 2D 종스크롤
www.youtube.com
탑다운 2D RPG: 서브메뉴와 저장기능 만들기[B26]
1. UI 구축하기
2. 계속하기 버튼
//GameManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameManager : MonoBehaviour {
public TalkManager talkManager;
public QuestManager questManager;
public Animator talkPanel;
public Image portraitImg;
public Animator portraitAnim;
public Sprite prevPortrait;
public TypeEffect talk;
public GameObject scanObject;
public GameObject menuSet;
public bool isAction; //대화창 활성화 여부 체크
public int talkIndex;
void Start() {
Debug.Log(questManager.CheckQuest());
}
void Update() {
//서브 메뉴
if (Input.GetButtonDown("Cancel")) { //Esc키
if (menuSet.activeSelf) //활성화 되어있다면
menuSet.SetActive(false); //비활성화
else
menuSet.SetActive(true);
}
}
public void Action (GameObject scanObj) {
scanObject = scanObj;
ObjData objData = scanObject.GetComponent<ObjData>();
Talk(objData.id, objData.isNPC);
//대화창 활성화, 비활성화
talkPanel.SetBool("isShow", isAction);
}
void Talk(int id, bool isNPC) {
int questTalkIndex = 0;
string talkData = "";
//대화 데이터 불러오기
if (talk.isAnim) { //대사 출력 중일때
talk.SetMsg("");
return;
}
else {
questTalkIndex = questManager.GetQuestTalkIndex(id);
talkData = talkManager.GetTalk(id + questTalkIndex, talkIndex);
}
//대화가 끝나면 멈추기
if (talkData == null) {
isAction = false;
talkIndex = 0;
Debug.Log(questManager.CheckQuest(id));
return;
}
//대화 전 체크하기
if (isNPC) { //NPC라면
talk.SetMsg(talkData.Split(':')[0]);
//초상화 보이기
portraitImg.sprite = talkManager.GetPortrait(id, int.Parse(talkData.Split(':')[1]));
portraitImg.color = new Color(1, 1, 1, 1);
//초상화 애니메이션
if (prevPortrait != portraitImg.sprite) {
portraitAnim.SetTrigger("doEffect");
prevPortrait = portraitImg.sprite;
}
}
else { //NPC가 아니라면
talk.SetMsg(talkData);
portraitImg.color = new Color(1, 1, 1, 0); //초상화 투명화
}
//대화창 띄우기 및 대화 인덱스 증가
isAction = true;
talkIndex++;
}
}
- 위 계속하기 버튼의 캡처처럼 GameObject 기본 함수는 Inspector 창에서 바로 할당 가능하다.
3. 퀘스트 확인 창
//GameManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameManager : MonoBehaviour {
public TalkManager talkManager;
public QuestManager questManager;
public Animator talkPanel;
public Image portraitImg;
public Animator portraitAnim;
public Sprite prevPortrait;
public TypeEffect talk;
public Text questText;
public GameObject scanObject;
public GameObject menuSet;
public bool isAction; //대화창 활성화 여부 체크
public int talkIndex;
void Start() {
questText.text = questManager.CheckQuest();
}
void Update() {
//서브 메뉴
if (Input.GetButtonDown("Cancel")) { //Esc키
if (menuSet.activeSelf) //활성화 되어있다면
menuSet.SetActive(false); //비활성화
else
menuSet.SetActive(true);
}
}
public void Action (GameObject scanObj) {
scanObject = scanObj;
ObjData objData = scanObject.GetComponent<ObjData>();
Talk(objData.id, objData.isNPC);
//대화창 활성화, 비활성화
talkPanel.SetBool("isShow", isAction);
}
void Talk(int id, bool isNPC) {
int questTalkIndex = 0;
string talkData = "";
//대화 데이터 불러오기
if (talk.isAnim) { //대사 출력 중일때
talk.SetMsg("");
return;
}
else {
questTalkIndex = questManager.GetQuestTalkIndex(id);
talkData = talkManager.GetTalk(id + questTalkIndex, talkIndex);
}
//대화가 끝나면 멈추기
if (talkData == null) {
isAction = false;
talkIndex = 0;
questText.text = questManager.CheckQuest(id);
return;
}
//대화 전 체크하기
if (isNPC) { //NPC라면
talk.SetMsg(talkData.Split(':')[0]);
//초상화 보이기
portraitImg.sprite = talkManager.GetPortrait(id, int.Parse(talkData.Split(':')[1]));
portraitImg.color = new Color(1, 1, 1, 1);
//초상화 애니메이션
if (prevPortrait != portraitImg.sprite) {
portraitAnim.SetTrigger("doEffect");
prevPortrait = portraitImg.sprite;
}
}
else { //NPC가 아니라면
talk.SetMsg(talkData);
portraitImg.color = new Color(1, 1, 1, 0); //초상화 투명화
}
//대화창 띄우기 및 대화 인덱스 증가
isAction = true;
talkIndex++;
}
}
4. 종료하기 버튼
//GameManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameManager : MonoBehaviour {
public TalkManager talkManager;
public QuestManager questManager;
public Animator talkPanel;
public Image portraitImg;
public Animator portraitAnim;
public Sprite prevPortrait;
public TypeEffect talk;
public Text questText;
public GameObject scanObject;
public GameObject menuSet;
public bool isAction; //대화창 활성화 여부 체크
public int talkIndex;
void Start() {
questText.text = questManager.CheckQuest();
}
void Update() {
//서브 메뉴
if (Input.GetButtonDown("Cancel")) { //Esc키
if (menuSet.activeSelf) //활성화 되어있다면
menuSet.SetActive(false); //비활성화
else
menuSet.SetActive(true);
}
}
public void Action (GameObject scanObj) {
scanObject = scanObj;
ObjData objData = scanObject.GetComponent<ObjData>();
Talk(objData.id, objData.isNPC);
//대화창 활성화, 비활성화
talkPanel.SetBool("isShow", isAction);
}
void Talk(int id, bool isNPC) {
int questTalkIndex = 0;
string talkData = "";
//대화 데이터 불러오기
if (talk.isAnim) { //대사 출력 중일때
talk.SetMsg("");
return;
}
else {
questTalkIndex = questManager.GetQuestTalkIndex(id);
talkData = talkManager.GetTalk(id + questTalkIndex, talkIndex);
}
//대화가 끝나면 멈추기
if (talkData == null) {
isAction = false;
talkIndex = 0;
questText.text = questManager.CheckQuest(id);
return;
}
//대화 전 체크하기
if (isNPC) { //NPC라면
talk.SetMsg(talkData.Split(':')[0]);
//초상화 보이기
portraitImg.sprite = talkManager.GetPortrait(id, int.Parse(talkData.Split(':')[1]));
portraitImg.color = new Color(1, 1, 1, 1);
//초상화 애니메이션
if (prevPortrait != portraitImg.sprite) {
portraitAnim.SetTrigger("doEffect");
prevPortrait = portraitImg.sprite;
}
}
else { //NPC가 아니라면
talk.SetMsg(talkData);
portraitImg.color = new Color(1, 1, 1, 0); //초상화 투명화
}
//대화창 띄우기 및 대화 인덱스 증가
isAction = true;
talkIndex++;
}
public void GameExit() {
//게임 종료
Application.Quit();
}
}
- 에디터에서는 당연하지만 종료 버튼을 눌러도 종료되지 않는다. 테스트 하려면 빌드를 해서 실행해야한다. 따라서 다음 저장하기 버튼까지 만든 뒤에 함께 다루도록 하겠다.
5. 저장하기 버튼
//GameManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameManager : MonoBehaviour {
public TalkManager talkManager;
public QuestManager questManager;
public Animator talkPanel;
public Image portraitImg;
public Animator portraitAnim;
public Sprite prevPortrait;
public TypeEffect talk;
public Text questText;
public GameObject scanObject;
public GameObject menuSet;
public GameObject player;
public bool isAction; //대화창 활성화 여부 체크
public int talkIndex;
void Start() {
GameLoad();
questText.text = questManager.CheckQuest();
}
void Update() {
//서브 메뉴
if (Input.GetButtonDown("Cancel")) { //Esc키
if (menuSet.activeSelf) //활성화 되어있다면
menuSet.SetActive(false); //비활성화
else
menuSet.SetActive(true);
}
}
public void Action (GameObject scanObj) {
scanObject = scanObj;
ObjData objData = scanObject.GetComponent<ObjData>();
Talk(objData.id, objData.isNPC);
//대화창 활성화, 비활성화
talkPanel.SetBool("isShow", isAction);
}
void Talk(int id, bool isNPC) {
int questTalkIndex = 0;
string talkData = "";
//대화 데이터 불러오기
if (talk.isAnim) { //대사 출력 중일때
talk.SetMsg("");
return;
}
else {
questTalkIndex = questManager.GetQuestTalkIndex(id);
talkData = talkManager.GetTalk(id + questTalkIndex, talkIndex);
}
//대화가 끝나면 멈추기
if (talkData == null) {
isAction = false;
talkIndex = 0;
questText.text = questManager.CheckQuest(id);
return;
}
//대화 전 체크하기
if (isNPC) { //NPC라면
talk.SetMsg(talkData.Split(':')[0]);
//초상화 보이기
portraitImg.sprite = talkManager.GetPortrait(id, int.Parse(talkData.Split(':')[1]));
portraitImg.color = new Color(1, 1, 1, 1);
//초상화 애니메이션
if (prevPortrait != portraitImg.sprite) {
portraitAnim.SetTrigger("doEffect");
prevPortrait = portraitImg.sprite;
}
}
else { //NPC가 아니라면
talk.SetMsg(talkData);
portraitImg.color = new Color(1, 1, 1, 0); //초상화 투명화
}
//대화창 띄우기 및 대화 인덱스 증가
isAction = true;
talkIndex++;
}
public void GameSave() {
//플레이어 위치, 퀘스트 진행도 데이터 세팅 및 저장
PlayerPrefs.SetFloat("PlayerX",player.transform.position.x);
PlayerPrefs.SetFloat("PlayerY", player.transform.position.y);
PlayerPrefs.SetInt("QuestID", questManager.questID);
PlayerPrefs.SetInt("QuestActionIndex", questManager.questActionIndex);
PlayerPrefs.Save();
//저장 후 메뉴 닫기
menuSet.SetActive(false);
}
public void GameLoad() {
if (!PlayerPrefs.HasKey("PlayerX")) //레지스트리가 없다면. 즉, 첫 시작이라면
return;
//레지스트리로부터 데이터 불러오기
float x = PlayerPrefs.GetFloat("PlayerX");
float y = PlayerPrefs.GetFloat("PlayerY");
int questID = PlayerPrefs.GetInt("QuestID");
int questActionIndex = PlayerPrefs.GetInt("QuestActionIndex");
//불러온 데이터 적용
player.transform.position = new Vector3(x, y, -2);
questManager.questID = questID;
questManager.questActionIndex = questActionIndex;
questManager.ControlObject();
}
public void GameExit() {
//게임 종료
Application.Quit();
}
}
//QuestManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class QuestManager : MonoBehaviour {
public int questID;
public int questActionIndex; //퀘스트 대화 순서
public GameObject[] questObject;
Dictionary<int, QuestData> questList;
void Awake () {
questList = new Dictionary<int, QuestData>();
GenerateData();
}
void GenerateData() {
//퀘스트 제목과와 해당 퀘스트에 연관된 NPC들의 ID 입력. 뒤 NPC ID 순서가 퀘스트 진행 순서.
questList.Add(10, new QuestData("마을 사람들과 대화하기.", new int[] { 2000, 1000 }));
questList.Add(20, new QuestData("루도의 동전 찾아주기.", new int[] { 5000, 1000 }));
questList.Add(30, new QuestData("퀘스트 완료!", new int[] { 0 }));
}
public int GetQuestTalkIndex(int id) { //NPC ID를 받아 퀘스트 번호 반환
//퀘스트 번호 + 퀘스트 대화 순서 = 퀘스트 대화 ID
return questID + questActionIndex;
}
public string CheckQuest() { //오버로딩
return questList[questID].questName;
}
public string CheckQuest(int id) {
//다음 퀘스트 대화로
if (id == questList[questID].npcID[questActionIndex])
questActionIndex++;
//퀘스트 오브젝트 관리
ControlObject();
//퀘스트 대화가 끝나면 다음 퀘스트로
if (questActionIndex == questList[questID].npcID.Length)
NextQuest();
//퀘스트 이름 반환
return questList[questID].questName;
}
void NextQuest() {
questID += 10;
questActionIndex = 0;
}
public void ControlObject() { //불러오기시 퀘스트 순서를 확인하고 오브젝트 관리하기위해 public으로 전환
switch (questID) {
case 10:
if (questActionIndex == 2) //루도와 대화가 끝나고 난 뒤
questObject[0].SetActive(true);
break;
case 20:
if (questActionIndex == 0) //루도와 대화가 끝나고 난 뒤
questObject[0].SetActive(true);
else if (questActionIndex == 1) //동전을 얻었을때
questObject[0].SetActive(false);
break;
}
}
}
- 참고로 파일 저장시 위치는 기기마다 다를 수 있으나 빌드 창의 Player Settings의 Company Name과 Product Name으로 컴퓨터 레지스트리에 저장된다. 방금 저장했었으니, 레지스트리 편집기를 보고 확인해보자.
- 자세히 보면 주소에 아까 입력했던 Company Name과 Product Name이 경로로 설정되어 있다.
- 우선 저장이 있다면 불러오기도 있어야한다. 보통 게임에서 불러오기를 할때는 유저가 저장했던 시점의 캐릭터 위치(이번 프로젝트는 2D이므로 Player의 x축과 y축에 해당)와 퀘스트 진행도(이번 프로젝트의 Quest ID와 Quest Action Index에 해당) 등이 그대로 유지된채로 불러온다. 즉, 저장할 때는 이러한 정보들을 반드시 저장해두어야한다.
- 퀘스트 진행도 값은 저장되어도 루도의 동전 같은 오브젝트는 활성화되지 않을 수 있으므로 QuestManager의 ControlObject()함수를 public으로 전환하여 불러오기시 실행하도록 한다.
- 추가로 루도와 대화가 끝날 때는 ControlObject() 함수에서 QuestID와 QuestAction Index가 10, 2일때와 불러오기시 20 0일 때로 2가지 경우가 존재하기 때문에, switch문에 20 0일 때도 동전이 활성화되도록 변경하였다.
- PlayerPrefs : 간단한 데이터 저장기능을 지원하는 클래스
- .SetFloat(string key, float value) : float 값을 key와 함께 세팅하는 함수. 데이터 타입에 따라 SetInt, SetString도 있다.
- .Save() : 세팅했던 데이터들을 레지스트리에 저장하는 함수.
- .GetFloat(string key) : key에 따라 float 값을 불러오는 함수. 마찬가지로 데이터 타입에 따라 GetInt, GetString도 있다.
번외. 대화창에 오브젝트 이름 출력하기
//ObjData 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjData : MonoBehaviour {
public int id;
public bool isNPC;
public string ObjectName;
}
//GameManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameManager : MonoBehaviour {
public TalkManager talkManager;
public QuestManager questManager;
public Animator talkPanel;
public Image portraitImg;
public Animator portraitAnim;
public Sprite prevPortrait;
public TypeEffect talk;
public Text questText;
public Text nameText;
public GameObject scanObject;
public GameObject menuSet;
public GameObject player;
public bool isAction; //대화창 활성화 여부 체크
public int talkIndex;
void Start() {
GameLoad();
questText.text = questManager.CheckQuest();
}
void Update() {
//서브 메뉴
if (Input.GetButtonDown("Cancel")) { //Esc키
if (menuSet.activeSelf) //활성화 되어있다면
menuSet.SetActive(false); //비활성화
else
menuSet.SetActive(true);
}
}
public void Action (GameObject scanObj) {
scanObject = scanObj;
ObjData objData = scanObject.GetComponent<ObjData>();
Talk(objData.id, objData.isNPC, objData.ObjectName);
//대화창 활성화, 비활성화
talkPanel.SetBool("isShow", isAction);
}
void Talk(int id, bool isNPC, string objectName) {
int questTalkIndex = 0;
string talkData = "";
nameText.text = objectName; //오브젝트 이름 출력
//대화 데이터 불러오기
if (talk.isAnim) { //대사 출력 중일때
talk.SetMsg("");
return;
}
else {
questTalkIndex = questManager.GetQuestTalkIndex(id);
talkData = talkManager.GetTalk(id + questTalkIndex, talkIndex);
}
//대화가 끝나면 멈추기
if (talkData == null) {
isAction = false;
talkIndex = 0;
questText.text = questManager.CheckQuest(id);
return;
}
//대화 전 체크하기
if (isNPC) { //NPC라면
talk.SetMsg(talkData.Split(':')[0]);
//초상화 보이기
portraitImg.sprite = talkManager.GetPortrait(id, int.Parse(talkData.Split(':')[1]));
portraitImg.color = new Color(1, 1, 1, 1);
//초상화 애니메이션
if (prevPortrait != portraitImg.sprite) {
portraitAnim.SetTrigger("doEffect");
prevPortrait = portraitImg.sprite;
}
}
else { //NPC가 아니라면
talk.SetMsg(talkData);
portraitImg.color = new Color(1, 1, 1, 0); //초상화 투명화
}
//대화창 띄우기 및 대화 인덱스 증가
isAction = true;
talkIndex++;
}
public void GameSave() {
//플레이어 위치, 퀘스트 진행도 데이터 세팅 및 저장
PlayerPrefs.SetFloat("PlayerX",player.transform.position.x);
PlayerPrefs.SetFloat("PlayerY", player.transform.position.y);
PlayerPrefs.SetInt("QuestID", questManager.questID);
PlayerPrefs.SetInt("QuestActionIndex", questManager.questActionIndex);
PlayerPrefs.Save();
//저장 후 메뉴 닫기
menuSet.SetActive(false);
}
public void GameLoad() {
if (!PlayerPrefs.HasKey("PlayerX")) //레지스트리가 없다면. 즉, 첫 시작이라면
return;
//레지스트리로부터 데이터 불러오기
float x = PlayerPrefs.GetFloat("PlayerX");
float y = PlayerPrefs.GetFloat("PlayerY");
int questID = PlayerPrefs.GetInt("QuestID");
int questActionIndex = PlayerPrefs.GetInt("QuestActionIndex");
//불러온 데이터 적용
player.transform.position = new Vector3(x, y, -2);
questManager.questID = questID;
questManager.questActionIndex = questActionIndex;
questManager.ControlObject();
}
public void GameExit() {
//게임 종료
Application.Quit();
}
}
- 영상을 보는 중에서 대화 중 캐릭터 이름이 추가된 것을 확인했다. 영상에서는 아무래도 간단한 내용이라 생각하고 생략한듯한데, 일단 이름이 있는게 명확하고 보기도 좋으니 복습겸 추가하도록 했다.
- 해결 방법은 굉장히 간단하다. 우선 UI 대화창에 텍스트를 하나 더 추가해서 배치한다. 그리고 각 오브젝트의 이름을 적을 수 있도록 ObjData 스크립트에 string형 변수를 하나 추가해서 NPC를 포함한 오브젝트들의 이름을 직접 설정한다. 이후 적었던 이름들을 사용할 수 있도록 GameManager 스크립트의 Talk() 함수의 매개변수로 ObjectName을 추가해주고, public Text형 변수 nameText를 선언해서 앞에서 만든 텍스트를 끌어 넣은 뒤, 그 텍스트에 매개 변수로 받아놓았던 ObjectName 문자열을 넣어주기만 하면 된다.
'유니티 프로젝트 > 탑다운 2D RPG' 카테고리의 다른 글
[Unity/유니티] 기초-탑다운 2D RPG: 모바일 UI & 안드로이드 빌드[BE3] (0) | 2022.02.26 |
---|---|
[Unity/유니티] 기초-탑다운 2D RPG: 대화 애니메이션 느낌있게 만들기[B25] (0) | 2022.02.21 |
[Unity/유니티] 기초-탑다운 2D RPG: 퀘스트 시스템 구현하기[B24] (0) | 2022.02.20 |
[Unity/유니티] 기초-탑다운 2D RPG: 대화 시스템 구현하기[B23] (0) | 2022.02.19 |
[Unity/유니티] 기초-탑다운 2D RPG: 대화창 UI 구축하기[B22] (0) | 2022.02.18 |