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:35]
alexandru.gradinaru
pjv:laboratoare:2025:a05 [2025/12/10 12:46] (current)
alexandru.gradinaru [Cerinte]
Line 1: Line 1:
 ===== 5. Servere multiplayer dedicate ===== ===== 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.1759124142.txt.gz · Last modified: 2025/09/29 08:35 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