Adam's Lair Forum

game development and casual madness
It is currently 2017/03/28, 00:33

All times are UTC + 1 hour [ DST ]




Post new topic Reply to topic  [ 5 posts ] 
Author Message
PostPosted: 2016/03/04, 20:40 
Junior Member
Junior Member
User avatar

Joined: 2016/01/20, 22:20
Posts: 31
Location: Brazil
Role: Hobbyist
Introduction:
Well, by what i have seen on this forum, a good amount of people have tried to implement multiplayer on Duality Projects, but have quitted for some reasons, or are still waiting for some plugin (that this guy and this other guy may be making).
Also, english is not my native language, so i previously apologize for any misunderstanding, ambiguites and/or gramatical errors :/

Setup:
First of all, to implement multiplayer on your games, you should know wich classes to use, and what they do. Basically you are using 2 classes, Socket and Thread. If you simply try to include System.Net.Sockets and System.Threading, you will get an error, that's because those classes are not supported by PCL based projects (wich Duality projects are), to change this, you'll need to change some properties of your project.
[WARNING]
By changing your project from PCL to normal class library you will loose the portability of your project, so be carefull about really changing this
------Step 1: converting from PCL to RCL (Portable Class Library to Regular Class Library)------
Sadly, Visual studio does not have a tool for easy convertion, so you have to change tour .csproj manually, go to your project folder, then Source>Code>CorePlugin>CorePlugin.cjproj and open with Notepad. You should see some XML codes, than you press Ctrl+F and paste
<TargetFrameworkProfile>
and just remove everything in between the tag (including the tag). Next you do the same with
<ProjectTypeGuids>

Now that you have removed those lines, press Ctrl+F again and paste this:
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
After finding this line, just replace it with this other line:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
After that, simply save the file and now your project should now be a Regular Class Library, but that's not enough yet.
------Step 2: Adding required references to your project------
If you try to import System.Net or System.Threading.Thread, you wil still get an error saying that those classes don't exist, that's because you still need to reference some things, just go to your Visual Studio, press right button on the CorePlugin project, go to Add, then Reference...

Image

You should open a window like this:

Image

Check if the left guide is on Assembly and Frameworks

and then select System and System.Net

Image

NOW you should be able to import those Classes (Sockets and Threads)


Last edited by RotcivOcnarb on 2016/03/07, 14:40, edited 3 times in total.

Top
 Profile  
 
PostPosted: 2016/03/04, 20:58 
Junior Member
Junior Member
User avatar

Joined: 2016/01/20, 22:20
Posts: 31
Location: Brazil
Role: Hobbyist
Part 2: Basics

Now that you are able to use those classes, let me explain some things:
Socket:
A socket is basically a class that handles package sending and receiving to other machines within your local network, we have two types of sending/receivig, UDP and TCP, for games UDP is highly recomended since is much faster for large packages and great quantities of information (that's our case since we should be sending for every player our position, and receiving from every player, their position)
Sockets are best worked by 2 components, a Server and a Client. A server receives all players information, and share all the information with every player. A client simply sends your information to the server, that sends that information to other players (clients). On a local area network (LAN) you always have the Host (Server) and the othe players (Clients), on big games that have massive multiplayers (MMORPGS for example) every player is a client, and the Server is probably on some massive computer server located in the headquarters of the company who develops the game.
Thread:
Threads are made to run parallel codes at the same time other code from your game is already running, we are using this to receive server's information, and to server to receive player's information. When you run your game, there is already a thread running, the Main Thread, if we run the code to receive sockets information from other pc, the current Thread stops until it receives some information, if we put this code on our Main Thread, the game simply stop, and only continue after we receive some information, so we need our own thread so the game can still run even if our network thread is stopped

Idealization:
Ok, so, how exacly do we use those things for make our game, Multiplayer?
Well, here is some basic concept for Client and Server, and how they will work on our game:

Client:

Code:
void onUpdate(){
//Do whatever we need to do
String information = myIp + "-" + transform.pos.X + "-" + transform.pos.Y //We condense our player's information to a string that can be read by the Server, in this case, our IP, and //our position X and Y
byte[] condensed = information.transformToByteArray(); //Not actual function, is just to demonstrate what will we do
socket.send(condensed, serverIP, port) //serverIP is the server's IP, something like //192.168.0.1 and wich port we are using to send those informations
}

void threadMethod(){ //This is our thread method, this will be running parallel to other //threads in the game, we will use this to receive server's information
while(true){//a loop so we can always be receiving new information
byte[] info = socket.receive(serverIP, port);// Again, not real methods, just using to //explain what we are doing
String data = into.toString()//dont know if this work (probably no)

Player p = players.get(data.split("-")[0]) //"players" is a hashmap wich contains all //players, keyed by its IP so we can retreive the players position by simply searching its IP
p.position.x = parseInt(data.split("-")[1]);
p.position.y = parseInt(data.split("-")[2]);
//If you want to retreive more information about the players, for example name, HP,MP, //etc you just need to send those informations to the server, by the fact that every client is //running this same code, if you send your name, then it means that everyone is sending //their name, and you just need to get it from the server
}
}


Server:

Code:
HashMap<Players> players;
void onUpdate(){

foreach(Player p in players){
String information = p.IP+ "-" + p.transform.pos.X + "-" + p.transform.pos.Y  //For every player, we get their information
byte[] condensed = information.transformToByteArray();
foreach(Player p2 in players){
if(p2 != p){
socket.send(condensed, p2.IP, port) //and send their information to every other player //(that is not itself for obvious reasons)
}
}
}
}
void threadMethod(){
while(true){//same loop of client so we can always receive new information

byte[] info = socket.receive(serverIP, port);
String data = into.toString()

Player p = players.get(data.split("-")[0])
p.position.x = parseInt(data.split("-")[1]);
p.position.y = parseInt(data.split("-")[2]);
}
}


and thats BASICALLY it, this is not everything you need to do, because depending on your game, you may have different approaches to sending and receiving to/from server. The thing is, you need to think WHAT you need to send, and WHAT you need to receive to make your game actually multiplayer, on a top shooter for example, you need to receive players posX, posY, HP, maybe their Name. Remember that in MOST cases you need to send and receive your IP so the server can know who are you on his player's list. You also may need to send animation position, or simply if the player is actually moving, so you can play the animation of other players in your game


Top
 Profile  
 
PostPosted: 2016/03/04, 22:05 
Junior Member
Junior Member
User avatar

Joined: 2016/01/20, 22:20
Posts: 31
Location: Brazil
Role: Hobbyist
Step 3: Programming

Lets pretend you need to make a serverList so anyone on the LAN can see if you have a room to play or not, assuming you are the Host/Server (the one who created the room in first place), There is some code that i have actually implemented in my game that i'm working on:

Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;

using Duality;
using Duality.Components;

namespace MyGame.Multiplayer
{
    public class HostWaitRoom : Component, ICmpUpdatable, ICmpInitializable
    {
        Socket sender;//This socket is used to send server information
        IPAddress send_to_address;//wich ip are we sending to? [SPOILER] Everyone [/SPOILER]
        IPEndPoint sending_end_point;//really dont know why we need this, but we need it :/

        bool end = false;

        float timer = 0;//this is a timer that caps the sending to every 100ms (we dont need to refresh the page that often as once every frame)

        int NumPlayers = 0;//The number of player currently connected to your server

        public void OnInit(Component.InitContext context)
        {
            if (context == InitContext.Activate)
            {
                send_to_address = IPAddress.Parse("255.255.255.255");//255.255.255.255 is what we call a "Broadcast" IP, by sending
                //information to this ip, it sends to EVERY ip on the LAN
                sender = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);//we define wich protocol we are sending, UDP in our case
                sending_end_point = new IPEndPoint(send_to_address, 11000);//we are sending to our broadcast using the port 11000
                sender.EnableBroadcast = true;//rember to enable broadcast to receive our information
            }
        }
        public static string GetLocalIPAddress()//this function return the local ip in a string format (not 127.0.0.1, but our actual IP)
        {//you can simply copy/paste this to your code, is a useful method...
            var host = Dns.GetHostEntry(Dns.GetHostName());
            foreach (var ip in host.AddressList)
            {
                if (ip.AddressFamily == AddressFamily.InterNetwork)
                {
                    return ip.ToString();
                }
            }
            throw new Exception("Local IP Address Not Found!");
        }

        public void OnUpdate()
        {
            timer += Time.LastDelta;

            if (timer > 100)//cap our sending to 100ms
            {
                timer = 0;

                String data = "SERVER-" + GetLocalIPAddress() + "-Server Teste-" + NumPlayers;//That's our information compacted to a string
                byte[] send_buffer = Encoding.ASCII.GetBytes(data);//Converting our string to a byte array
                bool sent = true;
                try
                {
                    sender.SendTo(send_buffer, sending_end_point);//we TRY to send (it may give some errors)
                }
                catch (Exception e)
                {
                    sent = false;
                    Log.Game.Write(e.Message);//if we cant send, simply prints the error on the log (cmd file that opens with your game)
                }
                Log.Game.Write(sent + " - Sending to: " + sending_end_point.Address + " with port: " + sending_end_point.Port);//if we
                //got here, we sent succesfully
            }
        }

        public void OnShutdown(Component.ShutdownContext context)//We have to remember that when we close our game or change Scenes
            //we need to send to everyone that our server is not available anymore
        {
            String data = "SHUTDOWN-" + GetLocalIPAddress() + "-Server Teste-" + NumPlayers;
            byte[] send_buffer = Encoding.ASCII.GetBytes(data);
            bool sent = true;
            try
            {
                sender.SendTo(send_buffer, sending_end_point);
            }
            catch (Exception e)
            {
                sent = false;
                Log.Game.Write(e.Message);
            }
            sender.Close();
        }
    }
}



^^This up here is the Server code, now the Client code, that reads all the servers that are being hosted in the LAN, by reading the information that this server is sending

Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Net;
using System.Net.Sockets;

using Duality;
using Duality.Components;
using Duality.Components.Renderers;
using Duality.Resources;

namespace MyGame.Multiplayer
{
    public class ServersList : Component, ICmpInitializable
    {

        bool end = false;
        List<ServerInfo> servers;//thats the list of all available servers
        UdpClient listener;//this receives information from servers
        IPEndPoint groupEP;//also dont really know the purpose of this
        List<String> ips;//the same list of servers, but only by IP, so we can see if the ip being received is already on the serversList

        const int port = 11000;//the port we are receiving (must be the same we are sending);

        List<GameObject> renderers;//this is a gameobject list so we can put a button to click for every server in list

        public void OnInit(Component.InitContext context)
        {
            if (context == InitContext.Activate)
            {
                servers = new List<ServerInfo>();//just initialize everything
                renderers = new List<GameObject>();
                end = false;
                listener = new UdpClient(port);
                ips = new List<string>();
                Thread thread = new Thread(new ThreadStart(run));//here we create our thread and point it to run() method, so it will run
                //the code inside parallel to the main thread
                thread.Start();//actually starts the thread and calls the method
            }
        }

        void createServerObject(ServerInfo si)//just create a button and put it on the Game scene based on server information received
        {
            ContentRef<Prefab> ServerButton = ContentProvider.RequestContent<Prefab>(@"Data\Prefabs\GUI\ServerButton.Prefab.res");
            GameObject serverObj = ServerButton.Res.Instantiate();
            Transform trans = serverObj.GetComponent<Transform>();
            TextRenderer trend = serverObj.GetComponent<TextRenderer>();
            SpriteRenderer sprite = serverObj.GetComponent<SpriteRenderer>();


            sprite.Rect = new Rect(-250, -15, 500, 30);
            trans.Pos = new Vector3(100, renderers.Count * 30 - 250, 0);
            trend.Text.SourceText = si.RoomName + " - " + si.numPlayers + " players - IP: " + si.ip.ToString();

            Scene.Current.AddObject(serverObj);

            renderers.Add(serverObj);
        }

        void run()//our parallel code running
        {
            try
            {
                Log.Game.Write("Thread start " + end);

                listener.EnableBroadcast = true;
             
                groupEP = new IPEndPoint(IPAddress.Any, port);//we can receive from any IP that is sending server information
           
                string received_data;
                byte[] receive_byte_array;

                while (!end)//our loop so we can always receive information
                {
                    receive_byte_array = listener.Receive(ref groupEP);//the information being received in byte[]
                    received_data = Encoding.ASCII.GetString(receive_byte_array, 0, receive_byte_array.Length);//just converting to string

                    if (received_data.StartsWith("SERVER"))//we have 2 types of information that we can receive, the SERVER
                        //that is information about the availiable servers and SHUTDOWN, that receives an information that some server
                        //is closing and need to be put out of the list, and remove the button from the game scene
                    {
                        bool hasAlready = false;//check if the server being received is not already being displayed on screen
                        foreach (string si in ips)
                        {
                            if (si.Equals(received_data.Split('-')[1]))
                            {
                                hasAlready = true;
                            }
                        }
                        if (!hasAlready)//if not
                        {
                            ServerInfo newServer = new ServerInfo(received_data);//receive the server information
                            servers.Add(newServer);//put it on the list
                            ips.Add(received_data.Split('-')[1]);//put its ip on the list
                            Log.Game.Write("Server " + newServer.ip.ToString() + " Created, num servers active: " + servers.Count);
                            //printing just for debug
                            createServerObject(newServer);//create button to put in game scene
                        }

                    }
                    if (received_data.StartsWith("SHUTDOWN"))//if some server is closing
                    {
                        try
                        {
                            int i = 0;
                            foreach (ServerInfo si in servers)//loop through all servers to find the one being closed
                            {
                                if (si.hasSameIp(received_data))
                                {
                                    servers.Remove(si);//remove from servers list
                                    ips.Remove(si.ip.ToString());//remove from ips list
                                    Log.Game.Write("Server " + si.ip.ToString() + " Shutdown");//log just for debug
                                    Scene.Current.RemoveObject(renderers.ElementAt(i));//remove object from scene
                                    renderers.Remove(renderers.ElementAt(i));//remove object from objects list
                                    break;
                                }
                                i++;
                            }
                        }
                        catch (Exception e)
                        {
                            Log.Game.Write("--------------------------------" + e.Message);//just put those big lines so i can differ from other log things
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Log.Game.Write("--------------------------------"+e.Message);
            }

        }

        public void OnShutdown(Component.ShutdownContext context)
        {
            end = true;
            listener.Close();//always close your listener
           
        }
    }
}



That's basically how you make a server list that can actually read from other computers in LAN, remember that those 2 classes are inherited by Component, and have actually to be put in a game object on the scene (two different scenes, one for server host and other for server lists)


Top
 Profile  
 
PostPosted: 2016/05/06, 19:47 
Novice Member
Novice Member

Joined: 2016/05/06, 14:33
Posts: 16
Role: Hobbyist
Since the client code is a component, its easy to create a game objectin scene view and attach the Client Logic Component to the game object. But how can i access to the client object from my player component class for example so i can send the position of my player in onupdate for example?


Top
 Profile  
 
PostPosted: 2016/05/06, 20:44 
Site Admin
Site Admin
User avatar

Joined: 2013/05/11, 22:30
Posts: 1935
Location: Germany
Role: Professional
Not entirely sure what you're asking. If you just need to know how to access a Component from a different GameObject, there's some information on various methods to do this in the Best Practices article on the wiki.

_________________
Blog | GitHub | Twitter (@Adams_Lair)


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 5 posts ] 

All times are UTC + 1 hour [ DST ]


Who is online

Users browsing this forum: No registered users and 1 guest


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Jump to:  
cron
Powered by phpBB® Forum Software © phpBB Group