Extrayez la partie centrale de synchronisation de trame du projet de synchronisation de trame https://github.com/dudu502/LittleBee dans le SDK. Ce SDK est basé sur netstandard2.0. Fournir des composants SDK serveur et client. Actuellement, les principaux cas de code utilisent Unity pour écrire le client, et le programme console dotnetcore est le serveur.
Docs/
Diagrams/
: Document de description.Protocols/
: Outils et configurations de protocoles.Engine/
: répertoire du SDK.Client/
: Bibliothèque pour les projets clients, référençant le projet Common, basée sur le projet netstandard2.0.Common/
: La bibliothèque de référence de base pour les projets Client et Serveur, basée sur le projet netstandard2.0.Server/
: Une bibliothèque utilisée par les projets côté serveur, faisant référence au projet Common, basé sur le projet netstandard2.0.Examples/
: Projets de cas.Clients/
: Il existe actuellement un projet de cas développé avec Unity.Servers/
: Actuellement un programme de console netcore. Recherchez la classe Context dans le SDK. C'est la classe principale du framework. Cette classe est très importante. C'est l'entrée de l'ensemble du SDK. Cette classe est utilisée comme entrée du programme à la fois dans le client et dans le serveur. Utilisations d'API similaires. Le nom par défaut de Context Il est divisé en serveur et client. Lors de son utilisation, sélectionnez le type de client ou de serveur en fonction de vos besoins pour construire une instance de Context.
public const string CLIENT = "client" ;
public const string SERVER = "server" ;
Passez le nom dans le constructeur pour spécifier le Contexte correspondant :
Context clientContext = new Context ( Context . CLIENT ) ;
Context serverContext = new Context ( Context . SERVER ) ;
Méthode d'acquisition correspondante :
Context context = Context . Retrieve ( name ) ;
L'objet Context peut être obtenu via la méthode ci-dessus dans le SDK ou dans un jeu personnalisé. Vous pouvez ensuite facilement obtenir d'autres objets et données via l'objet Contexte.
Il s'agit du diagramme de classes de Context. Context contient toutes les entrées de modules fonctionnels fournies par le SDK aux développeurs.
diagramme de classe
classe INetworkServer{
+ Envoyer (IPEndPoint ep, ushort messageId, byte[] données)
+Envoyer (int clientId,ushort messageId,byte[] données)
+Envoyer (int[] clientIds,ushort messageId,byte[] données)
+Envoyer (ushort messageId,byte[] données)
+Exécuter(port int)
+int GetActivePort()
}
classe INetworkClient{
+Envoyer (ushort messageId,byte[] données)
+Connecter()
+ Connect (chaîne IP, port int, clé de chaîne)
+Fermer()
int GetActivePort()
}
classe SimulationController{
+GetFrameMsLength()
+ObtenirFrameLerp()
+ UpdateFrameMsLength (facteur flottant)
+Créer une simulation (simulation de simulation, ISimulativeBehaviour [] bhs)
+Début(DateTime sTime,int hist_kf_cout,Action<float> progress,Action runner)
+Arrêter()
+ObtenirSimulation()
+DisposeSimulation()
}
simulation de classe{
+Début()
+ObtenirComportement()
+ContainBehaviour(ISimulativeBehaviour beh)
+Ajouter un comportement (ISimulativeBehaviour beh)
+ Supprimer le comportement (ISimulativeBehaviour beh)
+Exécuter()
}
classe ISimulativeBehaviour{
+Début()
+Mise à jour()
+Arrêter()
}
contexte de classe {
+Serveur INetworkServer
+Client INetworkClient
+Enregistreur ILogger
+nom de chaîne
+Contexte (nom de chaîne, client INetworkClient, enregistreur ILogger)
+Contexte (nom de chaîne, serveur INetworkServer, enregistreur ILogger)
+ Contexte (nom de chaîne, enregistreur ILogger)
+Récupération statique (nom de la chaîne)
+ Contexte SetSimulationController (contrôleur SimulationController)
+SimulationController GetSimulationController()
+ GetMeta (nom de chaîne, valeur par défaut de chaîne)
+SetMeta (nom de chaîne, valeur de chaîne)
+Context SetModule (module AbstractModule)
+M GetModule<M>()
+Context RemoveModule (Type de type)
}
Les types intégrés définis dans ContextMetaId sont les suivants. Ces types seront utilisés en interne dans le SDK. Les utilisateurs peuvent également stocker et lire des types personnalisés via Context.SetMeta et 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" ;
}
Le serveur est divisé en deux parties : Gate et Battle. Le service Gate vise à fournir aux utilisateurs un service de lobby et à permettre aux utilisateurs de se connecter. Cet uid n'est actuellement testé qu'avec des chaînes différentes. doit être Guid dans la base de données pour garantir que l'ID de chaque utilisateur est unique. Ce service Gate fournit aux utilisateurs des services liés à l'équipe tels que la création de salles, la connexion à des salles, la sortie de salles, etc. Lorsque plusieurs utilisateurs démarrent une bataille dans la salle, le service Gate lancera un nouveau processus du service Battle et informera ces utilisateurs d'accéder au service Battle. Chaque bataille ouvrira un nouveau processus de bataille. Dans le service Battle, le service de synchronisation des images clés avec tous les utilisateurs est implémenté.
Voici un exemple de code du serveur Gate :
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 ( ) ;
}
Vient ensuite le cas du projet 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 ( ) ;
}
Le code côté client est similaire à celui côté serveur et est défini via la classe principale Context. Voici le code pour le cas 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的构造
}
Ce cas montre le résultat de plusieurs entités exécutées simultanément dans deux clients. Les objets ne peuvent pas être contrôlés pendant le fonctionnement, ce cas est donc également le cas le plus simple. Le mouvement de tous les objets change avec le cadre logique au moment de l'initialisation.
Ce cas montre que les utilisateurs peuvent contrôler le mouvement des objets et synchroniser les modifications du mouvement des objets sur plusieurs clients.
Tous les projets de cas font référence à la bibliothèque https://github.com/omid3098/OpenTerminal pour afficher les commandes de test afin de faciliter le débogage de l'API. Appuyez sur « » sur le clavier pour ouvrir la ligne de commande.
Commande OuvrirTerminal | API dans le SDK | Remarque |
---|---|---|
setuid | contextInst.SetMeta(ContextMetaId.USER_ID, uid); | Définir les données utilisateur en contexte |
connecter | contextInst.Client.Connect(127.0.0.1, 9030, "gate-room"); | Se connecter au serveur par défaut |
clé de port IP de connexion | contextInst.Client.Connect(ip, port, clé); | Connectez-vous au serveur spécifié |
créer | contextInst.GetModule().RequestCreateRoom(mapid); | Créer une salle après connexion au serveur |
rejoindre | contextInst.GetModule().RequestJoinRoom(roomId); | Rejoignez une salle désignée |
partir | contextInst.GetModule().RequestLeaveRoom(); | Quitter la pièce désignée |
liste des salles | contextInst.GetModule().RequestRoomList(); | Actualiser la liste des salles |
lancement | contextInst.GetModule().RequestLaunchGame(); | Commencer le jeu |
équipe de mise à jour | contextInst.GetModule().RequestUpdatePlayerTeam(roomId, userId, teamId); | Mettre à jour les données d'une pièce |
mettre à jour la carte | contextInst.GetModule().RequestUpdateMap(roomId, mapId, maxPlayerCount); | Changer la carte dans la chambre |
arrêt | Veuillez vérifier la méthode Stop dans Sample.cs et ses sous-classes | fin du jeu |
dessiner une carte | Veuillez vérifier la méthode DrawMap dans Sample.cs et ses sous-classes | Dessiner une carte |
sauvegarde | Veuillez vérifier la méthode SaveReplay dans Sample.cs et ses sous-classes | Enregistrer la lecture (enregistrement) |
représentant du jeu | Veuillez vérifier la méthode PlayReplay dans Sample.cs et ses sous-classes | Enregistrer la lecture (enregistrement) |
Vous trouverez ci-dessous certaines structures de données clés du SDK. Celles-ci peuvent également devenir des structures de protocole. Celles-ci peuvent être sérialisées et désérialisées et prendre en charge les structures de données facultatives de champ qui sont largement utilisées dans le SDK. Il peut être généré par les outils de Docs/Protocols/, sous la forme :
public class PtMyData
{
//Fields
public static byte [ ] Write ( PtMyData value ) { }
public static PtMyData Read ( byte [ ] bytes ) { }
}
Nom de la classe | Champ | Remarque |
---|---|---|
PtFrame | chaîne EntityId Programme de mise à jour PtComponentUpdaterList byte[] NewEntitiesRaw | une donnée d'image clé |
PtFrames | int FrameIdx ListKeyFrames | L'ensemble de toutes les images clés dans une certaine image |
PtCarte | version de chaîne EntitésListe d'entités | données cartographiques |
PtReplay | version de chaîne uint MapId ListeInitEntities Liste des cadres | Données d'enregistrement (lecture) |
Le mécanisme d'enregistrement (lecture) vidéo est le mécanisme le plus distinctif de la technologie de synchronisation d'images, et c'est également un point incontournable. Le SDK a également la capacité de sauvegarder et de charger des vidéos.
Le simulateur de synchronisation de trames est un élément clé du SDK pour effectuer la synchronisation de trames. Le point clé est que tous les appareils doivent maintenir un nombre constant de trames après un certain temps après le démarrage. Cela nécessite un étalonnage DateTime à chaque tick. la relation entre le time lapse local et le TICK logique. Le code détaillé peut être consulté en ouvrant le fichier SimulationController.cs.