little bee 고급 버전은 현재 프레임 동기화 로직을 SDK로 추출하고 있습니다. 프로젝트 https://github.com/dudu502/littlebee_libs를 참조하세요. 이것은 게임에서 수백 개의 개체와 수천 개의 상태를 동기화하는 프레임 동기화 게임 예제입니다. .. 게임의 배경은 행성계에서의 슈팅 게임입니다. 다음은 영상 링크입니다.
[동영상 재생보기(유튜브)]
[영상 다시보기(유튜브)]
[동영상 재생 보기(bilibili)]
[동영상 다시보기(bilibili)]
프레임 동기화 | 상태 동기화 | |
---|---|---|
일관성 | 디자인 수준은 불가피한 일관성을 결정합니다. | 일관성을 보장할 수 있음 |
플레이어 수 | 제한된 멀티플레이어 지원 | 여러 플레이어가 장점을 가지고 있습니다. |
크로스 플랫폼 | 부동소수점 연산의 일관성을 고려해야 합니다. | 주요 계산이 서버에서 수행되므로 플랫폼 간 문제가 없습니다. |
부정행위 방지 | 속이기 쉽지만 최적화할 수 있음 | 부정 행위를 예방하는 데 매우 효과적일 수 있습니다. |
연결을 끊었다가 다시 연결하세요. | 구현하기는 어렵지만 불가능하지는 않다 | 메시지를 한 번만 다시 보내면 됩니다. 구현하기 쉽습니다. |
재생 요구 사항 | 완벽하게 구현될 수 있습니다 | 달성할 수 없음 |
게임 일시 중지 | 구현이 용이함 | 구현이 쉽지 않음 |
네트워크 전송량 | 상대적으로 작은 | 상대적으로 큰 |
개발 난이도 | 비교적 복잡한 | 비교적 간단하다 |
RTS 게임 | 적합한 | 적합하지 않음 |
격투 게임 | 적합한 | 적합하지 않음 |
MOBA 게임 | 적합한 | 적합하지 않음 |
MMO 게임 | 적합하지 않음 | 적합한 |
프레임 동기화 개발 과정에서 극복해야 할 어려움을 이해한 후에는 더 나은 구현 방법이나 개발 프레임워크를 선택하는 것을 고려할 것입니다. 프레임 동기화 개발은 데이터와 성능의 분리를 요구하는데, 어느 정도로 분리해야 할까요? 데이터 계산 부분은 별도의 스레드에 배치할 수도 있습니다. 이런 방식으로 로직을 작성하는 것의 장점은 서버를 실행하여 게임을 빠르게 재생하는 기능도 달성할 수 있다는 것입니다. ECS만이 이러한 수준의 분리를 달성할 수 있다고 생각합니다. 프레임 동기화와 ECS는 완벽한 파트너입니다.
먼저 ECS를 소개해야 합니다. ECS는 완전히 새로운 기술도 아니고 Unity가 처음 제안한 것도 아닙니다. 이 용어는 아주 초기에 등장했고 최근에는 블리자드의 '오버워치' 때문에 갑자기 인기를 끌게 됐다. "오버워치"의 서버 및 클라이언트 프레임워크는 완전히 ECS를 기반으로 구축되었으며 게임 메커니즘, 네트워크 및 렌더링 측면에서 탁월한 성능을 발휘합니다. 솔직히 말해서 ECS는 디자인 패턴과 같지 않습니다. 이전에 사용했던 디자인 패턴은 모두 객체 지향 디자인에서 논의되었으며 ECS는 객체 지향이 아닙니다. Unity에도 ECS가 있습니다. 사실 Unity 자체 구성 요소도 일종의 ECS이지만 충분히 순수하지는 않습니다. ECS는 특히 게임플레이에 적합합니다. ECS에는 다양한 변형이 있으며 여기에는 약간 수정된 ECS가 있습니다.
이는 프레임 동기화 게임의 특징입니다. 게임에 리플레이 시스템이 있는 경우 프레임 동기화를 통해 게임을 구현해야 합니다. 재생은 영상녹화라고도 부르는데, 영상파일을 이용한 재생은 일반적으로 통신사가 대용량 파일을 차지하고, 재생과정에서 창을 전환할 수 없기 때문에 영상녹화와는 매우 다릅니다. 수정, 압축, 품질 저하가 발생하여 동영상 재생에 큰 단점이 있습니다. 프레임 동기화 재생을 사용하면 파일이 매우 작아질 수 있으며 변조할 수 없습니다. 사용자는 재생 프로세스 중에 창을 마음대로 전환할 수 있습니다. 프레임 동기화 게임에 꼭 필요한 시스템은 바로 리플레이 시스템이라고 할 수 있습니다.
RevenantX/LiteNetLib는 매우 강력하고 사용이 간편하며, 이것이 바로 제가 원하는 것입니다. 네트워크 통신을 위해 선택할 수 있는 데이터 프로토콜이 많이 있습니다. 저는 자체 제작한 바이너리 스트림 프로토콜을 사용하고 있습니다. 주요 기능은 직렬화 및 역직렬화입니다. 이 PtRoom 구조와 같습니다.
//Template auto generator:[AutoGenPt] v1.0
//Creation time:2021/1/28 16:43:48
using System ;
using System . Collections ;
using System . Collections . Generic ;
namespace Net . Pt
{
public class PtRoom
{
public byte __tag__ { get ; private set ; }
public uint RoomId { get ; private set ; }
public byte Status { get ; private set ; }
public uint MapId { get ; private set ; }
public string RoomOwnerUserId { get ; private set ; }
public byte MaxPlayerCount { get ; private set ; }
public List < PtRoomPlayer > Players { get ; private set ; }
public PtRoom SetRoomId ( uint value ) { RoomId = value ; __tag__ |= 1 ; return this ; }
public PtRoom SetStatus ( byte value ) { Status = value ; __tag__ |= 2 ; return this ; }
public PtRoom SetMapId ( uint value ) { MapId = value ; __tag__ |= 4 ; return this ; }
public PtRoom SetRoomOwnerUserId ( string value ) { RoomOwnerUserId = value ; __tag__ |= 8 ; return this ; }
public PtRoom SetMaxPlayerCount ( byte value ) { MaxPlayerCount = value ; __tag__ |= 16 ; return this ; }
public PtRoom SetPlayers ( List < PtRoomPlayer > value ) { Players = value ; __tag__ |= 32 ; return this ; }
public bool HasRoomId ( ) { return ( __tag__ & 1 ) == 1 ; }
public bool HasStatus ( ) { return ( __tag__ & 2 ) == 2 ; }
public bool HasMapId ( ) { return ( __tag__ & 4 ) == 4 ; }
public bool HasRoomOwnerUserId ( ) { return ( __tag__ & 8 ) == 8 ; }
public bool HasMaxPlayerCount ( ) { return ( __tag__ & 16 ) == 16 ; }
public bool HasPlayers ( ) { return ( __tag__ & 32 ) == 32 ; }
public static byte [ ] Write ( PtRoom data )
{
using ( ByteBuffer buffer = new ByteBuffer ( ) )
{
buffer . WriteByte ( data . __tag__ ) ;
if ( data . HasRoomId ( ) ) buffer . WriteUInt32 ( data . RoomId ) ;
if ( data . HasStatus ( ) ) buffer . WriteByte ( data . Status ) ;
if ( data . HasMapId ( ) ) buffer . WriteUInt32 ( data . MapId ) ;
if ( data . HasRoomOwnerUserId ( ) ) buffer . WriteString ( data . RoomOwnerUserId ) ;
if ( data . HasMaxPlayerCount ( ) ) buffer . WriteByte ( data . MaxPlayerCount ) ;
if ( data . HasPlayers ( ) ) buffer . WriteCollection ( data . Players , ( element ) => PtRoomPlayer . Write ( element ) ) ;
return buffer . Getbuffer ( ) ;
}
}
public static PtRoom Read ( byte [ ] bytes )
{
using ( ByteBuffer buffer = new ByteBuffer ( bytes ) )
{
PtRoom data = new PtRoom ( ) ;
data . __tag__ = buffer . ReadByte ( ) ;
if ( data . HasRoomId ( ) ) data . RoomId = buffer . ReadUInt32 ( ) ;
if ( data . HasStatus ( ) ) data . Status = buffer . ReadByte ( ) ;
if ( data . HasMapId ( ) ) data . MapId = buffer . ReadUInt32 ( ) ;
if ( data . HasRoomOwnerUserId ( ) ) data . RoomOwnerUserId = buffer . ReadString ( ) ;
if ( data . HasMaxPlayerCount ( ) ) data . MaxPlayerCount = buffer . ReadByte ( ) ;
if ( data . HasPlayers ( ) ) data . Players = buffer . ReadCollection ( ( rBytes ) => PtRoomPlayer . Read ( rBytes ) ) ;
return data ;
}
}
}
}
프레임 동기화를 기반으로 한 Unity 프로젝트입니다.
일부 도구: Pt 구조 생성 도구, Excel2Json 생성 도구, 일반 라이브러리 프로젝트, ServerDll 라이브러리 프로젝트
설계 문서: 개요 설계 문서, 프로토타입 설계 문서, 구성 테이블.
다음 세 가지 그림은 세 가지 시나리오에서 프레임 동기화 시뮬레이터를 사용하는 방법을 설명합니다.
아래 그림은 클라이언트와 서버의 일반적인 동작을 동시에 보여주며 재생 로직도 동일한 동작에 해당합니다.
이 그림은 각 논리 TICK에서 논리를 실행하는 클라이언트와 서버를 보여줍니다. 위쪽이 클라이언트이고, 클라이언트가 실행해야 하는 로직에는 ECSR 부분이 포함되며, 아래쪽이 서버 부분입니다.
마지막 그림은 재생의 각 논리적 프레임을 설명합니다.
이러한 그림과 특정 유형의 게임을 통해 관련 로직을 처리하기 위한 맞춤형 시스템과 구성 요소를 설정할 수 있습니다.
WebServer, GateServer, RoomServer 등을 포함한 서비스 수집 프로젝트입니다.