NPC 대화

깡통기사·2025년 6월 15일

개요

  • NPC와 대화하는 시스템을 구현한다

목표

  • NPC와 상호작용 시 대화문을 순차적으로 출력해 대화가 진행되게 한다
  • 대화 중 특정 키를 누르거나 대화문 UI의 버튼을 클릭하면 다흠 대화문으로 넘어간다
  • 대화문의 내용(포트레잇, NPC 이름, 대화 내용)등이 담긴 TalkData 클래스와, TalkData 클래스를 묶은 TalkBundle 스크립터블 오브젝트 클래스를 만들 것이다
  • TalkBundle을 넣을 InteractableObject를 상속하는 NPC 스크립트를 만들 것이다
  • 대화문을 표기할 UI를 만들 것이다
  • 대화문의 진행을 담당하는 TalkManager 스크립트를 만들어 대화문을 TalkBundle 내의 TalkData 클래스를 순차적으로 UI에 표기한다
  • 기타 키 입력과, GameManager의 상태를 만들고, 해당 상태일 경우 다른 조작이 불가능하게 한다

구현

대화 UI, UI 애니메이션 만들기

  • Canvas에 다음과 같이 대화문 UI를 만들었다
  • TalkPanel - 대화문 UI 전체
    └ Portrait - 대화중인 NPC의 포트레잇
    └ NamePanel, Name - 대화중인 NPC의 이름
    └ TalkText - 대화문 내용 텍스트
    └ TalkCursor - 대화문 넘기는 버튼

  • 대화 UI의 위치를 조정해 올라갔다, 내려갔다 하는 애니메이션도 제작했다
  • bool IsShow로 제어한다

TalkBundle 스크립터블 오브젝트

using UnityEngine;

[CreateAssetMenu(fileName = "TalkBundle", menuName = "Scriptable Objects/TalkBundle")]
public class TalkBundle : ScriptableObject
{
    [System.Serializable]
    public class TalkData
    {
        public string Name; //이름 칸
        public Sprite Portrait; //포트레이트 칸
        public bool IsLeft; //이름, 포트레이트가 왼쪽인지

        public string Text; //대화 내용
    }
    
    public TalkData[] talkdatas; //대화 내용 목록
}

  • 코드 작성 후 다음과 같은 형태로 스크립터블 오브젝트를 만든다

Object_NPC 스크립트 생성

using UnityEngine;

public class Object_NPC : InteractableObject
{
    public TalkBundle talkBundle;
}

  • 작성 후 NPC 오브젝트에 추가하고 TalkBundle도 넣어준다

TalkManager 스크립트 생성

using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class TalkManager : MonoBehaviour
{

    public TextMeshProUGUI talkText; //대화문 내용 텍스트
    public RectTransform namePanelPosition; //이름 패널 위치
    public TextMeshProUGUI nameText; //이름 텍스트
    public RectTransform portraitPosition; //포트레잇 위치
    public Image portraitImg; //포트레잇 이미지
    public Animator talkPanelAnimator; //대화문 애니메이션
    public GameManager gameManager;  // GameManager 참조
    private TalkBundle currentTalkBundle;  // 현재 대화 번들
    private int currentIndex = 0; //대화 인덱스

    public void StartTalk(TalkBundle talkBundle) //대화 시작
    {
        currentTalkBundle = talkBundle;
        currentIndex = 0;
        gameManager.currentState = GameManager.GameState.Talking;  // 대화 시작 상태로 변경
        talkPanelAnimator.SetBool("IsShow", true);  // 대화 UI 올리기
        NextTalk();  // 첫 번째 대화 내용 표시
    }

    public void NextTalk() //대화문 출력
    {
        int lastIndex = currentTalkBundle.talkdatas.Length - 1; //출력할 대사가 있는지, 없는지 확인용

        // 아직 출력할 대사가 있다면
        if (currentIndex <= lastIndex)
        {
            //TalkData의 내용(대화문, 이름, 포트레잇) 받아와서 UI에 표기
            var talkData = currentTalkBundle.talkdatas[currentIndex]; 
            nameText.text = talkData.Name;
            talkText.text = talkData.Text;
            portraitImg.sprite = talkData.Portrait;

            //이름 패널, 포트레잇 위치 결정
            float xPosition = talkData.IsLeft ? -200f : 200f;
            namePanelPosition.anchoredPosition = new Vector2(xPosition, namePanelPosition.anchoredPosition.y);
            portraitPosition.anchoredPosition = new Vector2(xPosition, portraitPosition.anchoredPosition.y);

            currentIndex++;  // 다음 대화로 진행
        }
        else
        {
            EndTalk(); //대화 종료
        }
    }

    private void EndTalk()
    {
        gameManager.currentState = GameManager.GameState.None;  // 대화 종료 상태로 변경
        currentIndex = 0;
        talkPanelAnimator.SetBool("IsShow", false);  // 대화 UI 내리기
    }

    public bool IsTalking()
    {
        return gameManager.currentState == GameManager.GameState.Talking;
    }
}
  • 대화문의 요소들을 인덱스에 따라 대화 UI에 순차적으로 표기하는 스크립트
  • StartTalk()함수로 대화 시작
    └ 상태를 대화중으로 변경
    └ 대화 UI 애니매이션을 실행(UI 등장)
    └최초 인덱스의 대화문을 표기
  • NextTalk()함수로 대화문 표기
    └ 대화 내용 UI에 표시
    └ 다음 인덱스로 넘기기
    └ 인덱스가 끝나면 자동으로 대화를 종료
  • EndTalk()함수로 대화 종료
    └ 상태를 None으로 변경
    └ 대화 UI 애니메이션을 실행(UI 퇴장)

  • 비어있는 오브젝트 생성 후 스크립트 추가

  • TalkCursor 오브젝트에 버튼 컴포넌트 추가
    └ 이제 클릭 시 NextTalk() 함수로 다음 대화로 넘어간다

GameManager 스크립트 수정

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using TMPro;

public class GameManager : MonoBehaviour
{
    //게임 상태
    public enum GameState { None, Talking,}
    public GameState currentState = GameState.None;

    public TalkManager talkManager;  // TalkManager 참조

    public void Action(GameObject scanObj) //상호작용
    {
        //상호작용한 오브젝트 가져오기
        InteractableObject obj = scanObj.GetComponent<InteractableObject>();

        //상호작용 함수 실행
        switch (obj.interactableType)
        {
            case InteractableType.NPC:
                Object_NPC npc = scanObj.GetComponent<Object_NPC>();
                talkManager.StartTalk(npc.talkBundle); //대화 함수 실행
                break;
            default:
                Debug.Log("상호작용 오브젝트입니다");
                break;
        }
    }

    public bool IsBusy()
    {
        return currentState != GameState.None;
    }
}
  • TalkManager 참조, InteractableObject가 NPC일 경우 TalkManager에서 함수를 실행하도록 코드 추가

  • 대화 상태(Talk) 추가

  • 이제 대화 상태일 때 플레이어는 다른 행동(이동, 상호작용)을 하지 못할 것이다

  • 인스펙터에서 TalkManager 추가

PlayerAction 스크립트 수정

using UnityEngine;

public class PlayerAction : MonoBehaviour
{
	/// ~~~ 생략 ~~~
    void Update()
    {
		/// ~~~ 생략 ~~~
        //상호작용
        if (Input.GetButtonDown("Interact") && scanObject != null && !manager.IsBusy())
        {
            manager.Action(scanObject);
        }

        //대화 넘기기
        if (Input.GetButtonDown("Submit") && manager.currentState == GameManager.GameState.Talking)
        {
            manager.talkManager.NextTalk();
        }
}
  • Input Manager로 대화를 넘기는 Submit 키(스페이스 바) 추가
  • 대화 중일 때 Submit 키를 누르면 다음 대화문 출력

결과

  • 정상적으로 대화문이 출력된다
  • 버튼을 누르거나, 스페이스 바를 누르면 다음 대화문으로 넘어간다

출저

https://www.youtube.com/watch?v=JY-KFx3OsJo&list=PLO-mt5Iu5TeYfyXsi6kzHK8kfjPvadC5u
골드메탈 유니티 기초 강좌 - 탑다운 2D RPG를 따라해 만들었다

0개의 댓글