├── Entities ├── Client.cpp └── Client.h ├── Makefile ├── README.md ├── Unity ├── Position.cpp ├── Position.h ├── Rotation.cpp └── Rotation.h ├── Util ├── InterfaceManager.cpp ├── InterfaceManager.h ├── NetworkManager.cpp └── NetworkManager.h ├── gif └── demonstration.gif ├── network_server └── network_server.cpp /Entities/Client.cpp: -------------------------------------------------------------------------------- 1 | #include "Client.h" 2 | 3 | Client::Client(SmartSocket socket, std::shared_ptr i_manager) { 4 | sock = socket; 5 | // For broadcast 6 | this->manager = i_manager; 7 | 8 | // Create UUID 9 | boost::uuids::uuid uuid = boost::uuids::random_generator()(); 10 | id = to_string(uuid); 11 | std::cout << "Client " << id << " connected." << std::endl; 12 | 13 | // Define position and rotation 14 | this->position = SmartPosition(new Position(0, 0, 0)); 15 | this->rotation = SmartRotation(new Rotation(0, 1, 0, 0)); 16 | 17 | // Start client thread 18 | std::thread(&Client::session, this).detach(); 19 | } 20 | 21 | void Client::send_to_client(std::string json_str) { 22 | try { 23 | // Add json length at the start of the message 24 | /*unsigned int json_length = json_str.size(); 25 | //std::vector length = int_to_bytes(json_length); 26 | unsigned char bytes[4]; 27 | 28 | bytes[0] = json_length & 0xFF; 29 | bytes[1] = (json_length >> 8) & 0xFF; 30 | bytes[2] = (json_length >> 16) & 0xFF; 31 | bytes[3] = (json_length >> 24) & 0xFF; 32 | 33 | char* length = reinterpret_cast(bytes); 34 | char* json = const_cast(json_str.c_str()); 35 | std::string total(std::string(length) + json);*/ 36 | 37 | const char* data = json_str.c_str(); 38 | size_t data_length = std::strlen(data); 39 | boost::asio::write(*sock, boost::asio::buffer(data, data_length)); 40 | } catch (std::exception& e) { 41 | std::cerr << "Exception in send_to_client(): " << e.what() << "\n"; 42 | } 43 | } 44 | 45 | // Session thread 46 | void Client::session() { 47 | try { 48 | for (;;) { 49 | char data[1024]; 50 | boost::system::error_code error; 51 | size_t size = sock->read_some(boost::asio::buffer(data), error); 52 | 53 | if (error == boost::asio::error::eof) { 54 | puts("Connection closed cleanly by peer"); 55 | break; 56 | } else if (error) { 57 | puts("Some other error"); 58 | throw boost::system::system_error(error); 59 | } 60 | 61 | std::string json(data, size); 62 | std::cout << json << std::endl; 63 | 64 | tree ptree = get_ptree(json); 65 | update_transform(ptree); 66 | manager->broadcast(id, json); 67 | } 68 | } catch (std::exception& e) { 69 | std::cerr << "Exception in thread: " << e.what() << "\n"; 70 | } 71 | std::cout << "Client " << id << " closed its connection." << std::endl; 72 | sock->close(); 73 | manager->remove_client(id); 74 | } 75 | 76 | void Client::update_transform(tree ptree) { 77 | std::string action = ptree.get_child("action").get_value(); 78 | if (action != "logout" && action != "animation") { 79 | position->x = std::stof(ptree.get_child("position").get_child("X").get_value()); 80 | position->y = std::stof(ptree.get_child("position").get_child("Y").get_value()); 81 | position->z = std::stof(ptree.get_child("position").get_child("Z").get_value()); 82 | 83 | rotation->x = std::stof(ptree.get_child("rotation").get_child("X").get_value()); 84 | rotation->y = std::stof(ptree.get_child("rotation").get_child("Y").get_value()); 85 | rotation->z = std::stof(ptree.get_child("rotation").get_child("Z").get_value()); 86 | rotation->w = std::stof(ptree.get_child("rotation").get_child("W").get_value()); 87 | } 88 | } 89 | 90 | tree Client::get_ptree(std::string json) { 91 | std::stringstream ss; 92 | ss << json; 93 | 94 | boost::property_tree::ptree ptree; 95 | boost::property_tree::read_json(ss, ptree); 96 | return ptree; 97 | } 98 | 99 | Client::~Client() { 100 | } 101 | 102 | std::string Client::get_id() const { 103 | return id; 104 | } 105 | 106 | std::shared_ptr Client::get_position() const { 107 | return position; 108 | } 109 | 110 | std::shared_ptr Client::get_rotation() const { 111 | return rotation; 112 | } 113 | -------------------------------------------------------------------------------- /Entities/Client.h: -------------------------------------------------------------------------------- 1 | #ifndef ENTITIES_CLIENT_H_ 2 | #define ENTITIES_CLIENT_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "../Unity/Position.h" 19 | #include "../Unity/Rotation.h" 20 | #include "../Util/InterfaceManager.h" 21 | 22 | using boost::asio::ip::tcp; 23 | 24 | typedef boost::property_tree::ptree tree; 25 | typedef boost::shared_ptr SmartSocket; 26 | typedef std::shared_ptr SmartPosition; 27 | typedef std::shared_ptr SmartRotation; 28 | 29 | class Client { 30 | private: 31 | SmartSocket sock; 32 | std::string id; 33 | std::shared_ptr manager; 34 | std::shared_ptr position; 35 | std::shared_ptr rotation; 36 | 37 | void session(); 38 | void update_transform(tree); 39 | tree get_ptree(std::string); 40 | public: 41 | void send_to_client(std::string); 42 | 43 | // Constructor and deconstructor 44 | Client(SmartSocket, std::shared_ptr); 45 | virtual ~Client(); 46 | 47 | // Getters and Setters 48 | std::string get_id() const; 49 | std::shared_ptr get_position() const; 50 | std::shared_ptr get_rotation() const; 51 | }; 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS = -O3 -g -Wall -fmessage-length=0 -std=c++11 -fopenmp 2 | 3 | OBJS = network_server.o Entities/Client.o Unity/Position.o Unity/Rotation.o Util/NetworkManager.o Util/InterfaceManager.o 4 | 5 | LIBS = -fopenmp -L/usr/lib -lmysqlcppconn -lboost_thread -lboost_system -lboost_chrono -pthread 6 | 7 | TARGET = network_server 8 | 9 | $(TARGET): $(OBJS) 10 | $(CXX) -o $(TARGET) $(OBJS) $(LIBS) 11 | 12 | all: $(TARGET) 13 | 14 | clean: 15 | rm -f $(OBJS) $(TARGET) 16 | 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Multiplayer game server for Unity games 2 | 3 | ## Simple example 4 | An example of movement and and animation sync. In this illustration, the server is running on Ubuntu while the clients are running on Windows 10 5 | 6 | ![](gif/demonstration.gif) 7 | -------------------------------------------------------------------------------- /Unity/Position.cpp: -------------------------------------------------------------------------------- 1 | #include "Position.h" 2 | 3 | Position::Position(float x, float y, float z) { 4 | this->x = x; 5 | this->y = y; 6 | this->z = z; 7 | } 8 | 9 | void Position::print_position() { 10 | std::cout << "Position" << " x: " << x << " y: " << y << " z: " << z << std::endl; 11 | } 12 | 13 | Position::~Position() { 14 | } 15 | -------------------------------------------------------------------------------- /Unity/Position.h: -------------------------------------------------------------------------------- 1 | #ifndef UNITY_POSITION_H_ 2 | #define UNITY_POSITION_H_ 3 | 4 | #include 5 | #include 6 | 7 | class Position { 8 | public: 9 | float x, y, z; 10 | 11 | void print_position(); 12 | 13 | Position(float, float, float); 14 | virtual ~Position(); 15 | }; 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /Unity/Rotation.cpp: -------------------------------------------------------------------------------- 1 | #include "Rotation.h" 2 | 3 | Rotation::Rotation(float x, float y, float z, float w) { 4 | this->x = x; 5 | this->y = y; 6 | this->z = z; 7 | this->w = w; 8 | } 9 | 10 | void Rotation::print_rotation() { 11 | std::cout << "Rotation" << " x: " << x << " y: " << y << " z: " << z << std::endl; 12 | } 13 | 14 | Rotation::~Rotation() { 15 | } 16 | 17 | -------------------------------------------------------------------------------- /Unity/Rotation.h: -------------------------------------------------------------------------------- 1 | #ifndef UNITY_ROTATION_H_ 2 | #define UNITY_ROTATION_H_ 3 | 4 | #include 5 | #include 6 | 7 | class Rotation { 8 | public: 9 | float x, y, z, w; 10 | 11 | void print_rotation(); 12 | 13 | Rotation(float, float, float, float); 14 | virtual ~Rotation(); 15 | }; 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /Util/InterfaceManager.cpp: -------------------------------------------------------------------------------- 1 | #include "InterfaceManager.h" 2 | 3 | InterfaceManager::~InterfaceManager() { 4 | } 5 | 6 | void InterfaceManager::broadcast(std::string id, std::string msg) { 7 | } 8 | 9 | void InterfaceManager::remove_client(std::string id) { 10 | } 11 | -------------------------------------------------------------------------------- /Util/InterfaceManager.h: -------------------------------------------------------------------------------- 1 | #ifndef UTIL_INTERFACEMANAGER_H_ 2 | #define UTIL_INTERFACEMANAGER_H_ 3 | 4 | #include 5 | 6 | class InterfaceManager { 7 | public: 8 | virtual ~InterfaceManager(); 9 | virtual void broadcast(std::string, std::string); 10 | virtual void remove_client(std::string); 11 | }; 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /Util/NetworkManager.cpp: -------------------------------------------------------------------------------- 1 | #include "NetworkManager.h" 2 | 3 | NetworkManager::NetworkManager(int dist) { 4 | this->max_dist_update = dist; 5 | } 6 | 7 | // Virtual methods 8 | void NetworkManager::broadcast(std::string id, std::string msg) { 9 | std::shared_ptr c1; 10 | for (auto client : clients) 11 | if (client->get_id() == id) 12 | c1 = client; 13 | for (auto client : clients) { 14 | if (c1 != client) { 15 | float dist = get_distance(c1, client); 16 | if (dist < max_dist_update) 17 | client->send_to_client(msg); 18 | } 19 | } 20 | } 21 | 22 | void NetworkManager::remove_client(std::string id) { 23 | for (unsigned int i = 0; i < clients.size(); i++) { 24 | if (clients[i]->get_id() == id) { 25 | clients.erase(clients.begin() + i); 26 | break; 27 | } 28 | } 29 | } 30 | 31 | // Custom methods 32 | void NetworkManager::add_client(std::shared_ptr client) { 33 | clients.push_back(client); 34 | std::cout << "Client added to the list of clients" << std::endl; 35 | std::cerr << "Current number of the clients: " << clients.size() << std::endl; 36 | send_start(client); 37 | send_new_client_to_all(client); 38 | send_all_to_new_client(client); 39 | } 40 | 41 | void NetworkManager::send_start(std::shared_ptr client) { 42 | std::string json = get_transform_info(client, "start"); 43 | client->send_to_client(json); 44 | std::cout << "Start sent." << std::endl; 45 | std::cout << json << std::endl; 46 | } 47 | 48 | void NetworkManager::send_new_client_to_all(std::shared_ptr client) { 49 | for (auto connected_client : clients) { 50 | if (connected_client != client) { 51 | std::string json = get_transform_info(client, "newPlayer"); 52 | connected_client->send_to_client(json); 53 | } 54 | } 55 | } 56 | 57 | void NetworkManager::send_all_to_new_client(std::shared_ptr client) { 58 | for (auto connected_client : clients) { 59 | if (connected_client != client) { 60 | std::cout << connected_client->get_position()->x << std::endl; 61 | std::cout << connected_client->get_position()->y << std::endl; 62 | std::cout << connected_client->get_position()->z << std::endl; 63 | 64 | std::string json = get_transform_info(connected_client, "newPlayer"); 65 | client->send_to_client(json); 66 | } 67 | } 68 | } 69 | 70 | float NetworkManager::get_distance(std::shared_ptr c1, std::shared_ptr c2) { 71 | float x = std::pow(c1->get_position()->x, 2) + std::pow(c2->get_position()->x, 2); 72 | float y = std::pow(c1->get_position()->y, 2) + std::pow(c2->get_position()->y, 2); 73 | float z = std::pow(c1->get_position()->z, 2) + std::pow(c2->get_position()->z, 2); 74 | return std::sqrt(x + y + z); 75 | } 76 | 77 | std::string NetworkManager::get_transform_info(std::shared_ptr client, std::string action) { 78 | tree pos; 79 | pos.put("X", client->get_position()->x); 80 | pos.put("Y", client->get_position()->y); 81 | pos.put("Z", client->get_position()->z); 82 | tree rot; 83 | rot.put("X", client->get_rotation()->x); 84 | rot.put("Y", client->get_rotation()->y); 85 | rot.put("Z", client->get_rotation()->z); 86 | rot.put("W", client->get_rotation()->w); 87 | 88 | tree json; 89 | json.put("action", action); 90 | json.put("id", client->get_id()); 91 | json.add_child("position", pos); 92 | json.add_child("rotation", rot); 93 | 94 | std::stringstream ss; 95 | boost::property_tree::json_parser::write_json(ss, json); 96 | std::string json_str = ss.str(); 97 | 98 | return json_str; 99 | } 100 | 101 | NetworkManager::~NetworkManager() { 102 | } 103 | 104 | -------------------------------------------------------------------------------- /Util/NetworkManager.h: -------------------------------------------------------------------------------- 1 | #ifndef UTIL_NETWORKMANAGER_H_ 2 | #define UTIL_NETWORKMANAGER_H_ 3 | 4 | #include 5 | 6 | #include "../Entities/Client.h" 7 | #include "InterfaceManager.h" 8 | 9 | typedef boost::property_tree::ptree tree; 10 | 11 | class NetworkManager: public virtual InterfaceManager { 12 | private: 13 | int max_dist_update; 14 | std::vector> clients; 15 | public: 16 | virtual void broadcast(std::string, std::string); 17 | virtual void remove_client(std::string); 18 | 19 | void add_client(std::shared_ptr); 20 | void send_start(std::shared_ptr); 21 | void send_new_client_to_all(std::shared_ptr); 22 | void send_all_to_new_client(std::shared_ptr); 23 | float get_distance(std::shared_ptr, std::shared_ptr); 24 | std::string get_transform_info(std::shared_ptr, std::string); 25 | NetworkManager(int); 26 | virtual ~NetworkManager(); 27 | }; 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /gif/demonstration.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Willtl/multiplayer-server-unity/8afa7a9ed5826468e10f6d3ebdb1e8b7a4a403db/gif/demonstration.gif -------------------------------------------------------------------------------- /network_server: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Willtl/multiplayer-server-unity/8afa7a9ed5826468e10f6d3ebdb1e8b7a4a403db/network_server -------------------------------------------------------------------------------- /network_server.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "Entities/Client.h" 10 | #include "Util/NetworkManager.h" 11 | 12 | using boost::asio::ip::tcp; 13 | 14 | void run_server(boost::asio::io_service&, tcp::endpoint&); 15 | 16 | int main(int argc, char* argv[]) { 17 | try { 18 | boost::asio::io_service io_service; 19 | tcp::endpoint end_point(tcp::v6(), 9990); 20 | run_server(io_service, end_point); 21 | io_service.stop(); 22 | } catch (std::exception& e) { 23 | std::cerr << "Exception: " << e.what() << "\n"; 24 | } 25 | return 0; 26 | } 27 | 28 | inline void run_server(boost::asio::io_service& io_service, tcp::endpoint& end_point) { 29 | std::shared_ptr manager(new NetworkManager(50)); 30 | tcp::acceptor acceptor(io_service, end_point); 31 | for (;;) { 32 | std::cerr << "Waiting for new connection." << std::endl; 33 | acceptor.listen(); 34 | SmartSocket sock(new tcp::socket(io_service)); 35 | acceptor.accept(*sock); 36 | std::cerr << "New connection accepted." << std::endl; 37 | 38 | std::shared_ptr client(new Client(std::move(sock), manager)); 39 | manager->add_client(client); 40 | } 41 | acceptor.close(); 42 | } 43 | --------------------------------------------------------------------------------