내일배움캠프 WIL : Sniper Beginner

김정환·5일 전
1

키워드

  • TPS 장르 프로젝트
    • Unity IK
    • 시네머신
    • Vector3.Dot 사용
    • 투사체 물리 처리 설정
    • Raycast 외 다양한 검사 방법
    • 게임의 구조 : 간단한 프레임워크

원래는 이러면 안되는데 이번주는 유난히 정신이 없어서 밀린 걸 한번 써봤다...

팀프로젝트를 하면서 정말 오랜만에 재밌게 만든 것 같다.
힘이 드는 것이야 어떤 상황에서든 마찬가지이나 이번에는 팀원분들이 워낙 좋은 분들이었고
적극적이어서 나도 덩달아 열심히 만들었던 거 같다.

TPS 장르

TPS 장르를 만들면서 다양한 것들을 복습하고 새로운 방법을 찾아봤다.
개인적으로 실력 키우고 Unity 3D에 적응해보려면 FPS,TPS 계열을 만들어보는 게 가장 좋은 것 같다.

목표

TPS 장르이긴 하되 그냥은 심심하니까 레퍼런스로 스나이퍼 엘리트를 참고했다.
완벽하게 클론 프로젝트를 만들기 보다는 스나이퍼 엘리트의 프로토타입 버전을 만드는 것을 목표로 잡았다.

파트

이번에는 플레이어 파트를 맡아서 제작에 들어갔다.

  • 플레이어 전반 기능
  • 플레이어 관련한 UI
  • (중요) 사격 기능

이 외에 다른 필요한 기능은 우선 작업 현황을 보고 다시 분배하도록 상의가 되었다.

작업 1일차 (금)

  • TPS 장르니까 캐릭터를 구해왔다.

    • 멀리 갈 필요없이 캐릭터 에셋과 애니메이션은 Mixamo에서 구해왔다.
      (프로토타입 제작에는 여기만한 곳이 없다.)
    • 플레이어로 사용할 캐릭터와 적으로 사용할 캐릭터 3개를 구해왔다.
  • 공통적으로 캐릭터가 필요하니까 팀장님이 dev 브랜치에 올려주셨다.
    근데 보다시피... 텍스쳐가 많다. 크기도 2048 사이즈더라...

당연히 대용량 파일이므로 우리들의 github는 전송을 거부했다.
올릴 용량이 대략 400~500 mb 라고 뱉어냈다.

지금까지는 가벼운 용량의 프로젝트만 사용해서 마주칠 일이 없었는데
깃허브는 100 mb 이상의 용량을 올리지 못하게 하고 있다.

그럼 100 mb 이상 파일을 올리고 싶으면 어떻게하면 되는가?
언젠가 튜터님 중 한 분께서 대용량 파일을 올릴려면 git lfs를 사용하라고 하셨다.

Git LFS (Large File Storage)

Git LFS

설정방법

  • github를 설치할 때, git도 같이 설치할 것이므로 git lfs만 보면된다.

설치

CLI로 몇 가지를 해야한다.
cmd를 열든 다른 터미널로 열든 상관없었다.

이런 거 신경쓰기 싫을 때는 데스크탑 앱에서 바로 열 수 있다.

git lfs // 설치 여부 확인

git lfs install // 설치 명령어

git lfs로 설치가 되어있는지 확인하고, 만약 설치가 안 되어 있다면 install 해주면 된다.

추적 설정

대용량 파일을 lfs가 추적할 수 있도록 설정한다.
이 작업은 레포지토리 단위로 적용된다.

git lfs track "*.fbx"

이번에 문제가 된 파일은 mixamo에서 받은 캐릭터 파일인데 fbx에 내장된 텍스쳐의 용량이 문제가 되었다.
텍스쳐가 내장되어 있기 때문에 .fbx 확장자가 fbx인 파일들을 추적 대상으로 설정했다.

이게 사실은 그렇게 좋은 방법은 아닌게, 모든 fbx 파일이 이 만큼 용량이 크지 않을 수도 있다.
그런데 위의 코드는 그런거 구분없이 모든 fbx를 추적 대상으로 설정하고 lfs를 통해서 업로드하라는 의미이다.

가급적이면 좀 더 디테일하게 설정하는 것이 lfs 관리에 좋다.

git add .gitattributes

추적 설정을 했다면 gitattributes에 반영해주어야한다.
이렇게만 해두면 설정은 끝났고 평소대로 github를 사용할 수 있다.

제약 사항

  • git lfs는 무제한은 아니고 당연히 제약이 있다.
  • 무료 플랜의 경우
    • 무료 스토리지 크기 : 1 GB
    • 1개월 마다 제공받는 무료 대역폭 : 1 GB
      • 대역폭 사용 판정은 Pull인 경우만 계산
    • 최대 파일 크기 : 2 GB
      • 한번에 2 GB가 넘는 파일은 올리지 못한다.
      • 어차피 무료 버전에서 스토리지 크기가 1 GB이므로 사실상 1 GB만 쓸 수 있다고 봐야한다.
  • 사용에 유의하자

InputSystem 활용하기

lfs 적용 문제가 끝났으니까 다시 작업을 진행했다.
프로젝트를 URP로 만들었으니까 캐릭터 재질을 다시 할당해주고 다음 작업을 이어갔다.

플레이어 제어는 InputSystem으로 만들었다.

당장에 필요한 입력을 설정했다.
이제 이걸 C# Generate 설정으로 만들어서 사용할 것이다.

다만, 이번 프로젝트에서는 인벤토리 대신에 퀵슬롯 방식으로 구현하기로 했다.
숫자 1~5번을 누르면 그 번호에 해당하는 슬롯의 아이템을 사용하는 식이다.

이걸 보고 잠시 생각했다.

  • QuickSlot 입력을 QuickSlot 1~5까지 각각 만들어서 받아야하나...
  • QuickSlot이라는 입력을 만들고 1~5을 그냥 맵핑할 순 없나...
    • 이렇게하면 하나의 입력으로 5개의 맵핑을 처리할 수 있으니 더 효과적인 방법이지 않을까 생각했다.
    • 입력에 대한 구분은 1번을 누르면 1을 주고, 2번을 누르면 2를...5번을 누르면 5를 반환한다.

여러 입력 맵핑하기

이 생각을 어쩌다 했냐면 Move 부분을 만들 때였다.

위 이미지처럼 Move는 W,A,S,D를 입력받아서 처리할 수 있다.
유니티에서 저렇게 할 수 있게 만들어서 프리셋 형태로 제공한다면
개발자들도 비슷하게라도 할 수 있을 것이다.

이 작업할 때 도움이 크기 됐던게 스탠다드의 InputSystem 특강이었다.
이 특강에서 InputSystem을 이용하는 여러 방법이 있었고
그 중에서도 세부적으로는 Interactions과 Processors 탭을 사용하는 방법이 있었다.


Interactions 탭은 플레이어가 더블 탭, 홀딩 등에 대한 디테일한 부분을 처리한다면
Processors는 입력을 어떻게 처리할지 설정할 수 있다.

Processors : Scale

이번에 사용한 것은 Scale 이다.

  • Button 형식으로 바인딩된 입력은 값을 읽어올 때 0 or 1을 반환한다.
    • 0 (false)이 입력받지 않는 상태, 1 (true)이 누른 상태이다.
  • Processors의 Scale은 입력값에 factor를 곱한 값을 출력한다.
    • 바인딩한 버튼마다 내가 원하는 factor를 설정하면 그 값을 받아올 수 있는 것이다.

  • 버튼으로 받은 값은 Single로 반환하는데 이걸 Int로 형변환해주면 내가 원하는대로 쓸 수 있었다.

이렇게해서 숫자 1~5를 누르면 코드에서도 값을 1~5로 받을 수 있다.

마무리

1일차는 이렇게 플레이어 입력을 받아서 캐릭터가 걸을 수 있도록 애니메이션 연동하고 끝냈다.

작업 1.5일차 (토,일)

주말에도 쉴 순 없지.
플레이어 파트가 어느정도 진행되어야 작업이 가능한 파트들이 있어 기초적인 부분은 미리 해두려했다.

FSM

플레이어의 조작은 FSM으로 만들었다.
이걸 위해서 플레이어에게 필요한 상태를 먼저 정리해보았다.

상태 정리

  • Idle
  • Walk
  • Run
  • Aim
  • Attack
  • Jump
  • Fall

필요한 부분은 이 정도일 것 같았다.
근데 보통 내가 캐릭터의 애니메이션을 만든다고 하면 Blend Tree를 자주 이용한다.

이렇게 하나의 Walk, Run 상태에 Idle이 포함되어있기 때문에 위에서 정리한 형태가
잘 맞지 않았다.

HFSM


그래서 다시 정리한 게 위의 이미지이다.

Idle이라는 큰 개념으로 Run, Walk를 묶는 건데 그러다가 한 가지 아이디어가 생겼다.
TPS에서 자주 볼 수 있는 앉기, 엎드리기 상태를 추가한 건데
이렇게 정리하면 다음과 같이 정리된다.

  • Idle
    • Stand
      • Walk
      • Run
    • Crouch
      • Walk
      • Run
    • Crawl
      • Move
  • Aim
  • Attack
  • Jump
  • Fall

이에 따라 우선 Stand 애니메이션과 상태를 정리했고
러프하게 조준과 사격 기능을 애니메이션으로 제어했다.

마무리

형태가 FSM에서 부분적으로 HFSM으로 변화했는데
작업하다가 세부적인 Walk, Run은 일반 메서드로 변경했다.

  • 다만, 고민인 것이 Aim, Attack관련 상태이다.
    상식적으로 걸으면서도 총은 쏠 수 있어야하는데 상태로 구현하자니 움직이는 부분이 계속 걸린다.
    움직임 + Attack 상태인데 아직은 이걸 어떻게 결합하는지가 모호하다.
  • 움직임이 가능한 상태 + Aim, Attack을 추가하자니 한 상태에 많은 기능을 넣게 된다.
    • 일단은 Attack 관련 부분은 기능적으로 규모가 커질 것이라고 생각했고
      이걸 PlayerShootingController로 분리했다.

작업 2일차 (월)

주말에 어느정도 작업과 테스트를 한 덕에 플레이어와 연계된 기능 작업이 가능해졌을 것이다.
나는 주말에 기초 작업해둔 조준 기능을 고도화하기로 했다.

조준

건 슈팅 장르에서 대상을 조준하는 기능은 필수 사항이다.
여기서 중요한 것이 플레이어의 에임이 향하는 곳으로 3D 상의 캐릭터도 정확하게 조준할 수 있어야한다는 점이다.

애니메이션으로 Aim을 구현하는 방법도 있긴한데 정확도가 굉장히 떨어진다.
캐릭터가 좌우(X축 방향)로 조준하는 애니메이션과 상하(Y축 방향)으로 조준하는 애니메이션을
Blend Tree로 제어하는 방법인데 이것보다는 다른 방법이 더 효과적이었다.

IK 사용하기

애니메이션은 크게 FK 방식과 IK 방식으로 구분할 수 있다.

FK : Forward Kinematics

3D 캐릭터는 리깅이라는 절차를 거쳐야 애니메이션을 적용할 수 있다.
리깅 과정은 캐릭터에게 뼈를 심는 작업이고 이 뼈는 위의 이미지처럼 사람의 각 뼈에 해당하는 구조를 갖는다.

여기에 애니메이션을 준다고하면 내가 움직이고자하는 뼈들을 회전시켜서 자세를 잡고
다음 프레임에 이어질 자세의 회전값을 키프레임에 새겨넣는 것이다. (상당히 빡센 작업이다)

그 중에서 최상단의 뼈를 회전시켜서 가장 끝 부분의 뼈까지 순차적으로 회전시켜서
애니메이션을 만드는 방법이 Forward Kinematics이다.

IK : Inverse Kinematics

IK는 이렇게 외부나 말단에 타겟이 있어서 그 타겟의 위치에 따라 역순으로 뼈들의 회전을 제어하는 방식을 의미한다.

위의 이미지를 보면 Aim Point라는 오브젝트를 향해서 캐릭터의 Spine1이 바라보고 있다.

화면 중앙에서 레이캐스트로 쏜 지점에 Aim Point를 위치시키면 캐릭터는
무슨 일이 있어도 Aim Point 지점을 바라보게 할 수 있는 것이다.

IK 방식을 쓰는 곳은 이외에도 상당히 많아서 매우 유용한 방법이다.

Animation Rigging

유니티에서도 공식적으로 IK를 쓸 수 있게 패키지를 제공하고있다.
이게 없을 때는 그렇게 IK 에셋을 찾아다녔는데...

쓰는 방법도 굉장히 간단했다.

캐릭터에서 오브젝트를 선택하고

  • 조건 1. 가급적이면 root bone에 해당하는 오브젝트의 부모에
  • 조건 2. 그리고 애니메이터가 있는 위치에

Animation Rigging > Rig Setup을 선택하면 된다.

그러면 이렇게 Rig 오브젝트가 생기고 Rig 컴포넌트가 붙는다.
Weight는 IK 반영 정도를 나타낸다.

  • 0 : IK를 반영하지 않는 것
  • 1 : IK를 100% 반영하는 것

여기에 자식 오브젝트를 만들고 Spine 이라 설정했다.

Multi-Aim Constraint 컴포넌트를 붙여주고 다음과 같이 설정했다.

  • Animation Rigging 패키지에서는 따로 Single Aim이 없던 모양이라 Multi-Aim Constraint를 사용했다.

Settings > Offset

IK를 적용할 때 Offset을 적용해주어야했다.
이유는 아래와 같이 애니메이션 자체의 방향이 정면을 향해있지 않았기 때문이다.

그러므로 대략적으로 정면을 향할 수 있도록 위처럼 offset 값을 설정하고
정확한 조준은 총을 쥔 손을 Aim Point로 향하도록 값을 코딩하기로 했다.

여기서 또 디테일을 챙겨야하는 것이 왼손의 위치이다.
소총을 쥘 때 왼손으로 총기를 받쳐주어야하는데, 우리는 총기의 방향을 세부적으로 제어해줄 것이다.
그러다보면 필연적으로 왼손의 위치가 살짝 어긋날 것이기에 왼손에도 IK를 적용해주었다.

이걸 위해서 총기별로 왼손을 받칠 위치를 만들어주었다.
총을 장착하면 왼손의 IK 타겟을 저 위치로 이동시켜줌으로써 왼손은 무조건 해당 위치를 받치는 형태를 취할 것이다.

마무리

이 날은 조준 관련한 세부작업으로 거의 하루를 다 썼다.
좋은 특강도 많았던 날이라 상당히 알찬 하루였던 것 같다.

작업 3일차 (화)

이 날은 팀장님 요청으로 InputSystem 관련 부분을 손을 봤다.
요청사항은 ESC를 누르면 메뉴키를 켜줘야하는데 어디로 접근하면 되냐는 것이었다.

현재 구조는 PlayerInputController를 만들어서 이 곳에서 PlayerInput을 활성화해주고
이벤트를 구독하는 방식이다.

요청사항을 듣고 잠시 고민했다. 개별 UI가 Player에게 접근해야한다.
물론 CharacterManager가 있기 때문에 접근 자체는 가능하다.

InputSystem 개선

기존에는 플레이어만 인풋에 접근하고 관리하고 있다.
근데 새로운 파트에서 인풋에 접근해야하는데 현재 구조를 유지하는 것이 과연 장기적으로 도움이 될까 싶었다.

그래서 차라리 InputManager로 따로 분리하고 여기서 공통으로 접근하지고 제안드렸다.

이유

  • 개별 UI들이 CharacterManager를 통해서 있을지 없을지 모를 Player로 접근해야하는 것이
    안정성 차원에서 문제가 될 소지가 있어보였다.
    • UI는 있는데, Player는 없는 경우가 있을리는 없겠지만, 혹시 모를 경우를 위해서 애초에 전제를 없애는 것이 깔끔해보였다.
  • 참조의 방향이 깔끔하지 않아 보였다.
    • 개별 UI -> CharacterManager -> Player로 접근한다.
  • 입력을 받는다는 기능(책임)은 게임 전반에서 상당히 중요한 요소이기에 Manager급으로 올려서 관리해도 된다고 생각했다.
    • 지금까지는 플레이어만 Input에 접근할테니 이렇게 두었지만
      이 요청사항이 들어옴으로써 2개 이상의 파트에서 Input을 요구하게 되었다.
    • 책임이 프로젝트 전반에 걸칠 수 있는 기능이기에 Manager로 관리하면 더 편하게 프로젝트 진행이 가능할 것이라 생각했다.

관리자 작업

키보드나 마우스의 입력을 받는 기능은 Game Scene에서만 필요로 하고 있어서
DontDestroy하지 않는 싱글톤을 상속했다.

또한 기존에 Player에서 쓰는 방식은 최대한 수정하지 않고 유지하되 (결코 귀찮았던 것이 아님)
플레이어 외에서 사용할 입력은 이미지처럼 별도의 이벤트를 만들어서 구독하도록 했다.

상호작용 작업

InputSystem 작업 이후에는 플레이어의 상호작용 기능을 구현했다.

지금까지는 플레이어가 보는 방향에 레이캐스트를 쏘아 아이템을 검사하고 사용하도록 구현했다.
하지만 슈팅 장르에서 아이템을 줍거나 사용하기 위해 에임을 아이템으로 두게 하는 것이 매우 불편할 것이라고 생각했다.

그래서 레퍼런스로 삼았던 스나이퍼 엘리트나 다른 TPS 장르 게임은 어떻게 하는지 자료를 좀 찾아봤다.

레퍼런스

다수의 동일한 장르에서는 아이템의 주변에 다가가면 상호작용 UI가 활성화되고
플레이어가 상호작용하는 방식이다.

요점은 아이템 주변에 다가가서 인식하는 것이다.


우선은 주변을 인식하기 위해서 캐릭터 자식에 구형의 트리거를 넣고,
위의 스크립트를 작성했다.

UnityEvent를 사용해서 UI에 이벤트 넣듯 PlayerInteraction 스크립트의 메서드들을 할당했다.

플레이어가 아이템 주변에 가면 IInteractable 인터페이스가 있는지 검사하고 있다면
PlayerInteraction에 things 리스트에 추가해줄 것이고 벗어나면 things 리스트에서 해당 객체를 제거해줄 것이다.

비교

이 기능을 구현할 때 사실은 다른 방법이 하나 더 있었다.

  • 방법 1. 트리거를 이용해서 주변에 가면 인식하고 사용할 수 있게 하기. (채택한 방법)
  • 방법 2. 아이템들의 거리를 매 프레임 계산해서 해당 아이템이 일정거리 안에 들어오면 먹을 수 있게하기.

방법 2의 경우 제대로 검증해보진 않았다.
다만, 우려되는 지점이 매 프레임마다 떨어져 있는 아이템들의 거리를 계산해야한다는 점이다.

게임 씬에 몇개나 나올 지 모를 아이템들을 매 프레임마다 찾아 거리를 계산하기 보다는
트리거를 이용해서 특정한 이벤트일 때만 반응하도록 하는 것이 낫다고 판단한 것인데
이 부분은 나중에 퍼포먼스 테스트를 해봐야겠다.

너무 정성적인 판단으로 했던 것이라 떳떳하지 못한데
정량적인 지표가 따로 있으면 다음에는 더 자신있게 고를 수 있겠지.

마무리

이 날은 이외에도 추락, 점프 기능을 구현하는 것까지 마무리했다.
다만, 나중에 퍼포먼스 테스트를 해야한다는 빚을 쌓아두어 다시 찾아봐서
결과를 정리해야한다.

작업 4일차 (수)

시네머신 연결하기

시네머신을 맡고 싶다하신 팀원분께 요청이 들어왔다.
사실은 저장, 불러오기 기능을 맡고 계셨는데 우선순위를 살짝 수정해볼 수 있느냐고 월요일 저녁에 내가 요청했다.

저장, 불러오기를 완성하려고 해도 아직 스테이지에 플레이어 정보, 적 정보가 덜 구성되었기 때문에 작업에 제한이 있을 것이라서 바로 작업이 가능한 시네머신을 먼저해볼 수 있겠냐고 요청을 드렸다.

들어온 요청은 시네머신 관련 작업이 완료되었으니까 한번 연결해볼 수 있겠느냐는 것이었다.
기존에는 플레이어 단에서 시네머신을 테스트할 수 있도록 적을 쏘면 무조건 이벤트를 발생시키도록 해두었다.

이제 본격적으로 적 오브젝트도 있으니 한번 제대로 연결해볼 차례이다.

저격

지금 사격에는 투사체 방식을 적용하고 있다.
즉, 총을 쏘면 적에게 투사체가 날아가서 데미지를 주고 있다.

적에게 총을 맞는 부위도 좀 더 디테일하게 하기 위해서 팀장님께 Ragdoll을 넣어보자고 제안 드렸다.
이건 사실 저격 시네머신 때문에라도 필요했다.

부위별 타격과 Rag Doll

헤드샷과 같은 경우 거의 즉사급의 데미지가 들어가야한다.
그러기 위해선 어디에 맞았는지를 구분해서 특정 부위는 데미지가 배로 들어가도록 해야한다.
이럴 때 편한 것이 Rag doll이다.

본래는 각 뼈에 충돌체와 Rigid Body를 넣어 캐릭터를 애니메이션 상관없이
흐느적거리거나 흡사 봉제 인형같이 보여주기 위해서 사용한다.

이번에는 Rag doll 기능과 더불어 부위별 타격을 위해서 이 충돌체를 써보기로 했다.

Rag doll을 적용하면 위처럼 각 부분에 충돌체와 Rigid Body, Joint가 생긴다.
이제 이 콜라이더가 붙은 부분에 부위별 타격 스크립트를 붙여둔다.

이건 머리 부위인데 보다시피 맞으면 10배 데미지가 들어간다.

그리고 스크립트 내부를 보면 데미지 처리 메서드와 함께 저격 관련 인터페이스와 메서드가 있다.
플레이어은 이 스크립트를 이용해서 저격의 조건을 확인할 것이다.

저격 조건

플레이어 슈팅의 Fire 메서드의 일부이다.
표시한 부분을 보면 총을 쏨과 동시에 사전 검사를 진행한다.

검사 과정

레이캐스트를 쏴서 맞는 부위가 ISnipable을 구현하고 있다면 저격이 가능한 오브젝트라는 의미이다.

이때 IsSnipable 메서드를 통해서 오브젝트가 데미지를 받을 때
이 공격으로 사망한다면 True를 반환한다.

이 과정을 통해서 저격 가능한지 여부를 판단했다.

  • 처음에는 레이캐스트를 써서 판단했는데
    레이캐스트를 써보니까 미세하게 빗나가는 경우가 너무 많아 스피어캐스트로 변경했다.
    대략적으로 총알의 굵기에 맞는 구를 날려서 최대한 유사한 지점을 맞도록 시도했다.

저격 이벤트

검사 과정에서 저격을 했다면 Snipe 메서드를 호출하고
여기서 OnSnipe 이벤트를 발생시킴으로써 시네머신 이벤트를 시작한다.

시네머신 보정

연결을 하고 성공적으로 작동하는 것을 확인했다.
근데 이 시점에서는 시네머신 작동이 좀 투박했다보니 의도했던대로 연출을 하려면 좀 더 다듬을 필요가 있었다.

다듬을 필요가 있다고 생각한 부분은 크게 두 곳이었다.

  1. 총알이 총구에서 시작해서 날아가기 시작할 때
    • 카메라가 의도하지 않는 곳에서 불쑥 튀어나와 총알을 쫓아가서 어색함이 있었다.
  2. 총알이 적에게 날아가는 도중 과정
    • 적에게 날아가는 장면을 보여주는데 항상 총알의 뒷편에서만 보여주는 것이 아쉬웠다.

1. 시네머신 카메라의 초기 위치 세팅

1번 문제를 해결하기 위해서 카메라의 초기 위치를 랜덤하게 위치시키도록 했다.

생각한 계획은 총구를 중심으로 랜덤한 반지름의 구의 표면에서 시작하듯이 위치시키고자했다.
이때, 중요한 것은 카메라가 캐릭터의 후방에서 시작하면 총알을 쫓다가 캐릭터를 뚫어버릴 수 있으니
되도록 캐릭터의 전방에서 날아가도록 해야한다.

이번에 사용한 방식은 두 벡터의 내적을 이용해보려고 했다.
카메라가 받은 랜덤 위치를 향한 총구의 방향과 투사체의 정면 방향의 내적을 구한다.

이때 투사체 정면 방향과 유사한 방향이라면 양수 값을 갖고
반대 방향이라면 음수 값을 갖는다.

만약 음수 값을 갖는 경우라면 랜덤으로 도출한 위치를 모두 역전시켜서 반대방향으로 향하게하여
카메라가 정면에 위치시키려 시도했다.

다만, 실제 씬에서 적용했을 때 종종 의도와 다르게 캐릭터의 후면에서 위치가 초기화되기도 했기에
프로젝트가 끝나고 다시 디버깅을 해봐야한다.

2. 총알이 날아가는 도중 과정

2번 문제를 해결하기 위해서는 코드로 제어하기 보다는 시네머신의 설정 부분을 만지기로 했다.
Aim 부분은 따로 만질 필요가 없었고, 따라가는 부분이 문제이므로 Body를 수정했다.
이때 카메라의 Body의 설정이 Hard look to target이었다.

Hard look to target 에서는 총알로부터 거리를 제어하는 것이 안되었기에
Orbital transposer나 transposer로 offset을 제어하기로 했다.

Orbital transposer는 타겟을 중심으로 궤도를 도는 방식인데
플레이어의 마우스 이동으로 이상한 곳을 보면 안되니까 반영되지 않도록 설정했다보니
지금 상황에서는 큰 의미가 없는 설정이었다.

자연스럽게 transposer로 설정을 했고 다음과 같이 설정하여
총알로부터 랜덤으로 떨어진 거리에서 보도록했다.

저장, 불러오기 적용

추가적으로 저장 기능이 마무리되어 불러오기 기능 작업을 진행해야했다.

  • 데이터 저장의 대상
    • 플레이어 파트
      • 캐릭터의 위치
      • 체력
      • 보유 무기
      • 잔여 탄수
    • 적 유닛 파트
      • 남은 적들의 위치
      • 남은 적들의 체력

불러오기로 적용하기 위해서는 추가로 구조를 잡을 필요가 있었다.
지금은 씬에 프리팹을 두고 작업하고 있기 때문에 캐릭터가 스스로를 초기화하거나
적이 스스로를 초기화하는 방식으로는 너무 작업이 난잡해질 것이 예상되었다.

지금까지 큰 역할을 하지 않던 관리자들을 정리해보았다.

당시에 팀원 분들과 상의하면서 작성한 간략한 구조도(?)이다.

주된 내용은 타이틀 씬에서 데이터매니저로 저장된 데이터를 받아오고,
이를 가지고 게임에 들어가면 필요한 오브젝트들을 매니저들이 동적으로 생성하고 초기화해주자는 것이었다.

역할이 살짝 모호했던 GameManager에게 이름에 걸맞게 타 매니저들을 초기화시키는 역할을 주었다.

GameManager가 CharacterManager에게 Initialize() 메서드를 호출하면
데이터 적용 여부에 따라 캐릭터와 적 유닛을 동적으로 생성해준다.

마무리

이날의 작업에 사실 총알 투사체의 데미지 방식을 변경하는 등의 작업도 있었다.

투사체가 적 유닛을 뚫는 문제가 있어서 잠시 레이캐스트로 적에게 데미지를 주도록 했는데,
실습 시간을 틈타 오정호 튜터님께 한번 물어보았다.

레이캐스트 방식이 옛날 서든어택에서 사용하던 방법과 유사하고
현대 슈팅 게임에서는 대체로 투사체를 직접 날려서 3D의 공간감과 디테일을 살리는 추세라고 하셨다.

그 이야기를 듣고 조언으로 해주신 물리 관련 설정과 투사체의 Rigid Body 설정을 변경해서
뚫는 문제를 최대한 완화했다.

작업 5일차 (목)

이 날은 사실상 작업의 마지막 날이기도하기에 오전까지만 최대한 개발하도록하고
오후부터는 발표를 준비했다.

플레이어 작업에서 필수였던 사망 상태가 없었기에 급하게 사망상태를 바로 구현하고
게임오버 UI도 만들어서 적용했다.

작업 리스트에서도 보이듯 거의 다 버그 고치는 작업 위주로 진행했다.

버그 : 자신이 쏜 총알에 맞는 문제

버그 중에서 플레이어가 자기 총에 맞는 버그가 있는데
거의 신경을 안 쓰다가 지금 발생한 이유가 있다.

이전까지는 총알의 크기에 맞게 콜라이더를 적용했었다.
그리고 대체로 총기 오브젝트의 총구가 플레이어로부터 충분히 멀었다 보니까
굳이 이 버그를 처리할 필요를 못 느꼈었다.

하지만 투사체 방식으로 데미지를 주기로 했고 최대한 뚫는 현상을 완화하기 위해서
투사체의 충돌체를 좀 길게 설정해서 혹여 지나가더라도 끝부분에서 걸리도록하는 꼼수를 써보았다.
튜터님 조언을 들으면서 내가 한번 체크했어야 했는데 너무 안일하게 진행하다가 발생한 버그였다.

대처법

사실 이 버그가 나오고 살짝 당황하긴 했는데 대처 방법이 빠르게 떠올라서 큰 문제가 되진 않았다.

투사체에 총격한 사람의 태그를 넣어주고
데미지를 주는 시점에서 맞은 사람의 태그와 총격자의 태그를 비교하는 것이다.

이렇게 간단하게 태그 검사로 자신에게 데미지를 주는지 거르는 것으로 문제는 해결했다.

발표자 정하기

오전 중으로 급한 버그들을 최대한 빠르게 마무리했다.
그리고 발표 준비를 하는데 갑자기 팀장님께서 발표할 것처럼 말씀하셨다.

사실 발표자를 자처하는게 굉장히 감사한 일이지만
혹시 발표하시려는건지 물어보았고, 아마도 모두 발표를 하고 싶지 않아할 것 같아서
자처하신다고 하셨다.

...
물론 매우 감사한 일이지만, 너무 억지로 떠넘기는 것 같아서 발표자는 사다리 타기로 정하자고 했다.
... 그리고 내가 뽑혔다. (왜 항상 말하는 사람이 뽑히는 거지)

마무리


이 날은 거의 버그 수정 위주로 작업을 끝냈고 발표 준비에 들어갔다.
팀원 분들도 이번에 한 프로젝트는 매우 만족스러우셨는지 발표 준비에 열성적이셨다.

마지막날 (금)

발표일이 되었고, 제출 전에 간단하게 다시 버그 테스트를 해봤다.
캐릭터 후면에 카메라가 초기화되는 것 외엔 다행히 심각한 버그는 없었다.

불필요한 주석과 과하게 사용된 virtual 키워드들을 정리했고
테스트용으로 만들었던 파일들을 정리하면서 프로젝트 제출 준비를 끝마쳤다.

회고

제출도 완료하고 오후에 있던 발표도 마무리하면서
팀원 분들과 잠시 이야기할 시간이 있었다.

다들 이번 프로젝트만큼은 매우 재미있었고 적극적으로 진행했다고 하셨다.

나 또한 마찬가지였고 간만에 재미있게 작업을 진행하다보니
어떻게 하면 더 찰지게(?) 총을 쏠 수 있을까 등등을 고민했다.

이전에도 팀프로젝트들이 많았지만 이렇게 재미있게한 건 이 프로젝트가 유일했다.
차이는 아마도 팀장님일 것이다.

역할

나는 개인적으로 아무리 생각해도 팀을 이끌기보다는
팀원 입장에서 맡은 역할을 수행하는게 가장 효율이 잘 나오는 타입이었다.

반면 팀장님은 팀원을 대할 때 친화력이 상당히 높았고
팀을 이끄는 부분에서도 매우 유하게 대처하셨던 걸보고 좀 많은 생각을 했다.

내가 팀장일 때는 거의 하지 않았던 일이었고 나는 프로젝트 진척도에만 관심이 있었으니
팀원들 입장에서는 상당히 딱딱한 사람이지 않았을까 생각한다.

타고난 게 있을까 싶었는데 저렇게 사람을 대하는 부분은 벽을 느끼게할 정도로
압도적이구나 생각했다.

팀 프로젝트에서 가장 중요한 건 개개인의 능력도 있겠지만
개개인이 능력을 100% 발휘하게하는 환경을 만들어주는 것도 매우 중요하다.

이번 팀장님은 빠르게 서로와 소통하고 소통하는 환경을 만들어서
진짜로 협업이 가능하도록 해주셨던 것 같다.

사람으로서 정말 배우고 기억해야할 것을 경험했다.

#내일배움캠프 #스파르타내일배움캠프 #스파르타내일배움캠프TIL

profile
사파 개발자

0개의 댓글