フレーム同期プロジェクト https://github.com/dudu502/LittleBee のフレーム同期コア部分を SDK に抽出します。 この SDK は netstandard2.0 に基づいています。サーバーとクライアントの SDK パーツを提供します。現在、主なコード ケースでは Unity を使用してクライアントを作成し、dotnetcore コンソール プログラムがサーバーになります。
Docs/
Diagrams/
: 説明文書。Protocols/
: プロトコルのツールと構成。Engine/
: SDK ディレクトリ。Client/
: netstandard2.0 プロジェクトに基づく、Common プロジェクトを参照するクライアント プロジェクト用のライブラリ。Common/
: netstandard2.0 プロジェクトに基づく、クライアントおよびサーバー プロジェクトの基本リファレンス ライブラリ。Server/
: netstandard2.0 プロジェクトに基づいた、Common プロジェクトを参照する、サーバー側プロジェクトによって使用されるライブラリ。Examples/
: ケースプロジェクト。Clients/
: 現在、Unity を使用して開発されたプロジェクトがあります。Servers/
: 現在は netcore コンソール プログラムです。 SDK で Context クラスを見つけます。このクラスは、SDK 全体への入り口として、クライアントとサーバーの両方で使用されます。 Context のデフォルト名は、サーバーとクライアントに分かれており、必要に応じてクライアントまたはサーバーの種類を選択して Context インスタンスを構築します。
public const string CLIENT = "client" ;
public const string SERVER = "server" ;
コンストラクターに名前を渡して、対応するコンテキストを指定します。
Context clientContext = new Context ( Context . CLIENT ) ;
Context serverContext = new Context ( Context . SERVER ) ;
対応する取得方法:
Context context = Context . Retrieve ( name ) ;
Context オブジェクトは、SDK またはカスタム ゲームで上記のメソッドを通じて取得できます。その後、Context オブジェクトを通じて他のオブジェクトやデータを簡単に取得できます。
これは Context のクラス図です。Context には、SDK が開発者に提供するすべての機能モジュールの入り口が含まれています。
クラス図
クラス INetworkServer{
+Send(IPEndPoint ep、ushort messageId、byte[] データ)
+Send(int clientId,ushort messageId,byte[] データ)
+Send(int[] clientId,ushort messageId,byte[] データ)
+Send(ushort メッセージ ID,byte[] データ)
+実行(intポート)
+int GetActivePort()
}
クラス INetworkClient{
+Send(ushort メッセージ ID,byte[] データ)
+接続()
+Connect(文字列ip、intポート、文字列キー)
+閉じる()
int GetActivePort()
}
クラス SimulationController{
+GetFrameMsLength()
+GetFrameLerp()
+UpdateFrameMsLength(浮動小数点係数)
+CreateSimulation(シミュレーション シミュレーション,ISimulativeBehaviour[] bhs)
+Start(DateTime sTime,int hist_kf_cout,Action<float> 進行状況,アクション ランナー)
+停止()
+GetSimulation()
+DisposeSimulation()
}
クラスシミュレーション{
+開始()
+GetBehavior()
+ContainBehaviour(ISimulativeBehaviour です)
+AddBehaviour(ISimulativeBehaviour です)
+RemoveBehaviour(ISimulativeBehaviour です)
+実行()
}
クラス ISimulativeBehaviour{
+開始()
+更新()
+停止()
}
クラスコンテキスト{
+INetworkServer サーバー
+INetworkClient クライアント
+ILogger ロガー
+文字列名
+Context(文字列名、INetworkClientクライアント、ILoggerロガー)
+Context(文字列名、INetworkServerサーバー、ILoggerロガー)
+Context(文字列名,ILogger ロガー)
+静的取得(文字列名)
+Context SetSimulationController(SimulationController コントローラー)
+SimulationController GetSimulationController()
+GetMeta(文字列名,文字列デフォルト値)
+SetMeta(文字列名,文字列値)
+Context SetModule(AbstractModuleモジュール)
+M GetModule<M>()
+Context RemoveModule(型タイプ)
}
ContextMetaId で定義される組み込みタイプは次のとおりです。これらのタイプは、SDK で内部的に使用され、Context.SetMeta および Context.GetMeta を通じてカスタム タイプを保存および読み取ることもできます。
public sealed class ContextMetaId
{
public const string USER_ID = "user_id" ;
public const string SERVER_ADDRESS = "server_address" ;
public const string MAX_CONNECTION_COUNT = "max_connection_count" ;
public const string ROOM_MODULE_FULL_PATH = "room_module_full_path" ;
public const string STANDALONE_MODE_PORT = "standalone_mode_port" ;
public const string GATE_SERVER_PORT = "gate_server_port" ;
public const string SELECTED_ROOM_MAP_ID = "selected_room_map_id" ;
public const string PERSISTENT_DATA_PATH = "persistent_data_path" ;
}
サーバーは、ゲートとバトルの 2 つの部分に分かれています。ゲート サービスは、ユーザーにロビー サービスを提供し、ユーザーがログインできるようにすることを目的としています。この uid は、現在、異なる文字列でのみテストされています。各ユーザーの ID が確実に一意になるように、データベース内の GUID にする必要があります。このゲートサービスは、ルームの作成、ルームへの参加、ルームからの退出など、チーム関連のサービスをユーザーに提供します。複数のユーザーがルームで戦闘を開始すると、ゲート サービスは戦闘サービスの新しいプロセスを開始し、これらのユーザーに戦闘サービスに参加するように通知します。各戦闘では、新しい戦闘プロセスが開きます。バトルサービスでは、全ユーザーにキーフレームを同期するサービスが実装されています。
以下は、ゲート サーバーのコード例です。
const string TAG = "gate-room" ;
static void Main ( string [ ] args )
{
// 使用Context.SERVER 构造Context
Context context = new Context ( Context . SERVER , new LiteNetworkServer ( TAG ) , new DefaultConsoleLogger ( TAG ) )
. SetMeta ( ContextMetaId . ROOM_MODULE_FULL_PATH , "battle_dll_path.dll" ) // the battle project's build path.
. SetMeta ( ContextMetaId . MAX_CONNECTION_COUNT , "16" )
. SetMeta ( ContextMetaId . SERVER_ADDRESS , "127.0.0.1" )
. SetModule ( new RoomModule ( ) ) ;
context . Server . Run ( 9030 ) ;
Console . ReadLine ( ) ;
}
次は Battle プロジェクトのケースです。
static void Main ( string [ ] args )
{
string key = "SomeConnectionKey" ;
int port = 50000 ;
uint mapId = 1 ;
ushort playerNumber = 100 ;
int gsPort = 9030 ;
// 一些从Gate服务中传入的参数
if ( args . Length > 0 )
{
if ( Array . IndexOf ( args , "-key" ) > - 1 ) key = args [ Array . IndexOf ( args , "-key" ) + 1 ] ;
if ( Array . IndexOf ( args , "-port" ) > - 1 ) port = Convert . ToInt32 ( args [ Array . IndexOf ( args , "-port" ) + 1 ] ) ;
if ( Array . IndexOf ( args , "-mapId" ) > - 1 ) mapId = Convert . ToUInt32 ( args [ Array . IndexOf ( args , "-mapId" ) + 1 ] ) ;
if ( Array . IndexOf ( args , "-playernumber" ) > - 1 ) playerNumber = Convert . ToUInt16 ( args [ Array . IndexOf ( args , "-playernumber" ) + 1 ] ) ;
if ( Array . IndexOf ( args , "-gsPort" ) > - 1 ) gsPort = Convert . ToInt32 ( args [ Array . IndexOf ( args , "-gsPort" ) + 1 ] ) ;
}
Context context = new Context ( Context . SERVER , new LiteNetworkServer ( key ) , new DefaultConsoleLogger ( key ) )
. SetMeta ( ContextMetaId . MAX_CONNECTION_COUNT , playerNumber . ToString ( ) )
. SetMeta ( ContextMetaId . SELECTED_ROOM_MAP_ID , mapId . ToString ( ) )
. SetMeta ( ContextMetaId . GATE_SERVER_PORT , gsPort . ToString ( ) )
. SetModule ( new BattleModule ( ) ) ;
SimulationController simulationController = new SimulationController ( ) ;
simulationController . CreateSimulation ( new Simulation ( ) , new ISimulativeBehaviour [ ] { new ServerLogicFrameBehaviour ( ) } ) ;
context . SetSimulationController ( simulationController ) ;
context . Server . Run ( port ) ;
Console . ReadKey ( ) ;
}
クライアント側のコードはサーバー側のコードと似ており、Context メイン クラスを通じて設定されます。以下はケース 1 のコードです。
void Awake ( ) {
// 用Context.CLIENT构造Context,客户端的Context还需要指定SimulationController等,因此比服务端的Context稍微复杂一些
MainContext = new Context ( Context . CLIENT , new LiteNetworkClient ( ) , new UnityLogger ( "Unity" ) ) ;
MainContext . SetMeta ( ContextMetaId . STANDALONE_MODE_PORT , "50000" )
. SetMeta ( ContextMetaId . PERSISTENT_DATA_PATH , Application . persistentDataPath ) ;
MainContext . SetModule ( new GateServiceModule ( ) ) //大厅组队相关服务
. SetModule ( new BattleServiceModule ( ) ) ; //帧同步服务
// 构造模拟器控制器,SDK提供了一个Default版本的控制器,一般情况下用Default就可以了
DefaultSimulationController defaultSimulationController = new DefaultSimulationController ( ) ;
MainContext . SetSimulationController ( defaultSimulationController ) ;
defaultSimulationController . CreateSimulation ( new DefaultSimulation ( ) , new EntityWorld ( ) ,
new ISimulativeBehaviour [ ] {
new FrameReceiverBehaviour ( ) , //收取服务器的帧数据并处理
new EntityBehaviour ( ) , //执行ECS中System
} ,
new IEntitySystem [ ]
{
new AppearanceSystem ( ) , //外观显示系统
new MovementSystem ( ) , //自定义移动系统,计算所有Movement组件
new ReboundSystem ( ) , //自定义反弹系统
} ) ;
EntityWorld entityWorld = defaultSimulationController . GetSimulation < DefaultSimulation > ( ) . GetEntityWorld ( ) ;
entityWorld . SetEntityInitializer ( new GameEntityInitializer ( entityWorld ) ) ; // 用于初始化和构造Entity
entityWorld . SetEntityRenderSpawner ( new GameEntityRenderSpawner ( entityWorld , GameContainer ) ) ; //ECSR中Renderer的构造
}
このケースは、2 つのクライアントで同時に実行されている複数のエンティティの結果を示しています。動作中にオブジェクトを制御することはできないため、このケースは、すべてのオブジェクトの動きが初期化の瞬間の論理フレームに応じて変化する最も単純なケースでもあります。
このケースは、ユーザーがオブジェクトの動きを制御し、複数のクライアント間でオブジェクトの動きの変化を同期できることを示しています。
すべてのケース プロジェクトは https://github.com/omid3098/Openterminal ライブラリを参照して、API のデバッグを容易にするテスト コマンドを表示します。キーボードの「`」を押してコマンド ラインを開きます。
ターミナルを開くコマンド | SDKのAPI | 述べる |
---|---|---|
セットアップ | contextInst.SetMeta(ContextMetaId.USER_ID, uid); | コンテキストにユーザーデータを設定する |
接続する | contextInst.Client.Connect(127.0.0.1, 9030, "ゲートルーム"); | デフォルトでサーバーに接続します |
接続IPポートキー | contextInst.Client.Connect(ip、ポート、キー); | 指定したサーバーに接続します |
作成する | contextInst.GetModule().RequestCreateRoom(mapid); | サーバーに接続したらルームを作成します |
参加する | contextInst.GetModule().RequestJoinRoom(roomId); | 指定されたルームに参加する |
離れる | contextInst.GetModule().RequestLeaveRoom(); | 指定された部屋を出る |
ルームリスト | contextInst.GetModule().RequestRoomList(); | リフレッシュルーム一覧 |
打ち上げ | contextInst.GetModule().RequestLaunchGame(); | ゲームを開始する |
アップデートチーム | contextInst.GetModule().RequestUpdatePlayerTeam(ルームId、ユーザーId、チームId); | ルーム内でルームデータを更新する |
アップデートマップ | contextInst.GetModule().RequestUpdateMap(roomId, mapId, maxPlayerCount); | 部屋のマップを変更する |
停止 | Sample.cs の Stop メソッドとそのサブクラスを確認してください。 | エンドゲーム |
ドローマップ | Sample.cs の DrawMap メソッドとそのサブクラスを確認してください。 | 地図を描く |
セーブレップ | Sample.cs の SaveReplay メソッドとそのサブクラスを確認してください。 | 再生(録音)を保存する |
プレイレップ | Sample.cs の PlayReplay メソッドとそのサブクラスを確認してください。 | 再生(録音)を保存する |
以下に、SDK の主要なデータ構造をいくつか示します。これらは、シリアル化および逆シリアル化することができ、SDK で広く使用されているフィールド オプションのデータ構造をサポートします。これは、Docs/Protocols/ のツールによって次の形式で生成できます。
public class PtMyData
{
//Fields
public static byte [ ] Write ( PtMyData value ) { }
public static PtMyData Read ( byte [ ] bytes ) { }
}
クラス名 | 分野 | 述べる |
---|---|---|
ポイントフレーム | 文字列エンティティId PtComponentUpdaterリスト アップデーター byte[] NewEntitiesRaw | キーフレームデータ |
ポイントフレーム | int FrameIdx キーフレームのリスト | 特定のフレーム内のすべてのキーフレームのセット |
ポイントマップ | 文字列バージョン EntityListエンティティ | 地図データ |
ポイントリプレイ | 文字列バージョン 単位マップID ListInitEntities リストフレーム | データの記録(再生) |
映像の記録(再生)機構はフレーム同期技術の中で最も特徴的な機構であり、避けては通れないポイントでもある。 SDK にはビデオを保存およびロードする機能もあります。
フレーム同期シミュレーターは、フレーム同期を実行するための SDK の重要な部分です。重要な点は、すべてのデバイスが起動後の一定期間後に一貫したフレーム数を維持する必要があるということです。これを達成するには、ティックごとに DateTime を調整する必要があります。ローカルタイムラプスと論理TICKの関係。詳細なコードは、SimulationController.cs ファイルを開くと表示できます。