개발 낙서장

[Unity 3D] NPC 퀘스트 팝업 본문

Unity/Devlog

[Unity 3D] NPC 퀘스트 팝업

권승준 2023. 1. 16. 21:00

구현 동기

퀘스트가 있는 게임에서 퀘스트 표시가 없는 게임은 아마 없을 것이다.(있나? 한 두 개쯤은 있을 수도)
유저 입장에선 당연히 퀘스트 표시가 있는 대로 따라갈 것이라 가시성은 물론이고 설계 또한 잘해두어야 한다.

어찌 됐건 저번에 퀘스트를 만들었으니 오늘은 NPC에 퀘스트 표시를 달아보도록 하자.

구현 내용

설계

먼저 현재 내 게임에서 NPC, Quest, Player 간 데이터 관계도를 정리할 필요가 있을 것 같다.

Quest와 NPC는 Scriptable Object로 존재하며 실제 게임에 배치되는 NPCAI 스크립트에서 정보를 가지고 사용한다.

플레이어는 퀘스트를 수락할 경우 별도의 진행 데이터를 갖고 있어야 하기 때문에 Quest 정보를 QuestData로 변환(?)하여 QuestManager에 저장한다.

퀘스트 팝업은 플레이어의 퀘스트 진행 정도에 따라 달라지기 때문에 NPCAI가 갖고 있는 정보만으로는 나타낼 수가 없다.
그럼 어쩔 수 없이 NPCAI에서 QuestManager 혹은 PlayerController 쪽의 값을 가져와야 한다.
나는 플레이어의 퀘스트에 관한 값들을 전부 QuestManager 쪽에서 처리되도록 구현했기 때문에 QuestManager의 값만 가져오면 될 것 같다.

  1. 플레이어가 수락한 대상 npc의 퀘스트 데이터 리스트
  2. 어떤 quest를 현재 플레이어가 수락한 퀘스트 목록에 있는지
  3. 어떤 quest를 현재 플레이어가 완료한 퀘스트 목록에 있는지

크게 3가지 조건에 따라 퀘스트 팝업을 결정하면 될 것 같다.

구현

    int _questState = 0;
    public int QuestState
    {
        get
        {
            return _questState;
        }
        set
        {
            _questState = value;
            if (QuestState == 3)
            {
                _textMeshPro.text = "?";
                _textMeshPro.color = Color.yellow;
            }
            else if (QuestState == 1)
            {
                _textMeshPro.text = "!";
                _textMeshPro.color = Color.yellow;
            }
            else if (QuestState == 2)
            {
                _textMeshPro.text = "?";
                _textMeshPro.color = Color.gray;
            }
            else
                _textMeshPro.text = "";
        }
    }

우선 현재 팝업 상태를 나타내기 위한 QuestState 변수이다. (_textMeshPro는 npc 위에 떠있는 퀘스트 팝업 텍스트이다.)

3이면 완료 가능, 1이면 수락 가능, 2이면 진행 중, 0이면 없음 으로 설정했다.

public void CheckQuestState()
    {
        List<QuestData> playerCurrentQuests = _questManager.GetCurrentQuestData(_npc);
        QuestState = 0;

        foreach (var quest in playerCurrentQuests)
        {
            if (quest.CanComplete)
            {
                QuestState = 3;
                return;
            }
        }
        foreach (var quest in _npc._quests)
        {
            if (_questManager.CurrentQuestContain(quest))
            {
                if(quest._type != Quest.Type.DIALOGUE)
                    continue;

                DialogueQuestData dqd = _questManager.GetQuest(quest) as DialogueQuestData;
                if (dqd._currentIndex < dqd._quest._targetNPC.Length)
                {
                    if (_npc == dqd._quest._targetNPC[dqd._currentIndex])
                        QuestState = 3;
                    else
                        QuestState = 2;
                }
            }
            else if (!_questManager.CompletedQuestContain(quest) && quest.CanAccept())
            {
                QuestState = 1;
                return;
            }
        }
        if(playerCurrentQuests.Count > 0)
            if(QuestState == 0)
                QuestState = 2;
    }

다음으로 NPCAI에서 1초마다 실행될 CheckQuestState 함수이다.

GetCurrentQuestData는 현재 진행 중인 퀘스트 목록에서 파라미터로 받은 npc 혹은 QuestType에 맞는 퀘스트를 QuestData로 변환해 반환하도록 작성한 함수이고 ~~QuestContain 함수는 파라미터로 받은 Quest가 현재 혹은 완료된 퀘스트 목록에 존재하는지 bool 값으로 반환하도록 작성한 함수이다.

아무래도 대화 퀘스트 때문에 조금 애먹었던 것 같다. 단순히 플레이어의 진행 중인 퀘스트, 완료된 퀘스트 리스트를 뽑아서 그것만 조건을 따진다면 굉장히 쉬웠겠지만 대화 퀘스트는 NPC의 대화 진행 정도에 따라 대상 NPC 모두에게 팝업을 띄워줘야 하기 때문에 고민을 좀 했다.

사냥 퀘스트도 몬스터를 잡을 때마다 체크해줘야 했는데 이건 생각보다 간단했다.
유니티 이벤트를 활용해 옵저버 패턴처럼 적용했는데 QuestData에 퀘스트 완료 이벤트를 만들고 퀘스트를 수락할 때 위의 함수를 리스너로 달아주었다.
그럼 타겟 몬스터를 다 잡으면 QuestData의 CanComplete가 true로 바뀌어 이벤트가 발생할 것이고 위의 함수가 실행되어 해당 NPC의 팝업이 바뀔 것이다.

뭐 사실 1초마다 실행되는 함수이기 때문에 굳이 이럴 필요는 없긴 하지만 그냥 옵저버 패턴을 사용해보고 싶었다.

구현 후기

내가 좀 NPC와 Quest의 구조를 복잡하게 짜놓은 건지 설계하는데 생각을 많이 했다.

되게 간단하게 퀘스트 정보를 받아와서 그거대로 띄우면 될 줄 알았는데 처음엔 맘처럼 되지 않았다.

그래도 뭐... 고민하는 시간도 개발의 일부니까 구현을 성공적으로 했다는 것에 의의를 두어야겠다.

'Unity > Devlog' 카테고리의 다른 글

[Unity 2D] 유닛 확장  (0) 2023.01.23
[Unity 2D] 캐릭터 움직임  (0) 2023.01.23
[Unity 3D] NPC 대화  (0) 2023.01.14
[Unity 3D] 퀘스트  (0) 2023.01.09
[Unity 3D] Collider의 크기를 넘겨줄 때 주의사항  (0) 2023.01.09
Comments