Unity_Dev(1)
이번 포스트에서는 유니티 게임 프로젝트를 진행하면서, 공부하고 생각했던 내용들을 정리하고자 합니다. 깃허브의 Jaeun-Choi98/GameWorld 프로젝트를 진행하면서 적용했던 내용입니다.
배틀그라운드 3인칭 시점 구현
배틀그라운드와 같은 3인칭 시점을 구현하려고 할 때, 구면 좌표계를 이용한 카메라 포지션을 활용하는 것을 고려해볼 수 있습니다. 구면 좌표계를 사용하면 카메라가 캐릭터를 중심으로 자연스럽게 회전하고 위치를 조절할 수 있습니다. 구면 좌표계는 한 점을 각 축의 각도로 나타내는 좌표계입니다.
구면 좌표계를 이용하여 카메라의 포지션을 조절하는 경우, 카메라가 주변 오브젝트를 뚫고 가는 문제가 발생할 수 있습니다. 이때, Raycast를 사용하여 캐릭터와 카메라의 사이에 오브젝트를 탐지하고 카메라의 위치를 조정할 수 있습니다.
Rigidbody를 이용한 오브젝트 움직임(feat. FPS 게임 스탑 움직임)
Rigidbody를 이용해서 오브젝트를 움직일 때, 3가지 방법이 있습니다: AddForce, MovePosition 메서드를 이용하는 방법, 그리고 velocity 필드를 사용하는 방법입니다. 이 세 가지 방법을 사용해본 후 느낀 점은 다음과 같습니다.
먼저, AddForce를 사용하는 경우 오브젝트에 힘을 가해 물리적인 운동과 상호작용을 통해 오브젝트를 움직입니다. 반대 방향으로 힘을 주어도 오브젝트가 즉시 그 방향으로 꺾이지 않습니다. AddForce는 오브젝트에 순간적인 힘을 가할 때 유용합니다. 예를 들어, 오브젝트가 점프하거나 물체가 물리적으로 이동해야 할 때 사용하기 좋습니다.
MovePosition을 사용하는 경우, 오브젝트의 위치를 즉시 설정합니다. 이 방법은 오브젝트를 정확한 위치로 이동시키는 데 유용합니다. 한 방향 키를 누르다가 반대 방향 키를 눌렀을 때, Rigidbody의 속도가 순간적으로 0이 됩니다.
velocity 필드를 사용하는 경우, MovePosition을 사용한 움직임과 비슷한 결과를 얻을 수 있습니다. 그러나 차이점은 한 방향의 키를 누르다가 반대 방향의 키를 눌렀을 때, Rigidbody의 속도가 즉시 반대 방향으로 전환된다는 점입니다.(순간적으로 velocity가 0인 지점이 존재하지만, MovePosiont보다 더욱 민감하게 반응합니다.)
결론적으로, 제가 원하는 움직임은 FPS 게임에서 브레이킹 효과를 포함한 움직임이었고, MovePosition을 사용한 움직임 처리가 가장 적합했습니다. 이 방법은 오브젝트의 위치를 정확하게 제어할 수 있고, 방향 전환 시 순간적으로 속도가 0인 지점을 구현할 수 있었습니다.
오브젝트 풀을 이용한 오브젝트 관리와 오브젝트 라이프 사이클
오브젝트 풀은 자주 생성되고 소멸되는 오브젝트에 한해서 사용되는 디자인 패턴입니다. 빈번하게 생성 및 소멸되는 오브젝트의 경우, 메모리 단편화, GC에 의한 프레임 드랍 등의 문제를 야기할 수 있기 때문에 ‘오브젝트 풀’을 이용해서 생명 주기를 관리합니다. 다만, 오브젝트 풀을 사용할 때는 Start 함수가 단 한 번만 호출된다는 점을 신경 써야 합니다. 오브젝트가 활성화되거나 비활성화될 때 설정 값이 변해야 한다면, 이를 초기화하는 코드를 적절한 위치에 추가해야 합니다. 보통 이러한 설정은 OnEnable, OnDisable 또는 사용자 정의 메서드를 통해 처리할 수 있습니다.
총 계열의 발사 무기를 구현할 때
총알을 하나의 오브젝트로 구현하려는 경우 충돌 처리, 생성 및 파괴를 각 총알의 개수만큼 반복하면 메모리 부담이 증가하거나 성능에 좋지 못할 수도 있습니다. 또한, 총알의 속도를 매우 빠르게 설정하면 프레임 당 이동 거리가 너무 커져 충돌 오차가 발생할 수 있습니다.
이 문제를 해결하기 위해 ‘레이캐스트(Raycast)’ 기능을 사용할 수 있습니다. 레이캐스트 기능은 ‘레이(Ray)’를 지정한 방향으로 발사하고, 어떤 물체에 닿으면 그 물체에 대한 정보를 담는 ‘RaycastHit’라는 구조체 변수가 생성됩니다.
레이캐스트를 사용하면 실제 총알 오브젝트를 생성하지 않고도 충돌을 감지할 수 있어 메모리 사용을 크게 줄일 수 있습니다. 레이를 발사하여 특정 방향에 있는 물체와의 충돌 여부를 빠르게 계산할 수 있기 때문에, 총알의 속도가 빠르더라도 정확한 충돌 감지가 가능합니다.
FSM(Finite State Machine) 설계 및 애니메이터 컨트롤
오브젝트의 각각의 상태를 이벤트에 따라 전이하는 설계 모델을 상태 전이도(State Transition Diagram)라고 합니다. 각 상태는 독립적으로 관리되며, 상태 전이도를 통해 각 상태의 전이를 명확하게 정의하는 것이 중요합니다. 이를 통해 코드의 복잡도를 낮추고, 코드의 가독성과 확장성을 높일 수 있습니다.
상태 전이도는 FSM(Finite State Machine) 설계의 핵심 요소입니다. FSM 설계를 잘해놓으면 애니메이터 컨트롤러에서 애니메이션 상태와 상태 간의 전환을 관리하는 데도 편리합니다. FSM을 사용하면 캐릭터의 행동이나 게임 오브젝트의 상태를 체계적으로 관리할 수 있어, 각 상태의 전이 조건을 명확하게 정의하고 디버깅할 때도 유리합니다.
예를 들어, 게임 캐릭터의 상태 전이를 설계할 때, ‘Idle’, ‘Run’, ‘Jump’, ‘Attack’ 등의 상태를 정의하고, 각 상태 간의 전환 조건을 명시할 수 있습니다. 이렇게 하면 캐릭터가 어떤 조건에서 어떤 상태로 전이되는지 쉽게 파악할 수 있습니다.
애니메이션 동작과 동작 효과 타이밍이 안 맞는 경우
플레이어가 공격받을 때 피격 효과의 타이밍이 적의 공격 동작과 맞지 않는 경우, 혹은 점프 모션이 끝나기 전에 점프가 될 때 등 특정 애니메이션 동작에 맞춰 이벤트를 실행할 필요가 있습니다. 이를 위해 애니메이션 뷰에서 특정 프레임에 이벤트를 설정하고, 애니메이터 컴포넌트가 있는 오브젝트에서 해당 이벤트 함수를 구현할 수 있습니다.
하지만, 애니메이터 컴포넌트가 있는 오브젝트에서 이벤트 함수를 구현할 경우, 피격 효과를 발생시키는 오브젝트에서 해당 함수를 호출해야 하기 때문에 코드 관리가 불편할 수 있습니다. 이러한 경우, 주석을 사용하여 해당 오브젝트와 함수의 역할을 명확하게 설명하는 것이 도움이 될 거라 생각합니다.
범위 안에 있는 대상을 충돌 처리하기 위한 방법
범위 안에 있는 게임 오브젝트의 충돌 처리를 위해 Physics 클래스의 Overlap() 함수 계열을 사용할 수 있습니다. Overlap 함수는 호출되는 프레임에서 특정 영역 내에 있는 모든 게임 오브젝트의 콜라이더 컴포넌트를 배열로 반환합니다. 영역의 형태로는 구, 큐브, 캡슐 형태가 있습니다. Overlap 함수의 세 번째 파라미터는 Layer의 인덱스 값을 받으며, 이를 통해 특정 Layer를 가진 게임 오브젝트에 대해서만 충돌 처리를 할 수 있습니다. 추가적으로 2개 이상의 Layer 처리를 하고 싶다면, LayerMask를 사용하여 여러 Layer를 포함할 수 있습니다.
비동기 씬 로딩
씬에 배치된 오브젝트가 많거나 많은 폴리곤을 사용하는 모델링을 사용할 경우 로딩 시간이 길어질 수 있습니다. 씬에 배치된 게임 오브젝트가 생성되는 동안 화면이 멈춰 있기 때문에, 사용자에게는 버그처럼 느껴질 수도 있습니다. 따라서 현재 씬을 유지한 채 다음 씬을 미리 로드하고, 로딩이 완료되면 씬을 전환하는 비동기 로딩 방식을 사용하는 것이 사용자 경험을 향상시키는 데 도움이 됩니다.
Newtonsoft.Json 패키지와 DB Json 타입 컬럼
게임 개발을 하면서 처음으로 db 테이블을 만들면서 Json 타입 컬럼을 사용하게 되었습니다. Json 타입의 컬럼을 사용하면서 느꼈던 편리함은 외래키를 사용한 테이블 간 참조를 줄일 수 있다는 점과 데이터 구조가 자주 변경되어도 테이블 스키마를 다시 짤 필요가 없다는 점이 었습니다. 예를 들어, 인벤토리 테이블을 설계하는 과정에서 인벤토리의 컬럼에는 해당 인벤토리가 어느 유저의 것인지 알기 위한 참조키와 아이템 정보를 가지고 있는 테이블에 대한 참조키 두 개의 참조키가 필요합니다. 유저 테이블에서 Json 타입의 컬럼으로 인벤토리를 설계하면 인벤토리에는 아이템 정보를 가진 테이블에 대한 참조키만 저장하면 됩니다. 또한, 이렇게 설계함으로써 이후 인벤토리 테이블의 컬럼을 수정함에 있어서 데이터만 바꾸면 됨으로 수정에도 편리합니다.
하지만 json 컬럼을 사용하여 테이블을 설계한다면, 서버와 클라이언트에서 직렬화 및 역직렬화를 함에 신경을 써야할 수도 있습니다. 유니티에서는 내장된 JsonUtility 패키지는 이중 json 구조에 대한 직렬화 및 역직렬화는 가능하지 않습니다. 그래서, Newtonsoft.Json 패키지를 사용해서 이중 json 구조에 대한 직렬화 및 역직렬화를 수행할 수 있습니다.