2022. 8. 26. 21:48ㆍ코딩 2막 <C#개념편>
대화 다이얼로그
스크립터블 오브젝트를 사용하여 npc와 상호작용할 때 대화를 할 수 있는 컴포넌트를 만들었습니다
스크립트
1. ConversationController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class ConversationController : MonoBehaviour
{
[SerializeField]
private GameObject conversationUI;
[SerializeField]
private TextMeshProUGUI conversationTalkerName;
[SerializeField]
private TextMeshProUGUI conversationContent;
private Conversation curConversation;
private int activeIndex = 0;
private bool isConversation = false;
private void Awake()
{
StartConversation(curConversation);
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.X))
{
ProgressConversation();
}
}
public void StartConversation(Conversation conversation)
{
if (curConversation != null)
{
curConversation = conversation;
}
if (curConversation != conversation)
{
conversationUI.SetActive(true);
curConversation = conversation;
activeIndex = 0;
}
isConversation = true;
ProgressConversation();
}
public void ProgressConversation()
{
if (curConversation == null) return;
if (activeIndex < curConversation.Talks.Length)
{
conversationTalkerName.text = curConversation.Talks[activeIndex].talkerName;
conversationContent.text = curConversation.Talks[activeIndex].content;
if (curConversation.Talks[activeIndex].proprait != null)
{
// 초상화 설정
}
activeIndex++;
}
else
{
if (curConversation.nextConversation != null)
{
StartConversation(curConversation.nextConversation);
}
else
{
EndConversation();
}
}
}
public void EndConversation()
{
conversationUI.SetActive(false);
curConversation = null;
isConversation = false;
}
}
|
cs |
2. NPC.cs
상호작용 인터페이스를 상속받는 npc
대화를 하는 UIManager를 받는다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NPC : MonoBehaviour, IInteractable
{
[SerializeField]
private Conversation conversation;
public void Interaction()
{
UIManager.Instance.conver.StartConversation(conversation);
}
public void OnFocused()
{
Debug.Log("플레이어의 대상이 됨");
}
public void OnUnFocused()
{
Debug.Log("플레이어의 대상에서 벗어남");
}
}
|
cs |
3. IInteractable.cs
OnFocused(), OnUnFocused() 그리고 Interaction()을 확정받는다
1
2
3
4
5
6
7
8
9
10
11
12
|
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface IInteractable
{
void OnFocused();
void OnUnFocused();
void Interaction();
}
|
cs |
4. 싱글톤 UIManager.cs
대화 컨트롤러를 관리한다
1
2
3
4
5
6
7
8
9
10
11
12
13
|
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UIManager : Singleton<UIManager>
{
[SerializeField]
private ConversationController _conversationController;
public ConversationController conver { get { return _conversationController; } }
}
|
cs |
5. PlayerNormalMoveBehaviour.cs
플래이어 기본 행동패턴 normalMoveBehaviour을 통해
플레이어에게 Move(), Jump(), ChangeMode() 그리고 Interation()의 메소드를 구현한다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.TestTools;
public class PlayerNormalMoveBehaviour : StateMachineBehaviour
{
[SerializeField]
private LayerMask layerMask;
private Animator animator;
private Transform transform;
private CharacterController characterController = null;
private PlayerController playerController = null;
private Camera cam = null;
private float curRate;
// OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
this.animator = animator;
this.transform = animator.transform;
if (characterController == null)
{
characterController = animator.GetComponent<CharacterController>();
}
if (playerController == null)
{
playerController = animator.GetComponent<PlayerController>();
cam = playerController.cam;
}
}
// OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
Move();
Jump();
Interaction();
ChangeMode();
}
private void Move()
{
Vector3 moveInput = new Vector3(Input.GetAxis("Horizontal"), 0f, Input.GetAxis("Vertical"));
if (moveInput.sqrMagnitude > 1f)
{
moveInput = moveInput.normalized;
}
if (Input.GetButton("Walk"))
{
curRate -= Time.deltaTime;
if (curRate < playerController.walkRate)
curRate = playerController.walkRate;
}
else
{
curRate += Time.deltaTime;
if (curRate > 1f)
curRate = 1f;
}
moveInput *= curRate;
Vector3 forwardVec = new Vector3(cam.transform.forward.x, 0f, cam.transform.forward.z).normalized;
Vector3 rightVec = new Vector3(cam.transform.right.x, 0f, cam.transform.right.z).normalized;
Vector3 moveVec = moveInput.x * rightVec + moveInput.z * forwardVec;
// 이동 방향 바라보기
if (moveVec.magnitude > 0f)
transform.forward = moveVec;
animator.SetFloat("MoveSpeed", moveInput.magnitude);
characterController.Move(moveVec * playerController.moveSpeed * Time.deltaTime);
}
private void Jump()
{
if (Input.GetButtonDown("Jump"))
{
animator.SetTrigger("Jump");
}
}
private void ChangeMode()
{
if (Input.GetButtonDown("Battle"))
{
animator.SetBool("Battle", true);
}
}
private IInteractable preTarget = null;
private void Interaction()
{
animator.SetBool("Show", true);
Collider[] colliders = Physics.OverlapSphere(playerController.interactionPoint.position, playerController.interactionRange, layerMask);
if (colliders.Length <= 0)
{
preTarget?.OnUnFocused();
preTarget = null;
return;
}
IInteractable target = colliders[0].GetComponent<IInteractable>();
if (preTarget != target)
{
preTarget?.OnUnFocused();
preTarget = target;
preTarget?.OnFocused();
}
if (!Input.GetButtonDown("Interaction"))
{
animator.SetBool("Show", false);
return;
}
target?.Interaction();
}
}
|
cs |
Scriptable Object
스크립터블 오브젝트는 유니티에서 제공하는 대량의 데이트를 저장하여 쓸 수 있는
일명 데이터 컨테이너, 파일이라고 비유할 수 있다
스크립터블 오브젝트 클래스는 유니티에서 기본적으로 제공하는 것으로 MonoBehaviour 클래스와 마찬가지로
기본 유니티 오브젝트에서 파생되지만, 모노비헤이비어와 다른점은 게임오브젝트에 컴포넌트로 부착할 수 없고
프로젝트에 에셋으로 저장된다는 것이다.
대화자체에 내용을 더 추가(예를 들면 대사가 나올때 나오는 이미지, 이름, 오디오클립이나 카메라 효과 등 대사출력)
하기 위해서 인스펙터창에서 클래스를 건드릴 수 없다
하지만 다음과 같이 클래스가 직렬화할수 있도록 확인해주는 방법이 있다
이 방법을 통해 클래스를 직렬화하여 접근이 가능하다
1
2
3
4
5
6
7
8
|
[System.Serializable]
public class Talk
{
public string talkerName;
[TextArea(2,5)]
public string content;
public Sprite proprait;
}
|
cs |
Conversation.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
[CreateAssetMenu (menuName = "Conversation/Base")]
public class Conversation : ScriptableObject
{
[System.Serializable]
public class Talk
{
public string talkerName;
[TextArea(2,5)]
public string content;
public Sprite proprait;
}
// 대사창 영역 표시 textArea
[SerializeField]
private Talk[] _Talks;
// 읽기 전용 프로퍼티
public Talk[] Talks { get { return _Talks; } }
// 코드리뷰 //
// 1. 이벤트를 발생시킬 수는 없지만
// 2. 이벤트가 발생했을 때 필요한 정보는 저장해놓을 수 있다
// 1. 퀘스트를 받는다
// 2. 퀘스트 정보를 보관하고, 나중에 게임에서 퀘스트 정보에 해당하는 퀘스트를 받는걸로 하자
[SerializeField]
private Conversation _nextConversation;
public Conversation nextConversation { get { return _nextConversation; } }
// 다이얼로그 선택지
// 퀘스트
// 보상
}
|
cs |
공감해주셔서 감사합니다
'코딩 2막 <C#개념편>' 카테고리의 다른 글
[C#, 유니티] Unity3D_RPG 몬스터 (타입 & 제네릭 상태패턴) (2) | 2022.09.01 |
---|---|
[C#, 유니티] Unity3D_RPG 몬스터 (FSM, ViewDector) (0) | 2022.08.31 |
[C#, 유니티] Unity3D_RPG 상호작용 (1) | 2022.08.25 |
[C#, 유니티] Unity3D_RPG 상태패턴머신 (디자인패턴) (4) | 2022.08.24 |
[C#, 유니티] Unity3D_RPG 애니메이션 (2) | 2022.08.23 |