14장. 좀비 서바이벌(캐릭터 이동 구현, 시네머신으로 캐릭터 추적 카메라 구현하기)
@ 캐릭터 이동 구현
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