Unity/Unity 프로그래밍

01. (대화창) 다이얼로그 및 분기점 구현

독학백수 2020. 12. 31. 14:34
반응형

 

구현 영상


우선, 기능을 구현하기 위해 신경을 모두 쓴 탓에

대사 스크립팅은 대충 짯기 때문에 오타나 뭔가 말이 안되는 건 무시해줍시다.. ㅎㅎ

우선 구현 방식은 엑셀의 csv 파일을 파싱해서 데이터를 읽어오는 방식입니다.

참고 영상은 아래를 참고해주세요 

 

자 위 강의 영상을 보셨다면 한 가지 부족한 부분이 있음을 느낄 수 있을 겁니다. 그것은 바로 '분기점' 

네, 선택지 창을 구현하는 강의는 없었습니다. 각종 Google을 검색하고 유튜브 등을 뒤져봐도 관련 강의가 없기에

직접 위 강의를 참고하고 스크립트를 몇 개 더 추가하여 만들게 되었습니다. 

그럼 서론은 이쯤 하고 바로 천천히 구현하는 방법을 알려드리겠습니다. 

 


Dialogue

우선 첫번째로 Dialogue 라는 스크립트를 만들어줍시다. 아래 코드블럭에 나온 스크립트는 이름에 오타가 있네요.. 핳

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

//인스펙터창에서 수정가능
[System.Serializable]
public class Dialgoue 
{

   [Tooltip("캐릭터 이름")]
    public string name;

    [Tooltip("대사 내용")]
    public string[] contexts;

    [Tooltip("이벤트 번호")]
    public string[] number;

    [Tooltip("스킵라인")]
     public string[] skipnum;
    //[Tooltip("이벤트번호")]
    //public string[] number;

}

[System.Serializable]
public class DialgoueEvent
{

    //이벤트 이름
    public string name;

    //public Vector2 line;
    public Dialgoue[] dialgoues;

}

대충 살펴봅시다. 우선 Dialogue는 본인 클래스와 DialogueEvent클래스만 만들고 다른 곳에서 사용하게 할 겁니다. [System.Serializable]은 무엇인가? 데이터를 직렬화 해준다는 의미로 간단히 유니티 내의 인스펙터창에서 수정할 수 있게끔 해줍니다.

Tooltip은 무엇인가? 

인스펙터창에서 보시는 것 처럼 저것이 무엇을 하는 용도인지 알려줄 수 있습니다. 

자 그럼 Dialogue 클래스를 살펴보시면 모든 변수들이 전부 String 혹은 String 배열로 선언 되었음을 보실 수 있을겁니다. 왜 그런지에 대한 이유는 차후 알려드리며 우선 따라 작성해줍시다.


interationEvent

자, 두번째로 만들어주실 스크립트는 interationEvent라는 스크립트를 만들어줍시다.

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

public class interactionEvent : MonoBehaviour
{

    public int lineY;
    public int s_lineY;
    [SerializeField] DialgoueEvent dialgoue;
    [SerializeField] SelectEvent select;
    
    public Dialgoue[] GetDialgoues()
    {
        dialgoue.dialgoues = DatabaseManager.instance.GetDialgoues(1, lineY);
        return dialgoue.dialgoues;
    }

    public SelectDialogue[] GetSelectes()
    {
        select.Selecter = DatabaseManager.instance.GetSelects(1, s_lineY);
        return select.Selecter;
    }

}

 

자, 일단 제일 먼저 우리가 Dialogue 스크립트를 만들어줬는데 거기서 [System.Serializable]를 각 클래스위에 만들어 줬습니다.

그 이유가 인스펙터창에 나오게끔 하기 위함이였고, 지금부터 해당 interactionEvent를 컴포넌트한 오브젝트의 인스펙터창에 해당 Dialogue 클래스의 값들을 확인하기 위해선 위와 같이 [SerializeField]를 해줌으로써 커스텀 클래스를 불러와야 합니다. 

여기까지 따라오셨다면 윗 부분에 있는 lineY와 s_lineY는 무엇인가 물어보실 수 있는데 앞서 동영상 강의를 이미 확인하신 분들은 아시겠지만 해당 강의에선 직접 인스펙터창에서 엑셀의 크기 만큼 x 와 y값을 넣어줘야합니다.

그 말은 즉슨, 처음 엑셀 범위가 3줄이였으나 수정하게 되어 10줄이 될 경우 일일히 인스펙터창에 들어가서 Y값을 조정해주어야 합니다.

추후 파싱 스크립트를 설명할 때 제차 설명하겠지만

본인은 그것이 귀찮아 파싱할때 해당 클래스를 불러와서 라인의 범위를 지정해주었고 x값은 1로 고정시켜 놨습니다.

그 밑에 SelectDialogue와 Dialogue 배열 함수에 대해선 DataManager 를 설명할 때 설명드리겠으며 미리 영상을 보신 분들은 Dialogue 배열 함수에 대해선 아시겠지만 SelectDialogue 또한 Dialogue 처럼 새로 분기점을 만들기 위해 Dialogue 스크립트 처럼 똑같이 만들어줬습니다. 


CSV 파일 만들기

그럼 우선 두 스크립트 작성이 완료되면 엑셀로 csv 파일을 만들어 볼겁니다. 영상 강의를 이미 보신 분은 대충 아래 사진 두 장만 확인하시고 넘어가주셔도 됩니다.

우선 저는 구글 시트 엑셀로 구현하였습니다. 제일 먼저 대사 스크립트 부분 부터 ID, 캐릭터 이름, 대사, 이벤트 번호, 스킵라인 , 비고 로 나누었습니다. 

일단 대사 부분 까지는 대충 저것이 무엇인지 파악이 가능하실테니 설명은 따로 드리지 않고 이벤트 번호 스킵라인 비고 에 관련해서 설명드리자면

이벤트 번호는 선택스크립트 엑셀의 ID를 불러오기 위함입니다. 정확히 설명드리자면 대사 스크립트의 10번째 줄을 확인해보시면 1 이라고 적혀있을 것 인데. 

그것은 선택 스크립트의 ID를 비교해서 불러오는 것이며 가령 만들어두진 않았지만 선택 스크립트의 ID2를 만들어 뒀다 가정하고 대사 스크립트 어디에서든 이벤트 번호에 2를 대입하면 선택 스크립트의 ID2번을 불러오게 됩니다. 

두번째로 스킵라인은 분기점에 대한 대사 이후 곧바로 다른 라인으로 가기 위해 만들어두었습니다.

정확히 설명드리자면 대사 스크립트는 대사가 끝나면 ID 번호 순서대로 진행됩니다 예를들어 대사 스크립트의 

ID5번 부터 진행하게 된다면 

ID 5

공란 : 그런데 말이야 주인공

공란 : 너는 내가 이쁘다고 생각해?

ID 6 

공란 : 뭐라고!? 보는 눈 없네 정말!

ID7 

공란 : 그래? 역시 너도 내가 이쁘다고 생각하는 구나

 . . . 

이 처럼 순서대로 진행되겠지요 하지만 저희가 원하는 건 ID5번에서 너는 내가 이쁘다고 생각해? 라는 질문에 주인공이 답변 할 수 있도록 선택지를 만들어두고 선택에 대한 결과에 따라 ID6번 혹은 ID7번만 작동해야합니다.

공란이가 쉽게 너는 내가 이쁘다고 생각해? 라고 질문하였으면

주인공은 그렇다 , 아니다 라는 선택지가 있을 것이고 그렇다 라고 할 경우 ID7번을 실행

아니다 라고 답변할 경우 ID6 번이 실행되게끔 해야 된다는 거죠

그렇기 때문에 ID 6번 뒤에 스킵라인을 8로 지정해주었습니다. 그러면 곧바로 저 대사가 끝나면 

순서대로 ID 7로 진행하는게 아니라 ID 8 로 넘어가게 됩니다. 

두번째로 선택 스크립트도 마찬가지로 옮길라인은 이와 비슷한 맥락을 가지고 있습니다. 

각각 6 과 7을 넣어줬는데 답변에 따라 어느 몇 번의 ID로 옮겨줄까에 대한 내용입니다.

마지막으로 뒤에 비고를 넣어준 까닭은 스크립팅을 하는 제작자의 간단한 메모같은 편의도 있겠지만

저걸 안넣어주고 만들었을 시 추후 원인을 알기 어려운 에러 사항이 발생하였고 직접 이것저것 시도해보며

비고를 넣음으로써 에러가 방지 되었음으로 넣었습니다. 

자 우선 선택 스크립트와 대사 스크립트 작성이 끝나셨으면 

위 와같이 csv로 다운로드 해주시면 끝이납니다. 


Parser

이제 CSV 파일도 만들어 두셨으면 이걸 유니티에서 데이터를 불러올 수 있어야합니다. 그러기 위해서 

Parser 라는 스크립트를 작성해볼겁니다. 

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

public class DialgoueParser : MonoBehaviour
{
    
    public Dialgoue[] Parse(string _CSVFilieName)
    {
        List<Dialgoue> dialgoueList = new List<Dialgoue>(); // 대사 리스트 생성
        TextAsset csvData = Resources.Load<TextAsset>(_CSVFilieName); // CSV 데이터를 받기 위한 그릇 

        string[] data = csvData.text.Split(new char[]{'\n'}); //엔터를 만나면 쪼개어 넣음
        //엔터를 만났다 data[0] - 엑셀시트의 맨 1번째 줄 의미 

        for (int i = 1; i < data.Length;) // i++는 대한 내용은 그다음 내용은 조건문을 통해서 
        {
            string[] row = data[i].Split(new char[] { ',' }); //, 단위로 row 줄에 저장
            
            Dialgoue dialgoue = new Dialgoue(); // 대사 리스트 생성
            dialgoue.name = row[1];
            List<string> contextList = new List<string>(); // 대사 리스트 생성
            List<string> EventList = new List<string>(); // 이벤트 넘버 생성
            List<string> SkipList = new List<string>(); // 엑셀 맨끝줄 비고 추가 안하면 오류남
            
            //dialgoue.contexts = row[2]; // 배열의 크기를 미리 지정해줘야되는데 강제로 넣고있어서 위 리스트를 이용
            do
            {
                contextList.Add(row[2]);
                EventList.Add(row[3]);
                SkipList.Add(row[4]);

                if (++i < data.Length) // i가 미리 증가한 상태에서 비교해준다 dataLentg보다 작다면
                {
                    row = data[i].Split(new char[] { ',' }); 
                }
                else
                {
                    break;
                }

            } while (row[0].ToString() == ""); // 최초 1회 조건 비교 없이 한 차례 실행시키고 조건문을 비교
                                               // row 0번째 줄에는 ID가 들어가 있고 Tostring으로 빈 공간인지 비교해줌
           
            
            dialgoue.contexts = contextList.ToArray();
            dialgoue.number = EventList.ToArray();
            dialgoue.skipnum = SkipList.ToArray();

            dialgoueList.Add(dialgoue);

            GameObject obj = GameObject.Find("DialgoueManager");
            obj.GetComponent<interactionEvent>().lineY = dialgoueList.Count;

        }

        return dialgoueList.ToArray();
    }
 

}

 

뭔가 코드가 많이 길어졌지만 크게 어려운 내용은 아니니 천천히 설명드리겠습니다. 제일 먼저 배열형식의 Dialogue 클래스를 함수로 사용하고 인자로는 string 형으로 CSVFileName을 받아옵니다  

이후 대사를 저장할 List를 만들어주기 위해 dialogueList를 선언해줍시다. 

다음으로 TextAsset은 CSVFile을 받아오기 위한 그릇 같은 개념으로 생각하시면 되고 중요한것은 저렇게 불러올 경우

반드시 유니티 폴더안에 Resources 라는 폴더가 만들어져있고 그 안에 아까 만들어두셨던 CSV파일이 들어가 있어야만 불러와집니다. 

이제 스트링 배열로 data라는 변수를 하나 선언해주고 Split이라는 걸 이용해서 각 라인을 쪼개어주는 작업이 필요합니다. 간단하게 엑셀의 1번째줄 2번째줄을 인식하기 위함 입니다. 

완료가 되었으면 for문을 보시게 되면 i가 1부터 시작하게 될겁니다. 

0 으로 시작하면 안되는 이유는 0부터 시작하게 되면 아까 만들어두었던 엑셀의 맨 첫번째 문장 ID,캐릭터이름,대사 ... 등등 필요 없는 내용까지 모두 들어가기 때문에 1부터 시작합니다.

CSV 파일은 각각 행을 ' , ' 로 구분하게 됩니다.  대충 CSV파일을 메모장으로 열어보게 되면 

ID,캐릭터이름,대사,이벤트번호,스킵라인,비고
1,공란,안녕` 내이름은 공란이야 반가워.,,,
,,너의 이름은 뭐야?,,,
2,주인공,반가워 봉란.,,,
,,내이름은 주인공이야.,,,
3,공란,그렇구나.,,,
,,잘부탁해.,,,
4,주인공,그래 공란아 잘부탁행,,,
5,공란,그런데 말이야 주인공..,,,
,,너는 내가 이쁘다고 생각해?,1,,
6,공란,뭐라고!? 보는 눈 없네 정말!,,8,
7,공란,그래? 역시 너도 내가 이쁘다고 생각하는구나,,,
,,보는 눈이 좀 있네~,,,
8,공란,어쨌든 정말 고마워,,,
,,덕분에 힘을 낼 수 있을것 같아.,,,

처럼 보이게 될 것입니다.

그래서 data에서 , 로 쪼개어진 내용들을 각각 한줄 씩 구분하기 위해 string 배열의 row 변수를 선언해주고

data에서 다시 split 함수를 사용하여 쪼개어 준 뒤 넣어줍니다. 

이후 쪼개어진 데이터값들을 담을 dialogue 클래스를 새로 만들어줍니다.

dialogue.Name 에는 row[1] 가 들어가게 됩니다. 0번에 id가 들어가게 되고 1번에 캐릭터 이름이 들어가게 됩니다. 2번에는 대사가 들어가게 되겠죠.

그럼 마찬가지로 dialogue.Context 또한 row[2]로 그냥 넣어주면 되겠지? 하시겠지만

제일 처음으로 돌아가 dialogue 클래스에서 Name은 일반 string으로 받지만 Context는 string[] 배열로 받고 있습니다. 

 즉슨, 배열의 크기를 지정해주지 않았기에 오류가 나게 되며 그렇기에 배열의 크기를 정해줄 수 없기 때문에 List를 사용하게 됩니다. 

자 여기서, 이벤트 넘버와 스킵라인 또한 배열 string으로 넣어주었는데 그냥 int로 넣어주면 되지 않나? 하시고 생각하실 수 있겠지만 row 자체가 string 배열 타입이기 때문에 따로 int로 받아오기가 어려워 그냥 똑같이 대사처럼 List로 만들어 관리하게 되었습니다. 

또 그럼 굳이 string 배열로 넣어줘야하나? Name 처럼 그냥 string 타입으로 넣어주면 되지 않나 생각하실 수 있겠지만. 

csv파일을 보시면 대사가 1줄이 아닌 2줄 이상인 대사가 있을 수 있고 그렇기에 한줄 한줄 넣어주어야 하기 때문에 배열로 넣게 되었습니다. 

 

 

반응형