이 엔터티 시스템을 네트워킹하는 방법?


33

FPS를위한 엔터티 시스템을 설계했습니다. 기본적으로 다음과 같이 작동합니다.

우리는 GameWorld라는 "세계"오브젝트를 가지고 있습니다. 여기에는 GameManager 배열과 ComponentManager 배열이 있습니다.

GameObject는 Component의 배열을 보유합니다. 또한 매우 간단한 이벤트 메커니즘을 제공합니다. 컴포넌트 자체는 이벤트를 엔티티에 보낼 수 있으며, 이는 모든 컴포넌트에 브로드 캐스트됩니다.

컴포넌트는 기본적으로 게임 오브젝트에 특정 속성을 제공하는 것입니다. 게임 오브젝트는 실제로 컨테이너의 컨테이너이므로 게임 오브젝트와 관련된 모든 것은 컴포넌트에서 발생합니다. 예를 들어 ViewComponent, PhysicsComponent 및 LogicComponent가 있습니다. 그들 사이의 의사 소통이 필요하다면, 그것은 이벤트의 사용을 통해 이루어질 수 있습니다.

ComponentManager는 Component와 같은 인터페이스이며 각 Component 클래스마다 일반적으로 하나의 ComponentManager 클래스가 있어야합니다. 이러한 구성 요소 관리자는 구성 요소를 만들고 XML 파일과 같은 속성에서 읽은 속성으로 구성 요소를 초기화해야합니다.

ComponentManager는 외부 라이브러리를 사용하는 PhysicsComponent와 같은 구성 요소의 대량 업데이트도 처리합니다.

구성 기능을 위해 XML 파일 또는 스크립트를 읽고 파일에 지정된 구성 요소를 작성하는 엔티티 (대량 업데이트를 위해 올바른 구성 요소 관리자에 참조를 추가 함)에 팩토리를 사용합니다. 그런 다음 GameObject 오브젝트에 주입하십시오.

이제 내 문제가 온다 : 멀티 플레이어 게임에 이것을 사용하려고합니다. 나는 이것에 접근하는 방법을 모른다.

첫째 : 고객이 처음부터 어떤 엔티티를 가져야합니까? 싱글 플레이어 엔진이 어떤 엔티티를 만들지 결정하는 방법부터 설명해야합니다.

레벨 편집기에서 "브러시"및 "엔티티"를 만들 수 있습니다. 브러쉬는 벽, 바닥 및 천장과 같은 것, 기본적으로 단순한 모양입니다. 엔터티는 내가 말한 게임 오브젝트입니다. 레벨 편집기에서 엔티티를 작성할 때 각 컴포넌트의 특성을 지정할 수 있습니다. 이러한 속성은 엔터티 스크립트의 생성자와 같은 것으로 직접 전달됩니다.

엔진의로드 레벨을 저장하면 엔티티 목록 및 관련 특성으로 분해됩니다. 브러시는 "월드폰"개체로 변환됩니다.

해당 레벨을로드하면 모든 엔티티를 표시합니다. 간단하게 들리나요?

이제 엔터티를 네트워킹하기 위해 수많은 문제가 발생했습니다. 첫째, 처음부터 클라이언트에 어떤 엔티티가 존재해야합니까? 서버와 클라이언트 모두에 레벨 파일이 있다고 가정하면 클라이언트는 서버의 게임 규칙 목적을 위해 존재하더라도 레벨의 모든 엔티티를 인스턴스화 할 수 있습니다.

또 다른 가능성은 서버가 서버에 대한 정보를 보내 자마자 클라이언트가 엔티티를 표시하는 것이므로 클라이언트는 필요한 엔티티 만 갖게됩니다.

또 다른 문제는 정보를 보내는 방법입니다. 서버가 델타 압축을 사용할 수 있다고 생각합니다. 즉, 모든 프레임에서 클라이언트로 스냅 샷을 보내는 것이 아니라 무언가가 변경 될 때만 새로운 정보를 보냅니다. 그러나 서버는 현재 각 클라이언트가 알고있는 것을 추적해야합니다.

마지막으로 엔진에 네트워킹을 어떻게 주입해야합니까? 네트워크 구성 요소 인 NetworkComponent를 생각하고 있습니다. 그러나 네트워크 구성 요소는 네트워크에 어떤 변수를, 변수에 액세스 하는지 , 그리고 클라이언트의 해당 네트워크 구성 요소가 네트워크 변수를 변경하는 방법을 알아야하는 방법을 어떻게 알아야합니까?

이 문제에 접근하는 데 큰 어려움이 있습니다. 당신이 도중에 나를 도와 주면 정말 감사하겠습니다. 구성 요소 시스템 디자인을 개선하는 방법에 대한 팁을 제공하므로 제안하는 것을 두려워하지 마십시오.

답변:


13

이것은 많은 세부 사항이 +1 인 질문의 신의 짐승입니다. 그것을 우연히 발견하는 사람들을 도울만큼 확실히.

물리 데이터를 보내지 않는 것에 대해 2 센트를 추가하고 싶었습니다. 나는 솔직히 이것을 충분히 강조 할 수 없다. 지금까지 최적화 된 경우에도 실제로 미세 충돌로 튀어 나오는 40 개의 구를 보낼 수 있으며 프레임 속도를 줄이지 않는 떨리는 방에서 최대 속도로 이동할 수 있습니다. "델타 압축 / 인코딩"을 데이터 차등이라고도합니다. 내가 제기하려고하는 것과 매우 유사합니다.

Dead Reckoning VS Data Differencing : 그것들은 충분히 다르며 실제로 같은 방법을 사용하지 않으므로 최적화를 더욱 향상시키기 위해 두 가지를 모두 구현할 수 있습니다! 참고 : 나는 두 가지를 함께 사용하지 않았지만 두 가지를 모두 사용했습니다.

델타 인코딩 또는 데이터 차이 : 서버는 클라이언트가 알고있는 것에 대한 데이터를 전달하며 이전 데이터와 클라이언트에 변경해야 할 사항의 차이점 만 보냅니다. 예를 들어 pseudo-> 예에서 데이터가 이미 "310 435 210 4000 40"인 경우 "315 435 222 3546 33"데이터를 전송할 수 있습니다. 일부는 약간만 변경되었으며 하나는 전혀 변경되지 않았습니다! 그 대신에, 당신은 (델타로) "5 0 12 -454 -7"을 상당히 짧게 보낼 것입니다.

더 좋은 예는 예를 들어 45 개의 연결된 객체가있는 링크 된 목록이 있다고 말해줍니다. 나는 그들 중 30 명을 죽이고 싶습니다. 그런 다음 새 패킷 데이터가 무엇인지 모든 사람에게 보내십시오. 이것은 서버가 이와 같은 일을하기 위해 아직 구축되지 않은 경우 서버 속도를 늦추고 시도했기 때문에 발생했습니다. 예를 들어 자신을 수정합니다. 델타 인코딩에서는 단순히 "의사" "list.kill 30 at 5"를 넣고 5 번째 이후 목록에서 30 개의 개체를 제거한 다음 서버가 아닌 각 클라이언트에서 데이터를 인증합니다.

장점 : (지금 당 하나만 생각할 수 있음)

  1. 속도 : 분명히 마지막 예제에서 설명했습니다. 이전 예제와는 크게 다를 것입니다. 일반적으로 경험에서 말하면 죽은 계산법으로 더 많이 일할 때 더 공통적 인 경험을 말할 수 없습니다.

단점 :

  1. 시스템을 업데이트하고 델타를 통해 편집해야 할 데이터를 더 추가하려면 해당 데이터를 변경하는 새로운 기능을 만들어야합니다! (예 : 이전의 "list.kill 30 at 5"와 같이 클라이언트에 추가 된 실행 취소 방법이 필요합니다! "list.kill undo")

데드 레커닝 : 간단히 말하면, 여기에 비유가 있습니다. 나는 위치에 도달하는 방법에 대한 누군가를위한지도를 작성하고 있으며, 일반적으로 어디로 가야할 지에 대한 점만 포함하고 있습니다. 다른 사람의지도에는 거리 이름이 포함되어 있으며 왼쪽으로 몇도 회전해야합니까? (아니...)

데드 레커닝은 각 클라이언트에 클라이언트마다 일정한 알고리즘이 있습니다. 데이터는 변경해야 할 데이터와 수행 방법을 말함으로써 거의 변경됩니다. 클라이언트는 자체적으로 데이터를 변경합니다. 예를 들어, 플레이어가 아닌 캐릭터가 있지만 다른 사람과 함께 움직인다면 많은 데이터가 일관성이 있기 때문에 매 프레임마다 데이터를 업데이트 할 필요가 없습니다!

캐릭터가 어떤 방향으로 움직이고 있다고 말하면 많은 서버가 플레이어가있는 프레임 (거의 프레임 당)이라고 말하고 움직이는 애니메이션 (애니메이션 이유로)을 클라이언트에게 보냅니다. 너무 많은 불필요한 데이터입니다! 왜 모든 단일 프레임을 업데이트해야합니까? 유닛의 위치와 방향과 이동 방향은 무엇입니까? 간단히 말하면 : 나는하지 않습니다. 방향이 변경 될 때, 동사가 변경 될 때 (isMoving = true?) 객체가 무엇인지 클라이언트 만 업데이트합니다! 그런 다음 각 클라이언트는 그에 따라 개체를 이동합니다.

개인적으로 이것은 상식의 전술입니다. 내가 오래 전에 사용하는 것이 현명하다고 생각한 것은 항상 사용되는 것으로 밝혀졌습니다.

대답

솔직히 말해서 James의 게시물을 읽고 데이터에 대해 내가 말한 것을 읽으십시오. 예, 델타 인코딩을 가장 확실하게 사용해야하지만 데드 레 코닝 사용도 고려해야합니다.

개인적으로 서버에서 정보를 수신 할 때 클라이언트에서 데이터를 인스턴스화하려고합니다 (제안한 것).

변경할 수있는 객체 만 처음부터 편집 가능한 것으로 표시되어야합니까? 구성 요소 및 엔터티 시스템을 통해 개체에 네트워크 데이터가 있어야한다는 아이디어를 좋아합니다! 영리하고 잘 작동합니다. 그러나 네트워킹 방법에 브러시 (또는 절대적으로 일관된 데이터)를 제공해서는 안됩니다. 그들은 심지어 변화조차 할 수없는 것 (클라이언트에서 클라이언트로)이기 때문에 필요하지 않습니다.

그것이 문과 같은 것이면 네트워킹 데이터를 제공하지만 열려 있는지 여부에 대한 부울 만 제공합니다. 그런 다음 분명히 어떤 종류의 객체인지 확인하십시오. 클라이언트는이를 수정하는 방법을 알고 있어야합니다 (예 : 열려 있고 닫고 각 클라이언트가 모두 닫아야한다는 것을 수신하므로 부울 데이터를 변경 한 다음 문이 닫히도록 문에 애니메이션을 적용하십시오.

네트워크에 어떤 변수가 있는지 알아야하는 방법에 대해서는 진정으로 SUB 객체 인 구성 요소가 있고 네트워크에 구성 요소를 제공 할 수 있습니다. 또 다른 생각은하지하는 것입니다 AddComponent("whatever")뿐만 아니라 AddNetComponent("and what have you")이 똑똑 개인적으로 소리를해서.


이것은 엄청나게 긴 대답입니다! 정말 유감입니다. 나는 단지 적은 양의 지식을 제공하고 그다음에 2 센트를 공급하려고했습니다. 따라서 많은 것을 주목해야 할 필요가 있음을 이해합니다.
Joshua Hedges

3

의견을 쓰려고했지만 이것이 충분한 답변 정보라고 판단했습니다.

먼저 답변을 판단하기 위해 많은 세부 정보가 담긴 멋진 질문에 +1하십시오.

데이터 로딩의 경우 클라이언트가 월드 파일에서 월드를로드하도록합니다. 엔터티에 데이터 파일에서 가져온 ID가 있으면 기본적으로로드하여 네트워킹 시스템이 참조하는 개체를 알 수 있도록 네트워킹 시스템에서 참조 할 수 있습니다. 동일한 초기 데이터를로드 할 때마다 해당 객체에 대해 동일한 ID가 있어야합니다.

둘째, NetworkComponent 구성 요소를 만들지 마십시오. 기존의 다른 구성 요소 (물리, 애니메이션 등이 전송하는 일반적인 요소 임)에서 데이터를 복제 할뿐입니다. 고유 한 이름을 사용하려면 NetworkComponentManager를 만들 수 있습니다. 이것은 다른 Component to ComponentManager 관계에서 약간 떨어져 있지만 네트워크 게임을 시작하고 네트워킹 측면이있는 모든 유형의 구성 요소가 관리자에게 데이터를 제공하여 패키지화 할 수있을 때 인스턴스화 될 수 있습니다 그것을 보내십시오. 여기에 언급 된 것처럼 데이터를 패키지화하는 데 사용할 수있는 직렬화 / 직렬화 메커니즘이있는 경우 저장 /로드 기능을 사용할 수 있습니다.

귀하의 질문과 정보 수준을 감안할 때 더 자세히 설명 할 필요는 없다고 생각하지만 명확하지 않은 의견이 있으면 의견을 게시하고 답변을 업데이트하여 답변을 드리겠습니다.

이것이 도움이되기를 바랍니다.


따라서, 네트워크로 연결해야하는 컴포넌트는 다음과 같은 인터페이스를 구현해야한다고 말하는 것입니다. void SetNetworkedVariable (string name, NetworkedVariable value); NetworkedVariable GetNetworkedVariable (문자열 이름); NetworkedVariable은 보간 및 기타 네트워크를 위해 사용됩니다. 그래도 이것을 구현하는 구성 요소를 식별하는 방법을 모르겠습니다. 런타임 유형 식별을 사용할 수는 있지만 추악한 것 같습니다.
카터
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.