Unity/Tip

다이얼로그 구현하기

코샵 2024. 5. 17. 14:14
반응형
#region
using System;
using System.Collections;

using TMPro;

using UnityEngine;
using UnityEngine.UI;
#endregion

public class DialogSystem : MonoBehaviour
{
	[SerializeField] Speaker[] speakers;
	[SerializeField] DialogData[] dialogs;
	[SerializeField] bool isAutoStart = true;
	bool isFirst = true;
	int currentDialogIndex = -1;
	int currentSpeakerIndex;
	const float TypingSpeed = 0.1f;
	bool isTypingEffect;

	bool isClicked;

	void Setup()
	{
		for (int i = 0; i < speakers.Length; ++i)
		{
			SetActiveObjects(speakers[i], false);
			speakers[i].imageDialog.GetComponent<Button>().onClick.AddListener(() => isClicked = true);
			speakers[i].speakerImage.gameObject.SetActive(true);
		}

		Debug.Log("Dialog SetUp");
	}

	public bool UpdateDialog()
	{
		if (isFirst)
		{
			Setup();

			if (isAutoStart) SetNextDialog();

			isFirst = false;
		}

		if (!isClicked) return false;

		isClicked = false;
		if (isTypingEffect)
		{
			isTypingEffect = false;

			StopCoroutine("OnTypingText");
			speakers[currentSpeakerIndex].textDialogue.text = dialogs[currentDialogIndex].dialogue;

			return false;
		}

		if (dialogs.Length > currentDialogIndex + 1) { SetNextDialog(); }
		else
		{
			EndDialog();
			return true;
		}

		return false;
	}

	void SetNextDialog()
	{
		SetActiveObjects(speakers[currentSpeakerIndex], false);

		currentDialogIndex++;

		currentSpeakerIndex = dialogs[currentDialogIndex].speakerIndex;
		// SoundManager.Instance.PlayOnlyOneEffect(dialogs[currentDialogIndex].effectSound);
		// SoundManager.Instance.PlayBackgroundMusic(dialogs[currentDialogIndex].backgroundSound);
		SetActiveObjects(speakers[currentSpeakerIndex], true);

		if (speakers[currentSpeakerIndex].textName is not null) speakers[currentSpeakerIndex].textName.text = dialogs[currentDialogIndex].name;

		StartCoroutine("OnTypingText");
	}

	void SetActiveObjects(Speaker speaker, bool visible)
	{
		speaker.imageDialog.gameObject.SetActive(visible);
		speaker.textName?.gameObject.SetActive(visible);
		speaker.textDialogue.gameObject.SetActive(visible);

		Color color = speaker.speakerImage.color;
		color.a = visible ? 1 : 0f;

		speaker.speakerImage.color = color;
	}

	IEnumerator OnTypingText()
	{
		int index = 0;

		isTypingEffect = true;

		while (index < dialogs[currentDialogIndex].dialogue.Length)
		{
			speakers[currentSpeakerIndex].textDialogue.text = dialogs[currentDialogIndex].dialogue.Substring(0, index);

			index++;

			yield return new WaitForSeconds(TypingSpeed);
		}

		isTypingEffect = false;
	}

	void EndDialog()
	{
		foreach (Speaker speaker in speakers)
		{
			SetActiveObjects(speaker, false);
			speaker.speakerImage.gameObject.SetActive(false);
		}

		// SoundManager.Instance.StopBackgroundMusic();
	}
}

[Serializable] public struct Speaker
{
	public Image speakerImage;
	public Image imageDialog;

	public TextMeshProUGUI textName;
	public TextMeshProUGUI textDialogue;
}

[Serializable] public struct DialogData
{
	public int speakerIndex;
	public string name;

	[TextArea(3, 5)]
	public string dialogue;

	public AudioClip effectSound;
	public AudioClip backgroundSound;
}

 

사용 방법

아래의 사진과 같이 화자(Speakers)들을 생성한 뒤, 다이얼로그에 화자의 인덱스를 맞게 생성하면 된다.

배경음악이나 효과음을 사용하고 싶다면, 해당 다이얼로그에 오디오 클립을 넣어주고 주석처리된 부분처럼 Sound Player를 연결해 오디오를 재생할 수 있다. 

 

다이얼로그가 끝나는 시점은 UpdateDialog() 메서드가 true를 반환 할 때로,

IEnumerator Dialog() { yield return new WaitUntil(() => dialogSystem.UpdateDialog()); }

이처럼 코루틴으로 끝날 때 까지 대기할 수 있다. 

 

타이핑 효과로 글자가 전부 출력되지 않은 상황에 Image Dialog를 클릭 시 타이핑 효과는 종료되고 즉시 텍스트가 전부 출력된다. 

주의 사항

Image Dialog는 Button 컴포넌트를 장착해야 한다. 

 

테스트

테스트