📌
다양한 유니티 기능을 사용하는 숙련 주차 강의를 다시 한 번 복습하게 되었다. 클래스 구조보다 이론 및 기능과 코드 해석에 초점을 맞춘 TIL이 이어질 것 같다.
학습 내용
1. 물리 엔진과 ForceMode
2. 플레이어
# 기초 2강 플레이어 만들기 - 이론
1. 물리 엔진과 ForceMode
게임 오브젝트에 물리적인 움직임을 줄 때 보통 Rigidbody 컴포넌트를 사용한다.
✅ Rigidbody?
GameObject 가 물리 제어로 동작하게 하며, 힘과 토크를 받아 오브젝트가 사실적으로 움직이도록 해준다. 게임 오브젝트에 Rigidbody 컴포넌트를 할당했다면, 스크립트에서 선언-초기화-호출의 단계를 거쳐서 사용할 수 있다. 간단하게 오브젝트를 왼쪽으로 움직이게 하는 코드를 작성해보자.
// 선언
// 초기화
// 호출
void start
{
rigid.velocity = Vector3.left;
}
이때, Vector3.left는 (−1, 0, 0)으로 정의되며, velocity에 의하여 초당 1 유닛(1m)의 속도로 움직이게 된다.
✅ Transform , velocity, AddForce의 차이점
여기서 궁금한 점이 생겼다. 게임 오브젝트는 물리 엔진 없이 Transform 으로도 움직일 수 있지 않나? 물리 엔진이지만 다른 메서드인 velocity와 AddForce의 차이점은 뭘까?
그래서 유니티에서 게임 오브젝트를 움직이게 하는 방법으로 자주 사용되는 Transform, velocity, AddForce의 차이점과 용도에 대해 가볍게 짚고 가려고 한다. 이 세 가지는 모두 오브젝트의 움직임을 구현할 수 있지만, 작동 방식과 의도에 따라 다르게 사용된다.
[1] Transform
물리적 힘을 적용하지 않고, 단순히 좌표(위치값)를 변경하여 오브젝트를 이동시키는 방법이다. 이를 체스말에 비유하자면, 체스말을 손으로 들어서 원하는 위치에 놓는 것과 같다. 물리적인 반작용이나 충돌 등을 고려하지 않고 바로 이동시키는 것이 특징이다.
void Update()
{
transform.Translate(Vector3.left * speed * Time.deltaTime);
}
void Update()
{
transform.position += Vector3.left * speed * Time.deltaTime;
}
이 경우 충돌 및 물리 효과가 적용되지 않으며 움직임이 부자연스럽다. 따라서 Transform은 물리적 움직임을 구현할 때 사용하기보다 단순히 위치만 조정하고자 할 때 적합하다.
[2] velocity
현재 오브젝트의 속도를 지정하여 이동시키는 방법이다. 이는 체스말을 지정한 속도로 밀면서 이동시키는 것과 비슷하다. 물리 엔진의 속성 중 하나로, 지정한 속도로 오브젝트가 즉각적으로 이동하게 되지만, 물리적 힘을 가한 것은 아니라고 볼 수 있다.
void start
{
rigid.velocity = Vector3.left;
}
void Update()
{
rigid.velocity = new Vector3(0, 0, speed);
}
물리적인 가속도나 감속 과정 없이 지정된 속도로 이동하며, 중력과 같은 외부 물리 요소의 영향을 받는다.
[3] AddForce
오브젝트에 일시적인 물리적 힘을 가하여 가속도를 발생시키는 방법이다. 체스말을 손가락으로 툭 건드리는 것처럼(알까기) 순간적인 물리적 힘을 적용해 이동시키는 방법과 비슷하다.
void Update()
{
rb.AddForce(Vector3.forward * force, ForceMode.Impulse);
}
가속도와 감속도가 고려된 자연스러운 움직임을 구현할 수 있는 게 특징으로 힘을 추가하여 이동하기 때문에 속도나 방향을 정확하게 조절하는 데는 추가적인 계산이 필요하다.
✅ AddForce, ForceMode
이번 강의에서는 AddForce를 집중적으로 배운다.
// 사용 방법
Rigidbody.AddForce(필수 매개변수, 선택 매개변수);
이때 필수 매개변수는 힘의 방향과 정도를 나타내는 벡터 값이 들어가고, 선택 매개변수는 가해지는 힘의 정보가 들어간다.
선택 매개변수에서 사용되는 핵심 ForceMode 종류는 다음과 같다.
[1] Force
Rigidbody.AddForce(Vector3 force, ForceMode.Force);
힘을 지속적으로 적용한다. 질량에 비례하기 때문에 무거운 물체는 더 천천히 가속하게 된다.
사용 예시: 점진적인 가속
[2] Acceleration
Rigidbody.AddForce(Vector3 force, ForceMode.Acceleration);
물체의 질량과 관계없이 동일한 가속도를 적용한다.
사용 예시: 지속적인 가속
[3] Impulse
Rigidbody.AddForce(Vector3 force, ForceMode.Impulse);
충격량을 사용하여 순간적으로 힘을 가한다. 질량의 영향을 받는다.
사용 예시: 점프, 발사
[4] VelocityChange
Rigidbody.AddForce(Vector3 force, ForceMode.VelocityChange);
물체의 질량에 상관없이 즉각적인 속도 변화를 준다.
사용 예시 : 충돌 처리, 순간적인 방향 변경
# 기초 3강 플레이어 만들기 - 구현
2. 플레이어
해당 게임은 1인칭 시점으로 카메라가 움직이며 인풋 시스템을 통해 플레이어의 움직임이 구현된다. 카메라의 경우 플레이어 오브젝트 하위에 main camara를 넣어주면 쉽게 해결된다.
✅ 플레이어 다이어그램
우선 플레이어를 싱글톤으로 만들어 작업한다. 플레이어를 싱글턴으로 만들면 어느 클래스에서도 플레이어를 생성할 수 있기 때문에 데이터 관리가 효율적이다. 플레이어 클래스에서는 싱글턴으로 만든 플레이어를 자신이라고 선언하며 플레이어 컨트롤러 클래스를 참조한다. 컨트롤러 클래스에서는 플레이어의 실제 움직임과 관련된 함수를 작성해 관리한다.
✅ 캐릭터 매니저 클래스
이전 블로그 글에 정리해둔 싱글톤 작업에 관한 내용이다.
https://note8918.tistory.com/38
✅ 캐릭터 매니저 클래스와 플레이어 클래스의 역할은 뭘까?
캐릭터 매니저 클래스는 객체를 싱글톤으로 만드는 역할이다. 플레이어 뿐만 아니라 NPC 등 싱글톤으로 만들어야 하는 객체를 확장성 있게 관리할 수 있다.
플레이어 클래스는 인스턴스로 만든 단 하나의 플레이어를 자신이라고 정의하면서, 플레이어와 관련된 스크립트를 참조 받아서 관리하고 있다.
✅ 플레이어 컨트롤러 클래스
이전 블로그 글에 정리해둔 인풋 시스템을 활용한 플레이어 움직임 구현에 관한 내용이다.
https://note8918.tistory.com/40
✅ FixedUpdate, LateUpdate의 차이점
플레이어 컨트롤러 클래스에서 Move 함수는 FixedUpdate에서, CameraLook은 LateUpdate에서 호출되고 있다. 차이점이 뭘까?
[1] FixedUpdate
기존의 Update 메서드는 1 프레임당 한 번씩 호출된다. 이때의 프레임은 사용자의 컴퓨터 설정에 따라 차이가 발생하기 때문에 일관적인 간격으로 호출되기 어렵다.
하지만 설정에서 지정된 고정된 간격인 Fixed Timestep에 따라 호출된다. 기본값은 약 0.02초 (50fps), Time.fixedDeltaTime 으로 조절할 수 있다. 핵심은 일정한 시간 간격으로 호출되기 때문에, 물리 연산과 같은 규칙적이고 일정한 처리를 필요로 하는 로직에 적합하다는 것이다.
private void Move()
{
Vector3 dir = transform.forward * curMovementInput.y + transform.right * curMovementInput.x;
// 이동 방향 결정
dir *= moveSpeed; // 속도 조절
dir.y = rigidbody.velocity.y; // y축 현재값 유지 (강제로 0으로 만들지 않음)
rigidbody.velocity = dir; // 이동 구현
}
따라서 다음 Move 함수에서 rigidbody.velocity처럼 물리 연산을 해야 할 때는 FixedUpdate에서 호출하는 게 좋다.
[2] LateUpdate
LateUpdate는 Update의 모든 함수가 수행되고 난 뒤에 호출되는 메서드이다. 카메라의 경우 플레이어 움직임을 따라가기 때문에 플레이어가 움직인 뒤에 호출되는 게 자연스럽다. 애니메이션 호출도 이곳에서 자주 사용된다.
✅ InputSystem
SendMassage vs Invoke Unity Events
인풋 시스템의 Behavior의 두 가지 설정을 비교해보고자 한다.
[1] SendMassage
InputAction에서 설정한 액션들이 함수로 생성된다. 만약 Move를 만들었다면, 이름 앞에 On이 붙어서 OnMove라는 함수가 생성되며 스크립트에서 OnMove를 호출해서 코드를 작성할 수 있다. 이때 InputValue를 매개변수로 받는다.
void OnMove(InputValue value) // 입력키를 받았다!
{
// 이동 코드
}
[2] Invoke Unity Events
Invoke는 Onclick 컴포넌트처럼 액션에 쓸 함수를 인스펙터에서 직접 할당해주는 형식이다. InputAction.CallbackContext를 매개변수로 받으며 콜백 시점을 정할 수 있다. 즉, 입력이 발생한 시점에 따라 메서드를 분리할 수 있다. 예를 들어, 버튼을 눌렀을 때, 떼었을 때, 또는 유지하고 있을 때 등 다양한 입력 상태를 구분할 수 있다.
이벤트에서 생성한 함수를 직접 할당해야 돼서 함수명으로 인한 휴먼에러가 발생할 가능성이 낮다.
void OnMove(InputAction.CallbackContext context)
{
if(context.phase == InputActionPhase.Performed) // 입력키가 눌렸을 때
{ // 이동 코드 }
}
두 방법 모두 인풋 시스템을 사용하고 액션을 만들어줘야 하는 부분은 같지만 스크립트에서 호출하는 방식과 매개변수에서 차이점이 두드러진다. Invoke Unity Events은 구조가 더 복잡하지만 콜백 시점을 정할 수 있어서 유연성이 더 좋다는 장점이 있다.
✅ (추가) 더 알면 좋은 지식
Update에서 DeltaTime을 사용하는 것과 FixedUpdate는 계산하는 단위에서 차이가 난다. 업데이트는 프레임으로 계산하는 반면 픽시드 업데이트는 초당으로 간격을 계산하게 된다.
velocity는 엄밀히 말해서 단위 시간당 위치 변화량을 의미한다. 물리적인 힘은 가해지지 않지만 변화량을 시스템에서 체크하는 주기와 Update 주기가 맞지 않을 수 있기 때문에 FixedUpdate에서 체크해주는 게 좋다. velocity의 경우, public Vector3로 생성해서 실제 변화량을 인스펙터로 체크해본다면 의미를 쉽게 이해할 수 있을 것 같다.
✅ 참고 자료
'[내배캠] 본 캠프 개발 학습 > 매일매일 쓰는 TIL' 카테고리의 다른 글
11월 13일 수요일 본 캠프 개발 일지 | 오브젝트의 조작과 스크립트와의 연결 (3) | 2024.11.13 |
---|---|
11월 12일 화요일 본 캠프 개발 일지 (4) | 2024.11.12 |
11월 9일 토요일 본 캠프 개발 일지 | 베이지반 특강 1회차 (3) | 2024.11.09 |
11월 8일 금요일 본 캠프 개발 일지 | 델리게이트 특강 (0) | 2024.11.08 |
11월 7일 목요일 본 캠프 개발 일지 | 결국 못 정했조 팀 프로젝트 (end) (2) | 2024.11.07 |