우선 데디케이트 서버를 이용하지 않고 서버와 클라이언트가 완전히 분리된 상황에서 어떤 식으로 서버를 구성하고 컨텐츠를 구성해야 하는지 헤딩해볼 예정이다. 클라이언트는 언리얼 엔진을 이용하여 구현하고 서버는 클라이언트와 완전히 분리된 소켓 형식의 서버로 구현했다.
기획은 MMORPG의 각 컨텐츠들을 조각으로 기능을 구현해보고, 최종적으로 한번에 기능을 붙여서 전체적인 MMORPG 게임을 구현해볼 예정이다.
맵 구성 -
우선 서버에서도 클라이언트의 맵에 대한 정보를 들고 있어야 한다. 간단하게 프로토타입으로 가장 간단한 2차원 타일방식으로 맵을 구성하였다. 3D의 맵디자인이 필요한 게임이라면 복셀을 만들어서 복셀을 통해 맵데이터를 저장해야 한다.
GridBuilder 클래스를 만들어 언리얼엔진의 Trace channel을 이용하여 충돌검사를 한 후에 움직일 수 있는 부분을 2차원 타일로 저장하여 맵으로 저장한 데이터를 서버에서 사용하였다. 초록색 Trace는 움직일 수 있는 영역, 빨간색 부분은 움직일 수 없는 영역을 나타낸다.
몬스터 AI구현 -
나눠진 타일을 기반으로 A* 알고리즘을 기반으로 경로 검색 → 실질적으로 움직이는 과정은 언리얼 엔진 클라이언트 내부의 네비게이션 시스템(Navmesh, RecastMesh 기반)으로 이동하도록 구현하였다. 서버에서 매 프레임마다 정확한 위치를 지정하긴 힘들지만, 각 오브젝트들의 타일 위치를 통해 대략적인 오브젝트의 위치를 확인할 예정이다.
공간분할 -
공간을 분할하여 일정 크기, 또는 미리 나눠진 정적인 크기에 따라 각 구역(Region)을 구성. 각 Region은 서버에서 워커 스레드들의 업무단위가 될 예정이다. 위 프로젝트에서 하나의 월드를 4개로 나눠 마을(회색)과 몬스터 오브젝트가 리젠되는 필드 1,2,3 (빨강, 초록, 파랑)으로 구분하여 맵에 저장하였다.
서버에서는 각 필드들을 관리하는 Room 객체를 만들어 하나의 RoomManager에서 모든 필드에 대한 Room을 관리할 예정이다.
우선 방을 나누고 해당 Region에 들어갈때마다 클라이언트에서 해당 Region에 존재하는 몬스터를 spawn, despawn 해주는 방식으로 처리해보았다.
해야할 것 : 성능분석 + Region밖에 있으면서 각 Region의 경계에 있는 캐릭터들이 서로를 공격하려 할 때 처리를 추가해야 한다.
그전에 먼저 이동동기화 -
- 기본적으로 AI는 서버에서 보내주는 대로 움직임(결정형)
- 첫번째 변수 : 움직일려는 곳에 이미 다른 오브젝트가 있는 경우(플레이어가 움직인 경우) 현재는 플레이어가 먼저 움직이고 그 뒤에 서버에서 검증해주는 방식이므로 → 이거는 서버에서 검증해준후에 움직이도록 바꿔주면된다. 현재는 마우스 클릭방식이므로 생각보다 멀미가 크지는 않을 듯
- 두번째 변수 : 유저가 AI가 있던 곳을 공격했다고 보냈는데 AI는 이미 움직인 판정일 때는? 이거는 기획 마음대로. 공격이 가능하다고 보면은 아마 AI의 움직임을 프레임카운트별로 저장해놓은 후에 이를 비교해야함.
- 세번째 변수 : 클라에서는 서버보다 RTT만큼 느리게 움직이는데, 클라이언트마다 결국 RTT가 다르기 때문에 AI가 다음 움직임, 상태변화가 이전 상태변화가 끝나지 않은 상태에서 일어날 수도 있음 → 이는 서버에서 AI의 움직임에 약간의 시간 간격을 주면 해결됨(어차피 움직인 다음에 멈춘 후 움직이므로).
- 다른 유저의 움직임은 결국 이전 프레임의 움직임을 보는 것임.
- 사실.. MMORPG에서는 완전 정교한 수준의 이동 동기화까지는 필요하지 않음. 특히, 다른 유저라면 더 그럼. 따라서 움직임(예상 움직임)에 대한 약간의 보간(Entity Interpolation)을 해주면 움직임이 그렇게 차이가 심하게 나지는 않음.
- 하지만 현재는 유저의 입력을 먼저 반영해주고 하고 있기 때문에, 서버에서 유저의 움직임을 동기화하려면 미리 유저의 움직임을 RTT만큼 더 이동했다고 예측해야함.
- 만약 MMORPG에서도 대략적인 움직임이 필요하다면 데드레커닝(Dead Reckoing) 수준 정도는 생각해 볼 수 있음. 주로 입력의 변화가 급박하지 않은 경우에 많이 사용되는 방식임. FPS와 같이 자주 입력이 바뀔 경우에는 데드레커닝같은 방식은 좋지 않음.
- 상태변화(움직임 → 멈춤(진짜 유저가 s키를 눌러 멈추거나, 움직이다 도중에 공격을 눌러 멈추게 된 경우))의 경우에는 약간의 delay 로직이 필요함. 이것도 사실은 서버의 인증을 받아서 멈추게 하면 됨. 조금 생각해볼게 유저가 눌렀지만 다른 클라이언트에서는 움직이고 있는 상황이라면 실제 멈춘 위치보다 더 움직이는 상황이 생기게 된다.
- 차이가 심할 경우 바로 위치를 재조정하는 방법.
- 유저가 눌렀을때에(대신 바로 입력을 서버로 Broadcasting) 약간의 delay( 평균 RTT 절반만큼)을 주어서 멈추게 하는 방법. 이 방식은 서버에서 움직임을 허가하는 방식일때 유효하게 작용할 듯.
- 사실.. MMORPG에서는 완전 정교한 수준의 이동 동기화까지는 필요하지 않음. 특히, 다른 유저라면 더 그럼. 따라서 움직임(예상 움직임)에 대한 약간의 보간(Entity Interpolation)을 해주면 움직임이 그렇게 차이가 심하게 나지는 않음.
공격 - 충돌, 논타겟팅 VS 타겟팅
공격을 구현하기 위해서는 클라이언트와 서버에서 충돌 판정을 해주어야 한다.
만약 간단한 공격을 구현할 것이라면 복잡한 충돌판정이 필요하지는 않다.
공격도 타겟팅 vs 논타겟팅으로 나눠진다.
예를들어 리니지2, 와우 같은 경우는 타겟을 클릭하여 타겟을 정한 뒤 스킬, 공격을 행하게 된다. 따라서 해당 몹에 대한 충돌처리만 해주면 되고 충돌처리 자체도 간단하다. 따라서 서버부하를 매우 간단하게 줄일 수 있다.
반대로 논타겟팅은 로스트아크나 테라같은 게임을 예로 들 수 있다. 공격을 누르면, 공격 범위에 있는 몹들에 대해 데미지 처리가 이루어진다. 따라서 1vs1의 느낌이 아니라 1vs다의 느낌으로 플레이를 할 수 있으므로 우수한 전투 액션성과 조작감의 재미가 있다는 장점이 있다. 하지만 이에따른 충돌처리에 따른 서버 부하도 당연히 신경써야한다. 물론 MMORPG에서는 매우 정교한 충돌처리까지는 필요없다.
구상한 순서
클라이언트에서 먼저 충돌에 대한 처리를 시작한다. 클라이언트에서 충돌이 일어나면 서버에 공격처리를 요청 → 서버에서 검증 → 검증 완료 되면 다른 클라이언트에 똑같이 브로드캐스트해주는 식으로 생각해보았다.
서버에서의 검증은 3D 맵을 2D 타일로 변경하고 이를 통해 간단한 Collision으로 검증하였다.
Collision은 총 두개 정도가 필요하다. 첫번째는 피격자(공격을 맞는 대상)의 콜리전과 공격에 대한 콜리전이다.
- 물체의 대략적인 콜라이더(충돌범위)를 알아야 한다 → 각 모델의 메시를 전처리하여 데이터를 뽑아낸뒤에 이 데이터를 서버에서 이용한다. 데디 서버가 아닌이상 완벽한 시뮬레이션은 힘들기 때문에.. 2D 방식으로 현재 맵을 관리하고 있기 때문에 콜라이더도 2D로 만들면 된다. 대략적인 콜라이더를 만들고 이를 2D로 변환하는 과정을 겪었다.
서버 관점에서의 충돌 처리
- 일단 공격에 대한 고찰
- 공격의 종류 - 유저지정, 투사체.
- 투사체 → 위치 : 유저에서 시작, 방법 : 히트스캔 방식 또는 실제 투사체 고유의 움직임을 통해 충돌처리.
- 유저지정 → 위치 : 유저가 지정한 곳에 콜리전을 발생시키는 방식. 방법 : 콜리전은 고유한 콜리전을 가지므로 해당 콜리전을 통해 충돌처리를 해주면 됨.
ex) 평타, 기본공격 - 유저 중심으로 만들어진 콜리전을 통해 충돌검증
- 공격 판정 종류는 크게 3가지로 나눌 수 있는데, 1) AI → 유저, 2)유저 → AI, 3)유저 → 유저 정도 이다.
현재는 클라에서 먼저 계산을 하고, 계산 결과를 토대로 서버에 공격했음을 요청하고 서버에서는 이를 간단하게 검증하고 공격 요청을 받아주고 있기 때문에 충돌 처리에 대한 요청을 누가해야 할 지 생각해보아야 한다.- 맞은 사람이 공격 요청 → 클라이언트가 해킹되었을 때, 해당 유저가 맞았지만 요청을 보내지 않게 되는 문제가 발생할 수도 있음.
- 때린 사람이 공격 요청 → AI가 때렸을 때는? 공격판정이 서버 Initiate가 아니기 때문에 때린 사람이 공격 요청을하게되면 AI → 유저를 공격할때 공격판정을 할 수 없게 됨.
- 두 방법을 혼용해서 사용하는게 베스트 일 듯..
- 만약 때린 사람이 공격을 요청하게 되면 극단적인 예시로 때린 사람이 만약 튕겨버리면 공격이 사라지게 된다. 이에 대한 처리도 추가 해주어야 함.
- 공격의 종류 - 유저지정, 투사체.
충돌검증 -
논타겟팅으로 충돌을 검증할 때, 브루트포스하게 모든 충돌체에 대해 충돌 처리를 하게 되면 성능 저하가 발생하게 된다. 따라서 충돌이 일어날 가능성이 있는 충돌체들의 쌍을 만들어 해당 쌍들 끼리만 충돌 검증을 해주어야 한다.
1. Broad 범위 검증 - 모든 쌍을 검색하기 전에 먼저, 충돌이 가능한 쌍을 전처리해주는 과정. SAP(Sweep and Prune) 알고리즘등을 이용
2. Mid 범위 검증 - 주로 복잡한 도형에 대해서 사용
3. Narrow 범위 검증 - 실제적인 검증이 일어나는 과정
글을 쓰다보니 길어져서 정확한 충돌 처리 구현방법은 다음 게시물에서 해야겠다..