[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 22회차 미션 시작합니다.

02 디아블로 게임 - 39. 40 챕터를 진행합니다.

 


NPC와의 대화를 위한 Dialog System 구현입니다.


* Dialogue -> DialogManager에게 내용 전달하면 -> DialogUI에 표시
+ Name - 대화 이름
+ Sentences - 대화 내용


[Serializable]
public clas Dialogue_New
{
    public string name; // NPC 이름
    
    [TextArea(3, 10)]
    public string[] sentences; // 대화문장 배열
}



public class DialogueManager_New : MonoBehaviour
{
    private static DialogueManager_New instance; // Singleton Pattern
    public static DialogManager_New Instance = >instance;
    public Text nameText;
    public Text dialogueText;
    public Animator animator = null;
    private Queue<string> sentences;
    public event Action OnStartDialogue; // 다이알로그 시작 이벤트
    public event Action OnEndDialogue; // 다이알로그 종료 이벤트
    
    private void Awake() {
        instance = this;
    }
    
    void Start() {
        sentences = new Queue<string>();
    }
    
    public void StartDialogue(Dialogue_New dialogue) {
        OnStartDialogue?.Invoke();
        animator?.SetBool("IsOpen", true);
        nameText.text = dialogue.name;
        sentences.Clear();
        foreach (string sentence in dialogue.sentences) {
            sentences.Enqueue(sentence);
        }
        DisplayNextSentence();
    }
    
    public void DisplayNextSentence() {
        if (sentences.Count == 0) { EndDialogue(); return; }
        string sentence = sentences.Dequeue();
        StopAllCoroutines();
        StartCoroutine(TypeSentence(sentence));
    }
    
    public IEnumerator TypeSentence(string sentence) {
        dialogueText.text = string.Empty;
        yield return new WaitForSeconds(0.25f); // 애니메이션 완료 대기
        foreach (char letter in sentence.ToCharArray()) {
            dialogueText.text += letter; // 하나하나 문자로 찍히는 효과
            yield return null;
        }
    }
    
    pubic void EndDialogue() {
        animator?.SetBool("IsOpen", false);
        OnEndDialogue?.Invoke();
    }
}


 



자료 구조인 Queue와 Stack에 대한 설명도 간단히 해주십니다.
Queue : First Input First Out -> 먼저 넣은 것부터 뽑아서 사용.
Stack : First Input Last Out -> 먼저 넣은 것을 제일 마지막에 사용.

 

 

 


해당 대화 구현을 위한 DialogBox GameObject Unity 구성 화면입니다.

Continue 버튼으로 다음 문장을 진행. DisplayNextSentence()를 호출합니다.


이를 사용하는 DialogueNPC를 구현합니다.

public class DialogueNPC_New : MonoBehaviour, IInteractable
{
    [Serializable]
    Dialogue dialogue;
    
    bool isStartDialogue = false;
    GameObject interactGO;
    
    [SerializeField]
    float distance = 2.0f;
    public float Distance => distance;
    
    public void Interact(GameObject other) {
        float calcDistance = Vector3.Distance(other.transform.position, transform.position);
        if (calcDistance > distance) return;
        if (isStartDialogue) return;
        interactGO = other;
        DialogueManager_New.Instance.OnEndDialogue += OnEndDialogue;
        isStartDialogue = true;
        
        DialogueManager_New.Instance.StartDialogue(dialogue);
    }
    
    public void StopInteract(GameObject other) {
        isStartDialogue = false;
    }
    
    private void OnEndDialogue() {
        StopInteract(interactGO);
    }
}


 



Unity에서의 NPC 구성 화면입니다.
Sentences 3개를 넣어서 동작 테스트를 해보도록 합니다.





대화상대를 우클릭하면 대화 상대에게 이동을 하고 distance 거리내로 다가가게 되면 StartDialogue()를 시작으로 첫번째 문장이 표시됩니다.
[Continue] 버튼을 클릭하면 다음 문장을 보여주고 마지막 문장이 완료되면 EndDialogue()로 사라지는 애니메이션이 구동되어 없어집니다.




이제 Quest System을 구현해 보도록 하겠습니다.

QuestObject는 ScriptableObject를 상속받아 구현합니다.

QuestStatus
+ None: X
+ Accepted: 퀘스트 수락
+ Completed: 퀘스트 완료
+ Rewarded: 퀘스트 보상 완료



public enum QuestType_New {
    DestroyEnemy,
    AcquireItem,
}
[Serializable]
public class Quest_New
{
    public int id;
    public QuestType_New type;
    public int targetID; // 적의 정보
    public int count; // 적의 수
    public int completedCount;
    pubic int rewardExp;
    public int rewardGold;
    public int rewardItemId;
    public string title;
    public string description;
}



pubic enum QuestStatus {
    None,
    Accepted,
    Completed,
    Rewarded,
}
[CreateAssetMenu(filename = "New Quest", menuName = "Quest System/Quests/New Quest_New")]
public class QuestObject_New : ScriptableObject
{
    public Quest_New data = new Quest_New();
    public QuestStatus_New status;
}



[CreateAssetMenu(fileName = "Quest Database", menuName = "Quest System/Quests/New Quest Database")]
public class QuestDatabaseObject_New : ScriptableObject
{
    public QuestObject_New[] questObjects;
    
    public void OnValidate() {
        for (int index = 0; index < questObjects.Length; index++) {
            questObjects[index].data.id = index;
        }
    }
}






Quest를 2가지 추가하였고, 2가지 퀘스트를 구성하였습니다.
첫번째는 적을 잡아와라, 두번째는 특정 아이템을 구해오라는 퀘스트로 구성하였습니다.



public class QuestManager_New : MonoBehaviour
{
    private static QuestManager_New instance;
    public static QuestManager_New Instance => instance;
    public QuestDatabaseObject_New questDatabase;
    public event Action<QuestObject_New> OnCompletedQuest;
    
    private void Awake() {
        instance = this;
    }
   
    public void ProcessQuest(QuestType_New type, int targetId) {
        foreach (QuestObject_New questObject in questDatabase.questObjects) {
            if (questObject.status == QuestStatus.Accepted && questObject.data.type == type &&

                questObject.data.targetId == targetId) {
                questObject.data.completedCount++;
                if (questObject.data.completedCount >= questObject.data.count) {
                    questObject.status = QuestStatus.Completed;
                    OnCompletedQuest?.Invoke(questObject);
                }
            }
        }
    }
}



public class QuestNPC_New : MonoBehaviour, IInteractable
{
    public QuestObject questObject;
    public Dialogue readyDialogue; // 퀘스트 준비
    public Dialogue acceptedDialogue; // 이미 수락된 경우
    public Dialogue completedDialogue; // 완료된 퀘스트
    bool isStartQuestDialogue = false;
    GameObject interactGO = null;
    
    void Start() {
        QuestManager_New.Instance.OnCompletedQuest += OnCompletedQuest;
    }
    
    
    // IInteractable 관련 구현은 DialogueNPC_New와 거의 유사하므로 복사하여 수정.
}


그리고 EnemyController_Range에서 TakeDamage()가 발생할 때 적이 죽는 타이밍 즉, IsAlive가 아닌 경우가 발생할 때
QuestManager.Instance.ProcessQuest(QuestType.DestroyEnemy, 0);
를 호출하여 Quest를 확인하도록 처리합니다.

마찬가지로 InventoryObject에서 AddItem()이 발생할 때 관련 퀘스트를 확인하기 위하여
QuestManager.Instance.ProcessQuest(QuestType.AcquireItem, 1);
를 호출하여 Quest를 확인하도록 처리합니다.


 



Unity에서 QuestNPC를 2개 구성하였습니다.
하나는 적을 죽일 때의 Quest이고, 하나는 아이템을 얻는 Quest입니다.




실행을 하면 적을 죽이는 퀘스트와, 아이템을 얻어야 완료되는 2가지 퀘스트가 잘 구동되는 것을 확인할 수 있습니다.

실제로는 게임을 제작할 때 기획 의도에 맞게 Quest를 구성하면 되겠지요.







<위의 코드들은 제가 보면서 주요한 함수, 코드를 확인하기 위해 타이핑한 용도로, 전체 소스코드가 아님에 주의해 주세요. 전체 코드는 교육 수강을 하면 완벽하게 받으실 수가 있답니다 ^^>

패스트캠퍼스 - 올인원 패키지 : 유니티 포트폴리오 완성 bit.ly/2R561g0




 

유니티 게임 포트폴리오 완성 올인원 패키지 Online. | 패스트캠퍼스

게임 콘텐츠 프로그래머로 취업하고 싶다면, 포트폴리오 완성은 필수! '디아블로'와 '배틀그라운드' 게임을 따라 만들어 보며, 프로그래머 면접에 나오는 핵심 개념까지 모두 잡아 보세요!

www.fastcampus.co.kr

 

+ Recent posts