카테고리 없음

14장. 좀비 서바이벌(캐릭터 이동 구현, 시네머신으로 캐릭터 추적 카메라 구현하기)

MJ_119 2024. 4. 2. 23:31

@ 캐릭터 이동 구현

 

PlayerInput : 플레이어의 입력을 감지하고 이를 다른 컴포넌트에 알려줌

PlayerMovement : 플레이어 입력에 따라 캐릭터를 앞뒤로 움직이고 좌우로 회전함.

 

플레이어 입력 감지 스크립트를 따로 만드는 이유는 다른 컴포넌트에서 플레이어 입력 감지 코드를 작성 안해도 되기 때문. 즉 전체적인 코드양이 줄어들고, 필요할 때마다 입력 감지 코드를 쉽게 수정할 수있음.

ex. PC에서 모바일이나 콘솔로 입력 감지방법을 변경하고 개선한다고 하면, PlayerInput에서만 변경하면 됨.

 

 

PlayerInput 스크립트

using UnityEngine;

// 플레이어 캐릭터를 조작하기 위한 사용자 입력을 감지
// 감지된 입력값을 다른 컴포넌트들이 사용할 수 있도록 제공
public class PlayerInput : MonoBehaviour {
    public string moveAxisName = "Vertical"; // 앞뒤 움직임을 위한 입력축 이름
    public string rotateAxisName = "Horizontal"; // 좌우 회전을 위한 입력축 이름
    public string fireButtonName = "Fire1"; // 발사를 위한 입력 버튼 이름
    public string reloadButtonName = "Reload"; // 재장전을 위한 입력 버튼 이름

    // 값 할당은 내부에서만 가능
    public float move { get; private set; } // 감지된 움직임 입력값
    public float rotate { get; private set; } // 감지된 회전 입력값
    public bool fire { get; private set; } // 감지된 발사 입력값
    public bool reload { get; private set; } // 감지된 재장전 입력값

    // 매프레임 사용자 입력을 감지
    private void Update() {
        // 게임오버 상태에서는 사용자 입력을 감지하지 않는다
        if (GameManager.instance != null && GameManager.instance.isGameover)
        {
            move = 0;
            rotate = 0;
            fire = false;
            reload = false;
            return;
        }

        // move에 관한 입력 감지
        move = Input.GetAxis(moveAxisName);
        // rotate에 관한 입력 감지
        rotate = Input.GetAxis(rotateAxisName);
        // fire에 관한 입력 감지
        fire = Input.GetButton(fireButtonName);
        // reload에 관한 입력 감지
        reload = Input.GetButtonDown(reloadButtonName);
    }
}

 

 

move : 움직임 입력, -1.0 후진 , +1.0 전진

rotate : 회전 입력, -1.0 왼쪽 회전, +1.0 오른쪽 회전

fire : 발사 입력, true일때 탄알 발사

reload : 재장전 입력, true일때 탄알 재장전

 

@ 프로퍼티

 : 변숫값을 읽거나 쓰는 과정에서 유연한 처리를 삽입할 수 있는 클래스멤버, 변수처럼 보이지만 변수가 아닌 특수 메서드

https://www.youtube.com/watch?v=omLAXfibAwg

 

 

 

 - Update문에서 게임오버상태면 move, rotate등 사용자의 입력값을 초기화하고 update() 메서드를 종료함.

 

 - Input.GetAxis() : 입력으로 감지할 축 이름을 받아 감지된 입력을 숫자로 반환함.

 - Input.GetButton() : 입력으로 감지할 버튼 이름을 받아 해당 버튼을 누르고있으면 true, 아니면 false를 반환함

 - Input.GetButtonDown() : 누르기 시작한 순간에만 1번 true 반환, 나머지는 false, 즉 누르는 동안에는 false가 반환됨.

 

 

@ PlayerMovement 스크립트

 - 플레이어 입력에 맞춰 캐릭터를 이동하고 적절한 애니메이션을 재생.

 

using UnityEngine;

// 플레이어 캐릭터를 사용자 입력에 따라 움직이는 스크립트
public class PlayerMovement : MonoBehaviour {
    public float moveSpeed = 5f; // 앞뒤 움직임의 속도
    public float rotateSpeed = 180f; // 좌우 회전 속도


    private PlayerInput playerInput; // 플레이어 입력을 알려주는 컴포넌트
    private Rigidbody playerRigidbody; // 플레이어 캐릭터의 리지드바디
    private Animator playerAnimator; // 플레이어 캐릭터의 애니메이터

    private void Start() {
        // 사용할 컴포넌트들의 참조를 가져오기
        playerInput = GetComponent<PlayerInput>();
        playerRigidbody = GetComponent<Rigidbody>();
        playerAnimator = GetComponent<Animator>();
    }

    // FixedUpdate는 물리 갱신 주기에 맞춰 실행됨
    private void FixedUpdate() {
        // 물리 갱신 주기마다 움직임, 회전, 애니메이션 처리 실행
        
        // 회전 실행
        Rotate();
        // 움직임 실행
        Move();

        // 입력값에 따라 애니메이터의 Move 파라미터값 변경
        playerAnimator.SetFloat("Move", playerInput.move);
    }

    // 입력값에 따라 캐릭터를 앞뒤로 움직임
    private void Move() {
        // 상대적으로 이동할 거리 계산
        Vector3 moveDistance = playerInput.move * transform.forward * moveSpeed * Time.deltaTime;

        // 리지드바디를 이용해 게임 오브젝트 위치 변경
        playerRigidbody.MovePosition(playerRigidbody.position + moveDistance);
    }

    // 입력값에 따라 캐릭터를 좌우로 회전
    private void Rotate() {
        // 상대적으로 회전할 수치 계산
        float turn = playerInput.rotate * rotateSpeed * Time.deltaTime; // turn은 사용자 입력에 따라 한프레임 동안 회전할 각도를 저장하는 변수

        // 리지드바디를 이용해 게임 오브젝트 회전 변경
        playerRigidbody.rotation = playerRigidbody.rotation * Quaternion.Euler(0, turn, 0);

    }
}

 

 

 

 

FixedUpdate() : 화면 갱신 주기에 맞춰 실행되는 Update()와 달리 물리정보 갱신주기(기본값 0.02초)에 맞춰 실행됨.

 

@ 유니티는 개발자의 편의를 위해 FixedUpdate() 내부에서 Time.deltaTime의 값에 접근할 경우 자동으로 Time.fixedDeltaTiime의 값을 출력함.

 

 

@ 플레이어 캐릭터가 앞쪽 방향으로 이동한다고 가정했을 때 이동할 거리와 방향 = 앞쪽 방향 * 속력 * 시간

 = transform.forward * moveSpeed * Time.deltaTime;

 

@ 위에다가 사용자의 입력값 playerInput.move를 곱하면

 = playerInput.move * transform.forward * moveSpeed * Time.deltaTime;

 사용자의 입력이 없다면 playerInput.move의 값이 0이므로 캐릭터가 움직이지 않음.

 

@ playerRigidbody.position + moveDistance의 값은 '현재위치 + 상대적으로 더 이동할 거리'

 

@ 트랜스폼 컴포넌트를 사용하면

transform.position = transform.position + moveDistance;

이렇게 하지 않은 이유는 트랜스폼의 위칫값을 직접 변경하면 물리처리를 무시하고 위치를 덮어쓰기 때문.

그러므로 막힌 벽 등을 무시하고 벽 반대쪽으로 이동할 수도 있음.

 

리지드바디의 MovePosition() 메서드로 위칫값을 변경하면 다른 콜라이더가 존재하는 경우 밀어내거나 밀려나는 물리처리가 실행됨. 따라서 벽 반대로 순간이동 하는 사고를 방지할 수 있음.

 

@ 어떤 회전 상태에서 상대적으로 더 회전할 때는 쿼터니언 곱을 사용함.

 

@ 트랜스폼 컴포넌트를 사용하지 않는 이유는 물리처리를 무시하고 회전하는 사고를 방지하기 위함.

 

 

@ 시네머신 추적 카메라 구성하기

 - 브레인 카메라 : 게임 월드를 촬영하는 진짜 카메라, 씬에 하나만 존재

 - 가상 카메라 : 브레인 카메라의 분신 역할을 하며 씬에 여러개 존재할 수 있음. 가상카메라를 여러군데 배치해놓고 브레인 카메라가 가상카메라 위치로 전환해서 카메라를 활성화함.

 

 

하이어라키창에서 시네머신->Virtual Camera 클릭후 이름을 바꾼다음 Follow와 Look At에 Player Character를 할당.

Follow : 할당된 오브젝트를 따라다님, 이를 위해 자신의 위치를 변경.

Look At : 할당된 오브젝트를 주시함, 이를 위해 자신의 회전을 변경.

 

 @ 데드존과 소프트존의 크기를 줄이면 물체가 조금이라도 화면 중앙을 벗어나려 할 때 지연시간 없이 카메라가 즉시 물체를 향해 회전하게 됨. 대신 화면의 움직임이 딱딱하게 느껴질 수 있음.

 // 반대로 데드존과 소프트존의 크기를 늘리면 물체를 쫓는 화면의 움직임이 느리게 느껴질 수 있음.

 

 

@ FOV 20

 

@ FOV 40