Ekstrak bagian inti sinkronisasi bingkai dari proyek sinkronisasi bingkai https://github.com/dudu502/LittleBee ke dalam SDK. Menyediakan bagian SDK server dan klien. Saat ini, kasus kode utama menggunakan Unity untuk menulis klien, dan program konsol dotnetcore adalah servernya.
Docs/
Diagrams/
: Deskripsi dokumen.Protocols/
: Alat dan konfigurasi protokol.Engine/
: direktori SDK.Client/
: Perpustakaan untuk proyek klien, merujuk pada proyek Umum, berdasarkan proyek netstandard2.0.Common/
: Pustaka referensi dasar untuk proyek Klien dan Server, berdasarkan proyek netstandard2.0.Server/
: Pustaka yang digunakan oleh proyek sisi server, merujuk pada proyek Umum, berdasarkan proyek netstandard2.0.Examples/
: Proyek kasus.Clients/
: Saat ini ada proyek kasus yang dikembangkan menggunakan Unity.Servers/
: Saat ini merupakan program konsol netcore. Temukan kelas Konteks di SDK. Ini adalah kelas utama kerangka kerja. Kelas ini sangat penting. Ini adalah pintu masuk ke seluruh SDK. Kelas ini digunakan sebagai pintu masuk program di klien dan server penggunaan API yang serupa. Nama default Konteks Ini dibagi menjadi server dan klien. Saat menggunakannya, pilih jenis klien atau server sesuai dengan kebutuhan Anda untuk membuat instance Konteks.
public const string CLIENT = "client" ;
public const string SERVER = "server" ;
Berikan nama di konstruktor untuk menentukan Konteks yang sesuai:
Context clientContext = new Context ( Context . CLIENT ) ;
Context serverContext = new Context ( Context . SERVER ) ;
Metode akuisisi yang sesuai:
Context context = Context . Retrieve ( name ) ;
Objek Context dapat diperoleh melalui metode di atas di SDK atau di game kustom. Anda kemudian dapat dengan mudah memperoleh objek dan data lain melalui objek Konteks.
Ini adalah diagram kelas Konteks. Konteks berisi semua pintu masuk modul fungsional yang disediakan oleh SDK untuk pengembang.
diagram kelas
kelas INetworkServer{
+Kirim(IPEndPoint ep,upesan singkatId,byte[] data)
+Kirim(int clientId,ushort messageId,byte[] data)
+Kirim(int[] clientIds,ushort messageId,byte[] data)
+Kirim(ushort messageId,byte[] data)
+Jalankan(int pelabuhan)
+int GetActivePort()
}
kelas INetworkClient{
+Kirim(ushort messageId,byte[] data)
+Hubungkan()
+Hubungkan(string ip,port int,kunci string)
+Tutup()
ke dalam GetActivePort()
}
kelas Pengontrol Simulasi{
+DapatkanFrameMsLength()
+DapatkanFrameLerp()
+UpdateFrameMsLength(faktor float)
+BuatSimulasi(Simulasi simulasi,Perilaku Simulatif[] bhs)
+Mulai(DateTime sTime,int hist_kf_cout,Aksi<float> kemajuan,Aksi pelari)
+Berhenti()
+DapatkanSimulasi()
+BuangSimulasi()
}
simulasi kelas{
+Mulai()
+Dapatkan Perilaku()
+BerisiPerilaku(ISimulatifPerilaku beh)
+TambahkanPerilaku(ISimulatifPerilaku beh)
+HapusPerilaku(ISimulatifPerilaku beh)
+Jalankan()
}
kelas ISimulatifPerilaku{
+Mulai()
+Pembaruan()
+Berhenti()
}
Konteks kelas {
+Server INetworkServer
+Klien INetworkClient
+ Pencatat ILogger
+nama tali
+Konteks(nama string, klien INetworkClient, ILogger logger)
+Konteks(nama string, server INetworkServer, ILogger logger)
+Konteks(nama string, ILogger logger)
+ Pengambilan statis (nama string)
+Konteks SetSimulationController(Pengendali SimulasiController)
+SimulationController GetSimulationController()
+GetMeta(nama string,nilai default string)
+SetMeta(nama string, nilai string)
+Konteks SetModule(Modul AbstrakModule)
+M DapatkanModul<M>()
+Konteks HapusModule(Jenis tipe)
}
Tipe bawaan yang ditentukan dalam ContextMetaId adalah sebagai berikut. Tipe ini akan digunakan secara internal di SDK. Pengguna juga dapat menyimpan dan membaca tipe khusus melalui Context.SetMeta dan 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" ;
}
Server dibagi menjadi dua bagian: Gerbang dan Pertempuran. Layanan Gerbang bertujuan untuk menyediakan layanan lobi kepada pengguna dan memungkinkan pengguna untuk masuk. Setiap pengguna memiliki uid yang berbeda harus menjadi Panduan dalam database untuk memastikan bahwa setiap ID pengguna unik. Layanan Gerbang ini memberi pengguna layanan terkait tim seperti membuat ruangan, bergabung dengan ruangan, meninggalkan ruangan, dll. Ketika beberapa pengguna memulai pertempuran di dalam ruangan, layanan Gerbang akan memulai proses baru layanan Pertempuran dan memberi tahu pengguna tersebut untuk memasuki layanan Pertempuran. Setiap pertempuran akan membuka proses Pertempuran baru. Layanan Battle mengimplementasikan layanan sinkronisasi key frame untuk semua pengguna.
Berikut contoh kode Gate servernya:
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 ( ) ;
}
Berikutnya adalah kasus proyek Pertempuran:
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 ( ) ;
}
Kode di sisi klien mirip dengan di sisi server, dan diatur melalui kelas utama Konteks. Berikut ini adalah kode untuk kasus 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的构造
}
Kasus ini menunjukkan hasil dari beberapa entitas yang berjalan secara bersamaan di dua klien. Objek tidak dapat dikontrol selama pengoperasian, jadi kasus ini juga merupakan kasus paling sederhana. Pergerakan semua objek berubah mengikuti kerangka logika pada saat inisialisasi.
Kasus ini menunjukkan bahwa pengguna dapat mengontrol pergerakan objek dan menyinkronkan perubahan pergerakan objek di beberapa klien.
Semua proyek kasus mereferensikan pustaka https://github.com/omid3098/OpenTerminal untuk menampilkan perintah pengujian guna memfasilitasi debugging API. Tekan '`' pada keyboard untuk membuka baris perintah.
Perintah OpenTerminal | API di SDK | Komentar |
---|---|---|
setuid | konteksInst.SetMeta(ContextMetaId.USER_ID, uid); | Atur data pengguna dalam Konteks |
menghubungkan | konteksInst.Client.Connect(127.0.0.1, 9030, "ruang gerbang"); | Hubungkan ke server secara default |
sambungkan-ip-port-kunci | konteksInst.Klien.Connect(ip, port, kunci); | Hubungkan ke server yang ditentukan |
membuat | konteksInst.GetModule().RequestCreateRoom(mapid); | Buat ruangan setelah terhubung ke server |
bergabung | konteksInst.GetModule().RequestJoinRoom(roomId); | Bergabunglah dengan ruangan yang ditentukan |
meninggalkan | konteksInst.GetModule().RequestLeaveRoom(); | Tinggalkan ruangan yang ditentukan |
daftar kamar | konteksInst.GetModule().RequestRoomList(); | Segarkan daftar ruangan |
meluncurkan | konteksInst.GetModule().RequestLaunchGame(); | Mulai permainan |
tim pembaruan | konteksInst.GetModule().RequestUpdatePlayerTeam(roomId, userId, teamId); | Perbarui data ruangan di dalam ruangan |
peta pembaruan | konteksInst.GetModule().RequestUpdateMap(roomId, mapId, maxPlayerCount); | Ubah peta di kamar |
berhenti | Silakan periksa metode Stop di Sample.cs dan subkelasnya | permainan akhir |
peta gambar | Silakan periksa metode DrawMap di Sample.cs dan subkelasnya | Gambarlah peta |
saverep | Silakan periksa metode SaveReplay di Sample.cs dan subkelasnya | Simpan pemutaran (rekaman) |
pemutaran ulang | Silakan periksa metode PlayReplay di Sample.cs dan subkelasnya | Simpan pemutaran (rekaman) |
Di bawah ini tercantum beberapa struktur data utama di SDK. Struktur ini juga dapat menjadi struktur protokol. Struktur ini dapat diserialisasi dan dideserialisasi serta mendukung struktur data opsional lapangan yang banyak digunakan di SDK. Itu dapat dihasilkan oleh alat-alat di Docs/Protocols/, dalam bentuk:
public class PtMyData
{
//Fields
public static byte [ ] Write ( PtMyData value ) { }
public static PtMyData Read ( byte [ ] bytes ) { }
}
Nama kelas | Bidang | Komentar |
---|---|---|
Bingkai Pt | string EntitasId Pembaru PtComponentUpdaterList byte[] Entitas BaruMentah | data bingkai utama |
PtFrame | ke dalam FrameIdx DaftarKeyFrames | Himpunan semua keyframe dalam frame tertentu |
Peta Pt | Versi string Entitas Daftar Entitas | data peta |
Putar Ulang Pt | Versi string tidak ada MapId DaftarInitEntitas Daftar Bingkai | Merekam (memutar) data |
Mekanisme perekaman video (pemutaran) merupakan mekanisme yang paling khas dalam teknologi sinkronisasi bingkai, dan juga merupakan hal yang tidak dapat dihindari. SDK juga memiliki kemampuan untuk menyimpan dan memuat video.
Simulator sinkronisasi bingkai adalah bagian penting dari SDK untuk melakukan sinkronisasi bingkai. Poin utamanya adalah semua perangkat harus mempertahankan jumlah bingkai yang konsisten setelah jangka waktu tertentu setelah permulaan. Hal ini memerlukan kalibrasi DateTime di setiap centang hubungan antara selang waktu lokal dan TICK logis. Kode detailnya dapat dilihat dengan membuka file SimulasiController.cs.