(주말 공부)XR플밍 - (26) Dialog 만들기 1 - 기본 출력 시도해보기 (7/13)

이형원·2025년 7월 13일
0

XR플밍

목록 보기
133/215

저번 프로젝트의 경우 원래는 게임의 스토리 및 세계관을 다룬 다이얼로그가 적용되었어야 했다.

여기서 다이얼로그란 이른바 미연시 같은 데서 텍스트로 대사를 출력한다든지, 스토리 및 혼잣말 등을 출력하는 것을 말한다.

이런 식으로 일지, 대사에 대한 정보도 있었지만 결국엔 시간상 적용하지 못했던 부분이다.

실제로 다른 팀원이 원래는 대사 작업하려고 만들어 둔 UI다. 결국에는 써먹지 못했지만, 아쉬운 마음에 만약 대사를 구현했더라면 어떻게 구현했을까 고민해보는 시간을 가지기로 했다.

1. 다이얼로그의 요소 구성하기

다이얼로그 : 대화, 또는 사용자 인터페이스에서 특정 동작이나 정보 표시를 위해 나타나는 작은 창

다이얼로그는 처음 만들어보는 기능이 될 것 같지만, 사실 어디까지나 '구현' 자체는 그렇게 어려울 것 같지 않다. 다만 어떻게 구성할 건지에 대한 고민이 필요해 보인다.

여러 가지 방법을 찾아봤을 때 우선 참고할 만한 내용으로 아래 글을 참고했다.

https://wjs991.tistory.com/29

다만 여기서 나는 내용을 어느 정도 참고하되 캐릭터의 이미지까지 바꾸는 효과를 줄 수 있는 방법에 대해 생각해보면서 아래와 같이 데이터를 구성했다.

using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class Dialog_Info
{
    public string Name;
    [TextArea] public string Context;
    public Sprite ChararcterImage;
    public bool IsDialogReaded;
}

[System.Serializable]
public class Dialog_Cycle
{
    public string CycleName;
    public int CycleIndex;
    public List<Dialog_Info> Info = new List<Dialog_Info>();
    public bool IsCycleReaded;
}

이 부분은 우선 초기 테스트를 위해 이와 같이 디자인하였고, 추후에 Name과 Character Image에 해당하는 부분은 캐릭터 데이터로 따로 구성하는 방식을 취해보려고 생각 중이다.

그 다음 DialogEvent에 해당하는 싱글톤 관리자를 다음과 같이 구성했다.

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

public class DialogEvent : MonoBehaviour
{
    public static DialogEvent Instance { get; private set; }
    [SerializeField] private List<Dialog_Cycle> _dialogueCycle = new List<Dialog_Cycle>();
    private Queue<string> _textSequence = new Queue<string>();
    private string _context;

    [SerializeField] private Text _nameText;
    [SerializeField] private Text _contextText;
    [SerializeField] private SpriteRenderer _spriteRenderer;
    [SerializeField] private Button _nextButton;
    [SerializeField] private GameObject _dialogUI;

    IEnumerator _sequence;
    IEnumerator _skipSequence;

    [SerializeField] private float _delay;

    private void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
            return;
        }

        Instance = this;
        DontDestroyOnLoad(gameObject);
    }

    public IEnumerator DialogSystem_Start(int index)
    {
        foreach(Dialog_Info info in _dialogueCycle[index].Info)
        {
            _textSequence.Enqueue(info.Context);
        }

        _dialogUI.gameObject.SetActive(true);
        for(int i = 0; i < _dialogueCycle[index].Info.Count; i++)
        {
            _nameText.text = _dialogueCycle[index].Info[i].Name;
            _context = _textSequence.Dequeue();
            _spriteRenderer.sprite = _dialogueCycle[index].Info[i].ChararcterImage;

            _nextButton.onClick.AddListener(() => {DisplayNext(index, i); });
            _sequence = Sequence_Sentence(index, i);
            StartCoroutine(_sequence);

            yield return new WaitUntil(() =>
            {
                if (_dialogueCycle[index].Info[i].IsDialogReaded)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            });
        }

        _nextButton.onClick.RemoveAllListeners();

        _dialogueCycle[index].IsCycleReaded = true;
    }

    public void DisplayNext(int index, int number)
    {
        if(_textSequence.Count == 0)
        {
            _dialogUI.gameObject.SetActive(false);
        }
        StopCoroutine(_sequence);

        _dialogueCycle[index].Info[number].IsDialogReaded = true;
    }
    
    public IEnumerator Sequence_Sentence(int index, int number)
    {
        WaitForSeconds delay = new WaitForSeconds(_delay);
        _skipSequence = TouchWait(_sequence, index, number);
        StartCoroutine(_skipSequence);
        _contextText.text = "";
        foreach(char letter in _context.ToCharArray())
        {
            _contextText.text += letter;
            yield return delay;
        }
        StopCoroutine(_skipSequence);
        IEnumerator next = TouchNext(index, number);
        StartCoroutine(next);
    }
    

    public IEnumerator TouchWait(IEnumerator sequence, int index, int number)
    {
        yield return new WaitForSeconds(0.3f);
        yield return new WaitUntil(() => Input.GetMouseButtonDown(0));
        StopCoroutine(_sequence);
        _contextText.text = _context;
        IEnumerator next = TouchNext(index, number);
        StartCoroutine(next);
    }

    public IEnumerator TouchNext(int index, int number)
    {
        StopCoroutine(_sequence);
        StopCoroutine(_skipSequence);
        yield return new WaitForSeconds(0.3f);
        yield return new WaitUntil(() => Input.GetMouseButtonDown(0));
        DisplayNext(index, number);
    }
    

    public bool DialogIsReaded(int dialogIndex)
    {
        if (!_dialogueCycle[dialogIndex].IsCycleReaded)
        {
            return true;
        }

        return false;
    }
}

2. 화면 구성 및 테스트

화면은 우선 이와 같이 구성했다.

그 다음 테스트는 아래와 같이 간단하게 작성하고 실행해보았다.

using System.Collections;
using UnityEngine;

public class Test : MonoBehaviour
{
    IEnumerator _dialogCoroutine;

    void Start()
    {
        _dialogCoroutine = DialogEvent.Instance.DialogSystem_Start(0);
        StartCoroutine(_dialogCoroutine);
    }
}
  • 테스트 영상

주말 일정이 있었던 탓에 공부를 그다지 못했고, 다음 공부할 내용만 우선 아래에 링크로 첨부해두고자 한다.

  • 다음에 공부할 내용

https://studybacksu.tistory.com/11

profile
게임 만들러 코딩 공부중

0개의 댓글