본문 바로가기

유니티/스크립트

[유니티] npc와 대화하기 UniRX

반응형

UniRX를 공부한 김에 이를 이용한 npc와 대화 로직을 만들었습니다.

유니티 RPG게임이나 추리게임에서 유용하게 사용할 수 있는 코드입니다.

프로그램은 다음과 같이 구성하였습니다.

1. NPC 가까이 가면 대화할 수 있음 ( InAreaNPC 메서드 )

2. 스페이스바를 누르면 대화창이 뜨고 대화 시작

3. 대화문장은 한글자씩 천천히 출력됨

4. 만약 이를 기다리기 싫다면 스페이스바를 한번 더 눌려서 전체 문장을 한번에 볼 수 있음

5. 해당 문장을 보기 싫다면 스페이스바를 더블클릭하여 다음 대화로 스킵가능

6. 대화가 끝나거나 대화도중 npc영역에서 벗어나면 대화 끝

7. 대화가 끝나면 무조건 대화는 처음부터 시작

게임을 좋아하시는 분들이라면 익숙한 동작이시죠?

이를 구현하면 다음과 같습니다.

 


using System.Collections;
using UnityEngine;
using UniRx;
using UniRx.Triggers;
using System;
using System.Linq;

public class TalkManager : MonoBehaviour
{
    NPCInfo npcOBJ;

    public float setTalkSpeed = 0.1f;
    WaitForSeconds talkSpeed;
    bool isTalkToAnyNPC = false;
    bool isTalking = false;
    IObservable<Unit> clickStream;

    void Start()
    {
        talkSpeed = new WaitForSeconds(setTalkSpeed);

        // 대화 스트림
        // 대화가능 상태에서 트리거버튼을 누르면 대화 시작
        // StopCoroutine으로 기존 대화중이던 내용 삭제
        clickStream = this.UpdateAsObservable().Where(_ => (Input_Z.Is_Down_Left_Trigger() || Input_Z.Is_Down_Right_Trigger() || Input.GetKeyDown("space")))
            .Publish() // Hot 변환
            .RefCount(); // Observer 추가시 자동 Connect

        clickStream
            .Where(_ => isTalkToAnyNPC && !isTalking)
            .Subscribe(_ => { StartCoroutine("TalkTalk"); });

        clickStream // 한번 클릭
            .Buffer(clickStream.Throttle(TimeSpan.FromMilliseconds(150)))
            .Where(x => x.Count < 2 && isTalking)
            .Subscribe(_ => isClicked = true);

        clickStream // 더블 클릭
            .Buffer(clickStream.Throttle(TimeSpan.FromMilliseconds(150)))
            .Where(x => x.Count >= 2 && isTalking)
            .Subscribe(_ => isDoubleClicked = true);
    }

    // 대화 클릭감지를 위한 변수들
    bool isClicked = false;
    bool isDoubleClicked = false;

    IEnumerator TalkTalk()
    {
        // 대화창 초기화
        npcOBJ.talkContent.text = "";
        npcOBJ.canvas_TalkWindow.SetActive(true);
        isTalking = true;
        int currContentNum = 0;

        while (true) // 할말없으면 break
        {
            yield return null;
            while (true) //한 마디가 끝나거나 더블클릭, 클릭 후 클릭하면 break
            {
                yield return null;
                if (isClicked || isDoubleClicked) break;
            }

            DoubleClicked:
            isClicked = false;
            isDoubleClicked = false;
            npcOBJ.talkContent.text = "";

            // 한글자씩 대화 출력
            // 클릭시 대화 전체출력
            // 더블클릭시 다음 대화로 이동
            foreach (char c in npcOBJ.talkContentList[currContentNum])
            {
                npcOBJ.talkContent.text += c;
                yield return talkSpeed;
                if (isDoubleClicked && currContentNum < (npcOBJ.talkContentList.Count -1))
                {
                    currContentNum++;
                    goto DoubleClicked;
                }
                if (isClicked || isDoubleClicked) break;
            }

            npcOBJ.talkContent.text = npcOBJ.talkContentList[currContentNum];

            // 한마디 끝, 클릭초기화 후 다음 이벤트 대기
            currContentNum++;
            isClicked = false;
            isDoubleClicked = false;
            if (currContentNum >= npcOBJ.talkContentList.Count) break;
        }

        while (true) // 씬 이동전 클릭대기
        {
            yield return null;
            if (isClicked || isDoubleClicked) break;
        }
        // 대화 후 추가적인 로직
    }

    public void InAreaNPC(NPCInfo _npcOBJ)
    {
        // NPC 앞으로 들어가면 true 나오면 false
        if (isTalkToAnyNPC == true) return;
        npcOBJ = _npcOBJ;

        isTalkToAnyNPC = true;
        npcOBJ.canvas_CanTalk.SetActive(true);
        // NPC 애니메이션 동작
    }

    public void OutAreaNPC(NPCInfo _npcOBJ)
    {
        // NPC 앞으로 들어가면 true 나오면 false
        if (isTalkToAnyNPC == false) return;
        npcOBJ = _npcOBJ;

        isTalkToAnyNPC = false;
        npcOBJ.canvas_CanTalk.SetActive(false);

        // 대화 초기화
        StopCoroutine("TalkTalk");
        npcOBJ.canvas_TalkWindow.SetActive(false);
        isTalking = false;
        isClicked = false;
        isDoubleClicked = false;
    }
}

 

NPCINfo 클래스에서 LIST<string> talkContent 에 원하는 대화를 순서대로 집어넣으면 됩니다.

모든 NPC에 이 클래스를 넣고 스트림을 만드는 것은 정말 비효율적이라 생각해서

모든 NPC가 공유할 수 있는 한개의 TalkManager 코드를 제작하였습니다.

이보다 더 좋은 예제가 있을 수 있습니다.

특히 goto문 혐오자분들이 싫어하시는 코드도 포함되있죠 ㅎㅎ..(전 이정도는 좋은 활용법이라 생각합니다.)

이 코드보다 더 효율적인 방법이 있다고 생각하면 그 방법을 사용하시면 됩니다.

이건 그냥 참고용으로만 쓰시죠.

그리고 더 좋은 방법이 있다면 댓글로 공유해주세요!!

반응형