본문 바로가기

Study/Unity

3인칭 카메라 구현

Unity에서 3인칭 카메라를 구현해보자.

 

우선 3인칭 카메라의 생김새 부터 생각해보자.

 

일반적인 3인칭 카메라는 3D RPG게임에서 흔하게 찾아볼 수 있다.

 

이를 구현하기 위해 필요한 요소들을 정리해보자.

 

- 카메라가 회전할 중심점

- 카메라가 중심점으로 부터 떨어져있는 거리(변동 가능해야함.)

- 카메라가 중심점에서 구체 회전함.

- 카메라부터 정해진 거리 사이에 장애물이 있을경우 카메라가 앞으로 당겨져와야함.

 

대충 이렇게가 앞에서 만들었던 일인칭 카메라와 다르게 고민해봐야할 요소들이다.

 

여기서는 바로 중심점이 중요하다.

 

상대좌표계를 이용하면 이를 간단하게 구현 할 수있다.

 

대략적인 구상도는 이렇다.

그림실력 무엇;;;;

 

CameraArm이라는 오브젝트에 카메라를 자식 오브젝트로 배치한 뒤 CameraArm의 뒤로 ArmLength만큼 떨어뜨리면 우리가 원하는 3인칭 카메라의 기본이 만들어진다.

 

이렇게 되면 로컬좌표계의 특성상 CameraArm이 회전 할때 카메라가 같은 방향으로 구체 회전을 하게된다.

 

이제 여기서 카메라의 방향에 장애물이 검출하여 카메라를 당겨주면 되는것이다.

 

이제 실제로 구현해 보자.

 

일단 중심점인 CameraArm이라는 빈 오브젝트를 생성한 뒤,  카메라를 자식오브젝트로 배치한뒤 -z축 방향으로 일정 거리 떨어뜨린다.

 

캡슐의 중심점에 CameraArm이 배치되어 있다.
CameraArm 회전시 카메라가 구체 회전하는 모습

이제 세팅은 다 됐으니 즐거운 코딩 시간~

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

public class TPSCameraController : MonoBehaviour
{
    public GameObject Capsule; //테스트용 캡슐 오브젝트
    public GameObject Target; //CameraArm이 추적해야할 타겟 오브젝트
    public GameObject CameraArm;
    public Camera TPSCamera;

    public float ArmLength = 35f;
    public float SpeedRot = 360f;
    public float SpeedMove = 100f;
    public float SpeedZoom = 50f;

    private float _xRot;
    private float _yRot;
    private float _armLengthMin = 10f; //최소 CameraArm 길이
    private float _armLengthMax = 100f;//최대 CameraArm 길이
    private bool _isBoost = false;

    private Vector3 _originPos;
    private Quaternion _originRot;
    private float _originArmLength;
    
    

    private void Init()
    {
        //초기화
        CameraArm.transform.localPosition = _originPos;
        CameraArm.transform.localRotation = _originRot;
        ArmLength = _originArmLength;
        _xRot = 0f;
        _yRot = 0f;
    }
    
    private void CameraUpdate()
    {
        //카메라 위치값 업데이트
        //여기서 Raycast를 이용하여 장애물이 있는지 판단함.
        //장애물이 있다면 Raycast Hit 포인트로 카메라를 이동시킨다.
        
        Ray ray = new Ray(CameraArm.transform.position, -CameraArm.transform.forward);
        RaycastHit hit;
        if (Physics.Raycast(ray, out hit, ArmLength))
        {
            TPSCamera.transform.position = hit.point;
        }
        else
        {
            TPSCamera.transform.position = CameraArm.transform.position + (-CameraArm.transform.forward * ArmLength);
        }
    }

    private void Rotate()
    {
        //회전
        if (!Input.GetMouseButton(1)) return;

        var xMove = Input.GetAxis("Mouse X");
        var yMove = -Input.GetAxis("Mouse Y");

        _xRot += yMove * SpeedRot * Time.deltaTime;
        _yRot += xMove * SpeedRot * Time.deltaTime;

        _xRot = Mathf.Clamp(_xRot, -85f, 85f);

        CameraArm.transform.localRotation = Quaternion.Euler(_xRot, _yRot, 0f);
    }

    private void Move()
    {
        //이동
        if (Input.GetKey(KeyCode.LeftShift))
        {
            _isBoost = true;
        }
        else
        {
            _isBoost = false;
        }
        
        var speed = _isBoost ? SpeedMove * 2f : SpeedMove;

        var moveX = Input.GetAxis("Horizontal");
        var moveY = Input.GetAxis("UpDown");
        var moveZ = Input.GetAxis("Vertical");

        if (Target)
        {
            //추적하는 타겟 오브젝트가 있다면 타겟 오브젝트를 이동시킨다.
            Target.transform.position += (CameraArm.transform.right * moveX + CameraArm.transform.up * moveY + CameraArm.transform.forward * moveZ).normalized * (speed * Time.deltaTime);
        }
        else 
        {
            //타겟 오브젝트가 없다면 CameraArm이 움직인다.
            CameraArm.transform.position += (CameraArm.transform.right * moveX + CameraArm.transform.up * moveY + CameraArm.transform.forward * moveZ).normalized * (speed * Time.deltaTime);
        }
        
    }

    private void Zoom()
    {
        //확대 및 축소
        //1인칭 카메라처럼 FOV값을 조절하지 않고 CarmeraArm과의 거리값인 ArmLength를 조절한다.
        var zoom = Input.GetAxis("Mouse ScrollWheel") * SpeedZoom;

        ArmLength += zoom;
        ArmLength = Mathf.Clamp(ArmLength, _armLengthMin, _armLengthMax);
    }

    public void SetTarget(GameObject target)
    {
        //추적할 타겟 오브젝트를 세팅 (null로 세팅 가능)
        Target = target;
        if (target != null)
        {
            CameraArm.transform.SetParent(Target.transform);
            CameraArm.transform.localPosition = Vector3.zero;
            CameraArm.transform.localScale = Vector3.one;
            CameraArm.transform.localRotation = Quaternion.identity;
        }
    }
    
    // Start is called before the first frame update
    void Start()
    {
        _originPos = CameraArm.transform.localPosition;
        _originRot = CameraArm.transform.localRotation;
        _originArmLength = ArmLength;

        Init();
    }

    // Update is called once per frame
    void Update()
    {
        Rotate();
        Move();
        Zoom();
        CameraUpdate();

        if (Input.GetKeyDown(KeyCode.Space))
        {
            Init();
        }

        if (Input.GetKeyDown(KeyCode.T))
        {
            if (Target)
            {
                SetTarget(null);
            }
            else
            {
                SetTarget(Capsule);
            }
        }
    }
}

이제 해당 스크립트를 CameraArm에 컴포넌트로 넣어주고, 아래 사진처럼 변수들에 오브젝트들을 넣어준다.

 

이렇게 세팅!!

이렇게 세팅후 재생하면 영상처럼 작동하게 된다.

 

https://youtu.be/laAIn0Wyj5g

 

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

[Unity Shader] 1. 유니티 셰이더 소개  (0) 2023.06.27
1인칭 카메라 구현  (1) 2023.06.09
Unity Shader - 등고선 shader만들기  (0) 2022.07.30