본문 바로가기
유니티3D

유니티3D 플레이어 조작 #5. 3인칭 플레이어 조작

지난 챕터에서는 3인칭 시점으로 플레이어를 바라보는 카메라에 대해 알아보았는데요.

itadventure.tistory.com/398

 

유니티3D 플레이어 조작 #4. 3인칭 플레이어 뷰!

지난 시간에서는 카메라가 플레이어를 따라다니는 예제를 살펴보았지요. itadventure.tistory.com/397 유니티3D 플레이어 조작 #3. 카메라 팔로우 미! 드라마나 영화를 보면 카메라가 주연이나 조연을 향

itadventure.tistory.com

기획팀에서 이런 의뢰가 들어왔다고 칩시다. ( 예를 들면 ... )

1. 
마우스를 그냥 움직일 때는 마우스만 이동하고,
마우스 우측 버튼을 클릭하여 드래그하는 도중에만 카메라 무빙이 작동하게 해 주세요

2. 
WASD 또는 화살표 키로 이동할 때, 
카메라 시점이 조정된 경우 카메라가 바라보는 앞 방향으로 플레이어가 이동하게 해주세요

이번 시간에는 이 부분을 다루어 볼텐데요.
점점 3인칭 조작법의 성능이 고도화되어 가는것 같습니다 :)

1번은 사실 간단합니다.
카메라 무빙 동작을 평소에는 하지 않다가 마우스 우클릭 중에만 작동하도록 하면 되거든요.

마우스 클릭 여부를 판단하는 메소드는 아래와 같습니다.

Input.GetMouseButton(버튼번호)

버튼번호는 마우스 왼쪽버튼이 0번, 마우스 오른쪽 버튼이 1번이며, 가운데 휠버튼이 2번인데요.
만일 마우스 오른쪽 버튼이 눌려진 상태인지를 판단하려면 Input.GetMouseButton(1) 을 사용하면 됩니다.
이러한 정보는 유니티 공식 문서 사이트에서 확인이 가능하며 URL 은 아래와 같습니다. 영문으로 되어 있지요.

마우스 버튼 정보 : https://docs.unity3d.com/ScriptReference/Input.GetMouseButton.html

하여간 위의 메소드를 이용해서, 마우스 우클릭이 눌려진 경우만 무빙을 적용하면 되기 때문에.
CamController.cs 의 카메라 제어 소스를 아래와 같이 변경해주시면 됩니다.

//  마우스 우클릭 중에만 카메라 무빙 적용
if (Input.GetMouseButton(1))
{
    xmove += Input.GetAxis("Mouse X"); // 마우스의 좌우 이동량을 xmove 에 누적합니다.
    ymove -= Input.GetAxis("Mouse Y"); // 마우스의 상하 이동량을 ymove 에 누적합니다.
}

최종 소스는 아래와 같지요.
이 스크립트를 CamController 스크립트를 더블클릭 비주얼 스튜디오 창에 입력해 주세요.

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

public class CamController : MonoBehaviour
{
    public GameObject player; // 바라볼 플레이어 오브젝트입니다.
    public float xmove = 0;  // X축 누적 이동량
    public float ymove = 0;  // Y축 누적 이동량
    public float distance = 3;

    // Update is called once per frame
    void Update()
    {
        //  마우스 우클릭 중에만 카메라 무빙 적용
        if (Input.GetMouseButton(1))
        {
            xmove += Input.GetAxis("Mouse X"); // 마우스의 좌우 이동량을 xmove 에 누적합니다.
            ymove -= Input.GetAxis("Mouse Y"); // 마우스의 상하 이동량을 ymove 에 누적합니다.
        }
        transform.rotation = Quaternion.Euler(ymove, xmove, 0); // 이동량에 따라 카메라의 바라보는 방향을 조정합니다.
        Vector3 reverseDistance = new Vector3(0.0f, 0.0f, distance); // 카메라가 바라보는 앞방향은 Z 축입니다. 이동량에 따른 Z 축방향의 벡터를 구합니다.
        transform.position = player.transform.position - transform.rotation * reverseDistance; // 플레이어의 위치에서 카메라가 바라보는 방향에 벡터값을 적용한 상대 좌표를 차감합니다.
    }
}

그리고 플레이 해보시면 마우스를 움직여도 카메라 시선이 움직이지 않다가,
마우스 우클릭 버튼을 누른채로 드래그할 때 카메라의 시선이 이동하는 것을 보실 수 있습니다.

다시 유니티 화면으로 돌아옵니다.

2번째 안건은 아주 적절한 메소드가 있습니다.
바로 LookAt 이라는 메소드인데요. 물체가 특정 좌표를 바라보게 하는 함수입니다.

그러니까 카메라로 무빙 조작을 한 직후에, 또는 동시에라도
키보드의 방향키 이동이 발생하면 카메라의 앞 방향으로 물체를 바라보게 해주면 됩니다.
이 이외에 조작키에 의한 이동 부분은
이미 물체가 바라보는 앞방향쪽으로 이동하도록 원래 스크립트가 짜여져 있었기 때문에 신경쓰실 부분은 없습니다.

카메라를 참조하기 위해 플레이어 컨트롤러인 PlayerController.cs 앞 부분에 다음과 같은 코드가 추가될 필요가 있습니다. (전체 코드는 하단을 참조해 주세요)
이 코드만으로는 작동하지 않고, 나중에 카메라를 선택해주어야 합니다.
우선 이론적인 설명 후 진행하도록 하겠습니다.

public GameObject Cam; // 제어할 캐릭터 컨트롤러

카메라가 바라보는 앞방향은 어떻게 계산해야 할까요?
아주 간단합니다. 카메라가 바라보는 앞방향은 아래와 같습니다.

Cam.transform.forward

이를 앞방향 벡터라고도 부르는데요.

이 방향을 그대로 갖다 쓰는 건 문제가 있습니다.
왜냐하면 카메라의 앞방향이란 비스듬히 땅바닥을 향하고 있는 방향이기 때문입니다.

그러니까 이 방향으로 플레이어가 앞을 바라보게 하면 이와 같이 플레이어가 땅바닥을 비스듬히 바라보게 됩니다.

뭐 이 상황이 맞다면 문제는 없겠지만, 사실은 기획의도는 아래와 같은 것이었다고 가정해 보지요.

이쯤해서 유니티의 좌표계를 살펴보도록 할까요?
유니티는 하늘 방향을 Y 축으로 보고 있습니다.
그 외에 땅에서의 이동 방향은 X 축과 Z축을 사용하는데요.
현재 화면에서의 정확한 방향은 Scene 화면 우측의 나침판과 같은 안내 부분을 참조하시면 됩니다.

그러니까 이렇게 생각하면 간단합니다.
카메라가 바라보는 방향에서 얼굴 높이만 저 먼 지평선을 향해
위로도 아래로도 치우치지 않고 '똑바로 바라보게 해주자'라구요.
높이는 Y축이므로 Y축 방향값을 0을 바꿔주면 이 부분이 해결됩니다.

카메라가 바라보는 정면의 앞방향을 구하는 코드는 아래와 같습니다.

var offset = Cam.transform.forward;
offset.y = 0;

그리고 이 방향을 플레이어 캐릭터가 바라보게 하려면 아래와 같이 해주시면 됩니다.

transform.LookAt(SelectPlayer.transform.position + offset);

LookAt 은 현재 위치 기준이 아닌 절대적인 좌표 기준이기 때문에
현재 플레이어 위치 SelectPlayer.transform.position 위치에 앞 방향을 더해 주어야 합니다.

위 3줄의 코드는 WASD나 방향키로 조작을 할 경우에만 플레이어가 방향을 바꾸어 이동해야 하기 때문에
아래와 같이 이동이 발생할 경우에만 작동하도록 코드가 작성되어야 합니다.

if (Input.GetAxis("Horizontal") != 0 || Input.GetAxis("Vertical") != 0)
{
    var offset = Cam.transform.forward;
    offset.y = 0;
    transform.LookAt(SelectPlayer.transform.position + offset);
}

이론적인 설명은 이쯤하고,
이제 실전입니다.

아래의 전체 스크립트를 PlayerController 스크립트를 더블클릭, 비주얼 스튜디오 창에 입력해 주세요.

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

public class PlayerController : MonoBehaviour
{
    public GameObject Cam; // 제어할 캐릭터 컨트롤러
    public CharacterController SelectPlayer; // 제어할 캐릭터 컨트롤러
    public float Speed;  // 이동속도
    public float JumpPow;

    private float Gravity; // 중력   
    private Vector3 MoveDir; // 캐릭터의 움직이는 방향.
    private bool JumpButtonPressed;  //  최종 점프 버튼 눌림 상태
    private bool FlyingMode;  // 행글라이더 모드여부

    // Start is called before the first frame update
    void Start()
    {
        // 기본값
        Speed = 5.0f;
        Gravity = 10.0f;
        MoveDir = Vector3.zero;
        JumpPow = 5.0f;
        JumpButtonPressed = false;
        FlyingMode = false;
    }
    
    // Update is called once per frame
    void Update()
    {
        if (SelectPlayer == null) return;

        if (Input.GetAxis("Horizontal") != 0 || Input.GetAxis("Vertical") != 0)
        {
            var offset = Cam.transform.forward;
            offset.y = 0;
            transform.LookAt(SelectPlayer.transform.position + offset);
        }
        // 캐릭터가 바닥에 붙어 있는 경우만 작동합니다.
        // 캐릭터가 바닥에 붙어 있지 않다면 바닥으로 추락하고 있는 중이므로
        // 바닥 추락 도중에는 방향 전환을 할 수 없기 때문입니다.
        if (SelectPlayer.isGrounded)
        {
            // 키보드에 따른 X, Z 축 이동방향을 새로 결정합니다.
            MoveDir = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
            // 오브젝트가 바라보는 앞방향으로 이동방향을 돌려서 조정합니다.
            MoveDir = SelectPlayer.transform.TransformDirection(MoveDir);
            // 속도를 곱해서 적용합니다.
            MoveDir *= Speed;

            // 스페이스 버튼에 따른 점프 : 최종 점프버튼이 눌려있지 않았던 경우만 작동
            if (JumpButtonPressed == false && Input.GetButton("Jump"))
            {
                SelectPlayer.transform.rotation = Quaternion.Euler(0, 45, 0);
                JumpButtonPressed = true;
                MoveDir.y = JumpPow;
            }
        }
        // 캐릭터가 바닥에 붙어 있지 않다면
        else
        {
            // 하강중에 스페이스 버튼을 누르면 슬로우 낙하모드 발동!
            if (MoveDir.y < 0 && JumpButtonPressed == false && Input.GetButton("Jump"))
            {
                FlyingMode = true;
            }

            if (FlyingMode)
            {
                JumpButtonPressed = true;
                
                // 중력 수치를 감속합니다.
                MoveDir.y *= 0.95f;

                // 하지만 하늘에서 정지해 있는 일은 벌어지지 않게 하기 위해
                // 최소 초당 -1의 하강 속도는 유지합니다.
                if (MoveDir.y > -1) MoveDir.y = -1;

                // 또한 이 때는 방향전환이 가능합니다.
                MoveDir.x = Input.GetAxis("Horizontal");
                MoveDir.z = Input.GetAxis("Vertical");
            }
            else
                // 중력의 영향을 받아 아래쪽으로 하강합니다.           
                MoveDir.y -= Gravity * Time.deltaTime;
        }

        // 점프버튼이 눌려지지 않은 경우
        if (!Input.GetButton("Jump"))
        {
            JumpButtonPressed = false;  // 최종점프 버튼 눌림 상태 해제
            FlyingMode = false;         // 행글라이더 모드 해제
        }
        // 앞 단계까지는 캐릭터가 이동할 방향만 결정하였으며,
        // 실제 캐릭터의 이동은 여기서 담당합니다.
        SelectPlayer.Move(MoveDir * Time.deltaTime);
    }
}

그리고 유니티 화면으도 돌아와 플레이어 오브젝트를 선택하면,
Inspector 창 하단에 Cam 항목이 추가되어 있을 겁니다.

동그라미 과녁 아이콘을 클릭하고, Main Camera 항목을 선택해주세요.

그리고 플레이해보시면, 카메라로 시점을 조정하면서 WASD 키를 누르면 카메라의 앞방향으로 캐릭터가 이동하는 것을 보실 수 있습니다.

필요하신 분께 도움이 되셨을지요.
오늘도 여기까지 읽어주셔서 감사합니다 :)


성경에서 잠언은 '지혜의 서'라고 하지요.
잠언에서 배울 수 있는 교훈은 매우 많습니다 :)

유순한 대답은 분노를 쉬게 하여도 과격한 말은 노를 격동하느니라
- 잠언15장 1절 -


다음 챕터 1/3인칭 시점 전환

itadventure.tistory.com/400

 

유니티3D 플레이어 조작 #6. 1/3인칭 시점 전환

지난 시간에는 3인칭 시점 상태에서 플레이어 조작에 대해 알아보았습니다. itadventure.tistory.com/399 유니티3D 플레이어 조작 #5. 3인칭 플레이어 조작 지난 챕터에서는 3인칭 시점으로 플레이어를 바

itadventure.tistory.com