일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 2022년
- 2024년
- C/C++
- 2025년
- 프로그래머스
- 자료 구조
- 2월
- 유니티 심화과정
- 입문
- 10월
- 다이나믹 프로그래밍
- 단계별로 풀어보기
- 개인 프로젝트
- 코딩 테스트
- todolist
- 유니티
- 기초
- c++
- 골드메탈
- 7월
- 수학
- 3월
- 개인 프로젝트 - 런앤건
- 게임 엔진 공부
- 2023년
- 백준
- 5월
- 1월
- 코딩 기초 트레이닝
- 4월
- Today
- Total
기록 보관소
[Unity/유니티] 기초-탑다운 2D RPG: 퀘스트 시스템 구현하기[B24] 본문
개요
유니티 입문과 독학을 위해서 아래 링크의 골드메탈님의 영상들을 보며 진행 상황 사진 또는 캡처를 올리고 배웠던 점을 요약해서 적는다.
현재는 영상들을 보고 따라하고 배우는 것에 집중할 것이며, 영상을 모두 보고 따라한 후에는 개인 프로젝트를 설계하고 직접 만드는 것이 목표다.
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: 퀘스트 시스템 구현하기[B24]
1. 퀘스트 대화
//QuestManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class QuestManager : MonoBehaviour {
public int questID;
public int questActionIndex; //퀘스트 대화 순서
Dictionary<int, QuestData> questList;
void Awake () {
questList = new Dictionary<int, QuestData>();
GenerateData();
}
void GenerateData() {
//퀘스트 제목과와 해당 퀘스트에 연관된 NPC들의 ID 입력
questList.Add(10, new QuestData("마을 사람들과 대화하기.", new int[] { 2000, 1000 }));
}
public int GetQuestTalkIndex(int id) { //NPC ID를 받아 퀘스트 번호 반환
//퀘스트 번호 + 퀘스트 대화 순서 = 퀘스트 대화 ID
return questID + questActionIndex;
}
public void CheckQuest(int id) {
if (id == questList[questID].npcID[questActionIndex])
questActionIndex++;
}
}
//QuestData 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class QuestData {
public string questName;
public int[] npcID;
public QuestData(string name, int[] npc) { //생성자
questName = name;
npcID = npc;
}
}
//TalkManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TalkManager : MonoBehaviour {
Dictionary<int, string[]> talkData;
Dictionary<int, Sprite> portraitData;
public Sprite[] portraitArr;
void Awake () {
talkData = new Dictionary<int, string[]>();
portraitData = new Dictionary<int, Sprite>();
GenerateDate();
}
void GenerateDate () {
//대화 데이터. Luna : 1000, Ludo : 2000, Box : 100, Desk : 200
talkData.Add(1000, new string[] { "안녕?:0", "이 호수 정말 아름답지?:3", "사실 이 호수에는 무언가 비밀이 숨겨져있데.:2" });
talkData.Add(2000, new string[] { "안녕!:2", "이 곳에 처음 왔구나?:0", "한번 둘러보는건 어때?:1" });
talkData.Add(100, new string[] { "평범한 나무 상자다." });
talkData.Add(200, new string[] { "누군가 사용했던 흔적이 있는 책상이다." });
//퀘스트 대화 데이터, 퀘스트 번호 + NPC ID
talkData.Add(10 + 2000, new string[] { "어서와.:0", "이 마을에 놀라운 전설이 있다는데:1", "오른쪽 호수에 있는 루도가 알려줄거야.:0" });
talkData.Add(11 + 1000, new string[] { "안녕.:0", "혹시 이 호수의 전설을 들으러 온거야?:0", "그러면 대신 부탁 하나만 해도될까?:1", "얼마전에 내 집 근처에서 동전 하나를 잃어버렸어.:3", "그 동전 좀 찾아줄래?:2" });
//초상화 데이터, NPC ID + 초상화 번호
//0:Idle, 1:Talk, 2:Smile, 3:Angry
//Luna
portraitData.Add(2000 + 0, portraitArr[0]);
portraitData.Add(2000 + 1, portraitArr[1]);
portraitData.Add(2000 + 2, portraitArr[2]);
portraitData.Add(2000 + 3, portraitArr[3]);
//Ludo
portraitData.Add(1000 + 0, portraitArr[4]);
portraitData.Add(1000 + 1, portraitArr[5]);
portraitData.Add(1000 + 2, portraitArr[6]);
portraitData.Add(1000 + 3, portraitArr[7]);
}
public string GetTalk(int id, int talkIndex) {
//대화 데이터 불러오기
if (talkIndex == talkData[id].Length)
return null;
else
return talkData[id][talkIndex];
}
public Sprite GetPortrait(int id, int portraitIndex) {
//초상화 데이터 불러오기
return portraitData[id + portraitIndex];
}
}
//GameManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameManager : MonoBehaviour {
public TalkManager talkManager;
public QuestManager questManager;
public GameObject talkPanel;
public Image portraitImg;
public Text talkText;
public GameObject scanObject;
public bool isAction; //대화창 활성화 여부 체크
public int talkIndex;
public void Action (GameObject scanObj) {
scanObject = scanObj;
ObjData objData = scanObject.GetComponent<ObjData>();
Talk(objData.id, objData.isNPC);
talkPanel.SetActive(isAction);
}
void Talk(int id, bool isNPC) {
//대화 데이터 불러오기
int questTalkIndex = questManager.GetQuestTalkIndex(id);
string talkData = talkManager.GetTalk(id + questTalkIndex, talkIndex);
//대화가 끝나면 멈추기
if (talkData == null) {
isAction = false;
talkIndex = 0;
questManager.CheckQuest(id);
return;
}
//대화 전 체크하기
if (isNPC) { //NPC라면
talkText.text = talkData.Split(':')[0];
portraitImg.sprite = talkManager.GetPortrait(id, int.Parse(talkData.Split(':')[1]));
portraitImg.color = new Color(1, 1, 1, 1); //초상화 보이기
}
else { //NPC가 아니라면
talkText.text = talkData;
portraitImg.color = new Color(1, 1, 1, 0); //초상화 투명화
}
//대화창 띄우기 및 대화 인덱스 증가
isAction = true;
talkIndex++;
}
}
- 퀘스트 데이터는 퀘스트 제목(string)과 해당 퀘스트와 관련된 NPC들의 ID(new int[] { })를 입력해서 만든다.
- 퀘스트 대화 데이터 ID는 스프라이트를 띄웠던 것처럼 퀘스트 번호와 NPC ID를 더해서 구분한다.
- 퀘스트에는 순서가 있으므로, int형 변수 questActionIndex를 사용해서 questID와 함께 사용해서 퀘스트 대화 데이터에 사용한다. 이렇게 해서 루나에게 말을 걸지 않고, 루도에게 먼저 가서 말을 걸어도 퀘스트 대화가 출력되지 않는다.
2. 퀘스트 진행
//QuestManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class QuestManager : MonoBehaviour {
public int questID;
public int questActionIndex; //퀘스트 대화 순서
Dictionary<int, QuestData> questList;
void Awake () {
questList = new Dictionary<int, QuestData>();
GenerateData();
}
void GenerateData() {
//퀘스트 제목과와 해당 퀘스트에 연관된 NPC들의 ID 입력
questList.Add(10, new QuestData("마을 사람들과 대화하기.", new int[] { 2000, 1000 }));
questList.Add(20, new QuestData("루도의 동전 찾아주기.", new int[] { 300, 1000 }));
}
public int GetQuestTalkIndex(int id) { //NPC ID를 받아 퀘스트 번호 반환
//퀘스트 번호 + 퀘스트 대화 순서 = 퀘스트 대화 ID
return questID + questActionIndex;
}
public string CheckQuest(int id) {
if (id == questList[questID].npcID[questActionIndex])
questActionIndex++;
if (questActionIndex == questList[questID].npcID.Length) //퀘스트 대화 순서 끝에 도달했을 때
NextQuest();
return questList[questID].questName;
}
void NextQuest() {
questID += 10;
questActionIndex = 0;
}
}
//GameManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameManager : MonoBehaviour {
public TalkManager talkManager;
public QuestManager questManager;
public GameObject talkPanel;
public Image portraitImg;
public Text talkText;
public GameObject scanObject;
public bool isAction; //대화창 활성화 여부 체크
public int talkIndex;
public void Action (GameObject scanObj) {
scanObject = scanObj;
ObjData objData = scanObject.GetComponent<ObjData>();
Talk(objData.id, objData.isNPC);
talkPanel.SetActive(isAction);
}
void Talk(int id, bool isNPC) {
//대화 데이터 불러오기
int questTalkIndex = questManager.GetQuestTalkIndex(id);
string talkData = talkManager.GetTalk(id + questTalkIndex, talkIndex);
//대화가 끝나면 멈추기
if (talkData == null) {
isAction = false;
talkIndex = 0;
Debug.Log(questManager.CheckQuest(id));
return;
}
//대화 전 체크하기
if (isNPC) { //NPC라면
talkText.text = talkData.Split(':')[0];
portraitImg.sprite = talkManager.GetPortrait(id, int.Parse(talkData.Split(':')[1]));
portraitImg.color = new Color(1, 1, 1, 1); //초상화 보이기
}
else { //NPC가 아니라면
talkText.text = talkData;
portraitImg.color = new Color(1, 1, 1, 0); //초상화 투명화
}
//대화창 띄우기 및 대화 인덱스 증가
isAction = true;
talkIndex++;
}
}
- 퀘스트 진행을 위해서 퀘스트 대화 순서가 끝에 도달하면 questID가 증가하게 했다. questID는 10 단위로 구분한다. 일단 지금은 자세한 퀘스트 내용을 추가하기보다는, 다음 퀘스트로의 진행이 되는지 확인하기위해서 콘솔창에 출력한다.
3. 퀘스트 오브젝트
//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(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;
}
void ControlObject() {
switch (questID) {
case 10:
if (questActionIndex == 2) //루도와 대화가 끝났을때
questObject[0].SetActive(true);
break;
case 20:
if (questActionIndex == 1) //동전을 얻었을때
questObject[0].SetActive(false);
break;
}
}
}
//TalkManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TalkManager : MonoBehaviour {
Dictionary<int, string[]> talkData;
Dictionary<int, Sprite> portraitData;
public Sprite[] portraitArr;
void Awake () {
talkData = new Dictionary<int, string[]>();
portraitData = new Dictionary<int, Sprite>();
GenerateDate();
}
void GenerateDate () {
//대화 데이터. Luna : 1000, Ludo : 2000, Box : 3000, Desk : 4000, Coin : 5000
talkData.Add(1000, new string[] { "안녕?:0", "이 호수 정말 아름답지?:3", "사실 이 호수에는 무언가 비밀이 숨겨져있데.:2" });
talkData.Add(2000, new string[] { "안녕!:2", "이 곳에 처음 왔구나?:0", "한번 둘러보는건 어때?:1" });
talkData.Add(3000, new string[] { "평범한 나무 상자다." });
talkData.Add(4000, new string[] { "누군가 사용했던 흔적이 있는 책상이다." });
//퀘스트 대화 데이터, 퀘스트 번호 + NPC ID
talkData.Add(10 + 2000, new string[] { "어서와.:0", "이 마을에 놀라운 전설이 있다는데:1", "오른쪽 호수에 있는 루도가 알려줄거야.:0" });
talkData.Add(11 + 1000, new string[] { "안녕.:0", "혹시 이 호수의 전설을 들으러 온거야?:0", "그러면 대신 부탁 하나만 해도될까?:1", "얼마전에 내 집 근처에서 동전 하나를 잃어버렸어.:3", "그 동전 좀 찾아줄래?:2" });
talkData.Add(20 + 2000, new string[] { "루도의 동전?:1", "돈을 흘리고 다니면 못쓰지!:3", "나중에 루도에게 한마디 해야겠어.:3" });
talkData.Add(20 + 1000, new string[] { "찾으면 꼭 좀 가져다줘.:1" });
talkData.Add(20 + 5000, new string[] { "근처에서 동전을 찾았다." });
talkData.Add(21 + 1000, new string[] { "오, 찾아줘서 고마워.:2" });
//초상화 데이터, NPC ID + 초상화 번호
//0:Idle, 1:Talk, 2:Smile, 3:Angry
//Luna
portraitData.Add(2000 + 0, portraitArr[0]);
portraitData.Add(2000 + 1, portraitArr[1]);
portraitData.Add(2000 + 2, portraitArr[2]);
portraitData.Add(2000 + 3, portraitArr[3]);
//Ludo
portraitData.Add(1000 + 0, portraitArr[4]);
portraitData.Add(1000 + 1, portraitArr[5]);
portraitData.Add(1000 + 2, portraitArr[6]);
portraitData.Add(1000 + 3, portraitArr[7]);
}
public string GetTalk(int id, int talkIndex) {
//대화 데이터 불러오기
if (talkIndex == talkData[id].Length)
return null;
else
return talkData[id][talkIndex];
}
public Sprite GetPortrait(int id, int portraitIndex) {
//초상화 데이터 불러오기
return portraitData[id + portraitIndex];
}
}
- 동전 스프라이트를 만들고, 비활성화 해서 보이지 않게 한 뒤에 Quest ID와 퀘스트 흐름도 값인 Quest Action Index 값을 활용하여 루도와 대화한 뒤에 활성화해서 보이게 하고, 그 뒤에 QuestList의 NPC ID 순서에 따라 동전과 대화하면 Quest Action Index가 1 증가하여 비활성화되도록 만들었다.
4. 예외처리
//TalkManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TalkManager : MonoBehaviour {
Dictionary<int, string[]> talkData;
Dictionary<int, Sprite> portraitData;
public Sprite[] portraitArr;
void Awake () {
talkData = new Dictionary<int, string[]>();
portraitData = new Dictionary<int, Sprite>();
GenerateDate();
}
void GenerateDate () {
//대화 데이터. Luna : 1000, Ludo : 2000, Box : 3000, Desk : 4000, Coin : 5000
talkData.Add(1000, new string[] { "안녕?:0", "이 호수 정말 아름답지?:3", "사실 이 호수에는 무언가 비밀이 숨겨져있데.:2" });
talkData.Add(2000, new string[] { "안녕!:2", "이 곳에 처음 왔구나?:0", "한번 둘러보는건 어때?:1" });
talkData.Add(3000, new string[] { "평범한 나무 상자다." });
talkData.Add(4000, new string[] { "누군가 사용했던 흔적이 있는 책상이다." });
//퀘스트 대화 데이터 : 퀘스트 번호 + NPC ID
talkData.Add(10 + 2000, new string[] { "어서와.:0", "이 마을에 놀라운 전설이 있다는데:1", "오른쪽 호수에 있는 루도가 알려줄거야.:0" });
talkData.Add(11 + 1000, new string[] { "안녕.:0", "혹시 이 호수의 전설을 들으러 온거야?:0", "그러면 대신 부탁 하나만 해도될까?:1", "얼마전에 내 집 근처에서 동전 하나를 잃어버렸어.:3", "그 동전 좀 찾아줄래?:2" });
talkData.Add(20 + 2000, new string[] { "루도의 동전?:1", "돈을 흘리고 다니면 못쓰지!:3", "나중에 루도에게 한마디 해야겠어.:3" });
talkData.Add(20 + 1000, new string[] { "찾으면 꼭 좀 가져다줘.:1" });
talkData.Add(20 + 5000, new string[] { "근처에서 동전을 찾았다." });
talkData.Add(21 + 1000, new string[] { "오, 찾아줘서 고마워.:2" });
//초상화 데이터, NPC ID + 초상화 번호
//0:Idle, 1:Talk, 2:Smile, 3:Angry
//Luna
portraitData.Add(2000 + 0, portraitArr[0]);
portraitData.Add(2000 + 1, portraitArr[1]);
portraitData.Add(2000 + 2, portraitArr[2]);
portraitData.Add(2000 + 3, portraitArr[3]);
//Ludo
portraitData.Add(1000 + 0, portraitArr[4]);
portraitData.Add(1000 + 1, portraitArr[5]);
portraitData.Add(1000 + 2, portraitArr[6]);
portraitData.Add(1000 + 3, portraitArr[7]);
}
public string GetTalk(int id, int talkIndex) {
//대화 데이터 불러오기
if (!talkData.ContainsKey(id)) { //해당 퀘스트 진행도에 맞는 대사가 없다면
if (!talkData.ContainsKey(id - id % 10))
return GetTalk(id - id % 100, talkIndex); //기본 대사 출력하기
else
return GetTalk(id - id % 10, talkIndex); //해당 퀘스트 첫 대사 출력하기
}
if (talkIndex == talkData[id].Length)
return null;
else
return talkData[id][talkIndex];
}
public Sprite GetPortrait(int id, int portraitIndex) {
//초상화 데이터 불러오기
return portraitData[id + portraitIndex];
}
}
//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;
}
void ControlObject() {
switch (questID) {
case 10:
if (questActionIndex == 2) //루도와 대화가 끝났을때
questObject[0].SetActive(true);
break;
case 20:
if (questActionIndex == 1) //동전을 얻었을때
questObject[0].SetActive(false);
break;
}
}
}
//GameManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameManager : MonoBehaviour {
public TalkManager talkManager;
public QuestManager questManager;
public GameObject talkPanel;
public Image portraitImg;
public Text talkText;
public GameObject scanObject;
public bool isAction; //대화창 활성화 여부 체크
public int talkIndex;
void Start() {
Debug.Log(questManager.CheckQuest());
}
public void Action (GameObject scanObj) {
scanObject = scanObj;
ObjData objData = scanObject.GetComponent<ObjData>();
Talk(objData.id, objData.isNPC);
talkPanel.SetActive(isAction);
}
void Talk(int id, bool isNPC) {
//대화 데이터 불러오기
int questTalkIndex = questManager.GetQuestTalkIndex(id);
string talkData = talkManager.GetTalk(id + questTalkIndex, talkIndex);
//대화가 끝나면 멈추기
if (talkData == null) {
isAction = false;
talkIndex = 0;
Debug.Log(questManager.CheckQuest(id));
return;
}
//대화 전 체크하기
if (isNPC) { //NPC라면
talkText.text = talkData.Split(':')[0];
portraitImg.sprite = talkManager.GetPortrait(id, int.Parse(talkData.Split(':')[1]));
portraitImg.color = new Color(1, 1, 1, 1); //초상화 보이기
}
else { //NPC가 아니라면
talkText.text = talkData;
portraitImg.color = new Color(1, 1, 1, 0); //초상화 투명화
}
//대화창 띄우기 및 대화 인덱스 증가
isAction = true;
talkIndex++;
}
}
- 앞의 퀘스트를 진행하다보면 상자나 책상 같은 오브젝트, 그리고 첫 퀘스트인 마을 사람과 대화를 진행 중 일때 루나 등의 퀘스트 진행에 맞는 대사가 없는 경우 에러가 발생한다. 그래서 퀘스트 대화 데이터를 처리하는 과정에서 quest ID를 아예 빼거나, 혹은 퀘스트 진행도 초기의 대사를 출력하는 형태로 작성하였다.
- Dictionary.ContainsKey(값) : Dictionary에 해당 값의 Key가 존재하는지 검사
- 또한 게임 시작시에도 퀘스트 이름을 출력하기 위해서, CheckQuest(int id) 함수를 매개변수를 없애 오버로딩해서 사용했다.
- 오버로딩 : 이름은 같지만 매개변수 개수나 타입이 다른 메소드를 선언하는 것
- 오버라이딩 : 부모 클래스로부터 받은 메소드를 재정의해서 사용하는 것
'유니티 프로젝트 > 탑다운 2D RPG' 카테고리의 다른 글
[Unity/유니티] 기초-탑다운 2D RPG: 서브메뉴와 저장기능 만들기[B26] (0) | 2022.02.22 |
---|---|
[Unity/유니티] 기초-탑다운 2D RPG: 대화 애니메이션 느낌있게 만들기[B25] (0) | 2022.02.21 |
[Unity/유니티] 기초-탑다운 2D RPG: 대화 시스템 구현하기[B23] (0) | 2022.02.19 |
[Unity/유니티] 기초-탑다운 2D RPG: 대화창 UI 구축하기[B22] (0) | 2022.02.18 |
[Unity/유니티] 기초-탑다운 2D RPG: 쯔꾸르식 액션 구현하기[B21] (0) | 2022.02.17 |