기록 보관소

[Unity/유니티] 기초-탑다운 2D RPG: 대화 시스템 구현하기[B23] 본문

유니티 프로젝트/탑다운 2D RPG

[Unity/유니티] 기초-탑다운 2D RPG: 대화 시스템 구현하기[B23]

JongHoon 2022. 2. 19. 23:33

개요

유니티 입문과 독학을 위해서 아래 링크의 골드메탈님의 영상들을 보며 진행 상황 사진 또는 캡처를 올리고 배웠던 점을 요약해서 적는다.

현재는 영상들을 보고 따라하고 배우는 것에 집중할 것이며, 영상을 모두 보고 따라한 후에는 개인 프로젝트를 설계하고 직접 만드는 것이 목표다.

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: 대화 시스템 구현하기[B23]

1. 오브젝트 관리

오브젝트를 관리할 스크립트 파일 ObjData 생성

//ObjData 스크립트 파일

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ObjData : MonoBehaviour {
    public int id;
    public bool isNPC;
}

Ludo
Luna
Box
Desk

  • 간단하게 오브젝트의 id와 NPC인지 구분하는 변수 2개만 만들어서 설정했다. 일반 오브젝트는 100번대 id, NPC는 1000번대 id에 isNPC를 체크해두었다.

2. 대화 시스템

대화 시스템을 관리할 TalkManager

//TalkManager 스크립트 파일

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TalkManager : MonoBehaviour {
    Dictionary<int, string[]> talkData;
    
	void Awake () {
        talkData = new Dictionary<int, string[]>();
        GenerateDate();
	}

	void GenerateDate () {
        talkData.Add(1000, new string[] { "안녕?", "이 곳에 처음 왔구나?" });
        talkData.Add(1001, new string[] { "안녕!", "오늘 날씨가 정말 좋지?" });
        talkData.Add(100, new string[] { "평범한 나무 상자다." });
        talkData.Add(101, new string[] { "누군가 사용했던 흔적이 있는 책상이다." });
    }

    public string GetTalk(int id, int talkIndex) {
        if (talkIndex == talkData[id].Length)
            return null;
        else
            return talkData[id][talkIndex];
    }
}
//GameManager 스크립트 파일

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class GameManager : MonoBehaviour {
    public TalkManager talkManager;
    public GameObject talkPanel;
    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) {
        string talkData = talkManager.GetTalk(id, talkIndex);
        
        if (talkData == null) {
            isAction = false;
            talkIndex = 0;
            return;
        }

        if (isNPC) {
            talkText.text = talkData;
        }
        else {
            talkText.text = talkData;
        }

        isAction = true;
        talkIndex++;
    }
}

Ludo와 대화
다음 대화도 잘 출력된다
Desk 조사
Box 조사
Luna와 대화
다음 인덱스 대화도 잘 출력된다

  • 이전까지 대화창은 그냥 스프라이트 이름을 출력하고, 별도의 대화나 조사가 아니었다. 그래서 GameManager에서 스페이스바를 통해 레이어가 Object일때만 isAction이 true가 되어 스프라이트의 이름이 출력되고, 다시 누르면 isAction이 false가 되면서 꺼지는 방식이었다.
  • 이번 시간에는 대화를 직접 사용자가 입력하고 저장할 TalkManager를 만들었으니, 여기에서 해당 오브젝트의 ID와 대화 내용을 저장하고, 대화 내용이 모두 출력될 때까지 isAction이 true가 되도록한다. 대화 내용은 문자열 배열의 인덱스를 활용했다.
  • Dictionary<TKey, TValue> : 데이터 구조가 key와 연결된 value값이 필요하다. 그래서 제네릭에 두가지 데이터 타입이 필요하다.
    • .Add(key, value) : 데이터 추가

3. 초상화

대화창 초상화로 사용할 스프라이트 잘라주기
잘 잘렸다
초상화 UI로 사용할 Image 생성
이미지를 넣고 Set Native Size를 클릭해주었다
앵커와 이미지 중심을 오른쪽 아래로 잡고, 위치를 수정했다

//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 () {
        talkData.Add(1000, new string[] { "안녕?:0", "이 곳에 처음 왔구나?:2", "저기 있는 집은 루나네 집이야.:1" });
        talkData.Add(2000, new string[] { "안녕!:0", "오늘 날씨가 정말 좋지?:2" });
        talkData.Add(100, new string[] { "평범한 나무 상자다." });
        talkData.Add(200, new string[] { "누군가 사용했던 흔적이 있는 책상이다." });

        portraitData.Add(2000 + 0, portraitArr[0]); //Luna Idle
        portraitData.Add(2000 + 1, portraitArr[1]); //Luna Talk
        portraitData.Add(2000 + 2, portraitArr[2]); //Luna Smile
        portraitData.Add(2000 + 3, portraitArr[3]); //Luna Angry

        portraitData.Add(1000 + 0, portraitArr[4]); //Ludo Idle
        portraitData.Add(1000 + 1, portraitArr[5]); //Ludo Talk
        portraitData.Add(1000 + 2, portraitArr[6]); //Ludo Smile
        portraitData.Add(1000 + 3, portraitArr[7]); //Ludo Angry
    }

    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 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) {
        string talkData = talkManager.GetTalk(id, talkIndex);
        
        if (talkData == null) {
            isAction = false;
            talkIndex = 0;
            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++;
    }
}
  • 초상화는 NPC당 총 4개(Idle, Talk, Smile, Angry)의 스프라이트가 있다. 그래서 TalkManager에 각 스프라이트들을 모두 넣어두고, NPC 대사에 콜론(:)과 인덱스를 넣어 해당 대사에 어울리는 감정 표현의 초상화를 출력하도록 만들었다.
    • .Split() : 구분자를 통하여 배열로 나눠주는 문자열(string) 함수. 배열로 나눠주기때문에 사용할때는 뒤에 [0]처럼 배열 인덱스를 넣어줘야한다.
    • 데이터 타입.Parse() : 문자열을 해당 타입으로 변환해주는 함수. 위 코드에서는 int형으로 변환했다. 문자열 내용이 타입과 맞지않으면 오류가 발생하므로 주의해야한다.
  • 참고로 원활한 진행을 위해서 Luna와 Desk의 오브젝트 id를 1001에서 2000으로 바꾸고, 101에서 200으로 바꾸었다.

Ludo와의 대화. 초상화가 잘 나온다.
Ludo의 Smile 초상화
Ludo의 Talk 초상화
일반 사물인 Desk에는 초상화가 없다.
Luna와 대화. 역시 초상화가 잘 나온다
Luna의 Smile 초상화