DEG-118 [Feat] 대화 관리 시스템 및 npc와의 대화문 추가
This commit is contained in:
parent
23e411b6e1
commit
f8e7f539d7
BIN
Assets/KSH/DungeonTestScene.unity
(Stored with Git LFS)
BIN
Assets/KSH/DungeonTestScene.unity
(Stored with Git LFS)
Binary file not shown.
@ -1,113 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
using TMPro;
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.EventSystems;
|
|
||||||
using UnityEngine.UI;
|
|
||||||
|
|
||||||
public class ChatWindowController : MonoBehaviour, IPointerClickHandler
|
|
||||||
{
|
|
||||||
[SerializeField] private TMP_Text chatText;
|
|
||||||
[SerializeField] private Image clickIndicator;
|
|
||||||
|
|
||||||
private Coroutine _typingCoroutine;
|
|
||||||
private Coroutine _clickCoroutine;
|
|
||||||
private string _inputText;
|
|
||||||
private Queue<string> _inputQueue;
|
|
||||||
|
|
||||||
public delegate void OnComplete();
|
|
||||||
public OnComplete onComplete;
|
|
||||||
|
|
||||||
private void Start()
|
|
||||||
{
|
|
||||||
Init("마무리", () =>
|
|
||||||
{
|
|
||||||
Debug.Log("대화 끝.");
|
|
||||||
});
|
|
||||||
ShowText(_inputQueue.Dequeue());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Init(string text, OnComplete onComplete)
|
|
||||||
{
|
|
||||||
_inputQueue = new Queue<string>();
|
|
||||||
_inputQueue.Enqueue("아 망했어 오늘도 지각이다!!!! 이러면 진짜 해고당할 수도 있어!!!\n어떡하지 큰일이다!!!");
|
|
||||||
_inputQueue.Enqueue("톼사하셈 ㅋ");
|
|
||||||
_inputQueue.Enqueue("톼사하셈 ㅋ");
|
|
||||||
_inputQueue.Enqueue("스킵도 가능 톼사하셈 ㅋ톼사하셈 ㅋ톼사하셈 ㅋ톼사하셈 ㅋ톼사하셈 ㅋ");
|
|
||||||
_inputQueue.Enqueue(text);
|
|
||||||
this.onComplete = onComplete;
|
|
||||||
}
|
|
||||||
|
|
||||||
//화면에 표시할 텍스트 삽입 함수
|
|
||||||
private void ShowText(string text)
|
|
||||||
{
|
|
||||||
var clickIndicatorColor = clickIndicator.color;
|
|
||||||
clickIndicatorColor.a = 1;
|
|
||||||
clickIndicator.color = clickIndicatorColor;
|
|
||||||
_inputText = text;
|
|
||||||
if (_typingCoroutine != null)
|
|
||||||
{
|
|
||||||
StopCoroutine(_typingCoroutine);
|
|
||||||
}
|
|
||||||
_typingCoroutine = StartCoroutine(TypingEffectCoroutine(_inputText));
|
|
||||||
}
|
|
||||||
|
|
||||||
//텍스트 타이핑효과 코루틴
|
|
||||||
private IEnumerator TypingEffectCoroutine(string text)
|
|
||||||
{
|
|
||||||
StringBuilder strText = new StringBuilder();
|
|
||||||
for (int i = 0; i < text.Length; i++)
|
|
||||||
{
|
|
||||||
strText.Append(text[i]);
|
|
||||||
chatText.text = strText.ToString();
|
|
||||||
yield return new WaitForSeconds(0.1f);
|
|
||||||
}
|
|
||||||
|
|
||||||
_clickCoroutine = StartCoroutine(ClickIndicatorCoroutine());
|
|
||||||
_typingCoroutine = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerator ClickIndicatorCoroutine()
|
|
||||||
{
|
|
||||||
bool flag = true;
|
|
||||||
var clickIndicatorColor = clickIndicator.color;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
clickIndicatorColor.a = flag? 0:1;
|
|
||||||
flag = !flag;
|
|
||||||
clickIndicator.color = clickIndicatorColor;
|
|
||||||
yield return new WaitForSeconds(0.5f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//대화창 클릭 시 호출 함수
|
|
||||||
public void OnPointerClick(PointerEventData eventData)
|
|
||||||
{
|
|
||||||
if (_typingCoroutine != null)
|
|
||||||
{
|
|
||||||
StopCoroutine(_typingCoroutine);
|
|
||||||
_typingCoroutine = null;
|
|
||||||
chatText.text = _inputText;
|
|
||||||
_clickCoroutine = StartCoroutine(ClickIndicatorCoroutine());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (_clickCoroutine != null)
|
|
||||||
{
|
|
||||||
StopCoroutine(_clickCoroutine);
|
|
||||||
_clickCoroutine = null;
|
|
||||||
}
|
|
||||||
if (_inputQueue.Count > 0)
|
|
||||||
{
|
|
||||||
ShowText(_inputQueue.Dequeue());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
onComplete?.Invoke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
8
Assets/Resources/Dialogues.meta
Normal file
8
Assets/Resources/Dialogues.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: feed7c503b7a69549acfe2836b78976e
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
158
Assets/Resources/Dialogues/dialogue.json
Normal file
158
Assets/Resources/Dialogues/dialogue.json
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
{
|
||||||
|
"dialogues": [
|
||||||
|
{
|
||||||
|
"id": "fairy_intro_1",
|
||||||
|
"name": "???",
|
||||||
|
"text": "일어나라 소년, 아니 청년, ...아니 34세의 회사원... 그대의 도움이 필요하다.",
|
||||||
|
"nextId": "fairy_intro_2",
|
||||||
|
"phase": "intro"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fairy_intro_2",
|
||||||
|
"name": "냉장고 요정",
|
||||||
|
"text": "나는 냉장고의 요정. \n이 냉장고는 세상을 멸망시킬 던전이 잠들어 있다, 그대가 부디 클리어 해주길 바란다. \n...일주일 안에!",
|
||||||
|
"nextId": "player_intro_1",
|
||||||
|
"phase": "intro"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "player_intro_1",
|
||||||
|
"name": "주인공",
|
||||||
|
"text": "...(어쩐지 감자 마켓 온도가 -99도더니...) 내가 왜",
|
||||||
|
"nextId": "fairy_intro_3",
|
||||||
|
"phase": "intro"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fairy_intro_3",
|
||||||
|
"name": "냉장고 요정",
|
||||||
|
"text": "안 하면 냉장고 터진다.",
|
||||||
|
"nextId": "player_intro_2",
|
||||||
|
"phase": "intro"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "player_intro_2",
|
||||||
|
"name": "주인공",
|
||||||
|
"text": "안돼! 3만원에 사온 내 비X코프 냉장고가! \n(실제 냉장고 상호명과는 아무 관계 없습니다.)",
|
||||||
|
"nextId": "fairy_intro_4",
|
||||||
|
"phase": "intro"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fairy_intro_4",
|
||||||
|
"name": "냉장고 요정",
|
||||||
|
"text": "또한 냉장고 폭파로 인한 화재가 발생할 수 있으며, 이는 부주의로 인하였기에 보험 적용에 제한이 있을 수 있습니다.",
|
||||||
|
"nextId": "player_intro_3",
|
||||||
|
"phase": "intro"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "player_intro_3",
|
||||||
|
"name": "주인공",
|
||||||
|
"text": "할 수 밖에 없잖아...!!",
|
||||||
|
"nextId": "",
|
||||||
|
"phase": "intro"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fairy_gameplay_1",
|
||||||
|
"name": "냉장고 요정",
|
||||||
|
"text": "일주일 후 평판이 3이면 좋은 일이 발생할 지도 몰라",
|
||||||
|
"nextId": "",
|
||||||
|
"phase": "gameplay"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fairy_gameplay_2",
|
||||||
|
"name": "냉장고 요정",
|
||||||
|
"text": "출근 한 번 안했다고 평판 2씩이나 깎는 회사가 있으려나...",
|
||||||
|
"nextId": "player_gameplay_2",
|
||||||
|
"phase": "gameplay"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "player_gameplay_2",
|
||||||
|
"name": "주인공",
|
||||||
|
"text": "(여기 있다.)",
|
||||||
|
"nextId": "",
|
||||||
|
"phase": "gameplay"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fairy_gameplay_3",
|
||||||
|
"name": "냉장고 요정",
|
||||||
|
"text": "던전은 총 2스테이지까지 있으며 모두 클리어한다면...",
|
||||||
|
"nextId": "player_gameplay_3",
|
||||||
|
"phase": "gameplay"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "player_gameplay_3",
|
||||||
|
"name": "주인공",
|
||||||
|
"text": "한다면... 설마 부와 명예를?",
|
||||||
|
"nextId": "fairy_gameplay_4",
|
||||||
|
"phase": "gameplay"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fairy_gameplay_4",
|
||||||
|
"name": "냉장고 요정",
|
||||||
|
"text": "축하의 박수를 쳐줄게~",
|
||||||
|
"nextId": "",
|
||||||
|
"phase": "gameplay"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fairy_end_1",
|
||||||
|
"name": "냉장고 요정",
|
||||||
|
"text": "일주일이 지났네! 수고했어! 이제 이 비스X프 냉장고는 너의 것이야!",
|
||||||
|
"nextId": "player_end_1",
|
||||||
|
"phase": "end"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "player_end_1",
|
||||||
|
"name": "주인공",
|
||||||
|
"text": "(네 도움이 없었다면 불가능했을 거야. 정말 고마워.) \n아자!!!!!!!!!",
|
||||||
|
"nextId": "fairy_end_2",
|
||||||
|
"phase": "end"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fairy_end_2",
|
||||||
|
"name": "냉장고 요정",
|
||||||
|
"text": "네 덕분에 이 세계는 다시 평화를 되찾았어.",
|
||||||
|
"nextId": "player_end_2",
|
||||||
|
"phase": "end"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "player_end_2",
|
||||||
|
"name": "주인공",
|
||||||
|
"text": "그래... 이제 넌 어떻게 되는 거지?",
|
||||||
|
"nextId": "fairy_end_3",
|
||||||
|
"phase": "end"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fairy_end_3",
|
||||||
|
"name": "냉장고 요정",
|
||||||
|
"text": "여기 있지...?",
|
||||||
|
"nextId": "player_end_3",
|
||||||
|
"phase": "end"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "player_end_3",
|
||||||
|
"name": "주인공",
|
||||||
|
"text": "?",
|
||||||
|
"nextId": "fairy_end_4",
|
||||||
|
"phase": "end"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fairy_end_4",
|
||||||
|
"name": "냉장고 요정",
|
||||||
|
"text": "?",
|
||||||
|
"nextId": "player_end_4",
|
||||||
|
"phase": "end"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "player_end_4",
|
||||||
|
"name": "주인공",
|
||||||
|
"text": "... ?",
|
||||||
|
"nextId": "fairy_end_5",
|
||||||
|
"phase": "end"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fairy_end_5",
|
||||||
|
"name": "냉장고 요정",
|
||||||
|
"text": "... 엔딩 크레딧 출력!",
|
||||||
|
"nextId": "",
|
||||||
|
"phase": "end"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
3
Assets/Resources/Dialogues/dialogue.json.meta
Normal file
3
Assets/Resources/Dialogues/dialogue.json.meta
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: cac8b86426d9414c912671dbc2fa7959
|
||||||
|
timeCreated: 1745890651
|
8
Assets/Scripts/Common/Dialogue.meta
Normal file
8
Assets/Scripts/Common/Dialogue.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: cfc4230a3c9c2b24b80574293a72e411
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
250
Assets/Scripts/Common/Dialogue/ChatWindowController.cs
Normal file
250
Assets/Scripts/Common/Dialogue/ChatWindowController.cs
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using TMPro;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.EventSystems;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using Random = UnityEngine.Random;
|
||||||
|
|
||||||
|
// 대화 데이터 클래스
|
||||||
|
[Serializable]
|
||||||
|
public class DialogueStruct
|
||||||
|
{
|
||||||
|
public string id; // 대화 고유 ID, 세이브용
|
||||||
|
public string name;
|
||||||
|
public string text;
|
||||||
|
public string nextId;
|
||||||
|
public string phase; // 단계 (intro, gameplay, end)
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class DialogueData // 전체 데이터 클래스
|
||||||
|
{
|
||||||
|
public DialogueData()
|
||||||
|
{
|
||||||
|
dialogues = new List<DialogueStruct>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DialogueStruct> dialogues;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum GamePhase // 단계별로 출력되는 대화가 달라짐
|
||||||
|
{
|
||||||
|
Intro, // 인트로 설명문
|
||||||
|
Gameplay, // 게임 진행 팁? 등
|
||||||
|
End // 엔딩 대화
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ChatWindowController : MonoBehaviour, IPointerClickHandler
|
||||||
|
{
|
||||||
|
[SerializeField] private TMP_Text nameText;
|
||||||
|
[SerializeField] private TMP_Text chatText;
|
||||||
|
[SerializeField] private Image clickIndicator;
|
||||||
|
[SerializeField] private GameObject chatWindowObject; // 대화 종료용
|
||||||
|
|
||||||
|
private Coroutine _typingCoroutine;
|
||||||
|
private Coroutine _clickCoroutine;
|
||||||
|
private string _inputText;
|
||||||
|
private Queue<DialogueStruct> _inputQueue;
|
||||||
|
|
||||||
|
public delegate void OnComplete();
|
||||||
|
public OnComplete onComplete;
|
||||||
|
|
||||||
|
private bool _dialogueEnded = false; // 대화 종료 여부
|
||||||
|
|
||||||
|
private FairyDialogueManager _dialogueManager;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
_inputQueue = new Queue<DialogueStruct>();
|
||||||
|
chatWindowObject.SetActive(false); // 일단 비활성화로 시작
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
// FairyDialogueManager 초기화
|
||||||
|
_dialogueManager = new FairyDialogueManager(this);
|
||||||
|
|
||||||
|
// 완료 콜백 설정
|
||||||
|
onComplete = () => {
|
||||||
|
Debug.Log("대화가 완료되었습니다.");
|
||||||
|
};
|
||||||
|
|
||||||
|
// 테스트 코드: 인트로 대화 시작
|
||||||
|
Debug.Log("인트로 대화 시작");
|
||||||
|
_dialogueManager.SetGamePhase(GamePhase.Intro);
|
||||||
|
|
||||||
|
// 테스트 코드: 게임플레이 및 엔딩 대화 테스트를 위한 코루틴 시작
|
||||||
|
StartCoroutine(TestDialogueSequence());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 테스트 코드
|
||||||
|
private IEnumerator TestDialogueSequence()
|
||||||
|
{
|
||||||
|
// 인트로 대화가 끝날 시간을 대략적으로 기다림
|
||||||
|
yield return new WaitForSeconds(5f);
|
||||||
|
|
||||||
|
// 게임플레이 단계로 전환
|
||||||
|
Debug.Log("게임플레이 대화 시작");
|
||||||
|
_dialogueManager.SetGamePhase(GamePhase.Gameplay);
|
||||||
|
_dialogueManager.TalkToFairy();
|
||||||
|
|
||||||
|
// 3초 후 다른 게임플레이 대화 테스트
|
||||||
|
yield return new WaitForSeconds(3f);
|
||||||
|
_dialogueManager.StartDialogueById("fairy_gameplay_2");
|
||||||
|
|
||||||
|
// 3초 후 엔딩 대화 테스트
|
||||||
|
yield return new WaitForSeconds(3f);
|
||||||
|
Debug.Log("엔딩 대화 시작");
|
||||||
|
_dialogueManager.SetGamePhase(GamePhase.End);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 대화창 표시
|
||||||
|
public void ShowWindow()
|
||||||
|
{
|
||||||
|
chatWindowObject.SetActive(true);
|
||||||
|
_dialogueEnded = false;
|
||||||
|
|
||||||
|
if (_inputQueue.Count > 0)
|
||||||
|
{
|
||||||
|
ShowNextDialogue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 대화창 숨기기
|
||||||
|
public void HideWindow()
|
||||||
|
{
|
||||||
|
chatWindowObject.SetActive(false);
|
||||||
|
onComplete?.Invoke(); // 대화창 중지하며 콜백 호출
|
||||||
|
|
||||||
|
// 진행 중인 모든 코루틴 중지
|
||||||
|
if (_typingCoroutine != null)
|
||||||
|
{
|
||||||
|
StopCoroutine(_typingCoroutine);
|
||||||
|
_typingCoroutine = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_clickCoroutine != null)
|
||||||
|
{
|
||||||
|
StopCoroutine(_clickCoroutine);
|
||||||
|
_clickCoroutine = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 대화 시퀀스 설정
|
||||||
|
public void SetDialogueSequence(List<DialogueStruct> sequence)
|
||||||
|
{
|
||||||
|
// 기존 큐 초기화
|
||||||
|
_inputQueue.Clear();
|
||||||
|
_dialogueEnded = false;
|
||||||
|
|
||||||
|
// 새 대화 시퀀스를 큐에 추가
|
||||||
|
foreach (DialogueStruct dialog in sequence)
|
||||||
|
{
|
||||||
|
_inputQueue.Enqueue(dialog);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 다음 대화 표시
|
||||||
|
private void ShowNextDialogue()
|
||||||
|
{
|
||||||
|
if (_inputQueue.Count == 0)
|
||||||
|
{
|
||||||
|
_dialogueEnded = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DialogueStruct dialog = _inputQueue.Dequeue();
|
||||||
|
|
||||||
|
// 이름 텍스트 업데이트
|
||||||
|
if (nameText != null)
|
||||||
|
{
|
||||||
|
nameText.text = dialog.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 클릭 인디케이터 활성화
|
||||||
|
var clickIndicatorColor = clickIndicator.color;
|
||||||
|
clickIndicatorColor.a = 1;
|
||||||
|
clickIndicator.color = clickIndicatorColor;
|
||||||
|
|
||||||
|
_inputText = dialog.text;
|
||||||
|
|
||||||
|
// 이전 타이핑 코루틴 중단
|
||||||
|
if (_typingCoroutine != null)
|
||||||
|
{
|
||||||
|
StopCoroutine(_typingCoroutine);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 새 타이핑 코루틴 시작
|
||||||
|
_typingCoroutine = StartCoroutine(TypingEffectCoroutine(_inputText));
|
||||||
|
}
|
||||||
|
|
||||||
|
//텍스트 타이핑효과 코루틴
|
||||||
|
private IEnumerator TypingEffectCoroutine(string text)
|
||||||
|
{
|
||||||
|
StringBuilder strText = new StringBuilder();
|
||||||
|
for (int i = 0; i < text.Length; i++)
|
||||||
|
{
|
||||||
|
strText.Append(text[i]);
|
||||||
|
chatText.text = strText.ToString();
|
||||||
|
yield return new WaitForSeconds(0.05f);
|
||||||
|
}
|
||||||
|
|
||||||
|
_clickCoroutine = StartCoroutine(ClickIndicatorCoroutine());
|
||||||
|
_typingCoroutine = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator ClickIndicatorCoroutine()
|
||||||
|
{
|
||||||
|
bool flag = true;
|
||||||
|
var clickIndicatorColor = clickIndicator.color;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
clickIndicatorColor.a = flag? 0:1;
|
||||||
|
flag = !flag;
|
||||||
|
clickIndicator.color = clickIndicatorColor;
|
||||||
|
yield return new WaitForSeconds(0.5f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//대화창 클릭 시 호출 함수
|
||||||
|
public void OnPointerClick(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
if (_typingCoroutine != null)
|
||||||
|
{
|
||||||
|
StopCoroutine(_typingCoroutine);
|
||||||
|
_typingCoroutine = null;
|
||||||
|
chatText.text = _inputText;
|
||||||
|
_clickCoroutine = StartCoroutine(ClickIndicatorCoroutine());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_clickCoroutine != null)
|
||||||
|
{
|
||||||
|
StopCoroutine(_clickCoroutine);
|
||||||
|
_clickCoroutine = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 대화가 끝났으면 창 닫기
|
||||||
|
if (_dialogueEnded)
|
||||||
|
{
|
||||||
|
HideWindow();
|
||||||
|
onComplete?.Invoke();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_inputQueue.Count > 0) // 대화가 남은 경우
|
||||||
|
{
|
||||||
|
ShowNextDialogue();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_dialogueEnded = true; // 일단 대화창 닫고 이후 콜백 호출
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
255
Assets/Scripts/Common/Dialogue/FairyDialogueManager.cs
Normal file
255
Assets/Scripts/Common/Dialogue/FairyDialogueManager.cs
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEngine;
|
||||||
|
using Random = UnityEngine.Random;
|
||||||
|
|
||||||
|
// 일반 클래스로 변경된 FairyDialogueManager
|
||||||
|
public class FairyDialogueManager
|
||||||
|
{
|
||||||
|
private ChatWindowController _chatWindow;
|
||||||
|
private string _dialogueFileName;
|
||||||
|
|
||||||
|
// 현재 게임 단계
|
||||||
|
private GamePhase _currentGamePhase = GamePhase.Intro;
|
||||||
|
|
||||||
|
private DialogueData _database;
|
||||||
|
private Dictionary<string, DialogueStruct> _dialogueDict;
|
||||||
|
|
||||||
|
// 단계별 대화 모음
|
||||||
|
private Dictionary<string, List<DialogueStruct>> _phaseDialogues;
|
||||||
|
|
||||||
|
// 이미 보여준 게임플레이 대화 추적
|
||||||
|
private HashSet<string> _shownGameplayDialogueIds;
|
||||||
|
|
||||||
|
// 생성자
|
||||||
|
public FairyDialogueManager(ChatWindowController chatWindow, string dialogueFileName = "dialogue.json")
|
||||||
|
{
|
||||||
|
_chatWindow = chatWindow;
|
||||||
|
_dialogueFileName = dialogueFileName;
|
||||||
|
_shownGameplayDialogueIds = new HashSet<string>();
|
||||||
|
|
||||||
|
LoadDialogueData();
|
||||||
|
OrganizeDialogues();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 대화 데이터베이스 로드
|
||||||
|
private void LoadDialogueData()
|
||||||
|
{
|
||||||
|
string filePath = "Dialogues/" + _dialogueFileName.Replace(".json", "");
|
||||||
|
TextAsset jsonFile = Resources.Load<TextAsset>(filePath);
|
||||||
|
|
||||||
|
if (jsonFile == null)
|
||||||
|
{
|
||||||
|
Debug.LogError($"Failed to load dialogue database: {filePath}");
|
||||||
|
_database = new DialogueData { dialogues = new List<DialogueStruct>() };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Log($"JSON 파일 내용: {jsonFile.text.Substring(0, Mathf.Min(200, jsonFile.text.Length))}..."); // 파일 내용 확인
|
||||||
|
|
||||||
|
try {
|
||||||
|
_database = JsonUtility.FromJson<DialogueData>(jsonFile.text);
|
||||||
|
Debug.Log($"대화 항목 수: {_database.dialogues.Count}");
|
||||||
|
|
||||||
|
// 검증: 각 단계별 대화 항목 수 확인
|
||||||
|
Dictionary<string, int> phaseCount = new Dictionary<string, int>();
|
||||||
|
foreach (var dialogue in _database.dialogues)
|
||||||
|
{
|
||||||
|
if (!phaseCount.ContainsKey(dialogue.phase))
|
||||||
|
phaseCount[dialogue.phase] = 0;
|
||||||
|
phaseCount[dialogue.phase]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var phase in phaseCount)
|
||||||
|
{
|
||||||
|
Debug.Log($"단계 '{phase.Key}': {phase.Value}개 항목");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 대화 사전 초기화
|
||||||
|
_dialogueDict = new Dictionary<string, DialogueStruct>();
|
||||||
|
foreach (DialogueStruct entry in _database.dialogues)
|
||||||
|
{
|
||||||
|
_dialogueDict[entry.id] = entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
Debug.LogError($"JSON 파싱 오류: {e.Message}");
|
||||||
|
_database = new DialogueData { dialogues = new List<DialogueStruct>() };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 대화를 단계별로 분류
|
||||||
|
private void OrganizeDialogues()
|
||||||
|
{
|
||||||
|
_phaseDialogues = new Dictionary<string, List<DialogueStruct>>();
|
||||||
|
|
||||||
|
foreach (DialogueStruct entry in _database.dialogues)
|
||||||
|
{
|
||||||
|
// 단계별 분류
|
||||||
|
if (!_phaseDialogues.ContainsKey(entry.phase))
|
||||||
|
{
|
||||||
|
_phaseDialogues[entry.phase] = new List<DialogueStruct>();
|
||||||
|
}
|
||||||
|
_phaseDialogues[entry.phase].Add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 게임 단계 설정
|
||||||
|
public void SetGamePhase(GamePhase phase)
|
||||||
|
{
|
||||||
|
_currentGamePhase = phase;
|
||||||
|
|
||||||
|
// Intro 또는 End 단계로 진입 시 자동으로 해당 대화 시작
|
||||||
|
if (phase == GamePhase.Intro)
|
||||||
|
{
|
||||||
|
StartPhaseDialogue("intro");
|
||||||
|
}
|
||||||
|
else if (phase == GamePhase.End)
|
||||||
|
{
|
||||||
|
StartPhaseDialogue("end");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 단계별 시작 대화 찾기 및 시작
|
||||||
|
private void StartPhaseDialogue(string phaseName)
|
||||||
|
{
|
||||||
|
if (!_phaseDialogues.ContainsKey(phaseName) || _phaseDialogues[phaseName].Count == 0)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"No dialogues found for phase: {phaseName}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 해당 단계의 첫 대화 항목 찾기 (요정이 먼저 말하는 대화)
|
||||||
|
DialogueStruct startEntry = _phaseDialogues[phaseName]
|
||||||
|
.FirstOrDefault(d => d.name == "냉장고 요정" && !string.IsNullOrEmpty(d.nextId));
|
||||||
|
|
||||||
|
if (startEntry == null)
|
||||||
|
{
|
||||||
|
// 적합한 시작 대화가 없으면 첫 번째 대화 사용
|
||||||
|
startEntry = _phaseDialogues[phaseName][0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 대화 시퀀스 시작
|
||||||
|
StartDialogueSequence(startEntry.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 랜덤 게임플레이 대화 보여주기
|
||||||
|
public void ShowRandomGameplayDialogue()
|
||||||
|
{
|
||||||
|
if (_currentGamePhase != GamePhase.Gameplay)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("Random dialogue can only be shown during gameplay phase");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 게임플레이 단계의 요정 대화 중 아직 보여주지 않은 것 찾기
|
||||||
|
var availableDialogues = _phaseDialogues["gameplay"]
|
||||||
|
.Where(d => d.name == "냉장고 요정" && !_shownGameplayDialogueIds.Contains(d.id))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// 모든 대화를 다 보여줬다면 다시 초기화
|
||||||
|
if (availableDialogues.Count == 0)
|
||||||
|
{
|
||||||
|
_shownGameplayDialogueIds.Clear();
|
||||||
|
availableDialogues = _phaseDialogues["gameplay"]
|
||||||
|
.Where(d => d.name == "냉장고 요정")
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 랜덤 대화 선택
|
||||||
|
if (availableDialogues.Count > 0)
|
||||||
|
{
|
||||||
|
int randomIndex = Random.Range(0, availableDialogues.Count);
|
||||||
|
DialogueStruct randomDialogue = availableDialogues[randomIndex];
|
||||||
|
|
||||||
|
// 보여준 대화 기록
|
||||||
|
_shownGameplayDialogueIds.Add(randomDialogue.id);
|
||||||
|
|
||||||
|
// 대화 시퀀스 시작
|
||||||
|
StartDialogueSequence(randomDialogue.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 대화 ID로 대화 시작
|
||||||
|
public void StartDialogueSequence(string startDialogueId)
|
||||||
|
{
|
||||||
|
if (!_dialogueDict.ContainsKey(startDialogueId))
|
||||||
|
{
|
||||||
|
Debug.LogError($"Dialogue ID not found: {startDialogueId}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 시작 대화 항목 가져오기
|
||||||
|
DialogueStruct startEntry = _dialogueDict[startDialogueId];
|
||||||
|
|
||||||
|
// 대화 시퀀스 구성
|
||||||
|
List<DialogueStruct> sequence = BuildDialogueSequence(startEntry);
|
||||||
|
|
||||||
|
// 대화창에 대화 시퀀스 전달
|
||||||
|
_chatWindow.SetDialogueSequence(sequence);
|
||||||
|
_chatWindow.ShowWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 연결된 대화 시퀀스 구성
|
||||||
|
private List<DialogueStruct> BuildDialogueSequence(DialogueStruct startEntry)
|
||||||
|
{
|
||||||
|
List<DialogueStruct> sequence = new List<DialogueStruct>();
|
||||||
|
HashSet<string> visitedIds = new HashSet<string>(); // 순환 참조 방지
|
||||||
|
|
||||||
|
DialogueStruct currentEntry = startEntry;
|
||||||
|
while (currentEntry != null && !visitedIds.Contains(currentEntry.id))
|
||||||
|
{
|
||||||
|
sequence.Add(currentEntry);
|
||||||
|
visitedIds.Add(currentEntry.id);
|
||||||
|
|
||||||
|
// 다음 대화가 없으면 종료
|
||||||
|
if (string.IsNullOrEmpty(currentEntry.nextId) ||
|
||||||
|
!_dialogueDict.ContainsKey(currentEntry.nextId))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 다음 대화로 이동
|
||||||
|
currentEntry = _dialogueDict[currentEntry.nextId];
|
||||||
|
}
|
||||||
|
|
||||||
|
return sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 요정에게 말 걸기 (게임 중 언제든 사용 가능)
|
||||||
|
public void TalkToFairy()
|
||||||
|
{
|
||||||
|
switch (_currentGamePhase)
|
||||||
|
{
|
||||||
|
case GamePhase.Intro:
|
||||||
|
StartPhaseDialogue("intro");
|
||||||
|
break;
|
||||||
|
case GamePhase.Gameplay:
|
||||||
|
ShowRandomGameplayDialogue();
|
||||||
|
break;
|
||||||
|
case GamePhase.End:
|
||||||
|
StartPhaseDialogue("end");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 특정 대화 ID로 대화 시작 (외부에서 호출 가능)
|
||||||
|
public void StartDialogueById(string dialogueId)
|
||||||
|
{
|
||||||
|
if (_dialogueDict.ContainsKey(dialogueId))
|
||||||
|
{
|
||||||
|
StartDialogueSequence(dialogueId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogError($"Dialogue ID not found: {dialogueId}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 완료 콜백 설정
|
||||||
|
public void SetOnCompleteCallback(ChatWindowController.OnComplete callback)
|
||||||
|
{
|
||||||
|
_chatWindow.onComplete = callback;
|
||||||
|
}
|
||||||
|
}
|
11
Assets/Scripts/Common/Dialogue/FairyDialogueManager.cs.meta
Normal file
11
Assets/Scripts/Common/Dialogue/FairyDialogueManager.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5c08c5a9419d39e40b838ea04b98e85e
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
Loading…
x
Reference in New Issue
Block a user