NPC와 대화하는 시스템은 게임의 재미와 몰입감을 높여줄 뿐 아니라, 게임 내의 이야기 진행과 플레이어의 선택에 따라 행동이 결정되는 등 게임 플레이의 다양성을 높여줍니다. 대화 시스템을 구현하고 이를 정리하였습니다.
💭 서론
- 작성한 대화 시스템은 여러 기능을 포함하여 게임을 모두 만든 후에 정리하기위한 목적으로 글을 작성합니다.
- 하나의 큰 기능을 여러가지로 나누어 작성하였습니다.
- 이번 글에서는 대화에 대한 선택지를 눌러 서로 다른 동작을 수행하도록 하는 기능을 다루겠습니다.
💬 목차
- 총 세개의 글로 이루어져 있습니다.
- 시스템 자체는 하나의 스크립트 파일이지만, 분량을 고려하여 세 개로 나누었습니다.
1. [유니티] 대화 시스템(1) - Overlay Canvas
[📌현재 글] 2.[유니티] 대화 시스템(2) - 선택지
📺 예시
- 대사가 출력된 후 선택지 두개가 나와 플레이어가 선택을 하는 모습을 볼 수 있습니다.
- 선택지에 따라 동작이 다른것을 볼 수 있습니다.
✅ 구현(스크립트 작성)
· QuoteManager.cs
- 아래 작성된 스크립트는 현재 글의 주제 기능 뿐만아니라 이전 및 앞으로 다룰 모든 기능이 포함됩니다.
더보기
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Text;
/// <summary>
/// 대사 시스템
/// </summary>
public class QuoteManager : Singleton<QuoteManager>
{
private static bool mIsOverlayQuoteEnable = false;
/// <summary>
/// 오버레이 대사가 활성화 되어있는가?
/// </summary>
/// <value></value>
public static bool IsOverlayQuoteEnable
{
get
{
return mIsOverlayQuoteEnable;
}
}
private static bool mIsSelectQuoteEnable = false;
/// <summary>
/// 현재 선택형 대사가 활성화 되어있는가?
/// </summary>
/// <value></value>
public static bool IsSelectQuoteEnable
{
get
{
return mIsSelectQuoteEnable;
}
}
private readonly static float _MULTIPLY_LIGHT_CHAR = 1.0f;
private readonly static float _MULTIPLY_MIDDLE_CHAR = 3.0f;
private readonly static float _MULTIPLY_HEAVY_CHAR = 10.0f;
private readonly static float _QUOTE_SELECTOR_PARENT_FADE_DURATION = 0.5f;
[Header("스크립터블 오브젝트에서 읽은 데이터 파일")]
[Header("대사")][SerializeField] QuoteDataReader mReadedQuoteData;
[Header("선택형 대사")][SerializeField] SelectQuoteDataReader mReadedSelectQuoteData;
private Dictionary<int, QuoteData> mQuoteDataDictionary = new Dictionary<int, QuoteData>(); //대사를 저장해 둘 딕셔너리
private Dictionary<int, SelectQuoteData> mSelectQuoteDataDictionary = new Dictionary<int, SelectQuoteData>(); //대사를 저장해 둘 딕셔너리
// Global Quote
[Space(30)]
[Header("Overlay Quote UI 설정")]
[Header("오버레이 대사 출력 UI")]
[SerializeField] private QuoteTextGUI mOverlayTextGui;
// WorldSpace Quote
[Space(30)]
[Header("WorldSpace Quote 설정")]
[Header("월드 대사 출력 UI 프리팹")]
[SerializeField] private GameObject mWorldBubblePrefab;
// Quote Selector
[Space(30)]
[Header("Quote Select 설정")]
[Space(10)]
[Header("인스턴스 할 QupteSelector 오브젝트")]
[SerializeField] private GameObject mQuoteSelectorGo;
[Header("선택형 대사를 담을 스크롤바 오브젝트의 캔버스")]
[SerializeField] private CanvasGroup mQuoteSelectorCanvasGroup;
[Header("선택형 대사를 인스턴스 후 위치할 트랜스폼")]
[SerializeField] private RectTransform mQuoteSelectorPosition;
// 기타 변수들
private Coroutine mCoToggleQuoteSelector; //GlobalQuoteSelector 기능을 토글하는 코루틴
private List<GameObject> mInstantiatedQuoteSelectors = new List<GameObject>(); //런타임 도중 인스턴스된 선택형대사 오브젝트 리스트
private NPCBase mCurrentGlobalQuoteNPC; //현재 선택되어 활성화된 대사 NPC
private Dictionary<string, GameObject> mInstatntiatedWorldQuoteBubbles = new Dictionary<string, GameObject>(); // 월드에 인스턴스된 텍스트 버블 오브젝트들
private void Awake()
{
// 초기화시 전역 활성화상태 해제
QuoteManager.mIsOverlayQuoteEnable = false;
QuoteManager.mIsSelectQuoteEnable = false;
}
private void Start()
{
//최초로 데이터 로드
LoadData();
}
/// <summary>
/// 대사 데이터를 모두 읽어 초기화
/// </summary>
private void LoadData()
{
//대사
//비워주기(언어 설정)
mQuoteDataDictionary.Clear();
//각각 딕셔너리에 담아준다.
foreach (QuoteData data in mReadedQuoteData.DataList)
{
mQuoteDataDictionary.Add(data.id, data);
}
//선택형 대사
//비워주기(언어 설정)
mSelectQuoteDataDictionary.Clear();
//각각 딕셔너리에 담아준다.
foreach (SelectQuoteData data in mReadedSelectQuoteData.DataList)
{
mSelectQuoteDataDictionary.Add(data.id, data);
}
}
#region 일반 대사 기능
//////////////////////////////////////////////////////////////
//////////// //////////////
//////////// 일반 대사 기능 //////////////
//////////// //////////////
//////////////////////////////////////////////////////////////
/// <summary>
/// 대사 텍스트를 찾음
/// </summary>
/// <param name="quoteID">대사 ID</param>
/// <returns></returns>
private string GetQuote(int quoteID)
{
return mQuoteDataDictionary[quoteID].quoteText;
}
/// <summary>
/// Overlay 캔버스에서 대사를 출력
/// </summary>
/// <param name="quoteID">대사 Id</param>
/// <param name="caller">호출자</param>
/// <param name="disableDelay">모든 출력이 끝나고 비활성 대기시간</param>
/// <param name="delayPerChar">글자별 딜레이 시간</param>
/// <param name="fadeDuration">비활성 대기시간이 지난 후 사라지는 페이드 시간</param>
public float DisplayQuoteOverlay(int quoteID, NPCBase? caller, float disableDelay = 3.0f, float delayPerChar = 0.05f, float fadeDuration = 0.5f)
{
//선택형 대사들이 이미 있다면, 제거
ClearQuoteSelectors();
// 라벨 데이터 가져오기
QuoteData currentQuoteData = mQuoteDataDictionary[quoteID];
// 이름 라벨 변경
mOverlayTextGui.SetCallerNameLabel(currentQuoteData.quoteNPC);
// 텍스트 가져오기
string quoteText = GetQuote(quoteID);
// 라벨에 텍스트 쓰기
if (mOverlayTextGui.DisplayQuoteCoroutine != null) { StopCoroutine(mOverlayTextGui.DisplayQuoteCoroutine); }
mOverlayTextGui.DisplayQuoteCoroutine = StartCoroutine(CoDisplayQuote(mOverlayTextGui, quoteText, quoteID, false, caller, disableDelay, delayPerChar, fadeDuration));
// 현재 대사 매니저는 대사를 출력중임
QuoteManager.mIsOverlayQuoteEnable = true;
// 리턴
return GetQuoteLifetime(quoteText, disableDelay, delayPerChar, fadeDuration);
}
/// <summary>
/// 말풍선 형태의 대사를 출력
/// </summary>
/// <param name="keyName">말풍선 오브젝트를 재사용하기위한 고유 이름(key)</param>
/// <param name="forceText">id기반이 아닌 문자열 기반으로 대사를 출력</param>
/// <param name="isDestroyWhenDisable">비활성화시 오브젝트 자체를 파괴하는가</param>
/// <param name="parentTransform">말풍선을 위치시킬 트랜스폼</param>
/// <param name="offset">말풍선의 위치 오프셋</param>
/// <param name="disableDelay">모든 출력이 끝나고 비활성 대기시간</param>
/// <param name="delayPerChar">글자별 딜레이 시간</param>
/// <param name="fadeDuration">비활성 대기시간이 지난 후 사라지는 페이드 시간</param>
public void DisplayQuoteBubble(string keyName, string forceText, bool isDestroyWhenDisable, Transform parentTransform, Vector3 offset, float disableDelay = 3.0f, float delayPerChar = 0.05f, float fadeDuration = 0.5f)
{
GameObject? go = null;
mInstatntiatedWorldQuoteBubbles.TryGetValue(keyName, out go);
// keyName에 해당하는 프리팹이 없으면? 인스턴스
if (go == null)
{
go = Instantiate(mWorldBubblePrefab, Vector3.zero, Quaternion.identity, parentTransform);
mInstatntiatedWorldQuoteBubbles.Add(keyName, go);
}
QuoteTextGUI textGUI = go.GetComponent<QuoteTextGUI>();
textGUI.SetKeyName(keyName);
// 위치 변경
go.transform.localPosition = Vector3.zero;
go.transform.GetChild(0).localPosition = offset;
// 라벨에 텍스트 쓰기
if (textGUI.DisplayQuoteCoroutine != null)
StopCoroutine(textGUI.DisplayQuoteCoroutine);
textGUI.DisplayQuoteCoroutine = StartCoroutine(CoDisplayQuote(textGUI, forceText, -1, isDestroyWhenDisable, null, disableDelay, delayPerChar, fadeDuration));
}
/// <summary>
/// 말풍선 형태의 대사를 출력
/// </summary>
/// <param name="keyName">말풍선 오브젝트를 재사용하기위한 고유 이름(key)</param>
/// <param name="quoteID">대사 Id</param>
/// <param name="isDestroyWhenDisable">비활성화시 오브젝트 자체를 파괴하는가</param>
/// <param name="parentTransform">말풍선을 위치시킬 트랜스폼</param>
/// <param name="offset">말풍선의 위치 오프셋</param>
/// <param name="disableDelay">모든 출력이 끝나고 비활성 대기시간</param>
/// <param name="delayPerChar">글자별 딜레이 시간</param>
/// <param name="fadeDuration">비활성 대기시간이 지난 후 사라지는 페이드 시간</param>
public void DisplayQuoteBubble(string keyName, int quoteID, bool isDestroyWhenDisable, Transform parentTransform, Vector3 offset, float disableDelay = 3.0f, float delayPerChar = 0.05f, float fadeDuration = 0.5f)
{
DisplayQuoteBubble(keyName, GetQuote(quoteID), isActiveAndEnabled, parentTransform, offset, disableDelay, delayPerChar, fadeDuration);
}
#endregion
#region 선택형 대사 기능
//////////////////////////////////////////////////////////////
//////////// //////////////
//////////// 선택형 대사 기능 //////////////
//////////// //////////////
//////////////////////////////////////////////////////////////
/// <summary>
/// 선택형 대사 텍스트를 찾음
/// </summary>
/// <param name="quoteID">대사 ID</param>
/// <returns></returns>
private string GetSelectQuote(int quoteID)
{
return mSelectQuoteDataDictionary[quoteID].quoteText;
}
/// <summary>
/// 선택형 대사를 추가한다.
/// </summary>
/// <param name="quoteID">선택형 대사의 ID</param>
public void AddSelectQuote(int quoteID)
{
GlobalQuoteSelector newQuoteSel = Instantiate(mQuoteSelectorGo, Vector3.zero, Quaternion.identity, mQuoteSelectorPosition).GetComponent<GlobalQuoteSelector>();
mInstantiatedQuoteSelectors.Add(newQuoteSel.gameObject);
newQuoteSel.InitText(GetSelectQuote(quoteID), quoteID);
}
/// <summary>
/// 현재 인스턴스된 모든 선택형 대사들을 제거한다.
/// </summary>
private void ClearQuoteSelectors()
{
foreach (GameObject selectorGo in mInstantiatedQuoteSelectors)
Destroy(selectorGo);
mInstantiatedQuoteSelectors.Clear();
}
public void SelectQuote(int id)
{
//선택형 대사를 선택했을때, NPC가 있으면(등록되어있으면) 해당 NPC에게 보냄
if (mCurrentGlobalQuoteNPC != null) { MessageDispatcher.Instance.DispatchMessage(0, "", mCurrentGlobalQuoteNPC.EntityName, "QuoteSelected", id); }
//선택형 대사 토글 해제
ToggleQuoteSelector(false);
//선택형 대사 활성화 플래그 해제
mIsSelectQuoteEnable = false;
//커서 잠그기
UtilityManager.TryLockCursor();
}
/// <summary>
/// 선택형 대사창을 토글한다.
/// </summary>
/// <param name="isEnable">활성화 할것인가?</param>
/// <param name="caller">해당 선택창에서 대사를 선택하면 메시지를 누구에게 보낼것인가?</param>
public void ToggleQuoteSelector(bool isEnable, NPCBase caller = null)
{
//caller가 할당되었다면 이 선택형 대사는 선택의 결과를 전달하기위해 저장
//null인경우 사용하지 않거나, 대사를 선택하여 isEnable = false로 만들며 null로 설정
mCurrentGlobalQuoteNPC = caller;
if (mCoToggleQuoteSelector != null) { StopCoroutine(mCoToggleQuoteSelector); }
mCoToggleQuoteSelector = StartCoroutine(CoToggleQuoteSelector(isEnable));
}
private IEnumerator CoToggleQuoteSelector(bool isEnable)
{
if (isEnable)
{
//오브젝트 활성화
mQuoteSelectorCanvasGroup.gameObject.SetActive(true);
//선택형 대사 활성화
mIsSelectQuoteEnable = true;
//커서 잠금해제
UtilityManager.UnlockCursor();
}
float process = 0f;
float currentAlpha = mQuoteSelectorCanvasGroup.alpha;
while (process < 1f)
{
process += Time.deltaTime / _QUOTE_SELECTOR_PARENT_FADE_DURATION;
mQuoteSelectorCanvasGroup.alpha = Mathf.Lerp(currentAlpha, isEnable ? 1.0f : 0f, process);
yield return null;
}
if (!isEnable)
{
ClearQuoteSelectors();
mQuoteSelectorCanvasGroup.gameObject.SetActive(false);
}
}
#endregion
#region 공통 기능
private IEnumerator CoDisplayQuote(QuoteTextGUI textGUI, string quoteText, int quoteID, bool isDestroyWhenDisable, NPCBase? caller, float disableDelay, float delayPerChar, float fadeDuration)
{
// 텍스트 라벨 활성화
textGUI.ToggleLabel(true, fadeDuration, isDestroyWhenDisable);
//NPC 'caller'은 현재 대사를 출력중
if (caller is not null) { caller.IsQuotePlaying = true; }
StringBuilder builder = new StringBuilder();
WaitForSeconds lightDelay = new WaitForSeconds(delayPerChar * _MULTIPLY_LIGHT_CHAR);
WaitForSeconds middleDelay = new WaitForSeconds(delayPerChar * _MULTIPLY_MIDDLE_CHAR);
WaitForSeconds heavyDelay = new WaitForSeconds(delayPerChar * _MULTIPLY_HEAVY_CHAR);
//대사 텍스트 전체 검사
for (int i = 0; i < quoteText.Length; ++i)
{
//RichText
if (quoteText[i] == '<')
{
for (; i < quoteText.Length; ++i)
{
builder.Append(quoteText[i]);
if (quoteText[i] == '>') { ++i; break; }
}
}
//현재 텍스트 삽입
builder.Append(quoteText[i]);
if (quoteText[i] == ' ' || quoteText[i] == '.' || quoteText[i] == '!' || quoteText[i] == '?')
{
yield return middleDelay;
}
else if (quoteText[i] == '\n')
{
yield return heavyDelay;
}
else
{
yield return lightDelay;
}
//텍스트 업데이트
textGUI.UpdateText(builder.ToString());
}
// NPC 'caller'가 있는경우, NPC 'caller'에게 메시지 출력이 끝났다고 알린다.
if (caller is not null && quoteID != -1)
{
// 메시지 전송
MessageDispatcher.Instance.DispatchMessage(0, "", caller.EntityName, "WriteQuoteEnd", quoteID);
// NPC 'caller'은 현재 대사를 출력을 마침
caller.IsQuotePlaying = false;
}
// 현재 대사 출력이 끝남
QuoteManager.mIsOverlayQuoteEnable = false;
// 비활성화 시간을 대기하고 비활성화 호출
yield return new WaitForSeconds(disableDelay);
textGUI.ToggleLabel(false, fadeDuration, isDestroyWhenDisable);
if(textGUI.IsOverlayType)
mIsOverlayQuoteEnable = false;
}
private float GetQuoteLifetime(string quoteText, float disableDelay, float delayPerChar, float fadeDuration)
{
float lifeTime = disableDelay + fadeDuration;
float lightDelay = delayPerChar * _MULTIPLY_LIGHT_CHAR;
float middleDelay = delayPerChar * _MULTIPLY_MIDDLE_CHAR;
float heavyDelay = delayPerChar * _MULTIPLY_HEAVY_CHAR;
//대사 텍스트 전체 검사
for (int i = 0; i < quoteText.Length; ++i)
{
//RichText는 스킵
if (quoteText[i] == '<')
{
for (; i < quoteText.Length; ++i)
{
if (quoteText[i] == '>') { ++i; break; }
}
}
// 각 영역별로 시간 추가
if (quoteText[i] == ' ' || quoteText[i] == '.' || quoteText[i] == '!' || quoteText[i] == '?')
{
lifeTime += middleDelay;
}
else if (quoteText[i] == '\n')
{
lifeTime += heavyDelay;
}
else
{
lifeTime += lightDelay;
}
}
return lifeTime;
}
public bool TryRemoveBubbleFromDictionary(string keyName)
{
return mInstatntiatedWorldQuoteBubbles.Remove(keyName);
}
#endregion
}
· 선택지 기능
private static bool mIsSelectQuoteEnable = false;
/// <summary>
/// 현재 선택형 대사가 활성화 되어있는가?
/// </summary>
/// <value></value>
public static bool IsSelectQuoteEnable
{
get
{
return mIsSelectQuoteEnable;
}
}
- 현재 선택형 대사가 활성화 상태인지 확인하는 변수입니다.
[Header("스크립터블 오브젝트에서 읽은 데이터 파일")]
[Header("선택형 대사")][SerializeField] SelectQuoteDataReader mReadedSelectQuoteData;
private Dictionary<int, SelectQuoteData> mSelectQuoteDataDictionary = new Dictionary<int, SelectQuoteData>(); //대사를 저장해 둘 딕셔너리
- 선택형 데이터를 읽을 리더와 딕셔너리입니다.
- 구글 스프레드시트를 유니티의 데이터셋으로 이용하는 내용은 [유니티] 구글 스프레드 시트(엑셀) 연동 3 - 데이터 가져오기를 확인하세요.
/// <summary>
/// 선택형 대사 텍스트를 찾음
/// </summary>
/// <param name="quoteID">대사 ID</param>
/// <returns></returns>
private string GetSelectQuote(int quoteID)
{
return mSelectQuoteDataDictionary[quoteID].quoteText;
}
- 선택형 대사 텍스트를 찾습니다.
/// <summary>
/// 선택형 대사를 추가한다.
/// </summary>
/// <param name="quoteID">선택형 대사의 ID</param>
public void AddSelectQuote(int quoteID)
{
GlobalQuoteSelector newQuoteSel = Instantiate(mQuoteSelectorGo, Vector3.zero, Quaternion.identity, mQuoteSelectorPosition).GetComponent<GlobalQuoteSelector>();
mInstantiatedQuoteSelectors.Add(newQuoteSel.gameObject);
newQuoteSel.InitText(GetSelectQuote(quoteID), quoteID);
}
- 선택형 대사를 선택지 오브젝트에 추가합니다.
- GlobalQuoteSelector을 인스턴스하고 선택지를 위치시킬 부모 트랜스폼에 위치시킵니다.
/// <summary>
/// 현재 인스턴스된 모든 선택형 대사들을 제거한다.
/// </summary>
private void ClearQuoteSelectors()
{
foreach (GameObject selectorGo in mInstantiatedQuoteSelectors)
Destroy(selectorGo);
mInstantiatedQuoteSelectors.Clear();
}
- 선택지를 선택한경우, 또는 어떠한 이유에 의해 선택지를 선택하기 전에 제거해야하는경우 호출합니다.
public void SelectQuote(int id)
{
//선택형 대사를 선택했을때, NPC가 있으면(등록되어있으면) 해당 NPC에게 보냄
if (mCurrentGlobalQuoteNPC != null) { MessageDispatcher.Instance.DispatchMessage(0, "", mCurrentGlobalQuoteNPC.EntityName, "QuoteSelected", id); }
//선택형 대사 토글 해제
ToggleQuoteSelector(false);
//선택형 대사 활성화 플래그 해제
mIsSelectQuoteEnable = false;
//커서 잠그기
UtilityManager.TryLockCursor();
}
- 선택지를 선택하여 호출한경우 실행되며 특정 FSM에게 메시지를 보낼 수 있습니다.
/// <summary>
/// 선택형 대사창을 토글한다.
/// </summary>
/// <param name="isEnable">활성화 할것인가?</param>
/// <param name="caller">해당 선택창에서 대사를 선택하면 메시지를 누구에게 보낼것인가?</param>
public void ToggleQuoteSelector(bool isEnable, NPCBase caller = null)
{
//caller가 할당되었다면 이 선택형 대사는 선택의 결과를 전달하기위해 저장
//null인경우 사용하지 않거나, 대사를 선택하여 isEnable = false로 만들며 null로 설정
mCurrentGlobalQuoteNPC = caller;
if (mCoToggleQuoteSelector != null) { StopCoroutine(mCoToggleQuoteSelector); }
mCoToggleQuoteSelector = StartCoroutine(CoToggleQuoteSelector(isEnable));
}
- 선택형 대사를 모두 준비했다면 토글하여 활성화하거나, 사용을 완료한경우 비활성할 수 있습니다.
private IEnumerator CoToggleQuoteSelector(bool isEnable)
- 페이드 인, 아웃을 통해 서서히 토글하는 코루틴입니다.
· GlobalQuoteSelector
- 선택지를 클릭하여 선택지 인덱스 번호에 맞는 이벤트를 호출하기위한 기능을 수행하는 클래스입니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using TMPro;
public class GlobalQuoteSelector : MonoBehaviour, IPointerClickHandler, IPointerEnterHandler, IPointerExitHandler
{
/// <summary>
/// 포커스 하면 변경힐 버텍스 색상
/// </summary>
private readonly static Color _FOCUSED_COLOR = Color.cyan;
private readonly static float _FOCUSED_COLOR_CHANGE_DURATION = 0.25f;
[Header("선택할 하나의 텍스트 라벨")]
[SerializeField] private TextMeshProUGUI mQuoteSelectTextLabel;
private int mQuoteID; //현재 이 텍스트에 붙어있는 이벤트 ID
private Coroutine mColorChangeCoroutine; //색상을 바꾸는 코루틴 변수
/// <summary>
/// 선택형 텍스트 하나를 초기화
/// </summary>
/// <param name="text"></param>
/// <param name="id"></param>
public void InitText(string text, int id)
{
mQuoteID = id;
mQuoteSelectTextLabel.text = text;
}
public void OnPointerClick(PointerEventData eventData)
{
if(eventData.button == PointerEventData.InputButton.Left)
{
QuoteManager.Instance.SelectQuote(mQuoteID);
}
}
public void OnPointerEnter(PointerEventData eventData)
{
if(mColorChangeCoroutine != null) { StopCoroutine(mColorChangeCoroutine); }
mColorChangeCoroutine = StartCoroutine(COR_ColorChange(true));
}
public void OnPointerExit(PointerEventData eventData)
{
if(mColorChangeCoroutine != null) { StopCoroutine(mColorChangeCoroutine); }
mColorChangeCoroutine = StartCoroutine(COR_ColorChange(false));
}
private IEnumerator COR_ColorChange(bool isFocused)
{
float process = 0f;
Color currentVertexColor = mQuoteSelectTextLabel.color;
while(process < 1f)
{
process += Time.deltaTime / _FOCUSED_COLOR_CHANGE_DURATION;
mQuoteSelectTextLabel.color = Color.Lerp(currentVertexColor, isFocused ? _FOCUSED_COLOR : Color.white, process);
yield return null;
}
}
}
private readonly static Color _FOCUSED_COLOR = Color.cyan;
private readonly static float _FOCUSED_COLOR_CHANGE_DURATION = 0.25f;
- 현재 선택지에 마우스가 올라와 누를 수 있는 상태가 되면 설정하는 색상과 색상을 페이드 하는 시간입니다.
[Header("선택할 하나의 텍스트 라벨")]
[SerializeField] private TextMeshProUGUI mQuoteSelectTextLabel;
- 선택지에 해당하는 내용이 표시될 라벨입니다.
/// <summary>
/// 선택형 텍스트 하나를 초기화
/// </summary>
/// <param name="text"></param>
/// <param name="id"></param>
public void InitText(string text, int id)
{
mQuoteID = id;
mQuoteSelectTextLabel.text = text;
}
- 선택지 텍스트 하나를 초기화합니다.
- 선택지 텍스트 id와 텍스트를 할당합니다.
public void OnPointerClick(PointerEventData eventData)
{
if(eventData.button == PointerEventData.InputButton.Left)
{
QuoteManager.Instance.SelectQuote(mQuoteID);
}
}
- 선택지 텍스트 영역을 클릭하면 호출되며 자신이 선택되었다고 알립니다.
public void OnPointerEnter(PointerEventData eventData)
{
if(mColorChangeCoroutine != null) { StopCoroutine(mColorChangeCoroutine); }
mColorChangeCoroutine = StartCoroutine(COR_ColorChange(true));
}
public void OnPointerExit(PointerEventData eventData)
{
if(mColorChangeCoroutine != null) { StopCoroutine(mColorChangeCoroutine); }
mColorChangeCoroutine = StartCoroutine(COR_ColorChange(false));
}
- 선택지 텍스트 영역에 포인터가 진입하거나, 빠져나갈경우 호출되며 색상을 변경하도록 합니다.
- 선택지 색상 효과는 아래와 같습니다.
✅ 사용 방법
· 매니저
- 이전 글에 이어서 선택형 대사 및 Quote Select 설정을 이어서 합니다.
- Content 오브젝트는 Viewport를 사용하여 Vertical LayoutGroup을 사용합니다.
- M Quote Selector Go는 프리팹으로 아래 설정과 같습니다.
· GlobalQuoteSelector
- TextMeshPro를 사용하여 자기 자신을 레퍼런스하는 GlobalQuoteSelector 컴포넌트를 추가합니다.
· 함수 호출
- 이 내용은 FSM을 함께 사용하여 메시지를 받은 NPC가 특정 기능을 수행하도록 구현한 형태입니다.
public override void ExecNPCQuoteEndBehavior(int quoteID)
{
if(quoteID == 13)
{
QuoteManager.Instance.AddSelectQuote(5);
QuoteManager.Instance.AddSelectQuote(6);
QuoteManager.Instance.ToggleQuoteSelector(true, this);
}
}
- 영상에서 나오는 '보급상' 이라는 NPC의 대사 13번이 끝난경우 선택지를 활성화하기위해 위 함수를 수행합니다.
- 선택지를 삽입하고 ToggleQuoteSelector을 호출하여 선택지 전체 오브젝트를 활성화해줍니다.
public override void ExecQuoteSelectedBehavior(int selectedQuoteID)
{
switch (selectedQuoteID)
{
case 5:
mCraftingStation.TryOpenDialog();
break;
case 6:
QuoteManager.Instance.DisplayQuoteOverlay(14, this);
break;
}
}
- 선택지를 선택하면 선택지를 생성했을때 사용한 id를 이용하여 특정 기능을 수행하도록 제한할 수 있습니다.
'unity game modules' 카테고리의 다른 글
[유니티] 경사면 미끄러짐 (0) | 2023.03.27 |
---|---|
[유니티] 대화 시스템(3) - 말풍선 (1) | 2023.03.27 |
[유니티] 대화 시스템(1) - Overlay Canvas (0) | 2023.03.27 |
[유니티] 미니맵 아이콘 (0) | 2023.03.25 |
[유니티] 미니맵 (0) | 2023.03.25 |