-
[TIL] 스파르타) Chapter 5 주특기 플러스 개인 과제 진행 (TCP 게임 서버, D-1)TIL-sparta 2024. 7. 7. 17:27
> 과제 진행 간 완료한 사항 및 문제점과 해결 과정을 정리해보았다.
학습 키워드: C#, Node.js, net, TCP, socket, Buffer, Protobuf, latency, dead reckoning1. 완료한 과제 진행 사항
1) (도전 요구사항) Latency 기반 추측 항법 기능 구현 완료:
- 위 PR 및 연결된 Issue 참고
- 잡다한 버그 수정
2. 과제 진행 간 문제점
1) 캐릭터 충돌 시 관통하는 문제 (해결):
추측 항법을 구현하면서 클라이언트의 유저 x y좌표를 쓰지 않도록 변경했는데, 서버의 계산에만 의존하다 보니 캐릭터 간 충돌을 무시하고 밀어내거나 통과하는 문제가 발생했다. 전자는 밀리는 입장의 클라이언트에서, 후자는 미는 입장의 클라이언트에서 발생하여 화면 정보가 불일치하는 현상도 같이 발생했다. 또한, 유저 캐릭터의 speed 값을 30정도로 크게 늘릴수록 유저 캐릭터의 위치 정보 불일치가 심해져서 캐릭터가 이동 방향에 따라 상하 또는 좌우로 진동하는 현상이 생겼다.
// Player.cs ... public void MoveToNextPosition(Vector2 nextVec) { // rigid.MovePosition(nextVec); // 기존 설정 Vector2 newPos = Vector2.Lerp(rigid.position, nextVec, 0.1f); // lerp (linear interpolation) 적용 rigid.MovePosition(newPos); } void FixedUpdate() { // comment 처리 했던 함수 if (!GameManager.instance.isLive) { return; } // 위치 이동 Vector2 nextVec = inputVec * speed * Time.fixedDeltaTime; rigid.MovePosition(rigid.position + nextVec); }
이전에 추측 항법 파트를 구현하면서 Player.cs 파일에서 클라이언트의 자체 RigidBody 위치 계산을 해주는 FixedUpdate 부분을 비활성화 했었는데, 서버의 정보에만 의존하려니 위에서처럼 여러모로 문제가 많아서 다시 활성화 시키고, 서버에서 계산한 resultant x, y 좌표를 수신하여 최종 위치를 보정해주는 방식으로 변경했다. Lerp 계산의 경우 세 번째 인자로 고정 값을 넣어뒀는데, 기존에 this.speed * Time.deltaTime (=거리 이므로 잘 못된 값임)을 넣어줬더니 캐릭터 위치가 요동치는 문제가 있어서 고정 수치로 넣어준 것이다. 추후에 수정 필요 여부를 판단해봐야겠다.
2) 캐릭터 이동 시 다른 클라이언트에서 반대 방향을 쳐다보는 문제 (미해결):
// PlayerPrefab.cs ... private void UpdateAnimation() { // 현재 위치와 이전 위치를 비교하여 이동 벡터 계산 Vector2 inputVec = currentPosition - lastPosition; anim.SetFloat("Speed", inputVec.magnitude); if (inputVec.x != 0) { spriter.flipX = inputVec.x < 0; } }
일단 파악하기로는 보정치가 더해져있어서 멈추는 순간 전송된 정보가 바로 전 프레임에서 이동하기로 정한 위치보다 덜 움직이게 되어 미세하게 뒤로 이동하면서 PlayerPrefab 스크립트에서 캐릭터가 뒤로 이동했다고 판단하고 이미지를 뒤집는 것이 원인으로 보인다. 정확하게 말하면 currentPosition - lastPosition 값의 sign이 마지막 순간에 바뀌기 때문에 spriter.flipX 의 값인 inputVec.x < 0 값이 바뀌면서 sprite가 뒤집히는 것이다.
// PlayerPrefab.cs ... private float FLIP_THRESHOLD = 0.1f; ... private void UpdateAnimation() { // 현재 위치와 이전 위치를 비교하여 이동 벡터 계산 Vector2 inputVec = currentPosition - lastPosition; anim.SetFloat("Speed", inputVec.magnitude); // if (inputVec.x != 0) if (Mathf.Abs(inputVec.x) > FLIP_THRESHOLD) // 조건 변경 { spriter.flipX = inputVec.x < 0; } }
추측 항법 구현에 문제가 있다고 판단해야할지 아니면 바라보는 방향에 관련된 값만 추가로 전달받는 식으로 해결하는게 맞을 지 고민하다가 결국 후자로 진행하기로 했는데, 문득 패킷을 수정하는 대신에 threshold를 줘보면 어떨까 싶어서 한 번 구현해봤다. 현재 위치와 마지막 위치의 차이를 나타내는 inputVec 값의 sign(+ - 부호)을 판단할 때 미세한 수치의 threshold를 줘서 좌우 반전을 방지하는 방식인데, 현재는 고정 0.1f 값을 가지고 있지만 정확하게 하려면 뭔가 다른 값을 매겨줘야 한다. 왜냐하면 유저 캐릭터의 속도가 빠르다면 이동 거리가 커지면서 threshold를 무시할 수 있기 때문이다. 같은 이유로 서버쪽 추측 항법 코드도 큰 수치의 예측을 해서 유저 캐릭터의 위치 이동이 부자연스러워지는 현상이 있을 것 같아서 수정이 필요하겠다고 느껴진다. 추후 괜찮은 방법이 떠오른다면 도전 해봐야겠다.
3. 기타 사항
1) Lockstep 동기화 기법:
이번 프로젝트에서는 적은 수의 유저 위치를 동기화하기 위해 서버에서 모든 상태 정보(캐릭터 위치)를 관리했지만, RTS 게임같이 캐릭터가 엄청나게 많아지는 경우 이런 방식으로 계산한다면 서버의 부하가 매우 심할 것이다. 이럴때는 Dead Reckoning 대신 Lockstep Synchronization 기법을 사용한다. 락스텝 기법의 동작 원리를 간단하게 설명하면 다음과 같다.
1. 한 클라이언트가 특정 행동에 대한 입력 명령을 서버로 보낸다.
2. 서버가 다시 모든 클라이언트에 해당 입력 명령을 모든 클라이언트로 보내면 모든 클라이언트가 동시에 Scene을 업데이트한다.
입력 명령만 전달받고 상태를 저장하지 않는 구조여서 추측 항법 같은 기법에 비하면 통신 량이 상대적으로 매우 적다. 하지만 언급했듯이 상태를 저장하지 않기 때문에 클라이언트가 변조되어 서로 다른 월드 정보를 가지게 되는 현상이 있을 수 있는데, 이를 방지하기 위해 월드 상태의 checksum 값을 플레이어 끼리 주고받은 뒤 모든 정보가 일치하면 업데이트를 진행한다.
Lockstep Synchronization에 대해서는 추후에 별도의 게시글로 추가 조사를 해보도록 해야겠다.
--
REFERENCES:> 과제 spec
> 과제 repo
> TheBook, "게임 서버 프로그래밍 교과서: 5.5 실시간 전략 시뮬레이션 게임에서 네트워크 동기화"), 배현직 저, accessed: 2024-07-07
728x90'TIL-sparta' 카테고리의 다른 글
[TIL] 원격 프로시저 호출 (Remote Procedure Call, RPC) (0) 2024.07.09 강의 과제) CPU란 무엇인가? (1) 2024.07.08 [TIL] 스파르타) Chapter 5 주특기 플러스 개인 과제 진행 (TCP 게임 서버, D-2) (0) 2024.07.06 [TIL] 스파르타) Chapter 5 주특기 플러스 개인 과제 진행 (TCP 게임 서버, D-3) (0) 2024.07.05 [TIL] 스파르타) Chapter 5 주특기 플러스 개인 과제 진행 (TCP 게임 서버, D-4) (0) 2024.07.04