팀 프로젝트 하는중... NPC 대화시스템
생각보다 속도가 더뎌서 사실상 내일 완성작이 나와야 하는데 한 사이클도 못 돌리고 있다. 내일 게임이 대충 돌아가긴 해야할 수준까지 한번에 끌어올릴 수 있을까?? 완성은 해야할텐데 걱정이 되기 시작했다... 아무튼 오늘은 대화시스템을 구현해보았다. json을 사용했고, 사실 아직 좀 좋은 코드라고는 말할 수 없지만 지금 상황을 때울?정도는 됐다..
대화를 시작하면 움직임이 순간 멈추긴 했지만 자꾸 배회 로직이 도는지 걷는 모션이 잠깐 잠깐 나왔었다. 그래서 아래와 같이 코드를 수정하였다.
private void Update()
{
// 대화 중이라면 무조건 Idle
if (dialogueManager.isTalk)
{
EnterIdle();//강제로 Idle
return;
}
// 대화가 끝났고 Idle 상태이고 코루틴이 없을 때 다시 배회 시작
if (aiState == ALSTATE.IDLE && stateRoutine == null && dialogueManager.isTalk == false)
{
SetState(ALSTATE.WANDERING);
}
}
// 강제 Idle 진입: 코루틴 정리, 애니메이션 이동 멈춤
private void EnterIdle()
{
StopStateRoutine();
aiState = ALSTATE.IDLE;
anim.SetBool("IsWalk", false);
agent.ResetPath();
// Idle 상태에선 대기 루틴 실행하지 않고,
// 대화가 끝난 뒤 Update에서 배회 재개
Debug.Log("강제 Idle (대화 중)");
}
dialogueManager.isTalk이 true면 매 프레임 EnterIdle()을 호출하고, 기존 코루틴이 있다면 중단해 걷기애니메이션과 이동을 모두 멈춘다.일단 Resources폴더에 json파일을 넣어주었다.
{
"lines": [
{ "id": 1, "text": "돌쇠야 가끔은 쉬어도 된다" },
{ "id": 2, "text": "오늘 날씨가 좋으니 산책하기에 좋구나" },
{ "id": 3, "text": "이런 황무지도 공부할 것들이 있을진대, 돌쇠 너는 배움엔 관심이 없느냐?" },
{ "id": 10, "text": "밥 준비는 어떻게 되어가고 있느냐?",
"choices": [
{ "text": "잘 되어가고 있습니다", "nextId": 11 },
{ "text": "직접하시지요", "nextId": 12 }
]
},
{ "id": 11, "text": "항상 고맙구나" },
{ "id": 12, "text": "네 일이지 않느냐 열심히 하거라" }
]
}
가끔 차갑지만 좋은 선비님의 대사를 적어보았다. 하나뿐이지만 선택지도 넣어보았다.
DialogueUI 주요 함수 리뷰
//특정 선택지 설정
//number: 1 또는 2
//text: 버튼에 표시할 문장
//callback: 클릭 시 실행할 액션(다음 대사 호출)
public void SetChoice(int number, string text, Action callback)
{
if (number == 1)
{
btn1Text.text = text;//버튼 텍스트 변경
btn1.onClick.RemoveAllListeners();//이전에 있던 모든 클릭 이벤트 제거
btn1.onClick.AddListener(() => callback());//새로운 콜백 등록
}
else if (number == 2)
{
btn2Text.text = text;
btn2.onClick.RemoveAllListeners();
btn2.onClick.AddListener(() => callback());
}
}
RemoveAllListeners()로 이전에 붙었던 클릭 콜백이 있으면 그런걸 다 지우고 Addlistener()로 새로 전달된 callback만 등록한다.DialogueManager 주요 함수 리뷰
private void Awake()
{
// JSON 로드
TextAsset ta = Resources.Load<TextAsset>("Dialogue");
dialogueData = JsonUtility.FromJson<DialogueData>(ta.text);
// lookup 사전 생성 -> id로 라인 찾기 쉽게
lookup = new Dictionary<int, DialogLine>();
foreach (var line in dialogueData.lines)
lookup[line.id] = line;
// 후속 대사 ID 수집
var referencedIds = new HashSet<int>();
foreach (var line in dialogueData.lines)
if (line.choices != null)//선택지가 있는 라인이 있다면
foreach (var c in line.choices)
referencedIds.Add(c.nextId);
// 대화 시작용 라인 필터링
startableLines = new List<DialogLine>();
foreach (var line in dialogueData.lines)
{
bool hasChoice = line.choices != null && line.choices.Count > 0;
bool isReply = referencedIds.Contains(line.id);
//선택지가 있거나 선택지가 없는 대사를 시작용으로 인정(선택지 후속대사가 나오지 않게하기 위함)
if (hasChoice || (!hasChoice && !isReply))
startableLines.Add(line);
}
}
lookUp 딕셔너리에 모든 Dialogline을 id키로 저장해 나중에 nextid로 빠르게 조회한다.referencedIds에 모든 선택지의 nextId를 모아 후속 대사로 쓰이는 id집합을 생성한다.startableLines에는 질문 대사와 일반대사지만 다른 대사에 참조되지 않는것만 추가해 대화 시작 시에 뽑힐 수 있도록 필터링 하였다. private void ShowLine(DialogLine line)
{
dialogueUI.SetDialogueActive(true);
dialogueUI.SetDialogueText(line.text);
bool hasChoice = (line.choices != null && line.choices.Count > 0);
if (hasChoice)
{
// 선택지가 있는 대사일 때: 선택지 표시, 끝내기 버튼 숨기기
dialogueUI.ShowChoiceButtons();
dialogueUI.SetEndButtonActive(false);
// 첫 번째 선택지
dialogueUI.SetChoice(1, line.choices[0].text,
() => OnChoice(line.choices[0].nextId));
// 두 번째 선택지 (없으면 숨김)
if (line.choices.Count > 1)
dialogueUI.SetChoice(2, line.choices[1].text,
() => OnChoice(line.choices[1].nextId));
else
dialogueUI.HideChoice(2);
}
else
{
// 일반 대사일 때: 선택지 숨기고 끝내기 버튼 표시
dialogueUI.HideAllChoices();
dialogueUI.SetEndButtonActive(true);
}
}
아... 역시 오늘도 뭔가뭔가한 하루였다. 아직 생각보다 진도가 느린데 내일은 진짜 뭐가 나와야한다....
내 몫 중에 남은 일은 왼쪽 상단에 선비 ui 추가하는거랑 플레이어에게 음식을 받으면 스텟이 차게 하는 것, 그리고... 더 하자면 선비의 레벨이 2랑 5가 되었을 때 돌쇠를 불러(이것도 UI제작) 제작법을 주는 것 사실 제작법은 조금 후순위다. 왜냐면 지금 자원채취, 사냥, 적 처지 건축물 세우기 이런게 다 안되었기 때문에.... 이게 되면 그때 작업을 해볼까한다. 그러니까 실질적으로 남은건 UI추가, 스탯차기이다. 아 근데 생각해보니까 플레이어가 지금 제작을 못하니 선비에게 음식을 줄 수도 없다 젠장... 난.. 뭘해야하지?!?!