Differences

This shows you the differences between two versions of the page.

Link to this comparison view

pjv:laboratoare:2025:a05 [2025/09/29 08:26]
alexandru.gradinaru created
pjv:laboratoare:2025:a05 [2025/12/10 12:46] (current)
alexandru.gradinaru [Cerinte]
Line 1: Line 1:
-===== 5. Multiplayer dedicated ​server =====+===== 5. Servere multiplayer dedicate ===== 
 + 
 +<​hidden>​ 
 + 
 +laborator bonus 
 + 
 +de explicat sumar: 
 +  * - ce e darkrift si unde se gasesc resurse 
 + 
 + ​github [[https://​github.com/​DarkRiftNetworking/​DarkRift ]] 
 +si plugin pt client [[https://​assetstore.unity.com/​packages/​tools/​network/​darkrift-networking-2-95309]] 
 + 
 +  
 +[10.12.2025,​ 11:26:43] Alex Gradinaru: hmm, ma mai gasit aici un tutorial, care foloseste si playfab pentru lobby 
 +[10.12.2025,​ 11:26:43] Alex Gradinaru: https://​dev.to/​robodoig/​unity-multiplayer-bottom-to-top-46cj 
 +[10.12.2025,​ 11:28:39] Alex Gradinaru: pare interesant, dar probabil pt iteratia urmatoare de lab 🙂 
 +[10.12.2025,​ 11:28:55] Alex Gradinaru: vad ca e si hosted on playfab 
 +[10.12.2025,​ 11:29:50] Alex Gradinaru: si inca un tutorial interesant, cu predictii pt FPS https://​lukestampfli.github.io/​EmbeddedFPSExample/​guide/​introduction.html 
 +[10.12.2025,​ 11:30:04] Andrei Lapusteanu: https://​lukestampfli.github.io/​EmbeddedFPSExample/​guide/​networking-discussion.html#​networking-architectures 
 +[10.12.2025,​ 11:30:15] Andrei Lapusteanu: Haha de pe acelasi tutorial :) 
 +[10.12.2025,​ 11:31:42] Alex Gradinaru: nu mai e accesibila documentatia,​ dar au mai ramas niste links utile prin github 
 +https://​github.com/​DarkRiftNetworking/​DarkRift/​wiki/​%23-Useful-Links 
 +[10.12.2025,​ 11:31:51] Alex Gradinaru: nu gasesc inca legat de chat 
 +[10.12.2025,​ 11:32:44] Alex Gradinaru: ah, e tot pe git 
 +https://​github.com/​DarkRiftNetworking/​DarkRift.Documentation 
 +[10.12.2025,​ 11:34:07] Alex Gradinaru: mda, tot nu are ceva legat de chat, dar cred ca foloseam doar sistemul de message si broadcast simplu 
 + 
 +alte idei : https://​chatgpt.com/​share/​6935b217-e87c-8004-9a38-d216be4d634c 
 + 
 +</​hidden>​ 
 + 
 +==== Cerinte ===== 
 + 
 +Realizarea unui joc multiplayer de tip social, prin folosirea/​implementarea unui server ​multiplayer dedicat: 
 + 
 + 
 +  * Player avatar sincronizat la nivel de pozitie si animatii: minim 2 playeri/​clienti 
 +  * Comunicare chat cu interfata grafica 
 + 
 + 
 + 
 +  
 +===== Documentatie video ====== 
 + 
 + 
 +===== Documentatie text ====== 
 + 
 +Pentru acest laborator vom folosi [[https://​github.com/​DarkRiftNetworking/​DarkRift|DarkRift 2]], un server dedicat, modular, care implementeaza protocoale de comunicare ca TCP, UDP, dar si cu extensii pentru WebSocket sau RUDP. 
 +Pentru integrarea cu Unity, se poate folosi pachetul din Asset Store [[https://​assetstore.unity.com/​packages/​tools/​network/​darkrift-networking-2-95309|DarkRift Networking 2]] 
 + 
 +==== Server dedicat ===== 
 + 
 + 
 +Folosind un server dedicat trebuie gestionate mai multe elemente: 
 +  * conexiunile si lista curenta de clienti conectati 
 +  * datele stocate despre player 
 +  * mesajele de update 
 + 
 +DarkRift foloseste un sistem modular, astfel ca puteti programa mecanica serverului fie direct, fie bazat pe plugin-uri C#, pentru a nu recompila la fiecare modificare tot serverul. 
 + 
 +Exemplu minimal: 
 + 
 +<​code>​ 
 +using DarkRift; 
 +using DarkRift.Server;​ 
 + 
 +ServerSpawnData spawnData = ServerSpawnData.CreateFromXml("​Server.config"​);​ 
 + 
 +var server = new DarkRiftServer(spawnData);​ 
 + 
 +void Client_MessageReceived(object?​ sender, MessageReceivedEventArgs e) 
 +
 +    using Message message = e.GetMessage();​ 
 +    using DarkRiftReader reader = message.GetReader();​ 
 +    Console.WriteLine("​Received a message from the client: " + reader.ReadString());​ 
 +
 + 
 +void ClientManager_ClientConnected(object?​ sender, ClientConnectedEventArgs e) 
 +
 +    e.Client.MessageReceived += Client_MessageReceived;​ 
 + 
 +    using DarkRiftWriter writer = DarkRiftWriter.Create();​ 
 +    writer.Write("​World of Hel!"​);​ 
 + 
 +    using Message secretMessage = Message.Create(666,​ writer); 
 +    e.Client.SendMessage(secretMessage,​ SendMode.Reliable);​ 
 +
 + 
 +server.ClientManager.ClientConnected += ClientManager_ClientConnected;​ 
 + 
 +server.StartServer();​ 
 + 
 +Console.ReadKey();​ // Wait until key press. Not necessary in Unity. 
 + 
 +</​code>​ 
 + 
 +cu un sistem de configurare in XML 
 + 
 +<​code>​ 
 +<?xml version="​1.0"​ encoding="​utf-8"​ ?> 
 +<!-- 
 +  Configuring DarkRift server to listen at ports TCP 4296 and UDP 4297. 
 +--> 
 +<​configuration xmlns:​xsi="​http://​www.w3.org/​2001/​XMLSchema-instance"​ xsi:​noNamespaceSchemaLocation="​https://​www.darkriftnetworking.com/​DarkRift2/​Schemas/​2.3.1/​Server.config.xsd">​ 
 +  <server maxStrikes="​5"​ /> 
 +   
 +  <​pluginSearch/>​ 
 +  
 +  <​logging>​ 
 +    <​logWriters>​ 
 +      <​logWriter name="​ConsoleWriter1"​ type="​ConsoleWriter"​ levels="​trace,​ info, warning, error, fatal">​ 
 +        <​settings useFastAnsiColoring="​false"​ /> 
 +      </​logWriter>​ 
 +    </​logWriters>​ 
 +  </​logging>​ 
 + 
 +  <plugins loadByDefault="​false"/>​ 
 + 
 +  <data directory="​Data/"/>​ 
 + 
 +  <​listeners>​ 
 +    <​listener name="​DefaultNetworkListener"​ type="​BichannelListener"​ address="​0.0.0.0"​ port="​4296">​ 
 +      <​settings noDelay="​true"​ udpPort="​4297"​ /> 
 +    </​listener>​ 
 +  </​listeners>​ 
 +</​configuration>​ 
 + 
 +</​code>​ 
 + 
 +==== Client ===== 
 + 
 +Pentru realizarea clientului puteti folosi direct pachetul din Asset Store [[https://​assetstore.unity.com/​packages/​tools/​network/​darkrift-networking-2-95309|DarkRift Networking 2]] 
 + 
 +Exemplu de conectare si trimitere de mesaj simplu: 
 +<​code>​ 
 + 
 +using DarkRift; 
 +using DarkRift.Client;​ 
 +using System.Net;​ 
 + 
 +var client = new DarkRiftClient();​ 
 + 
 +void Client_MessageReceived(object?​ sender, MessageReceivedEventArgs e) 
 +
 +    using Message message = e.GetMessage();​ 
 +    using DarkRiftReader reader = message.GetReader();​ 
 +    Console.WriteLine("​Received a message from the server: " + reader.ReadString());​ 
 +
 + 
 +client.MessageReceived += Client_MessageReceived;​ 
 + 
 +client.Connect(IPAddress.Loopback,​ tcpPort:​4296,​ udpPort:​4297,​ noDelay:​true);​ 
 + 
 +Console.WriteLine("​Connected!"​);​ 
 + 
 +using DarkRiftWriter writer = DarkRiftWriter.Create();​ 
 +writer.Write("​Hello world!"​);​ 
 + 
 +using Message secretMessage = Message.Create(1337,​ writer); 
 +client.SendMessage(secretMessage,​ SendMode.Reliable);​ 
 + 
 +Console.ReadKey();​ // Wait until key press. Not necessary in Unity. 
 + 
 +</​code>​ 
 + 
 +Puteti folosi tag-uri pentru a delimita diverse tipuri de mesaje. 
 +De exemplu pentru actualizare de miscare puteti folosi tagul 1 si pentru chat puteti folosi tag-ul 10 
 + 
 +Exemplu de trimitere mesaj de chat 
 +<​code>​ 
 +    //This will be called when the user presses enter in the input field 
 +    public void MessageEntered() 
 +    { 
 +        //Check we have a client to send from 
 +        if (client == null) 
 +        { 
 +            Debug.LogError("​No client assigned to Chat component!"​);​ 
 +            return; 
 +        } 
 + 
 +        //First we need to build a DarkRiftWriter to put the data we want to send in, it'll default to Unicode  
 +        //encoding so we don't need to worry about that 
 +        using (DarkRiftWriter writer = DarkRiftWriter.Create()) 
 +        { 
 +            //We can then write the input text into it 
 +            writer.Write(input.text);​ 
 + 
 +            //Next we construct a message, in this case we can just use a default tag because there is nothing fancy 
 +            //that needs to happen before we read the data. 
 +            using (Message message = Message.Create(10,​ writer)) 
 +            { 
 +                //Finally we send the message to everyone connected! 
 +                client.SendMessage(message,​ SendMode.Reliable);​ 
 +            } 
 +        } 
 +    } 
 +</​code>​ 
 + 
 +Pentru actualizare miscarii, trebuie sa tinem cont de ficare client in parte:  
 +  * trimtem actualizari de la input doar pentru clientul curent 
 +<​code>​ 
 +void UpdateNetworkPosition() { 
 +        if ( 
 +            Vector3.Distance(lastPosition,​ transform.position) > moveDistance || 
 +            Vector3.Distance(lastRotation,​ transform.eulerAngles) > moveDistance 
 +             
 +            ) 
 +        { 
 +            using (DarkRiftWriter writer = DarkRiftWriter.Create()) 
 +            { 
 +                writer.Write(transform.position.x);​ 
 +                writer.Write(transform.position.y);​ 
 +                writer.Write(transform.position.z);​ 
 +                writer.Write(transform.eulerAngles.x);​ 
 +                writer.Write(transform.eulerAngles.y);​ 
 +                writer.Write(transform.eulerAngles.z);​ 
 + 
 +                using (Message message = Message.Create(1,​ writer)) 
 +                    Client.SendMessage(message,​ SendMode.Unreliable);​ 
 +            } 
 + 
 +            lastPosition = transform.position;​ 
 +            lastRotation = transform.eulerAngles;​ 
 +        } 
 +    } 
 +</​code>​ 
 + 
 +  * instantiem diferit playerii: unul controlabil din input local, pentru clientul curent, si ceilalti doar sincronizati la actualizarile din server 
 +<​code>​ 
 +void SpawnPlayer(object sender, MessageReceivedEventArgs e) 
 +    { 
 + 
 +        using (Message message = e.GetMessage()) 
 +        using (DarkRiftReader reader = message.GetReader()) 
 +        { 
 +            if (message.Tag == 0) 
 +            { 
 +                while (reader.Position < reader.Length) 
 +                { 
 +                     
 +                    ushort id = reader.ReadUInt16();​ 
 +               
 + 
 +                    Vector3 position = new Vector3(reader.ReadSingle(),​ reader.ReadSingle(),​ reader.ReadSingle());​ 
 +                    Vector3 euler = new Vector3(reader.ReadSingle(),​ reader.ReadSingle(),​ reader.ReadSingle());​ 
 +                     
 +                    GameObject obj; 
 +  
 +                    if (id == client.ID) 
 +                    { 
 +                        // instantiate local user 
 +                        obj = Instantiate(localPrefab,​ position, Quaternion.identity) as GameObject;​ 
 +                      
 +                    } 
 +                    else 
 +                    { 
 +                        // instantiate network user 
 +                        obj = Instantiate(networkPrefab,​ position, Quaternion.identity) as GameObject;​ 
 +                         
 +                        //keep a list with all network players 
 +                        networkPlayerManager.Add(id,​ playObj); 
 +                    } 
 + 
 +                     
 +                } 
 +            } 
 +        } 
 +    } 
 +     
 +</​code>​ 
 +  * actualizam sincronizarile din server pentru toti playerii. Pentru asta in general avem nevoie sa tinem minte toata lista de clienti  
 +<​code>​ 
 +Dictionary<​ushort,​ NetworkPlayerObj>​ networkPlayers = new Dictionary<​ushort,​ NetworkPlayerObj>​();​ 
 + 
 +void MessageReceived(object sender, MessageReceivedEventArgs e) 
 +    { 
 +        using (Message message = e.GetMessage() as Message) 
 +        { 
 +            Debug.Log("​network message "); 
 +            if (message.Tag == 1) 
 +            { 
 +                // Debug.Log("​position message "); 
 +                using (DarkRiftReader reader = message.GetReader()) 
 +                { 
 +                    ushort id = reader.ReadUInt16();​ 
 +                    Vector3 newPosition = new Vector3(reader.ReadSingle(),​ reader.ReadSingle(),​ reader.ReadSingle());​ 
 +                    Vector3 newEulerAngles = new Vector3(reader.ReadSingle(),​ reader.ReadSingle(),​ reader.ReadSingle());​ 
 + 
 +                    if (networkPlayers.ContainsKey(id)) 
 +                    { 
 +                        // Debug.Log("​new network position: "​+newPosition);​ 
 +                        networkPlayers[id].movePosition = newPosition;​ 
 +                        networkPlayers[id].currentEulerAngles = newEulerAngles;​ 
 +                    } 
 +                } 
 +            } 
 +        } 
 +    } 
 +</​code>​ 
 +==== Server Plugin ===== 
 + 
 +Putem folosi si un plugin modificand in configurare in felul urmator: 
 + 
 +<​code>​ 
 +  <!-- 
 +    Specifies where DarkRift should look for plugins. 
 +  --> 
 +  <​pluginSearch>​ 
 +    <​pluginSearchPath src="​Plugins/"​ createDir="​true"​ /> 
 +    <​pluginSearchPath src="​LogWriters/"​ /> 
 +    <​pluginSearchPath src="​NetworkListeners/"​ /> 
 +  </​pluginSearch>​ 
 +   <​plugins loadByDefault="​true">​ 
 +    <!-- Example: 
 +    <plugin type="​Sniffer"​ load="​false"​ /> 
 +    --> 
 +  </​plugins>​ 
 +</​code>​ 
 + 
 +In acest caz veti face un proiect separat pentru plugin si il veti compila intr-un dll, care va fi pus/​actualizat in folderul de Plugins. 
 + 
 +<​code>​ 
 +using DarkRift; 
 +using DarkRift.Server;​ 
 +using System; 
 +using System.Collections.Generic;​ 
 + 
 +public class NetPlugin : Plugin 
 +
 + 
 +    Dictionary<​IClient,​ Player> players = new Dictionary<​IClient,​ Player>​();​ 
 + 
 +    public override bool ThreadSafe => false; 
 + 
 +    public override Version Version => new Version(1, 2, 5); 
 + 
 +    public NetPlugin(PluginLoadData pluginLoadData) : base(pluginLoadData) 
 +    { 
 +        ClientManager.ClientConnected += ClientConnected;​ 
 +        ClientManager.ClientDisconnected += ClientDisconnected;​ 
 +    } 
 +
 +</​code>​ 
 +unde clasa Player reprezinta datele stocate despre player. Spre exemplu se pot stoca date legate de pozitie, rotatie sau id de utilizator. 
 +<​code>​ 
 +public struct Player 
 +
 +    public ushort ID; 
 +    public float X, Y, Z; 
 +    public float rotX, rotY, rotZ; 
 +
 +</​code>​ 
 + 
 +In momentul in care se conecteaza un client, trebuie realizate urmatoarele lucruri: 
 +  * instantierea Player-ului in server 
 +  * notificarea tuturor celorlalti clienti conectati de conectarea unui nou player 
 +  * trimiterea de informatii despre ceilalti clienti, clientului nou conectat 
 +  * ascultarea de mesaje de actualizare 
 + 
 +<code c#> 
 +void ClientConnected(object sender, ClientConnectedEventArgs e) 
 +    { 
 +        //new player instance 
 +        Player newPlayer = new Player(); 
 +        newPlayer.ID = e.Client.ID;​ 
 +        newPlayer.X = 0; 
 +        newPlayer.Y = 3; 
 +        newPlayer.Z = 0; 
 +        newPlayer.rotZ = 0; 
 +        newPlayer.rotY = 0; 
 +        newPlayer.rotZ = 0; 
 + 
 +        using (DarkRiftWriter newPlayerWriter = DarkRiftWriter.Create()) 
 +        { 
 +            newPlayerWriter.Write(newPlayer.ID);​ 
 +            newPlayerWriter.Write(newPlayer.X);​ 
 +            newPlayerWriter.Write(newPlayer.Y);​ 
 +            newPlayerWriter.Write(newPlayer.Z);​ 
 +            newPlayerWriter.Write(newPlayer.rotX);​ 
 +            newPlayerWriter.Write(newPlayer.rotY);​ 
 +            newPlayerWriter.Write(newPlayer.rotZ);​ 
 + 
 +            //notify other clients 
 +            using (Message newPlayerMessage = Message.Create(0,​ newPlayerWriter)) 
 +            { 
 +                foreach (IClient client in ClientManager.GetAllClients().Where(x => x != e.Client)) 
 +                    client.SendMessage(newPlayerMessage,​ SendMode.Reliable);​ 
 +            } 
 +        } 
 + 
 +        players.Add(e.Client,​ newPlayer);​ 
 + 
 +        using (DarkRiftWriter playerWriter = DarkRiftWriter.Create()) 
 +        { 
 +            foreach (Player player in players.Values) 
 +            { 
 +                playerWriter.Write(player.ID);​ 
 +                playerWriter.Write(player.X);​ 
 +                playerWriter.Write(player.Y);​ 
 +                playerWriter.Write(player.Z);​ 
 +                playerWriter.Write(player.rotX);​ 
 +                playerWriter.Write(player.rotY);​ 
 +                playerWriter.Write(player.rotZ);​ 
 +            } 
 +             
 +            //send all other players data to new client 
 +            using (Message playerMessage = Message.Create(0,​ playerWriter)) 
 +                e.Client.SendMessage(playerMessage,​ SendMode.Reliable);​ 
 +        } 
 + 
 +        e.Client.MessageReceived += MovementMessageReceived;​ 
 +    } 
 +</​code>​ 
 + 
 +==== Actualizare de informatii ===== 
 + 
 +La fiecare actualizare (de ex miscarea personajului) trebuie facut broadcast. 
 + 
 +<​code>​ 
 +void MovementMessageReceived(object sender, MessageReceivedEventArgs e) 
 +    { 
 +        using (Message message = e.GetMessage() as Message) 
 +        { 
 +            if (message.Tag == 1) 
 +            { 
 +                using (DarkRiftReader reader = message.GetReader()) 
 +                { 
 +                    float newX = reader.ReadSingle();​ 
 +                    float newY = reader.ReadSingle();​ 
 +                    float newZ = reader.ReadSingle();​ 
 +                    float newRotX = reader.ReadSingle();​ 
 +                    float newRotY = reader.ReadSingle();​ 
 +                    float newRotZ = reader.ReadSingle();​ 
 + 
 +                    Player player = players[e.Client];​ 
 + 
 +                    player.ID = players[e.Client].ID;​ //AA 
 +                    player.X = newX; 
 +                    player.Y = newY; 
 +                    player.Z = newZ; 
 +                    player.rotX = newRotX; 
 +                    player.rotY = newRotY; 
 +                    player.rotZ = newRotZ; 
 + 
 +                    players[e.Client] = player; //AA 
 + 
 +                    using (DarkRiftWriter writer = DarkRiftWriter.Create()) 
 +                    { 
 +                        writer.Write(player.ID);​ 
 +                        writer.Write(player.X);​ 
 +                        writer.Write(player.Y);​ 
 +                        writer.Write(player.Z);​ 
 +                        writer.Write(player.rotX);​ 
 +                        writer.Write(player.rotY);​ 
 +                        writer.Write(player.rotZ);​ 
 +                        message.Serialize(writer);​ 
 +                    } 
 + 
 +                    foreach (IClient c in ClientManager.GetAllClients().Where(x => x != e.Client)) 
 +                        c.SendMessage(message,​ e.SendMode);​ 
 +                } 
 +            } 
 +        } 
 +    } 
 +   </​code>​ 
 +    
 +    
 + 
 +Pentru sincronizarea de animatii se poate proceda in mai multe feluri, in functie si de modul de gestiune al animatiilor:​ 
 +     * fie se trimite si un indicator de stare al animatiei si se sincornizeaza over network 
 +     * fie se folosesc valori de speed, hieght etc (calculate prin diferenta sau luate/​transmise direct) 
 +==== Chat ===== 
 + 
 +Pentru a gestiona si mesajele chat, puteti adauga un event suplimentar in momentul in care se conecteaza clientul 
 + 
 +<​code>​ 
 +e.Client.MessageReceived += ChatMessageReceived;​ 
 +</​code>​ 
 + 
 +apoi sa ascultati mesajele si se face broadcast 
 + 
 +<​code>​ 
 +    void ChatMessageReceived(object sender, MessageReceivedEventArgs e) 
 +    { 
 +        using (Message message = e.GetMessage() as Message) 
 +        { 
 +            if (message.Tag == 10) 
 +            { 
 +                using (DarkRiftReader reader = message.GetReader()) 
 +                { 
 +                    using (DarkRiftWriter writer = DarkRiftWriter.Create()) 
 +                    { 
 +                        writer.Write(reader.ReadString());​ 
 +                    } 
 + 
 +                    foreach (IClient c in ClientManager.GetAllClients()) 
 +                        c.SendMessage(message,​ e.SendMode);​ 
 +                } 
 +            } 
 +        } 
 +    } 
 +</​code>​ 
 + 
 + 
 +==== Deconectarea ===== 
 +<​code>​ 
 + 
 +void ClientDisconnected(object sender, ClientDisconnectedEventArgs e) 
 +    { 
 +        players.Remove(e.Client);​ 
 + 
 +        using (DarkRiftWriter writer ​DarkRiftWriter.Create()) 
 +        { 
 +            writer.Write(e.Client.ID);​ 
 + 
 +            using (Message message ​Message.Create(2,​ writer)) 
 +            { 
 +                foreach (IClient client in ClientManager.GetAllClients()) 
 +                    client.SendMessage(message,​ SendMode.Reliable);​ 
 +            } 
 +        } 
 +    } 
 +     
 + </​code>​
pjv/laboratoare/2025/a05.1759123571.txt.gz · Last modified: 2025/09/29 08:26 by alexandru.gradinaru
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0