팀 프로젝트 트러블슈팅 (점프 오류, 장애물 끼임, 리소스 출력)
Unity_3D Team Project
3D 플랫폼 퍼즐 게임 만들기
발표에서 못 다한 트러블 슈팅을 기록해보자!
Trouble Shooting #1
Jump 출력 오류
이 장애물 위에서는 어떤 위치든 점프가 가능해야하는데, 특정 포인트에서 점프가 작동하지 않았다.
이 경우 문제의 근원지로 크게 3가지가 떠오른다.
1) 점프 기능에 문제가 있는건지? => PlayerController.cs
2) 해당 장애물에 오류가 있는건지? => ObstacleManager.cs
3) 특정 장애물에 잘못된 수치가 들어있는지? => Unity의 Scene 내 Obstacle_Inspector 확인
하지만 위 세 가지를 찾아봐도 점프를 구현하는데 크게 문제되는 곳은 없었다.
그래서 다시 문제가 왜 발생하는지 원인을 제대로 알기 위해 장애물 위에서 여러 각도로 플레이어를 뜯어보았다.
해결의 실마리는 '같은 패널 위에서도 점프가 안 되는 특정 부분이 존재한다.'
실제로 발이 장애물 표면보다 더 깊이 들어가는 위치가 있었고, 이런 경우 ray로 ground를 인지할 수 없게 된다.
Jump는 IsGrounded의 Bool값을 받고, IsGrounded는 Player의 발에서 나오는 ray에 의해 판별된다.
따라서 ray가 특정 부분에서 IsGrounded를 판별하지 못하는 경우 Player는 점프할 수 없게 된다.
이런 경우를 방지하고자 기존에 ray 값을 위해 올렸던 Vector3.up 값을 0.01f 에서 0.1f 로 조절했다.
// PlayerController.cs
private bool IsGrounded()
{
var transform1 = transform;
var forward = transform1.forward;
var position = transform1.position;
var right = transform1.right;
Ray[] rays =
{
new(position + forward * 0.2f + (Vector3.up * 0.1f) , Vector3.down),
new(position + -forward * 0.2f+ (Vector3.up * 0.1f), Vector3.down),
new(position + right * 0.2f + (Vector3.up * 0.1f), Vector3.down),
new(position + -right * 0.2f + (Vector3.up * 0.1f), Vector3.down),
};
foreach (var ray in rays)
{
if (!Physics.Raycast(ray, out var hit, 0.2f, groundLayerMask)) continue;
return true;
}
return false;
}
...
public void OnJumpInput(InputAction.CallbackContext context)
{
if (context.phase == InputActionPhase.Started && IsGrounded())
{
_rigidBody.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
}
}
Trouble Shooting #2
장애물에 끼이거나 붙는 현상
점프를 했을 때 이렇게 장애물과 붙거나 끼이면 게임 진행이 어려워진다.
컨트롤이 불편하면 유저 만족도 측면에서 불리하다.
골인 지점까지 빨리 가야하는 이런 플랫폼 게임에서는 특히 조작감에 유의해야 한다.
(장애물에 끼이거나 붙다) = (장애물을 만났을 때 자연스럽게 미끄러지지 않는다.)
그렇다면, 끼임 문제는 미끄러지게 만들면 해결할 수 있다!
GameObject에 운동 출력을 부여하는 방법에는 대표적으로 Addforce와 Velocity가 있다.
1) AddForce
힘을 누적시킴으로써 속도를 증가시킬 수 있다.
외부의 물리력, 개체의 질량, 관성 등에 의해 속도가 변화한다.
2) Velocity
주어진 속력으로만 항상 움직인다.
외부의 물리력, 개체의 질량, 관성 등에 의해 속도가 변하지 않는다.
플랫폼 게임에서는 달리기와 점프를 모두 구현하기 때문에 AddForce와 Velocity를 적절하게 섞어서 사용한다.
이번에 제작한 <ART F4> 에서도
이동하는 Player의 점프에서 AddForce, 달리기에서 velocity
움직이는 Obstacle의 위치 변환에서 transform.position 을 사용했다.
//PlayerController.cs
private void FixedUpdate()
{
if (_canMove) Move();
// 넉백
else
{
_rigidBody.velocity = _pushDir * _pushForce;
_rigidBody.AddForce(new Vector3(0, -gravity * _rigidBody.mass, 0));
}
}
...
public void OnJumpInput(InputAction.CallbackContext context)
{
if (context.phase == InputActionPhase.Started && IsGrounded())
{
_rigidBody.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
}
}
//Obs_WallMovable.cs
{
height = transform.localScale.y;
if(isDown)
posYDown = transform.position.y;
else
posYDown = transform.position.y - height;
}
이렇게 다양한 물리 연산들이 존재하는 경우,
특히 주변 물리 값을 무시하는 velocity가 작동할 때 연산 과정에서 오류가 발생하는 경우가 많다.
그래서 마찰력이 아주 조금이라도 존재한다면 미끄러지지 않고 자석처럼 박히는 현상이 발생할 수 있다.
이 게임에서는 각 obstacle 과 Player 모두 마찰력이 존재한다.
이 문제를 해결하기 위해 두 object가 가진 마찰력을 조절하면서 계속 테스트했고
둘 중 하나를 0으로 만들면 부딪힐 때 마찰력이 최솟값이 되어서 미끄러지게 만들 수 있었다.
많은 양의 장애물보다 player 를 수정하는 편이 유리할 것이다.
player 의 friction(마찰)을 0으로 만들어주니 버그가 해결되었다.
Trouble Shooting #3
Resource 출력 오류 (prefabs, Sound, Item 등)
출력하고자 하는 resource를 script와 엔진에 잘 정리하고 플레이를 누르면 무수한 NRE가 등장하는 현상.
아마 모든 팀이 겪어 본 문제이지 않을까?
[null]이 발생하는 이유는 많지만, 그 중 이번 프로젝트에서 겪은 2가지가 있다.
1) 참조할 객체 자체가 없는 경우
대부분의 null은 없는 대상을 향해 '제발 나와줘!' 라고 외치고 있어서 생기는 문제이다.
클래스와 메소드가 객체로 살아있고 호출하는데 지장이 없는 보안수준이라면 당연히 나올 것이다.
특히 Main.cs, MainScene.cs 를 사용하다보면
메인에 당연히 있을거라고 생각하고 호출한 개체도 알고보면 독립적이지 않거나
아예 존재하지 않는 것으로 인식하는 경우도 있다.
코드를 짜면서 습관적으로 MonoBehaviour에 의존하게 된다면
일부 클래스와 메소드가 객체화되지 않을 가능성이 높아진다.
어쩔 수 없이 MonoBehaviour 를 사용해야 한다면 new 사용은 불가능하니 add/get component 사용하자.
코드를 구성하는 요소들을 객체로 생성하는 습관이 중요하다.
클래스는 초기화하고 instance로 생성해서 호출하더라도 null이 뜨지 않게 하자.
개체들이 독립적이면서도 서로 희미하게 이어져있는 그런 코드를 만들면 [null]과 멀어질 수 있을 것이다.
2) Addressable 사용 과정에서의 오류
게임에 필요한 다양한 resource를 효율적으로 관리하기 위해 유니티의 addressable 을 사용했다.
활용하면 작업 효율이 상승하지만, 잘 쓰기 위해 숙달시키는 시간이 필요하다.
다음은 addressable을 사용하면서 마주한 몇 가지 주의사항이다.
(1) 생성한 prefab을 맞는 group에 넣지 않으면 오류로 인해 게임 실행이 되지 않을 것이다.
(2) prefab을 정리하면서 key value에 오타가 발생하거나 label을 잘못 붙이는 경우에도 충돌이 발생한다.
(3) prefab을 꺼내서 수정하거나 새로운 object를 추가했다면 prefab을 삭제하고 다시 group에 넣어주는 것이 좋다.
(4) prefab을 완성해 group에 넣었는데도 오류가 발생한다면 script에서 해당 개체를 선언했는지 확인하자.
마치며..
입문 주차보다 하루 적은 기간 동안, 그것도 처음 다루는 3D로 게임을 만들다보니 많이 빠듯했다.
기능 구현이 우선 사항이 되어서 클린 코드를 위해 노력하지 못한 게 아쉽다.
짧은 시간 내에 좋은 협업을 이루기 위해서는 프로젝트에 대한 공통된 이해와 좋은 커뮤니케이션이 중요하다고 생각한다.
이번 팀 프로젝트를 통해서 jira를 이용한 workflow 관리와
addressable 을 사용한 효율적인 메모리 관리 방법을 경험해볼 수 있어서 좋았다.
깃허브 문제로 최종 빌드가 늦어져서 발표 준비가 여러모로 힘들었는데, 리허설로 시간 체크를 못한 것이 너무 아쉽다.
제일 중요한 트러블 슈팅을 공유했어야 했는데, 이렇게 TIL로라도 작성해서 다른 분들과 나눌 수 있으면 좋겠다.
불철주야 고생하신 우리 1조 팀원분들, 매번 질문 드릴 때마다 깊이 고민해주신 튜터님들께도 정말 감사드립니다.
그럼 내일도!!