NPC에게 상호작용으로 대화를 할수있고, 퀘스트를 주고받을 수 있게 만들려고 한다.
퀘스트는 아직 미구현이지만, 대화는 어느정도 구현되있다
UI창 만들기
대화를 하는 NPC의 초상화와 어떤 대화를 하는지가 표시되도록 생각했다.
TalkCanvas에 Text와 Portrait(Image)을 넣어준다.
그리고 아래와같이 텍스트가 표시될 부분과 초상화부분을 만들었다.
대화를 시도하면 텍스트가 바뀌면서, 초상화부분에 npc의 얼굴을 넣을 것이다.
Canvas를 편하게 다루려면 UI Scale Mode를 Scale With Screen Size로 바꾸면 좋다.
대화할 NPC구현
우선 대화할 NPC를 대충 만들었다.
그리고 상호작용 할 수 있도록 만들어야 한다.
상호작용을 하는 것은 아래에 있다.
대화 데이터 구현
각 NPC는 대화할 데이터를 가지고 있어야 한다.
그냥 스크립트로 적으면 너무 길어지기때문에 ScriptableObject로 만들면 편해진다.
어떤 Type(NPC or object)가 어떤상태에 어떤표정으로 무슨 대화를 할지 가지고 있어야 한다.
Type은 추후에 옷장이나 일반물건에 대화를 거는 경우 "이건 돌맹이다" 라고 표시되는 것을 생각했기에 만들었다.
우선 편하게 보기위해 Enum을 만들었다.
using System;
using UnityEngine;
using UnityEditor;
public enum TYPE
{
Object = 0,
BakerHouseGirl = 100,
WitchHouseGirl = 200,
WindmillGile = 300,
MushroomHouseGirl = 400,
}
public enum STATE
{
Default = 0, // 오브젝트
Talk = 1, // 처음 만났을 때 하는 대사
Hint = 2, // ###를 nextQuestNPC로 변경
Quest = 3, // Quest대사는 끝나고 퀘스트가 주어짐
}
public enum FACE
{
Default,
Surprised,
Smile
}
enum이름을 전부 대문자로 썼는데 원래는 PascalCase로 써야한다.
리팩토링 해야하는데 수정하면 대화데이터가 싹다 사라질까봐 못하고있다.ㅜㅜ
NPC가 여러개의 대화데이터를 가지고있는것 보단,
하나의 대화데이터에 모든 대화를 들고있는게 관리하기 좋을 것 같았다.
그래서 NPC는 고정으로 두고 Talk상태일때 [Smile-안녕, Default-오랜만에 보는구나] 이렇게 묶어두고싶었다.
이걸 딕셔너리나 튜플로 묶는걸 생각했는데, 그러면 인스펙터에서 보이지않는다는 문제가 있다.
이걸 고민을 좀 했었는데, Class로 만들어서 사용하는걸로 해결했다.
[CreateAssetMenu(fileName = "TalkData", menuName = "Talk/talk data")]
public class TalkData : ScriptableObject
{
public TYPE type;
public talkContent[] talkContent;
}
[Serializable]
public class talkContent
{
public STATE state;
public Content[] content;
}
[Serializable]
public class Content
{
public FACE face;
[TextArea(2, 6)]
public string script;
}
이렇게 만들면 인스펙터에서 볼 수 있다.
유니티에 커스텀 에디터로 인스펙터를 꾸미는 방법도 있지만, 복잡해서 그렇게까진 안했다.
그리고 에셋스토어에 찾아보면 좋은 에셋들이 많아서 사서 써도된다
https://assetstore.unity.com/packages/tools/utilities/odin-inspector-and-serializer-89041
Odin Inspector and Serializer | 유틸리티 도구 | Unity Asset Store
Use the Odin Inspector and Serializer from Sirenix on your next project. Find this utility tool & more on the Unity Asset Store.
assetstore.unity.com
상호작용
이제 필요한건 다 만들었으니 상호작용을 통해 대화가 진행되도록 만들어야한다.
1. 플레이어는 대화를 요청하고 TalkManager를 통해 다음 대화로 넘길 수 있음
2. NPC는 플레이어가 말을 걸면 TalkManager를 통해 대화를 실행
3. 모든 대화를 관리할 TalkManager가 모든 대화 관리
우선 TalkManager를 만들었다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class TalkManager : Singleton<TalkManager>
{
private Content[] curTalkList; // 현재 대화할 내용들
private Sprite[] curPortraitList; //현재 대화에 사용될 ojbect의 얼굴(각 object가 가지고있음)
private int curTalkIndex = 0; //몇번째 대화를 하고있는지 저장
// Dictionary<int, Sprite> portraitDate;
//UI
[SerializeField] private GameObject TalkUI; //전체 대화창 UI
[SerializeField] private Text textUI; //텍스트
[SerializeField] private Image portraitUI; //얼굴
private void OnEnable()
{
Initialize();
}
// 초기화
public void Initialize()
{
textUI.text = "";
portraitUI.sprite = null;
curTalkIndex = 0;
curTalkList = null;
curPortraitList = null;
GameManager.Instance.ChangeGameState(GAMESTATE.TALK, false);
TalkUI.SetActive(false);
}
// 대화창 UI를 켜고 대화 데이터를 넣음
public void OpenTalkUI(Content[] _content, Sprite[] _portrait)
{
curTalkList = _content;
curPortraitList = _portrait;
NextTalk();
GameManager.Instance.ChangeGameState(GAMESTATE.TALK, true);
TalkUI.SetActive(true);
}
// 다음 대화로 넘어감. (플레이어가 클릭시 실행)
public bool NextTalk()
{
// 대화가 끝나기 전까지 다음 대화를 실행하고, 끝난다면 초기화 후 종료
if (curTalkIndex < curTalkList.Length)
{
textUI.text = curTalkList[curTalkIndex].script;
portraitUI.sprite = curPortraitList[(int)curTalkList[curTalkIndex].face];
curTalkIndex++;
return true;
}
else
{
Initialize();
return false;
}
}
}
Initialize는 모든 값을 초기화하고 대화 UI를 끈다.
OpenTalkUI는 현재 대화를 시도하려는 NPC가 사용하며, 대화를 시도하려고 하면 현재 대화상태에 맞춰서 대화를 시작한다.
State enum의 Talk, Hint, Quest에 맞는 대화를 선택해서 대화를 진행한다는 뜻
NextTalk는 대화를 끝까지 진행시켜주고, 모든 대화가 끝나면 종료하도록 만들었다.
여기서 텍스트와 이미지를 현재 대화에 맞게 표시한다.
그 다음 Player의 상호작용을 하는 상태를 정의한다.
public IEnumerator Idle() // (+Move)
{
while (true)
{
Vector2 moveInput = MoveTo();
animator.SetFloat("MoveSpeed", moveInput.magnitude);
if (Input.GetMouseButtonDown(0))
{
StartCoroutine("Punch");
}
if (Input.GetKeyDown(KeyCode.F))
{
Interact();
if (GameManager.Instance.GetGameState(GAMESTATE.TALK))
{
ChangeState(State.Interactive);
}
}
if (Input.GetButton("Jump"))
{
JumpTo();
}
if (!characterController.isGrounded)
{
ChangeState(State.Jump);
}
else if (Input.GetButton("Sit"))
{
ChangeState(State.SitDown);
}
yield return null;
}
}
public void Interact()
{
if (!characterController.isGrounded) return;
if (Physics.Raycast(transform.position + Vector3.up * 0.5f, unityChan.forward, out RaycastHit hit, 1f, interactiveLayer))
{
targetObj = hit.collider.GetComponent<Interactable>();
if(targetObj != null) targetObj.Interact();
}
}
우선 Idle상태에서 F를 누르면 상호작용을 할 수 있다.
Interact메소드를 통해 앞에 Interactable을 가진 오브젝트의 Interact 메소드를 호출한다.
public IEnumerator Interactive()
{
bool isTalk = true;
while (true)
{
if (Input.GetMouseButtonDown(0))
{
isTalk = TalkManager.Instance.NextTalk();
}
if (!isTalk)
{
ChangeState(State.Idle);
}
yield return null;
}
}
그리고 상호작용 상태를 만들었다.
Idle상태에서만 전환가능하도록 만들었는데, 움직임은 막아두고 마우스 좌클릭으로 다음 대화로 넘어가는것만 가능하다.
만약 모든 대화가 끝난다면 다시 Idle상태로 전환한다.
마지막으로 NPC를 구현해주면 된다.
NPC를 만들기전에, 옷장이나 일반물건들도 상호작용이 되도록 나중에 바꿀 예정이라서 위에 쓴 Interactable을 만들었다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Interactable : MonoBehaviour
{
public TalkData talkData;
public STATE curState; //현재 상태. NPC의 경우 quest에 따라 값이 달라짐
public Sprite[] portrait;
protected Animator anim;
// 플레이어가 f눌렀을 때 동작
// 현재 NPC의 state에 맞는 content를 보냄
public virtual void Interact()
{
foreach (var _talkContent in talkData.talkContent)
{
if(_talkContent.state == curState)
{
TalkManager.Instance.OpenTalkUI(_talkContent.content, portrait);
return;
}
}
}
}
NPC는 이 Interactable을 상속받는다.
앞에 만든 TalkData를 가지고있고, 퀘스트 진행도 등 여러가지 요소에 따라 curState를 바꿀것이다.
상호작용을 진행하면 TalkData에서 현재 상태에 맞는 대화내용을 TalkManager에게 보내준다.
결과
F를 눌러 대화를 시도하고, 클릭을 하면 다음대화로 넘어갈 수 있다.
대화가 끝나면 다시 이동이 가능하다.
끝
'유니티_일기 > 3D_RPG!' 카테고리의 다른 글
3D RPG 만들기! (6) 몬스터+아이템 만들기 (0) | 2023.08.17 |
---|---|
3D RPG 만들기! (5) 공격 만들기 - 레이어마스크, 애니메이션 레이어 (0) | 2023.08.09 |
3D RPG 만들기! (3) 이동 구현하기 (0) | 2023.08.06 |
3D RPG 만들기! (2) TPS로 만들기 (0) | 2023.08.03 |
3D RPG 만들기! (1) 유니티짱 적용하기 (0) | 2023.08.02 |
댓글