[C#, 유니티] Unity3D_RPG ScriptableObject (Conversation)

2022. 8. 26. 21:48코딩 2막 <C#개념편>

728x90

대화 다이얼로그

스크립터블 오브젝트를 사용하여 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 == nullreturn;
 
        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

공감해주셔서 감사합니다

 

 

 

 

 

728x90