본문 바로가기
유니티3D

유니티3D - 대화창에서 NPC 를 바라보는 카메라 #2

반응형

지난 시간에는 Scene(씬)에 카메라를 1대 추가로 세팅하여 캔버스 UI를 통해 월드의 특정 구역을 카메라가 보이는 화면을 띄우는 부분을 살펴보았습니다. 마치 CCTV처럼 말이죠 :)
※ 편의상 쉬운 이해를 위해 유니티의 단위 1을 1미터로 지칭하도록 하겠습니다.,

https://itadventure.tistory.com/427

 

유니티3D - 대화창에서 NPC 를 바라보는 카메라 #1

지난 게시글에서는 NPC곁에 다가가면 대화창이 나오는 부분을 살펴보았었지요. https://itadventure.tistory.com/426 유니티3D - NPC와 대화창 (설명편) 이번 게시글에서는 지난번에 작동해보았던 NPC와 대화

itadventure.tistory.com

이번 시간에는 그 후속편으로 플레이어가 NPC 앞으로 갔을떄 카메라가 NPC 얼굴을 정면으로 비추는 부분을 살펴보겠습니다.

지난번 잠깐 소개해드린 4번을 구현하는 것이지요.

이를 위해 스크립트를 약간씩 수정해 봅시다.

이를 위해 Main 스크립트를 수정해 보도록 하지요.
여지껏 Assets - Scripts 폴더를 꼭 선택해서 스크립트를 착실히 작성하셨다면 스크립트를 찾기 수월하실 겁니다.

그냥 이 폴더를 선택하시면 되거든요.
스크립트를 수정할 경우 꼭 오브젝트를 선택해야 할 필요는 없이 여기서 바로 수정하면 됩니다.

만약 중구난방으로 스크립트가 저장되어 찾기 어려운 경우 오브젝트를 선택하셔서 

Main Script 를 마우스 우클릭 - Edit Script 를 선택하셔도 됩니다.

내용을 아래와 같이 수정해 주세요. 그리고 Ctrl + S 로 저장해주시면 됩니다.
우선 결과를 보신 후에 설명은 뒷 부분에 드리겠습니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class MainScript : MonoBehaviour
{
    private GameObject NPCDialog;
    private Text NPCText;

    private GameObject NpcCam;

    void Start()
    {
        NPCDialog = GameObject.Find("NPCDialog");
        NPCText = GameObject.Find("NPCText").GetComponent<Text>();
        NPCDialog.SetActive(false);
        NpcCam = GameObject.Find("NpcCam");
    }

    public void NPCChatEnter(string text, GameObject NPCObj, float NPC_Height)
    {
        NPCText.text = text;
        NPCDialog.SetActive(true);

        Vector3 NPC_Height_Vector = new Vector3(0, NPC_Height, 0);        
        NpcCam.transform.position = NPCObj.transform.position + NPCObj.transform.rotation * Vector3.forward * 0.75f + NPC_Height_Vector;
        NpcCam.transform.LookAt(NPCObj.transform.position + NPC_Height_Vector);
    }

    public void NPCChatExit()
    {
        NPCText.text = "";
        NPCDialog.SetActive(false);
    }    
}

아울러 NPC_Trigger스크립트로 함께 아래와 같이 수정해 주세요.
앞과 비슷한 방법으로 스크립트를 열어서 수정해주시면 됩니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NPC_Trigger : MonoBehaviour
{
    public string ChatText = "";
    public float NPC_Height = 1.5f;
    private GameObject Main;
    
    void Start()
    {
        Main = GameObject.Find("Main");
    }

    private void OnTriggerEnter(Collider other)
    {
        if (other.tag == "myplayer")
        {
            Main.GetComponent<MainScript>().NPCChatEnter(ChatText, transform.parent.gameObject, NPC_Height);
        }
    }

    private void OnTriggerExit(Collider other)
    {
        if (other.tag == "myplayer")
        {
            Main.GetComponent<MainScript>().NPCChatExit();
        }
    }
}

참고로 NPC_Trigger 스크립트는 2개의 트리거 오브젝트에 삽입되었지만
스크립트 내용을 수정할 때는 한번만 수정해 주시면 됩니다.

자, 이제 플레이해보실 시간입니다.
한가지 더 손 볼게 있지만 이는 플레이하면서 맞춰보셔야 하는데요.
플레이하면서 살펴 보십시다 :)

플레이를 시작하고 자모로봇을 NPC 앞으로 가져다 봅시다.
아래와 같이 NPC 얼굴이 화면에 비추면 정상적으로 된 것입니다.

또 다른 NPC 앞으로 이동해 볼까요?
굳이 NPC 정면이 아니어도 이와 같이 NPC 앞얼굴을 비출 것입니다.

여기서 한가지 예외적인 부분이 있는데요.
만일, NPC 키가 비약적으로 크거나 작다면 어떤 일이 벌어질까요?
이렇게 됩니다 :)

왜냐하면 npc얼굴을 비추는 카메라는 항상 NPC 의 발바닥에서 1.5미터 높이만 비추기 때문입니다.
npc 키가 큰 경우 카메라가 비추는 범위를 벗어날 수 있습니다.

그러니까 이 경우는 결론적으로 카메라 위치를 조정해 주어야 합니다.
키 높이 조정에 대한 속성은 이번 스크립트에 이미 만들어두었습니다.
플레이가 진행중인 상태에서 Scene 탭을 선택하신 다음

현재 자모로봇이 만나고 있는 npc 의 트리거 오브젝트를 선택하신 다음,

Inspector(인스펙터) 창의 NPC_Trigger 컴포넌트를 보시면 NPC_Height 속성이 추가되어 있을 것입니다.
기본 값은 1.5일텐데요. NPC 발바닥으로부터 해당 높이를 비출 것입니다. 이를 적당한 값으로 조정해 주세요. 
크레이는 1.8로 정하도록 하겠습니다.

다시 Game 탭을 선택하신 다음,

자모로봇을 조정하여 NPC로부터 멀어졌다가 다시 가까이 다가가면
대화창이 뜨면서 변경된 카메라 높이로 NPC가 보여질 것입니다.

이렇게 카메라 위치를 실시간으로 플레이하면서 조정이 가능한 데요.
한가지 주의하실 점은 이 조정값은 플레이를 종료하면 사라지는 휘발성이라는 점입니다.

그러니까 적당한 높이를 잘 기록해 두셨다가
플레이를 종료하신 상태에서 다시 한번 트리거에서 키높이를 수정해 주셔야 합니다.


이제 스크립트의 변경된 부분에 대해 다뤄봅시다.
먼저 NPC 트리거 오브젝트에 심겨진 NPC_Trigger 스크립트 중 일부 내용이 변경되었는데요.
우선 NPC_Height 라는 속성이 추가되었습니다.

public float NPC_Height = 1.5f;

이는 방금전에 다루었던 NPC 키 높이 조정을 스크립트를 수정하지 않고
언제든지 Inspector(인스펙터) 창에서 제어하기 위한 것입니다.
이렇게 정의한 속성은 심지어 플레이 테스트하는 도중에도 인스펙터 창에서 조정이 가능합니다.

그리고 플레이어가 NPC 트리거에 도달하였을 때, 발생하는 onTriggerEnter 메소드에도 작은 수정이 있었는데요.

private void OnTriggerEnter(Collider other)
{
    if (other.tag == "myplayer")
    {
        Main.GetComponent<MainScript>().NPCChatEnter(ChatText, transform.parent.gameObject, NPC_Height);
    }
}

바로 Main 오브젝트에 심겨진 MainScript 에 NPCChatEnter() 메소드를 호출할 때,
대사 내용은 ChatText 외에도 추가로 transform.parent.gameObject 와 NPC_Height 2개 값을 추가로 넘겨줍니다.

transform.parent.gameObject 란 바로 이녀석입니다.

현재 스크립트가 Trigger_M01 에 심겨져 있다고 할 떄,
NPC_M01 오브젝트는 Trigger_M10 의 부모(parent) 오브젝트라고 하는데요.

부모 오브젝트를 접근할 때는 이렇게 transform.parent.gameObject 라는 명칭을 써주시면 됩니다.
그리고 NPC_Height 는 금방 정의했던 바로 이 값입니다.

이 2개의 값을 넘겨줌으로써 NPC가 어디에 있는 NPC이고 키 높이는 이정도이다~ 하고 넘겨주는 것입니다.

이 값을 받는 MainScript 에서도 약간의 변화가 있었는데요.

NpcCam 이라는 속성이 private 로 선언되었습니다.
이 속성은 NPC를 비출 카메라를 저장해놓기 위한 것입니다.

private GameObject NpcCam;

그리고 Start() 메소드에 NpcCam 오브젝트를 찾아 NpcCam  속성에 미리 저장해 놓습니다.
1번만 실행해도 되는 기능은 시작할 떄 한번만 수행하도록 해 놓으면 속도가 향상된다고 말씀드린바 있습니다. :)

void Start()
{
        :
    NpcCam = GameObject.Find("NpcCam");
}

그리고 결정적으로 NPC 가 트리거에 도달하면 실행되는 NPCChatEnter 메소드가 이번 기능의 핵심이자 꽃인데요

추가된 내용은 아래 부분 정도이지만 꽤 많은 부분을 함축하고 있습니다.

public void NPCChatEnter(string text, GameObject NPCObj, float NPC_Height)
{
       :
    Vector3 NPC_Height_Vector = new Vector3(0, NPC_Height, 0);        
    NpcCam.transform.position = NPCObj.transform.position + NPCObj.transform.rotation * Vector3.forward * 0.75f + NPC_Height_Vector;
    NpcCam.transform.LookAt(NPCObj.transform.position + NPC_Height_Vector);
}

먼저 NPC_Height_Vector 에는 new Vector3(0, NPC_Height, 0) 이라는 값이 대입되는데요.
이 값은 NPC 발바닥으로부터 상대적인 높이를 계산하기 위한 벡터값을 의미합니다.

3차원 세계에서는 3차원 좌표끼리의 위치 계산을 위해서 3차원 좌표까리 덧셈이나 뺄셈을 수행하는데요.

우리가 흔히 계산하는 커피값이나 일회용 컵라면 값을 계산하는 것을 1차원이라고 하고,
2차원 그래프에서 x,y 좌표를 통해 x축은 날짜, y축은 매출로 표현하는 것은 2차원이라고 한다면,
3차원 세계에서는 3차원 좌표를 사용합니다. x, y, z 측 3개의 숫자가 좌표값으로 사용되기 때문이지요.

여담으로 흔히들 4차원 세계라고 부르면 신비한 세계라고 생각할 수 있지만
수학의 세계는 그 개념은 아주 단순합니다. 4개의 숫자가 좌표로 사용될 뿐인 세계입니다 :)

다시 원점으로 돌아와 3차원 좌표에서의 이동을 위해서는 3차원 좌표끼리 덧셈이나 뺄셈을 해주어야 합니다.
( 곱셈이나 나눗셈 등은 예외입니다 )

그래서 카메라의 높이를 쉽게 계산하기 위해 상대높이값도 3차원 세계의 좌표로 바꿔주는 것인데요.
바로 그 부분이 아래 부분입니다.
이 코드로 NPC_Height_Vector 에는 바닥에서의 상대높이가 보관됩니다.

Vector3 NPC_Height_Vector = new Vector3(0, NPC_Height, 0);

이어서 나오는 코드는 꽤 긴 내용입니다.

NpcCam.transform.position = NPCObj.transform.position 
    + NPCObj.transform.rotation * Vector3.forward * 0.75f 
    + NPC_Height_Vector;

이 코드는 NPC를 비출 카메라 위치를 NPC 가 바라보는 정면 방향(발바닥 기준)에서 75cm 떨어진 지점에서 키높이만큼 올라간 위치를 계산하는 것입니다.

한글로 설명드려도 웬지 혼선되실것 같군요 :)

하나씩 풀어드리자면,
먼저 NPCObj.transform.position 은 트리거에서 넘겨받은 NPC 의 위치입니다.
정확히는 NPC root 라고 해서 발바닥 중심의 좌표가 되겠습니다.

이 위치에 아래 값을 더해 줍니다.

+ NPCObj.transform.rotation * Vector3.forward * 0.75f

먼저 NPCObj.transform.rotation 은 NPC 의 회전값입니다.
NPC를 선택하면 Inspector 창에는 Rotation 이라는 좌표가 보이실 겁니다.

이 값을 NPC의 rotation(로테이션), 회전값이라고 하는데요.
NPC를 어느 방향으로 얼만큼 회전시켰는지를 의미하는 값입니다.
x, y, z 값만으로 회전값이 정해지는 것 같지만 사실 약간 다릅니다.
유니티 내부적으로는 quaternion( 쿼터니온, 한글로 '사원수' ) 이라는 좌표값을 사용하기 때문인데요.
쿼터니온은 수학적으로 꽤 깊이가 있는 개념이라 크레이도 자세히는 모릅니다.
인스펙터 창에서는 쉬운 이해를 위해 단지 3차원 각도를 사용하는 것처럼 보일 뿐입니다.

중요한 것은 이 개념을 몰라도 이미 준비된 기능을 이용하면 사용할 수 있다는 점이지요 :)

유니티에서, 또는 가상세계 세컨드라이프를 비롯한 쿼터니온 좌표를 사용하는 게임엔진에서는 이 로테이션 값에 Vector3.forward 이라는 값을 곱해주면 회전치가 반영된 원래 앞 방향 1미터에 해당하는 상대좌표값을 구해줍니다.
바로 NPC의 앞 방향에서 1미터 떨어진 곳을 말이지요.

NPCObj.transform.rotation * Vector3.forward

참고적으로 Vector3.forward 는 (0, 0, 1) 벡터값입니다.

아울러 여기에 추가로 0.75를 곱해주면 더 재미있는 일이 일어납니다.
상대위치가 1미터가 아닌 0.75m, 즉 75센티미터만큼의 앞 위치를 계산합니다. 물론 상대적인 위치입니다.

NPCObj.transform.rotation * Vector3.forward * 0.75f

그리고 마지막으로 NPC의 키 높이값을 더해줍니다.

+ NPC_Height_Vector

그러니까 정리해볼까요.
우선 npc 의 중심 좌표를 먼저 구했습니다.

이 위치에 Npc의 정면 방향 75CM에 해당하는 상대좌표를 계산해 더해줍니다.

그리고 npc 키 높이 상대좌표를 더해줍니다.

이 최종 계산된 좌표값을 NpcCam 의 좌표값에 대입합니다.

NpcCam.transform.position = 계산된 좌표값;

그러면 NpcCam 오브젝트가 NPC 앞으로 딱 위치합니다.

하지만 아직 여기까지는 완성된 것은 아닙니다.
NpcCam 이 npc의 얼굴 앞 부분까지 이동은 했지만
NPC 얼굴 방향을 바라보는 것이 아니라 다른 방향을 바라보기 때문입니다.
그래서 엉뚱한 곳을 비출텐데요,

이제 NpcCam 이 NPC 얼굴을 딱 비춰주도록 카메라 앵글을 돌려주면 됩니다.
과거라면 삼각함수를 이용해서 싸인, 코싸인 값을 계산하는 등의 복잡한 코드가 필요하겠지만,
요새 게임엔진에서는 그런게 필요없습니다.

그냥 아래 명령어 하나면 됩니다.

NpcCam.transform.LookAt(NPCObj.transform.position + NPC_Height_Vector);

LookAt 이라는 명령은 특정 좌표를 바라봐! 이런 의미입니다.
기초영어에서도 Look at the sky! ( 저 하늘을 바라봐! ) 라는 예문이 있지요?
정확히 그런 의미입니다 :)

그래서 NPCCam 은 NPC의 좌표를 바라보는데 정확히는 NPC의 중심 ( 발바닥 ) 이 아니라,
NPC의 키 높이에 해당하는 위치를 바라봅니다.

이 코드들이 npc 트리거에 부딪힐 때마다 매번 실행되어 NPC 얼굴을 비추는 것이지요.

이렇게 해서 NPC 얼굴을 비추는 카메라에 대해 알아보았습니다 :)
이 원리를 이용하면 하늘에서 땅바닥을 비추는 미니맵도 만들수 있지요.
이해가 원활하셨다면 한번 도전해보시는 것도 추천드립니다.

아무쪼록 필요하신 분에게 도움이 되셨을지요.
오늘도 여기까지 읽어주신 분들께 감사드립니다 :)


우리는 NPC의 얼굴을 보조 카메라를 이용해서 살펴보지만.
성경에서 말씀하시는 하나님은 사람의 길을 '감찰'하신다고 합니다.
마치 CCTV를 보듯이 생생하게 말이지요.

하나님은 사람의 길을 주목하시며 
사람의 모든 걸음을 감찰하시나니
- 욥기 34장 21절 말씀 -


다음 게시글. 3인칭 무빙 카메라 - 사이드 뷰 #1

https://itadventure.tistory.com/429

 

유니티3D - 3인칭 무빙 카메라 - 사이드 뷰 #1

지난 시간까지는 NPC와의 대화창에 대한 부분 최종 마무리하였습니다. https://itadventure.tistory.com/428 유니티3D - 대화창에서 NPC 를 바라보는 카메라 #2 지난 시간에는 Scene(씬)에 카메라를 1대 추가로

itadventure.tistory.com

 

반응형