반응형

Programming 

TPS 캐릭터 조작 기능 구현하기

 

작성 기준 버전 :: 2019.2

 

[이 포스트의 내용은 유튜브 영상으로도 시청하실 수 있습니다]

 

지난 포스트 중 하나에서는 마우스를 이용해서 클릭한 위치로 캐릭터를 이동시키는 RPG나 AOS 게임에서 주로 사용되는 캐릭터 조작 방법을 구현했었다.

 

이번에는 키보드로 이동하고 마우스로 원하는 대상을 바라보게 하는 TPS 게임에서 사용되는 캐릭터 조작 방법을 구현해보도록 하자.

 

리소스 임포트와 기본 애니메이션 세팅

 

본격적인 구현에 들어가기 전에 캐릭터로 사용할 모델링과 애니메이션을 영상 하단의 링크에서 다운로드 받아서 임포트한다.

 

practice-animation.zip
다운로드

 

FBX 파일을 임포트하고 나면 먼저 캐릭터의 이동 애니메이션을 보여주기 위한 애니메이션 세팅을 해보자.

 

 

먼저 임포트된 FBX 파일 중에 Run, Stand의 애니메이션 임포트 세팅 중에서 Loop Time을 체크하고 적용한다.

 

 

그리고 애니메이터 컨트롤러를 생성한 뒤 Bool 타입으로 isMove 파라미터를 추가한다. 

 

 

그 다음에는 Stand 애니메이션을 기본 스테이트로 추가하고 Run 애니메이션을 추가한 뒤 isMove의 상태에 따라서 Stand와 Run을 왔다갔다 하도록 세팅해준다.

 

캐릭터 배치와 카메라 세팅

 

 

이 작업이 끝나면 씬에 캐릭터가 돌아다닐 바닥이 될 Plane을 하나 배치하고 BoxMan@Stand를 배치하고 BoxMan에 붙어있는 Animator 컴포넌트의 비어있는 Controller 프로퍼티에 방금 만든 애니메이터 컨트롤러를 할당해준다.

 

 

애니메이션에 대한 세팅이 끝나면 카메라를 이동시켜서 TPS 게임에서 자주 보이는 카메라 구도를 만들어보자. TPS 시점의 게임에서는 이런 구도의 카메라 시점이 주로 사용된다.

 

일인칭으로 보여지는 FPS 게임은 이것과는 약간 다르게 캐릭터의 몸이 거의 보이지 않고 손 정도만 보이게 시점을 세팅하게 되지만 이후의 기능 구현 자체는 거의 비슷하게 진행하면 된다.

 

본격적인 조작 기능 구현에 들어가기에 앞서 카메라와 캐릭터 구조를 조금 더 살펴보자.

 

지금 시점 자체는 나쁘지 않지만 조작 기능이 들어가기에는 아쉬움이 많다.

 

 

특히 마우스를 상하좌우로 움직이면 카메라 역시 마우스의 움직임에 맞게 회전해야 하는데, 카메라를 잡고 위/아래/좌/우로 회전시켜보면 전혀 적절하지 못하다는 것을 깨달을 수 있을 것이다.

 

해결책 1 : 카메라를 캐릭터의 자식 게임 오브젝트로 만들기

 

 

첫 번째로 생각해볼 만한 방법은 카메라를 캐릭터의 자식 게임 오브젝트로 만드는 것이다.

 

 

카메라를 BoxMan의 자식 게임 오브젝트로 만들면 좌우로 움직이는건 정상적으로 동작한다.

 

 

하지만 위/아래로 움직여보면 캐릭터가 같이 기울어져서 적절하지 못한 것을 알 수 있다. 적절한 카메라 회전을 위해서는 캐릭터를 회전시키지 않으면서도 캐릭터를 중심으로 카메라를 회전시키는 방법을 찾아야 할 것이다.

 

해결책 2 : 카메라 암(Camera Arm) 만들기

 

 

다른 해결책으로는 카메라 암(Camera Arm) 역할을 할 빈 게임 오브젝트를 하나 만들고 BoxMan의 머리 정도의 위치로 이동시켜 주고 카메라를 이 빈 게임 오브젝트의 자식 게임 오브젝트로 만들어준다. 

 

 

이렇게 세팅해주면 상하좌우 모든 방향의 회전에도 문제없이 카메라가 캐릭터를 중심으로 움직이는 것을 확인할 수 있다.

 

 

이대로 Camera Arm을 BoxMan 밑에 배치해도 되지만 그렇게 하면 BoxMan의 회전에 Camera Arm이 묶이게 되기 때문에 Character라는 이름의 빈 게임 오브젝트를 만들어서 BoxMan과 Camera Arm을 그 아래에 배치하자. 이렇게 해주면 BoxMan의 회전과 Camera Arm의 회전이 분리되서 관리하기가 쉬울 것이다.

 

카메라 회전 기능 구현하기

 

이제 본격적으로 캐릭터 조작 기능을 만들어보자.

 

TPSCharacterController라는 이름으로 C# 스크립트를 생성하고 다음 코드를 따라 작성하자.

 

public class TPSCharacterController : MonoBehaviour
{
    [SerializeField]
    private Transform characterBody;
    [SerializeField]
    private Transform cameraArm;

    Animator animator;

    void Start()
    {
        animator = characterBody.GetComponent<Animator>();
    }
}

 

Transform 타입으로 BoxMan 모델을 관리할 characterBody 변수와 카메라의 회전을 관리할 cameraArm 변수를 선언하고 애니메이션을 관리할 Animator 변수를 선언하고 Start 함수에서 characterBody.GetComponent 함수로 BoxMan에 붙어있는 Animator 컴포넌트를 가져와서 저장해둔자.

 

private void LookAround()
{
    Vector2 mouseDelta = new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"));
    Vector3 camAngle = cameraArm.rotation.eulerAngles;

    cameraArm.rotation = Quaternion.Euler(camAngle.x - mouseDelta.y, camAngle.y + mouseDelta.x, camAngle.z);
}

 

먼저 마우스의 움직임에 따라서 카메라를 회전시키는 기능을 구현하기 위해서 LookAround 함수를 만든다. 

 

LookAround 함수에서는 먼저 마우스가 이전 위치에 비해서 얼마나 움직였는지 알아내야 한다. 마우스가 움직인 수치는 Input.GetAxis 함수에 "Mouse X"와 "Mouse Y"를 매개변수로 넣어서 가져올 수 있다. 

 

"Mouse X"로는 마우스를 좌우로 움직인 수치를 가져올 수 있고 "Mouse Y"로는 마우스를 위아래로 움직인 수치를 가져올 수 있다.

 

이 두 값을 Vector2로 묶어서 mouseDelta 변수에 저장한다. 참고로 프로그래밍에서 이전 값과 현재 값의 차이를 주로 delta라는 용어로 표현한다. 

 

그 다음에는 cameraArm의 rotation 값을 오일러 각으로 변환해둔다. 그리고 camAngle과 mouseDelta를 합쳐서 새로운 회전값을 만들어낸 다음 cameraArm.rotation에 넣어줘야 한다.

 

우선 마우스 좌우 움직임으로 카메라 좌우 움직임을 제어해야 하기 때문에 mouseDelta의 x 값을 camAngle의 y에 더해준다. 그리고 마우스 수직 움직임은 카메라 상하 움직임을 제어해야 하기 때문에 mouseDelta의 y 값을 camAngle의 x에 추가해준다.

 

이 마우스 수직 움직임의 경우에는 한국에서는 마우스를 위로 움직이면 위를 쳐다보고 아래로 움직이면 아래를 쳐다보게 하는 방식을 사용하는 유저들이 더 많지만 북미에서는 이 움직임을 반대로 사용하는 경우가 꽤 있다고 한다. 그렇게 때문에 별도의 옵션을 둬서 두 가지 조작 방식 중에 사용자가 원하는 방식을 선택할 수 있도록 하는게 좋다. 

 

우선 한국에서 주로 쓰는 방식으로 구현하자. 마우스 방향과 바라보는 방향을 일치시키려면 mouseDelta.y를 camAngle.x에서 빼주면 된다.

 

void Update()
{
    LookAround();
}

 

그리고 LookAround 함수를 Update 함수에 호출하게 만들어준 뒤 코드를 저장하고 에디터로 돌아가보자.

 

 

그 다음 Character 게임 오브젝트에 생성한 컨트롤러 컴포넌트를 붙이고 프로퍼티들을 할당해준다.

 

 

게임을 플레이시키고 마우스를 움직여보면 우선 카메라는 제대로 움직이는 것 같지만 카메라 상하 회전에 제한이 없어서 그런지 마우스를 움직이다보면 화면이 뒤집어지는 경우가 발생한다.

 

카메라 각도 제한하기

 

이런 문제를 막기 위해서는 카메라의 x 회전을 제한해야 한다.

 

위쪽 회전은 약 70도를 제한하고 아래쪽 회전은 약 25도를 제한하도록 하자. 다만 x 회전 값을 출력해보면 알겠지만 수평을 기준으로 위쪽으로 회전하면 0도에서 90도까지 각도가 나오고 아래쪽으로 회전하면 360도부터 270도까지 각도가 나온다.

 

private void LookAround()
{
    Vector2 mouseDelta = new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"));
    Vector3 camAngle = cameraArm.rotation.eulerAngles;
    
    //추가
    float x = camAngle.x - mouseDelta.y;
    if (x < 180f)
    {
        x = Mathf.Clamp(x, -1f, 70f);
    }
    else
    {
        x = Mathf.Clamp(x, 335f, 361f);
    }

    cameraArm.rotation = Quaternion.Euler(x, camAngle.y + mouseDelta.x, camAngle.z);
}

 

우선 camAngle.x - mouseDelta.y로 만들어낸 값을 바로 회전값에 넣지 말고 따로 x에 저장한다. 그리고 회전값에 넣어주기 전에 x값의 상태에 따라서 처리하는 과정을 구현해야 한다.

 

먼저 x가 180도보다 작은 경우는 위쪽으로 회전하는 경우이므로 x의 값을 -1도에서 70도까지 제한시켜둔다. 여기서 0도가 아닌 -1도로 최저점을 잡는 이유는 0도로 잡으면 카메라가 수평면 아래로 내려가지 않는 문제가 발생하기 때문이다.

 

그리고 x각이 180도보다 큰 경우는 아래쪽으로 회전하는 경우로 25도를 제한하기 위해서 335도에서 361도 사이로 값을 제한시킨다.

 

이렇게 구해진 x값을 camAngle의 x값 자리에 넣어준다.

 

 

카메라 회전을 제한시킨 기능을 에디터에서 테스트해보면 위아래 회전에 제한이 걸려서 카메라가 뒤집어지는 문제가 발생하지 않는 것을 확인할 수 있다.

 

이동기능 추가하기

 

이제 여기에 이동 기능을 추가할 차례이다.

 

이동방향 구하기

 

private void Move()
{
    Debug.DrawRay(cameraArm.position, cameraArm.forward, Color.red);
}

 

움직이는 기능을 추가하기 위해서 Move 함수를 만든다. 

 

캐릭터를 이동시키기 위해서는 먼저 캐릭터가 이동할 방향을 찾아야 한다. 이동할 방향으로 가장 적합한 대상은 카메라가 바라보고 있는 방향일 것이다.

 

cameraArm.forward를 사용하면 될 것 같은데 우선 이 값이 적당한 방향인지 확인하기 위해서 Debug.DrawRay로 카메라 암 위치에서 cameraArm.forward 방향으로 에디터에서 선으로 그려보자.

 

void Update()
{
    LookAround();
    Move();
}

 

Update 함수에서 Move 함수를 호출하게 만든다.

 

 

게임을 플레이시키고 마우스를 움직여보면 씬 뷰에서 Camera Arm 위치에 빨간색 선이 카메라 암이 쳐다보는 방향을 표시하는 것을 볼 수 있다.

 

캐릭터의 이동 방향으로 쓰기에 적당해 보이지만 위쪽이나 아래쪽을 쳐다볼 때는 선이 기울어지는 것을 볼 수 있다. 정상적인 이동 방향을 보장하기 위해서는 이 선을 수평으로 변환해야 한다.

 

private void Move()
{
    Debug.DrawRay(cameraArm.position, 
        new Vector3(cameraArm.forward.x, 0f, cameraArm.forward.z).normalized, Color.red);
}

 

새 벡터를 만들면서 cameraArm.forward의 y값을 넣지 않는 것으로 높이를 없애고 normalized 시켜서 길이를 1로 맞춰준다.

 

 

다시 에디터에서 게임을 플레이시키고 마우스를 움직여보면 위나 아래를 쳐다봐도 빨간 선의 방향이 수평을 유지하는 것을 볼 수 있다.

 

이동기능 구현하기

 

private void Move()
{
    Vector2 moveInput = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
    bool isMove = moveInput.magnitude != 0;
    animator.SetBool("isMove", isMove);
    if (isMove)
    {
        Vector3 lookForward = new Vector3(cameraArm.forward.x, 0f, cameraArm.forward.z).normalized;
        Vector3 lookRight = new Vector3(cameraArm.right.x, 0f, cameraArm.right.z).normalized;
        Vector3 moveDir = lookForward * moveInput.y + lookRight * moveInput.x;

        characterBody.forward = lookForward;
        transform.position += moveDir * Time.deltaTime * 5f;
    }
}

 

그럼 다시 스크립트 에디터로 돌아가서 이동 기능을 구현하는 것을 진행해보자.

 

우선 Input.GetAxis 함수로 "Horizontal"과 "Vertical"로 이동 입력 값을 가져와서 moveInput 벡터를 만든다. 그리고 moveInput의 길이로 입력이 발생하고 있는지 판정하여 isMove 변수에 넣어준다.

 

moveInput의 길이가 0이면 이동 입력이 없는 것이고 0이 아니면 이동 입력이 발생하고 있는 것이다.

 

이 isMove를 animator의 "isMove"에 세팅해주면 이동 입력이 발생할 때는 걷는 애니메이션을 재생하고, 이동 입력이 없을 때는 대기 애니메이션을 재생할 것이다.

 

그리고 아까 전에 구해낸 방향을 바라보는 방향으로 저장한다. 거기에 추가로 cameraArm.right 역시 정면과 마찬가지로 평면화시켜서 저장해둔다.

 

이렇게 구해낸 lookForward와 lookRight에 moveInput을 곱해서 더하면 바라보고 있는 방향을 기준으로 이동 방향을 구할 수 있다. 그리고 구해낸 이동 방향과 Time.deltaTime, 이동속도를 모두 곱해서 transform.position에 더해주면 캐릭터를 이동시킬 수 있게 된다.

 

이동시 바라보는 방향 1 : 카메라가 바라보는 방향

 

private void Move()
{
    Vector2 moveInput = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
    bool isMove = moveInput.magnitude != 0;
    animator.SetBool("isMove", isMove);
    if (isMove)
    {
        Vector3 lookForward = new Vector3(cameraArm.forward.x, 0f, cameraArm.forward.z).normalized;
        Vector3 lookRight = new Vector3(cameraArm.right.x, 0f, cameraArm.right.z).normalized;
        Vector3 moveDir = lookForward * moveInput.y + lookRight * moveInput.x;

        characterBody.forward = lookForward;
        transform.position += moveDir * Time.deltaTime * 5f;
    }
}

 

마지막으로 캐릭터가 바라보는 방향을 정하는 방법을 여러 가지로 구현할 수 있다.

 

첫 번째 방법은 캐릭터를 이동시킬 때 시선을 정면에 고정시키는 방법이다.

 

characterBody의 forward를 lookForward로 정해주면 된다.

 

 

캐릭터를 움직여보면 캐릭터를 앞뒤좌우 어느 방향으로 움직이든 카메라가 바라보는 방향을 바라본다.

 

이 기능을 위해서는 캐릭터가 좌우로 움직일 때는 옆걸음을 치는 애니메이션을 넣고, 뒤로 움직일 때는 뒷걸음을 치는 애니메이션을 넣어줘야 할 것이다.

 

이런 식으로 움직이는 방법은 캐릭터가 언제나 정면을 바라보기 때문에 좀 더 직관적인 컨트롤을 할 수 있다는 장점이 있다.

 

이동시 바라보는 방향 2 : 이동방향

 

private void Move()
{
    Vector2 moveInput = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
    bool isMove = moveInput.magnitude != 0;
    animator.SetBool("isMove", isMove);
    if (isMove)
    {
        Vector3 lookForward = new Vector3(cameraArm.forward.x, 0f, cameraArm.forward.z).normalized;
        Vector3 lookRight = new Vector3(cameraArm.right.x, 0f, cameraArm.right.z).normalized;
        Vector3 moveDir = lookForward * moveInput.y + lookRight * moveInput.x;

        characterBody.forward = moveDir;
        transform.position += moveDir * Time.deltaTime * 5f;
    }
}

 

characterBody.forward를 moveDir로 지정해주면 된다.

 

 

캐릭터를 움직여보면 캐릭터는 카메라가 바라보는 방향을 기준으로 앞뒤좌우로 움직이지만 정면을 바라보지 않고 이동해야하는 방향을 바라보는 것을 알 수 있다.

 

이렇게 구현된 컨트롤은 정면만 바라보는 컨트롤에 비해서 복잡하기는 하지만 좀 더 섬세한 방향 컨트롤이 가능해진다.

 

이 두 가지 방식 중에서 게임에서 필요한 방식을 선택해서 구현하면 된다.

반응형
  1. 테샤르 2020.06.08 12:56 신고

    잘보고갑니다!!

  2. 아무거나 2021.03.13 01:00

    혹시 이동기능 코드좀 올려주실수 있나요? 변수 선언 부분에서 cameraarm 선언부랑 characterbody 부분 선언을 어떻게 해야할지 감이 안와서요

+ Recent posts