This shows you the differences between two versions of the page.
|
pjv:laboratoare:2025:a05 [2025/12/10 11:54] alexandru.gradinaru |
pjv:laboratoare:2025:a05 [2025/12/10 12:46] (current) alexandru.gradinaru [Cerinte] |
||
|---|---|---|---|
| Line 32: | Line 32: | ||
| ==== Cerinte ===== | ==== Cerinte ===== | ||
| - | Realizarea unui joc multiplayer de tip social: | + | 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 | ||
| - | <hidden> | ||
| - | * Player avatar sincronizat la nivel de pozitie si animatii | ||
| - | * Comunicare chat sau voice | ||
| - | * Server multiplayer dedicat, implementare proprie | ||
| - | </hidden> | ||
| Line 45: | Line 45: | ||
| ===== Documentatie text ====== | ===== 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 ===== | ==== Server dedicat ===== | ||
| - | [[https://github.com/DarkRiftNetworking/DarkRift|DarkRift 2]] este 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]] | ||
| Folosind un server dedicat trebuie gestionate mai multe elemente: | Folosind un server dedicat trebuie gestionate mai multe elemente: | ||
| Line 56: | Line 57: | ||
| * mesajele de update | * mesajele de update | ||
| - | DarkRift foloseste un sistem modular, bazat pe plugin-uri C#, pentru implementarea serverului. | + | 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. |
| - | Astfel, putem defini un plugin in felul urmator: | + | |
| + | Exemplu minimal: | ||
| <code> | <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 | public class NetPlugin : Plugin | ||
| { | { | ||
| Line 197: | Line 464: | ||
| } | } | ||
| </code> | </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 ===== | ==== Chat ===== | ||