Legacy GM C# Server Example

C

Cinderfire

Guest
GM Version: Studio
Target Platform: Windows
Download: https://www.dropbox.com/s/swvzhhdjdqdxlx0/GMS-CSharp-Server.zip?dl=0

Intro:

So I was in need for a dedicated server for online matchmaking for my game.

I found this old tutorial online: http://gmc.yoyogames.com/index.php?showtopic=622307

However I couldn't get it to work, and it didn't seem to be supported anymore. Not sure if there are any other topics regarding this, but I couldn't find, so I'm gonna share an example of how my game Crux does networking in both GML and C#.

I may expand the tutorial to be more general in the future and includes other features such as UDP, but for now my aim is to provide a full example for people looking for how to make a dedicated server in C# for networking.
 
Last edited by a moderator:
C

Cinderfire

Guest
Game Maker Client Code:

I'm going to assume you are already mostly familiar with networking in game maker studio, and the built in features they use. If you are not, there are plenty of simple tutorials out there for server client using game makers built in tools.

Making a client that works with the C# server is exactly the same as making one that works with a game maker server, however instead of using these:

Code:
network_connect(socket, server_address, port);
Code:
network_send_packet(socket, buffer, buffer_tell(buffer));
You use these functions instead:
Code:
network_connect_raw(socket, server_address, port);
Code:
network_send_raw(socket, buffer, buffer_tell(buffer));

The majority of the client side code will look the same. Below is an example of the obj_client_raw object I'm using in my game for networking. This is the exact code that is used in conjunction with the example of the C# server code.

Code:
/////Create Event

///Creation Code

//Global Variables
global.connected_to_server = false;

//Instance Variables
port = 10103;
socket = network_create_socket(network_socket_tcp);
//server_address = "127.0.0.1";
server_address = "185.134.28.88";
ping = 0;
ping_step = 0;
ping_timeout = room_seconds(3);

//Set config.
network_set_timeout(socket, 2000, 2000);
Code:
/////Destroy Event

///Clean-Up Leaks
network_destroy(socket);
Code:
/////Alarm 0 Event

///Ping Timeout Alarm

//Return to menu.
if (room = rm_boardgame)
{
    room_goto(rm_mainmenu);
    suicide(obj_match);
}

//Disconnects the client.
global.searching = false;
global.connected_to_server = false;
ctr_multiplayer.client_raw = instance_create(x, y, obj_client_raw);
suicide();
Code:
/////Alarm 1 Event

///Sends Ping
client_raw_send_ping();
Code:
/////Step Event

///Ping Timer
ping_step += 1;
Code:
/////Networking Event

///Processes Networking Data

//Get the id of the socket.
var socket_id = ds_map_find_value(async_load, "id");

//Check if socket is our socket.
if (socket_id = socket)
{
    //Check the type of network event.
    var type = ds_map_find_value(async_load, "type");
    switch (type)
    {
        //Connect Event.
        case network_type_non_blocking_connect:
        {
            var success = ds_map_find_value(async_load, "succeeded");
            if (success = 0)
            {            
                //Failure connection. Retry.
                global.connected_to_server = false;
            }
            else
            {
                //Succesful connection.
                global.connected_to_server = true;
                client_raw_send_connection();
                client_raw_send_ping();
                alarm[0] = -1;
                alarm[1] = room_seconds(1);
            }
            break;
        }
   
        //Incomming Data Event.
        case network_type_data:
        {
            //Get the packet from the server.
            var read_buffer = ds_map_find_value(async_load, "buffer");
            var constant = buffer_read(read_buffer, buffer_u16);
            switch (constant)
            {
                // 1003 = Start Matchmade Game
                case 1003:
                {
                    //Recive buffer data.
                    var map_name = buffer_read(read_buffer, buffer_string);
                    var name_1 = buffer_read(read_buffer, buffer_string);
                    var name_2 = buffer_read(read_buffer, buffer_string);
                    var race_1 = buffer_read(read_buffer, buffer_string);
                    var race_2 = buffer_read(read_buffer, buffer_string);
                    var number_1 = buffer_read(read_buffer, buffer_u16);
                    var number_2 = buffer_read(read_buffer, buffer_u16);              
               
                    //Finds the map based on name.
                    for (var i=0; i<ds_list_size(ctr_container_maps.map_list); i++)
                    {
                        var map = ds_list_find_value(ctr_container_maps.map_list, i);
                        if (map.name = map_name)
                        {
                            break;
                        }
                    }
               
                    //Setup multiplayer game.
                    var match = instance_create(x, y, obj_match);
                    match.map = map.object_index;
                    match.singleplayer = false;
                    match.online = true;
               
                    //Create the first client.
                    var account = instance_create(x, y, obj_account);
                    ds_list_add(match.accounts, account);
                    with (account)
                    {
                        self.name = name_1;
                        self.race = race_1;
                        self.color = make_colour_rgb(180, 11, 34);
                        self.hosting = false;
                        self.in_game = true;
                    }
               
                    //Check if local account.
                    if (global.name = account.name)
                    {
                        match.your_account = account;
                    }
               
                    //Create the second client.
                    var account = instance_create(x, y, obj_account);
                    ds_list_add(match.accounts, account);
                    with (account)
                    {
                        self.name = name_2;
                        self.race = race_2;
                        self.color = make_colour_rgb(0, 64, 190);
                        self.hosting = false;
                        self.in_game = true;
                    }
               
                    //Check if local account.
                    if (global.name = account.name)
                    {
                        match.your_account = account;
                    }
               
                    //Goto boardroom.
                    room_goto(rm_boardgame);
                    break;
                }
           
                // 1004 = Recive Input
                case 1004:
                {            
                    //Recive input data.
                    var name = buffer_read(read_buffer, buffer_string);
                    var input = buffer_read(read_buffer, buffer_u32);
                    var xx = buffer_read(read_buffer, buffer_u16);
                    var yy = buffer_read(read_buffer, buffer_u16);
               
                    //Check if not your client.
                    if (obj_match.your_account.name != name)
                    {
                        //Finds the input simulator.
                        var simulator = noone;
                        with (obj_multiplayer_input)
                        {
                            if (self.account.name = name)
                            {
                                simulator = id;
                            }
                        }
                   
                        //Simulates the inputs.
                        with (simulator)
                        {
                            if (input = mb_left)
                            {
                                input_left = true;
                            }
                            if (input = mb_right)
                            {
                                input_right = true;
                            }
                            x = xx;
                            y = yy;
                            alarm[0] = 2;
                        }
                    }
                    break;
                }
           
                // 1050 = Recive Ping
                case 1050:
                {            
                    //Update variables.
                    ping = ping_step;
                    alarm[0] = -1;
                    alarm[1] = room_seconds(1); //Send again in 1 second.
                    break;
                }
           
                // 1006 = Recive End Turn
                case 1006:
                {            
                    //Ends the turn.
                    event_user_ext(obj_game, 2);
                    break;
                }
           
                // 1007 = Recive Server Ping
                case 1007:
                {            
                    //Create buffer.
                    var buffer = buffer_create(128, buffer_grow, 1);
                    buffer_seek(buffer, buffer_seek_start, 0);
                    buffer_write(buffer , buffer_u16, 2005);
               
                    //Return ping to client.
                    network_send_raw(socket, buffer, buffer_tell(buffer));
                    buffer_delete(buffer);
                    break;
                }

                // 1008 = Recive Players Online
                case 1008:
                {            
                    //Recive player online data.
                    var players_online = buffer_read(read_buffer, buffer_s32);
               
                    //Update global.
                    global.players_online = players_online;
                    break;
                }
           
                // 1009 = Disconnected From Server
                case 1009:
                {
                    //Returns to main menu.
                    event_user_ext(ctr_multiplayer, 0);
                    room_goto(rm_mainmenu);
                    break;
                }
           
                // 1010 = Victory
                case 1010:
                {
                    //Wins the game.
                    event_user_ext(obj_match, 0);
                    suicide();
                    break;
                }            
            }
       
            //Breaks
            break;
        }
    }
}

The following are two examples of scripts that is used to send data to the server, from the object above.

Code:
///client_raw_send_connection();

//Create variables.
var ip_address = global.ip_address;

//Sends account data.
var buffer = buffer_create(256, buffer_grow, 1);
buffer_seek(buffer, buffer_seek_start, 0);
buffer_write(buffer , buffer_u16, 2000);
buffer_write(buffer, buffer_string, ip_address);

//Send the packet to server.
with (ctr_multiplayer.client_raw)
{
    network_send_raw(socket, buffer, buffer_tell(buffer));
}

//Delete the buffer.
buffer_delete(buffer);
Code:
///client_raw_send_ping();

//Sends ping constant.
var buffer = buffer_create(256, buffer_grow, 1);
buffer_seek(buffer, buffer_seek_start, 0);
buffer_write(buffer , buffer_u16, 2004);

//Send the packet to server.
with (ctr_multiplayer.client_raw)
{
    ping_step = 0;
    network_send_raw(socket, buffer, buffer_tell(buffer));
    alarm[0] = ping_timeout;
}

//Delete the buffer.
buffer_delete(buffer);

There are a million different ways of handling the client and the data and connection/disconnection for your game, so you might find one that is different from mine above. The most important thing however, is simply to remember that the only thing you really gotta worry about is the switch for incoming data.

Code:
//Incomming Data Event.
case network_type_data:
{
    //Get the packet from the server.
    var read_buffer = ds_map_find_value(async_load, "buffer");
    var constant = buffer_read(read_buffer, buffer_u16);
    switch (constant)
    {
        // Unique constant for reading buffer data.
        case 123456:
        {
            //Code here for reading and handling data.
        }
    }

    //Breaks
    break;
}
Most of the changes to your code is done here, where the first read from the buffer is a constant that determines what is being send. Then you handle all the logic and reading of the buffer in a switch for all cases of incoming data. This is also pretty much, how it is done on the server side.
 
C

Cinderfire

Guest
C# Server Code

Download Project: https://www.dropbox.com/s/swvzhhdjdqdxlx0/GMS-CSharp-Server.zip?dl=0


Special thanks to FatalSleep for the buffer code from his old tutorial. This was used in this project.
Link: http://gmc.yoyogames.com/index.php?showtopic=622307

There are only 3 objects of code that you can copy and paste or download above, into a project of yours, and you have a functioning server up and running. Make sure you have .Net 4.5 or above installed for it to work.

Code:
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Encoding = System.Text.Encoding;
using StringBuilder = System.Text.StringBuilder;
using BitConverter = System.BitConverter;
using Array = System.Array;

namespace GMS_CSharp_Server
{
    /// <summary>
    /// Enumerator that represents the size of each datatype supported by the BufferStream class.
    /// </summary>
    public enum BufferTypeSize
    {
        None = 0, Bool = 1, Byte = 1, SByte = 1,
        UInt16 = 2, Int16 = 2, UInt32 = 4, Int32 = 4,
        Single = 4, Double = 8, String = -1, Bytes = -1
    }

    /// <summary>
    /// Provides a stream designed for reading TCP packets and UDP datagrams by type.
    /// </summary>
    public class BufferStream
    {
        private const byte bTrue = 1, bFalse = 0;
        private int fastAlign, fastAlignNot;
        private int iterator, length, alignment;
        private byte[] memory;

        /// <summary>
        /// Gets the underlying array of memory for this BufferStream.
        /// </summary>
        public byte[] Memory { get { return memory; } }
        /// <summary>
        /// Gets the length of the buffer in bytes.
        /// </summary>
        public int Length { get { return length; } }
        /// <summary>
        /// Gets the current iterator(read/write position) for this BufferStream.
        /// </summary>
        public int Iterator { get { return iterator; } }

        /// <summary>
        /// Instantiates an instance of the BufferStream class with the specified stream length and alignment.
        /// </summary>
        /// <param name="length">Length of the BufferStream in bytes.</param>
        /// <param name="alignment">Alignment of the BufferStream in bytes.</param>
        public BufferStream(int length, int alignment)
        {
            fastAlign = alignment - 1;
            fastAlignNot = ~fastAlign;
            this.length = length;
            this.alignment = alignment;
            memory = new byte[AlignedIterator(length, alignment)];
            iterator = 0;
        }

        /// <summary>
        /// (forced inline) Takes an iterator and aligns it to the specified alignment size.
        /// </summary>
        /// <param name="iterator">Read/write position.</param>
        /// <param name="alignment">Size in bytes to align the iterator too.</param>
        /// <returns>The aligned iterator value.</returns>
        [MethodImpl(256)]
        public int AlignedIterator(int iterator, int alignment)
        {
            return ((iterator + (alignment - 1)) & ~(alignment - 1));
        }

        /// <summary>
        /// (forced inline) Checks if the specified index with the specified length in bytes is within bounds of the buffer.
        /// </summary>
        /// <param name="iterator">Read/write position.</param>
        /// <param name="length">Index to check the bounds of.</param>
        /// <param name="alignment">Size in bytes to align the iterator too.</param>
        /// <returns></returns>
        [MethodImpl(256)]
        public bool IsWithinMemoryBounds(int iterator, int length, bool align = true)
        {
            int iterBegin = (align) ? ((iterator + fastAlign) & fastAlignNot) : iterator;
            int iterEnd = iterBegin + length;
            return (iterBegin < 0 || iterEnd >= length);
        }

        /// <summary>
        /// Allocates a new block of memory for the BufferStream with the specified length and alignment--freeing the old one if it exists.
        /// </summary>
        /// <param name="length">Length in bytes of the new block of memory.</param>
        /// <param name="alignment">Alignment in bytes to align the block of memory too.</param>
        public void Allocate(int length, int alignment)
        {
            if (memory != null) Deallocate();

            memory = new byte[AlignedIterator(length, alignment)];
            this.alignment = alignment;
            this.length = length;
        }

        /// <summary>
        /// Frees up the existing block of memory and sets the iterator to 0.
        /// </summary>
        public void Deallocate()
        {
            memory = null;
            iterator = 0;
        }

        /// <summary>
        /// Sets all elements in this buffer to 0.
        /// </summary>
        /// <exception cref="System.ArgumentNullException"/>
        public void ZeroMemory()
        {
            Array.Clear(memory, 0, memory.Length);
        }

        /// <summary>
        /// Sets all elements in this buffer to the specified value.
        /// </summary>
        /// <param name="value">The value to zero the memory too.</param>
        public void ZeroMemory(byte value)
        {
            for (int i = 0; i++ < memory.Length;) memory[i] = value;
        }

        /// <summary>
        /// Creates a copy of this buffer and all it's contents.
        /// </summary>
        /// <returns>A new clone BufferStream.</returns>
        /// <exception cref="System.ArgumentNullException"/>
        public BufferStream CloneBufferStream()
        {
            BufferStream clone = new BufferStream(memory.Length, alignment);
            Array.Copy(memory, clone.Memory, memory.Length);
            clone.iterator = iterator;
            return clone;
        }

        /// <summary>
        /// Copies the specified number of bytes from this buffer to the destination buffer, given the start position(s) in each buffer.
        /// </summary>
        /// <param name="destBuffer">Buffer to copy the contents of this buffer too.</param>
        /// <param name="srceIndex">Start position to begin copying the data from in this buffer.</param>
        /// <param name="destIndex">Start position to begin copying the data to in the destination buffer.</param>
        /// <param name="length">Number of bytes to copy from this buffer to the destination buffer.</param>
        /// <exception cref="System.ArgumentNullException"/>
        /// <exception cref="System.ArgumentOutOfRangeException"/>
        /// <exception cref="System.ArgumentException"/>
        public void BlockCopy(BufferStream destBuffer, int srceIndex, int destIndex, int length)
        {
            Array.Copy(memory, srceIndex, destBuffer.Memory, destIndex, length);
        }

        /// <summary>
        /// Copes the specified number of bytes from this buffer to the destination buffer, given the shared start position for both buffers.
        /// </summary>
        /// <param name="destBuffer">Buffer to copy the contents of this buffer too.</param>
        /// <param name="startIndex">Shared start index of both buffers to start copying data from/to.</param>
        /// <param name="length">Number of bytes to copy from this buffer to the destination buffer.</param>
        /// <exception cref="System.ArgumentNullException"/>
        /// <exception cref="System.ArgumentOutOfRangeException"/>
        /// <exception cref="System.ArgumentException"/>
        public void BlockCopy(BufferStream destBuffer, int startIndex, int length)
        {
            Array.Copy(memory, startIndex, destBuffer.Memory, startIndex, length);
        }

        /// <summary>
        /// Copes the specified number of bytes from the start of this buffer to the start of the destination buffer.
        /// </summary>
        /// <param name="destBuffer">Buffer to copy the contents of this buffer too.</param>
        /// <param name="length">Number of bytes to copy from this buffer to the destination buffer.</param>
        /// <exception cref="System.ArgumentNullException"/>
        /// <exception cref="System.ArgumentOutOfRangeException"/>
        /// <exception cref="System.ArgumentException"/>
        public void BlockCopy(BufferStream destBuffer, int length)
        {
            Array.Copy(memory, destBuffer.Memory, length);
        }

        /// <summary>
        /// Copies the entire contents of this buffer to the destination buffer.
        /// </summary>
        /// <param name="destBuffer">Buffer to copy the contents of this buffer too.</param>
        public void BlockCopy(BufferStream destBuffer)
        {
            int length = (destBuffer.Memory.Length > memory.Length) ? memory.Length : destBuffer.Memory.Length;
            Array.Copy(memory, destBuffer.Memory, length);
        }

        /// <summary>
        /// Resizes the block of memory for this buffer.
        /// </summary>
        /// <param name="size">Size in bytes of the resized block of memory.</param>
        public void ResizeBuffer(int size)
        {
            Array.Resize<byte>(ref memory, size);
            length = memory.Length;
        }

        /// <summary>
        /// Writes a value of the specified type to this buffer.
        /// </summary>
        /// <param name="value">BOOLEAN value to be written.</param>
        /// <exception cref="System.IndexOutOfRangeException"/>
        public void Write(bool value)
        {
            memory[iterator++] = (value) ? bTrue : bFalse;
            iterator = (iterator + fastAlign) & fastAlignNot;
        }

        /// <summary>
        /// Writes a value of the specified type to this buffer.
        /// </summary>
        /// <param name="value">BYTE value to be written.</param>
        /// <exception cref="System.IndexOutOfRangeException"/>
        public void Write(byte value)
        {
            memory[iterator++] = value;
            iterator = (iterator + fastAlign) & fastAlignNot;
        }

        /// <summary>
        /// Writes a value of the specified type to this buffer.
        /// </summary>
        /// <param name="value">SBYTE value to be written.</param>
        /// <exception cref="System.IndexOutOfRangeException"/>
        public void Write(sbyte value)
        {
            memory[iterator++] = (byte)value;
            iterator = (iterator + fastAlign) & fastAlignNot;
        }

        /// <summary>
        /// Writes a value of the specified type to this buffer.
        /// </summary>
        /// <param name="value">USHORT value to be written.</param>
        /// <exception cref="System.IndexOutOfRangeException"/>
        public void Write(ushort value)
        {
            memory[iterator++] = (byte)value;
            memory[iterator++] = (byte)(value >> 8);
            iterator = (iterator + fastAlign) & fastAlignNot;
        }

        /// <summary>
        /// Writes a value of the specified type to this buffer.
        /// </summary>
        /// <param name="value">SHORT value to be written.</param>
        /// <exception cref="System.IndexOutOfRangeException"/>
        public void Write(short value)
        {
            memory[iterator++] = (byte)value;
            memory[iterator++] = (byte)(value >> 8);
            iterator = (iterator + fastAlign) & fastAlignNot;
        }

        /// <summary>
        /// Writes a value of the specified type to this buffer.
        /// </summary>
        /// <param name="value">UINT value to be written.</param>
        /// <exception cref="System.IndexOutOfRangeException"/>
        public void Write(uint value)
        {
            memory[iterator++] = (byte)value;
            memory[iterator++] = (byte)(value >> 8);
            memory[iterator++] = (byte)(value >> 16);
            memory[iterator++] = (byte)(value >> 24);
            iterator = (iterator + fastAlign) & fastAlignNot;
        }

        /// <summary>
        /// Writes a value of the specified type to this buffer.
        /// </summary>
        /// <param name="value">INT value to be written.</param>
        /// <exception cref="System.IndexOutOfRangeException"/>
        public void Write(int value)
        {
            memory[iterator++] = (byte)value;
            memory[iterator++] = (byte)(value >> 8);
            memory[iterator++] = (byte)(value >> 16);
            memory[iterator++] = (byte)(value >> 24);
            iterator = (iterator + fastAlign) & fastAlignNot;
        }

        /// <summary>
        /// Writes a value of the specified type to this buffer.
        /// </summary>
        /// <param name="value">FLOAT value to be written.</param>
        /// <exception cref="System.IndexOutOfRangeException"/>
        public void Write(float value)
        {
            byte[] bytes = BitConverter.GetBytes(value);
            for (int i = 0; i < bytes.Length; i++) memory[iterator++] = bytes[i];
            iterator = (iterator + fastAlign) & fastAlignNot;
        }

        /// <summary>
        /// Writes a value of the specified type to this buffer.
        /// </summary>
        /// <param name="value">DOUBLE value to be written.</param>
        /// <exception cref="System.IndexOutOfRangeException"/>
        public void Write(double value)
        {
            byte[] bytes = BitConverter.GetBytes(value);
            for (int i = 0; i < bytes.Length; i++) memory[iterator++] = bytes[i];
            iterator = (iterator + fastAlign) & fastAlignNot;
        }

        /// <summary>
        /// Writes a value of the specified type to this buffer.
        /// </summary>
        /// <param name="value">STRING value to be written.</param>
        /// <exception cref="System.IndexOutOfRangeException"/>
        public void Write(string value)
        {
            byte[] bytes = Encoding.ASCII.GetBytes((string)value);
            for (int i = 0; i < bytes.Length; i++) { memory[iterator++] = bytes[i]; }
            memory[iterator++] = 0;
            iterator = (iterator + fastAlign) & fastAlignNot;
        }

        /// <summary>
        /// Writes a value of the specified type to this buffer.
        /// </summary>
        /// <param name="value">BYTE[] array to be written.</param>
        /// <exception cref="System.IndexOutOfRangeException"/>
        public void Write(byte[] value)
        {
            for (int i = 0; i < value.Length; i++) memory[iterator++] = value[i];
            iterator = (iterator + fastAlign) & fastAlignNot;
        }

        /// <summary>
        /// Reads the value of the specified type from the buffer.
        /// </summary>
        /// <param name="value">The OUT variable to store the BOOL value in.</param>
        /// <exception cref="System.IndexOutOfRangeException"/>
        public void Read(out bool value)
        {
            value = memory[iterator++] >= 0;
            iterator = (iterator + fastAlign) & fastAlignNot;
        }

        /// <summary>
        /// Reads the value of the specified type from the buffer.
        /// </summary>
        /// <param name="value">The OUT variable to store the BYTE value in.</param>
        /// /// <exception cref="System.IndexOutOfRangeException"/>
        public void Read(out byte value)
        {
            value = memory[iterator++];
            iterator = (iterator + fastAlign) & fastAlignNot;
        }

        /// <summary>
        /// Reads the value of the specified type from the buffer.
        /// </summary>
        /// <param name="value">The OUT variable to store the SBYTE value in.</param>
        /// <exception cref="System.IndexOutOfRangeException"/>
        public void Read(out sbyte value)
        {
            value = (sbyte)memory[iterator++];
            iterator = (iterator + fastAlign) & fastAlignNot;
        }

        /// <summary>
        /// Reads the value of the specified type from the buffer.
        /// </summary>
        /// <param name="value">The OUT variable to store the USHORT value in.</param>
        /// <exception cref="System.ArgumentException"/>
        /// <exception cref="System.ArgumentNullException"/>
        /// <exception cref="System.ArgumentOutOfRangeException"/>
        public void Read(out ushort value)
        {
            value = BitConverter.ToUInt16(memory, iterator);
            iterator = (iterator + (int)BufferTypeSize.UInt16 + fastAlign) & fastAlignNot;
        }

        /// <summary>
        /// Reads the value of the specified type from the buffer.
        /// </summary>
        /// <param name="value">The OUT variable to store the SHORT value in.</param>
        /// <exception cref="System.ArgumentException"/>
        /// <exception cref="System.ArgumentNullException"/>
        /// <exception cref="System.ArgumentOutOfRangeException"/>
        public void Read(out short value)
        {
            value = BitConverter.ToInt16(memory, iterator);
            iterator = (iterator + (int)BufferTypeSize.Int16 + fastAlign) & fastAlignNot;
        }

        /// <summary>
        /// Reads the value of the specified type from the buffer.
        /// </summary>
        /// <param name="value">The OUT variable to store the UINT value in.</param>
        /// <exception cref="System.ArgumentException"/>
        /// <exception cref="System.ArgumentNullException"/>
        /// <exception cref="System.ArgumentOutOfRangeException"/>
        public void Read(out uint value)
        {
            value = BitConverter.ToUInt32(memory, iterator);
            iterator = (iterator + (int)BufferTypeSize.Int32 + fastAlign) & fastAlignNot;
        }

        /// <summary>
        /// Reads the value of the specified type from the buffer.
        /// </summary>
        /// <param name="value">The OUT variable to store the INT value in.</param>
        /// <exception cref="System.ArgumentException"/>
        /// <exception cref="System.ArgumentNullException"/>
        /// <exception cref="System.ArgumentOutOfRangeException"/>
        public void Read(out int value)
        {
            value = BitConverter.ToInt32(memory, iterator);
            iterator = (iterator + (int)BufferTypeSize.Int32 + fastAlign) & fastAlignNot;
        }

        /// <summary>
        /// Reads the value of the specified type from the buffer.
        /// </summary>
        /// <param name="value">The OUT variable to store the FLOAT value in.</param>
        /// <exception cref="System.ArgumentException"/>
        /// <exception cref="System.ArgumentNullException"/>
        /// <exception cref="System.ArgumentOutOfRangeException"/>
        public void Read(out float value)
        {
            value = BitConverter.ToSingle(memory, iterator);
            iterator = (iterator + (int)BufferTypeSize.Single + fastAlign) & fastAlignNot;
        }

        /// <summary>
        /// Reads the value of the specified type from the buffer.
        /// </summary>
        /// <param name="value">The OUT variable to store the DOUBLE value in.</param>
        /// <exception cref="System.ArgumentException"/>
        /// <exception cref="System.ArgumentNullException"/>
        /// <exception cref="System.ArgumentOutOfRangeException"/>
        public void Read(out double value)
        {
            value = BitConverter.ToUInt16(memory, iterator);
            iterator = (iterator + (int)BufferTypeSize.Double + fastAlign) & fastAlignNot;
        }

        /// <summary>
        /// Reads the value of the specified type from the buffer.
        /// </summary>
        /// <param name="value">The OUT variable to store the BYTE[] value in.</param>
        /// <exception cref="System.ArgumentException"/>
        /// <exception cref="System.ArgumentNullException"/>
        /// <exception cref="System.ArgumentOutOfRangeException"/>
        public void Read(out string value)
        {
            StringBuilder str = new StringBuilder();

            for (char c = '\0'; iterator < length;)
            {
                c = (char)memory[iterator++];
                if (c == '\0') break;
                str.Append(c);
            }

            value = str.ToString();
            iterator = (iterator + fastAlign) & fastAlignNot;
        }

        /// <summary>
        /// Reads the value of the specified type from the buffer.
        /// </summary>
        /// <param name="value">The OUT variable to store the BYTE[] value in.</param>
        /// <param name="length">Number of bytes to read.</param>
        /// <exception cref="System.ArgumentException"/>
        /// <exception cref="System.ArgumentNullException"/>
        /// <exception cref="System.ArgumentOutOfRangeException"/>
        public void Read(out byte[] value, int length)
        {
            value = new byte[length];
            for (int i = 0; i < length; i++) value[i] = memory[iterator++];
            iterator = (iterator + fastAlign) & fastAlignNot;
        }

        /// <summary>
        /// Sets the iterator(read/write position) to the specified index, aligned to this buffer's alignment.
        /// </summary>
        /// <param name="iterator">Index to set the iterator to.</param>
        public void Seek(int iterator)
        {
            this.iterator = (iterator + fastAlign) & fastAlignNot;
        }

        /// <summary>
        /// Sets the iterator(read/write position) to the specified index, aligned to this buffer's alignment if alignment is specified as true.
        /// </summary>
        /// <param name="iterator">Index to set the iterator to.</param>
        /// <param name="align">Whether to align the iterator or not.</param>
        public void Seek(int iterator, bool align = false)
        {
            this.iterator = (align) ? (iterator + fastAlign) & fastAlignNot : iterator;
        }
    }
}

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

namespace GMS_CSharp_Server
{
    class Program
    {
        static void Main(string[] args)
        {
            //Set console size.
            Console.WindowHeight = 20;
            Console.WindowWidth = 80;

            //Starts the server.
            Console.WriteLine("Starting Server...");
            Server server = new Server();
            server.StartServer(10103);
            Console.WriteLine("Server Started!");
        }
    }
}

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

namespace GMS_CSharp_Server
{
    public class Server
    {
        List<Lobby> Lobbies;
        List<SocketHelper> Clients;
        List<SocketHelper> SearchingClients;
        Thread TCPThread;
        Thread PingThread;
        Thread MatchmakingThread;
        TcpListener TCPListener = null;

        private const int BufferAlignment = 1;
        private const int BufferSize = 256;
        private readonly string[] Maps = { "Open#Plains", "Riverside", "Lonely#Mountain", "Four#Islands", "Twin#Oasis", "Pyramid#Grounds", "Canyon#Valley", "Stone#Arena", "Caverns", "Sister#Caves", "Frozen#Lake", "Melting#Snow" };
 
        /// <summary>
        /// Starts the server.
        /// </summary>
        public void StartServer(int tcpPort)
        {
            //Creates a client list.
            Clients = new List<SocketHelper>();
            Lobbies = new List<Lobby>();
            SearchingClients = new List<SocketHelper>();

            //Starts a listen thread to listen for connections.
            TCPThread = new Thread(new ThreadStart(delegate
            {
                Listen(tcpPort);
            }));
            TCPThread.Start();
            Console.WriteLine("Listen thread started.");

            //Starts a ping thread to keep connection alive.
            PingThread = new Thread(new ThreadStart(delegate
            {
                Ping();
            }));
            PingThread.Start();
            Console.WriteLine("Ping thread started.");

            //Starts a matchmaking thread to create lobbies.
            MatchmakingThread = new Thread(new ThreadStart(delegate
            {
                Matchmaking();
            }));
            MatchmakingThread.Start();
            Console.WriteLine("Matchmaking thread started.");
        }

        /// <summary>
        /// Stops the server from running.
        /// </summary>
        public void StopServer()
        {
            TCPListener.Stop();

            TCPThread.Abort();
            PingThread.Abort();
            MatchmakingThread.Abort();

            foreach (SocketHelper client in Clients)
            {
                client.MscClient.GetStream().Close();
                client.MscClient.Close();
                client.ReadThread.Abort();
                client.WriteThread.Abort();
            }

            Clients.Clear();
            Lobbies.Clear();
        }

        /// <summary>
        /// Constantly pings clients with messages to see if they disconnect.
        /// </summary>
        private void Ping()
        {
            //Send ping to clients every 3 seconds.
            while (true)
            {
                Thread.Sleep(3000);
                BufferStream buffer = new BufferStream(BufferSize, BufferAlignment);
                buffer.Seek(0);
                ushort constant_out = 1007;
                buffer.Write(constant_out);
                SendToAllClients(buffer);
            }
        }

        /// <summary>
        /// Sends a message out to all connected clients.
        /// </summary>
        public void SendToAllClients(BufferStream buffer)
        {
            foreach (SocketHelper client in Clients)
            {
                client.SendMessage(buffer);
            }
        }

        /// <summary>
        /// Sends a message out all clients in a lobby.
        /// </summary>
        public void SendToLobby(Lobby lobby, BufferStream buffer)
        {
            foreach (SocketHelper client in lobby.LobbyClients)
            {
                client.SendMessage(buffer);
            }
        }

        /// <summary>
        /// Listens for clients and starts threads to handle them.
        /// </summary>
        private void Listen(int port)
        {
            TCPListener = new TcpListener(IPAddress.Any, port);
            TCPListener.Start();

            while (true)
            {
                Thread.Sleep(10);
                TcpClient tcpClient = TCPListener.AcceptTcpClient();
                Console.WriteLine("New client detected. Connecting client.");
                SocketHelper helper = new SocketHelper();
                helper.StartClient(tcpClient, this);
                Clients.Add(helper);
            }
        }

        /// <summary>
        /// Handles matchmaking between clients searching for games.
        /// </summary>
        public void Matchmaking()
        {
            while (true)
            {
                Thread.Sleep(10);
                bool match_made = false;
                bool should_break = false;
                SocketHelper client_to_remove_1 = null;
                SocketHelper client_to_remove_2 = null;

                //Finds a match for clients.
                foreach (SocketHelper client1 in SearchingClients)
                {
                    //Finds a match for clients.
                    foreach (SocketHelper client2 in SearchingClients)
                    {
                        if (client1.ClientName != client2.ClientName)
                        {
                            //Randomily selects a map for matchmaking.
                            Random random = new Random();
                            int index = random.Next(0, Maps.Length);
                            string map = Maps[index];

                            //Create a lobby.
                            Lobby lobby = new Lobby();
                            lobby.SetupLobby(map, client1, client2);
                            Lobbies.Add(lobby);

                            //Remove clients from searching list.
                            client1.IsSearching = false;
                            client2.IsSearching = false;
                            client_to_remove_1 = client1;
                            client_to_remove_2 = client2;
                            should_break = true;
                            match_made = true;

                            //Send start game to clients.
                            BufferStream buffer = new BufferStream(BufferSize, BufferAlignment);
                            buffer.Seek(0);
                            UInt16 constant = 1003;
                            string send_map = map;
                            string name_1 = client1.ClientName;
                            string name_2 = client2.ClientName;
                            string race_1 = client1.ClientRace;
                            string race_2 = client2.ClientRace;
                            UInt16 number_1 = (UInt16)client1.ClientNumber;
                            UInt16 number_2 = (UInt16)client2.ClientNumber;
                            buffer.Write(constant);
                            buffer.Write(send_map);
                            buffer.Write(name_1);
                            buffer.Write(name_2);
                            buffer.Write(race_1);
                            buffer.Write(race_2);
                            buffer.Write(number_1);
                            buffer.Write(number_2);
                            SendToLobby(lobby, buffer);
                            Console.WriteLine("Matchmade between " + name_1 + " and " + name_2 + " on " + send_map);
                            break;
                        }
                    }

                    //Check if match have been made.
                    if (should_break)
                    {
                        break;
                    }
                }

                //Check if match was made.
                if (match_made)
                {
                    SearchingClients.Remove(client_to_remove_1);
                    SearchingClients.Remove(client_to_remove_2);
                }
            }
        }

        /// <summary>
        /// Handles sessions of clients.
        /// </summary>
        public class Lobby
        {
            public List<SocketHelper> LobbyClients;
            public String Map;

            /// <summary>
            /// Adds two clients and the map to the lobby from matchmaking.
            /// </summary>
            public void SetupLobby(String map, SocketHelper player1, SocketHelper player2)
            {
                LobbyClients = new List<SocketHelper>();
                LobbyClients.Add(player1);
                LobbyClients.Add(player2);
                player1.GameLobby = this;
                player2.GameLobby = this;
                player1.ClientNumber = 1;
                player2.ClientNumber = 2;
                player1.IsIngame = true;
                player2.IsIngame = true;
                this.Map = map;
            }
        }

        /// <summary>
        /// Handles clients. Reads and writes data and stores client information.
        /// </summary>
        public class SocketHelper
        {
            Queue<BufferStream> WriteQueue = new Queue<BufferStream>();
            public Thread ReadThread;
            public Thread WriteThread;
            public Thread AbortThread;
            public TcpClient MscClient;
            public Server ParentServer;
            public string ClientIPAddress;
            public string ClientName;
            public string ClientRace;
            public int ClientNumber;
            public Lobby GameLobby;
            public bool IsSearching;
            public bool IsIngame;

            /// <summary>
            /// Starts the given client in two threads for reading and writing.
            /// </summary>
            public void StartClient(TcpClient client, Server server)
            {
                //Sets client variable.
                MscClient = client;
                MscClient.SendBufferSize = BufferSize;
                MscClient.ReceiveBufferSize = BufferSize;
                ParentServer = server;

                //Starts a read thread.
                ReadThread = new Thread(new ThreadStart(delegate
                {
                    Read(client);
                }));
                ReadThread.Start();
                Console.WriteLine("Client read thread started.");

                //Starts a write thread.
                WriteThread = new Thread(new ThreadStart(delegate
                {
                    Write(client);
                }));
                WriteThread.Start();
                Console.WriteLine("Client write thread started.");
            }

            /// <summary>
            /// Sends a string message to the client. This message is added to the write queue and send
            /// once it is it's turn. This ensures all messages are send in order they are given.
            /// </summary>
            public void SendMessage(BufferStream buffer)
            {
                WriteQueue.Enqueue(buffer);
            }

            /// <summary>
            /// Disconnects the client from the server and stops all threads for client.
            /// </summary>
            public void DisconnectClient()
            {
                //Console Message.
                Console.WriteLine("Disconnecting: " + ClientIPAddress);

                //Check if client is ingame.
                if (IsIngame)
                {
                    //Find opposing client.
                    SocketHelper opponet = null;
                    foreach (SocketHelper lobbyClient in GameLobby.LobbyClients)
                    {
                        if (lobbyClient != this)
                        {
                            opponet = lobbyClient;
                        }
                    }
           
                    //Causes opponent to win.
                    BufferStream buffer = new BufferStream(BufferSize, BufferAlignment);
                    buffer.Seek(0);
                    UInt16 constant_out = 1010;
                    buffer.Write(constant_out);
                    opponet.SendMessage(buffer);
                    Console.WriteLine(ClientIPAddress + " is ingame. Granting win to opponent.");

                    //Remove lobby from server.
                    ParentServer.Lobbies.Remove(GameLobby);
                    GameLobby = null;
                    IsIngame = false;
                }

                //Removes client from server.
                ParentServer.Clients.Remove(this);
                if (IsSearching)
                {
                    Console.WriteLine(ClientIPAddress + " was searching for a game. Stopped searching.");
                    ParentServer.SearchingClients.Remove(this);
                    IsSearching = false;
                }

                //Closes Stream.
                MscClient.Close();

                //Starts an abort thread.
                AbortThread = new Thread(new ThreadStart(delegate
                {
                    Abort();
                }));
                Console.WriteLine("Aborting threads on client.");
                AbortThread.Start();
            }

            /// <summary>
            /// Handles aborting of threads.
            /// </summary>
            public void Abort()
            {
                //Stops Threads
                ReadThread.Abort();
                Console.WriteLine("Read thread aborted on client.");
                WriteThread.Abort();
                Console.WriteLine("Write thread aborted on client.");
                Console.WriteLine(ClientIPAddress + " disconnected.");
                Console.WriteLine(Convert.ToString(ParentServer.Clients.Count) + " clients online.");
                AbortThread.Abort();
            }

            /// <summary>
            /// Writes data to the client in sequence on the server.
            /// </summary>
            public void Write(TcpClient client)
            {
                while (true)
                {
                    Thread.Sleep(10);
                    if (WriteQueue.Count != 0)
                    {
                        try
                        {
                            BufferStream buffer = WriteQueue.Dequeue();
                            NetworkStream stream = client.GetStream();
                            stream.Write(buffer.Memory, 0, buffer.Iterator);
                            stream.Flush();
                        }
                        catch (System.IO.IOException)
                        {
                            DisconnectClient();
                            break;
                        }
                        catch (NullReferenceException)
                        {
                            DisconnectClient();
                            break;
                        }
                        catch (ObjectDisposedException)
                        {
                            //Do nothing - client is already disconnecting.
                            break;
                        }
                        catch (System.InvalidOperationException)
                        {
                            //Do nothing - client is already disconnecting.
                            break;
                        }
                    }
                }
            }

            /// <summary>
            /// Reads data from the client and sends back a response.
            /// </summary>
            public void Read(TcpClient client)
            {
                while (true)
                {
                    try
                    {
                        Thread.Sleep(10);
                        BufferStream readBuffer = new BufferStream(BufferSize, 1);
                        NetworkStream stream = client.GetStream();
                        stream.Read(readBuffer.Memory, 0, BufferSize);

                        //Read the header data.
                        ushort constant;
                        readBuffer.Read(out constant);

                        //Determine input commmand.
                        switch (constant)
                        {
                            //New Connection
                            case 2000:
                                {
                                    //Read out client data.
                                    String ip;
                                    readBuffer.Read(out ip);

                                    //Update client information.
                                    ClientIPAddress = ip;

                                    //Console Message.
                                    Console.WriteLine(ip + " connected.");
                                    Console.WriteLine(Convert.ToString(ParentServer.Clients.Count) + " clients online.");
                                    break;
                                }

                            //Find Game
                            case 2001:
                                {
                                    //Read out client data.
                                    String ip;
                                    String name;
                                    String race;
                                    readBuffer.Read(out ip);
                                    readBuffer.Read(out name);
                                    readBuffer.Read(out race);

                                    //Update client information.
                                    ClientIPAddress = ip;
                                    ClientName = name;
                                    ClientRace = race;
                                    IsSearching = true;
                                    IsIngame = false;

                                    //Add client to searching clients.
                                    ParentServer.SearchingClients.Add(this);
                                    Console.WriteLine(ip + " is searching for a game as " + race + " under the name " + name);
                                    break;
                                }

                            //Cancel Find Game
                            case 2002:
                                {
                                    //Read out client data.
                                    String ip;
                                    readBuffer.Read(out ip);

                                    //Update client information.
                                    IsSearching = false;

                                    //Removes client from searching list.
                                    ParentServer.SearchingClients.Remove(this);
                                    Console.WriteLine(ip + " stopped searching.");
                                    break;
                                }

                            //Recive Move Input
                            case 2003:
                                {
                                    //Read buffer data.
                                    String name;
                                    UInt32 input;
                                    UInt16 xx;
                                    UInt16 yy;
                                    readBuffer.Read(out name);
                                    readBuffer.Read(out input);
                                    readBuffer.Read(out xx);
                                    readBuffer.Read(out yy);

                                    //Send start game to clients.
                                    BufferStream buffer = new BufferStream(BufferSize, BufferAlignment);
                                    buffer.Seek(0);
                                    UInt16 constant_out = 1004;
                                    buffer.Write(constant_out);
                                    buffer.Write(name);
                                    buffer.Write(input);
                                    buffer.Write(xx);
                                    buffer.Write(yy);
                                    ParentServer.SendToLobby(GameLobby, buffer);
                                    Console.WriteLine("Recived input at " + Convert.ToString(xx) + "," + Convert.ToString(yy) + " from " + ClientIPAddress);
                                    break;
                                }

                            //Recive client ping.
                            case 2004:
                                {
                                    //Send ping return to client.
                                    BufferStream buffer = new BufferStream(BufferSize, BufferAlignment);
                                    buffer.Seek(0);
                                    UInt16 constant_out = 1050;
                                    buffer.Write(constant_out);
                                    SendMessage(buffer);
                                    break;
                                }

                            //Recive server ping.
                            case 2005:
                                {
                                    //Nothing - Ping handled in ping thread.
                                    break;
                                }

                            //Recive matchmaking players request.
                            case 2006:
                                {
                                    //Send players online return to client.
                                    BufferStream buffer = new BufferStream(BufferSize, BufferAlignment);
                                    buffer.Seek(0);
                                    UInt16 constant_out = 1008;
                                    int players_online = ParentServer.Clients.Count;
                                    buffer.Write(constant_out);
                                    buffer.Write(players_online);
                                    SendMessage(buffer);
                                    break;
                                }

                            // 7 = Recive End Turn
                            case 2007:
                                {
                                    //Send end turn input to clients.
                                    BufferStream buffer = new BufferStream(BufferSize, BufferAlignment);
                                    buffer.Seek(0);
                                    UInt16 constant_out = 1006;
                                    buffer.Write(constant_out);
                                    ParentServer.SendToLobby(GameLobby, buffer);
                                    Console.WriteLine("Recived end turn from " + ClientIPAddress);
                                    break;
                                }
                        }
                    }
                    catch (System.IO.IOException)
                    {
                        DisconnectClient();
                        break;
                    }
                    catch (NullReferenceException)
                    {
                        DisconnectClient();
                        break;
                    }
                    catch (ObjectDisposedException)
                    {
                        //Do nothing - client is already disconnecting.
                        break;
                    }
                    catch (System.InvalidOperationException)
                    {
                        //Do nothing - client is already disconnecting.
                        break;
                    }
                }
            }
        }
    }
}

That is a lot of code, but luckily for you, you can ignore most of it. As with the client code in game maker, what you really only need to focus on is the Read method.

Code:
/// <summary>
/// Reads data from the client and sends back a response.
/// </summary>
public void Read(TcpClient client)
{
    while (true)
    {
        try
        {
            Thread.Sleep(10);
            BufferStream readBuffer = new BufferStream(BufferSize, 1);
            NetworkStream stream = client.GetStream();
            stream.Read(readBuffer.Memory, 0, BufferSize);

            //Read the header data.
            ushort constant;
            readBuffer.Read(out constant);

            //Determine input commmand.
            switch (constant)
            {
                //New Connection
                case 2000:
                {
                    //Read out client data.
                    String ip;
                    readBuffer.Read(out ip);

                    //Update client information.
                    ClientIPAddress = ip;

                    //Console Message.
                    Console.WriteLine(ip + " connected.");
                    Console.WriteLine(Convert.ToString(ParentServer.Clients.Count) + " clients online.");
                    break;
                }
       
                //Recive client ping.
                case 2004:
                {
                    //Send ping return to client.
                    BufferStream buffer = new BufferStream(BufferSize, BufferAlignment);
                    buffer.Seek(0);
                    UInt16 constant_out = 1050;
                    buffer.Write(constant_out);
                    SendMessage(buffer);
                    break;
                }
            }
        }
    }
}
Here you basically do the same thing as on the client. The server have a switch that handles constants you send to the server, where it processes the data and sends it to other clients if needed. For sending data back to the client you create a buffer as show in the example above, add the data to it, then send the message to the client.

And that's it! Disconnection of clients and ping is already handled on both the server and the client. The only thing you have to worry about, is how you handle data received from the client on the server. For that I cannot help you, as this differs from game to game, however, I can tell you how my game works.
 
Last edited by a moderator:
C

Cinderfire

Guest
How Crux does networking.

Game Here: https://forum.yoyogames.com/index.php?threads/crux-fantasy-board-game.40754/

This is done with sending the input, and then simulating it on all clients. This makes it so, that the only data that is send is the x and y coordinates, and if it's left or right mouse button pressed. The game's code is then setup in such a way, that it can handle simulations of input for the opposing player.

For matchmaking, the client sends a request to find a match to the server. The server then adds the client to a list of searching clients. A separate thread then handles matchmaking, and when 2 clients is found, it creates a game between them and removes them from the list. They are then added to a lobby on the server and a start game buffer is send to clients.

The client receives this input, and processes the data, and starts a game, where the two players can now send inputs by clicking with their cursors. This sends the x and y position to the server as well as the clicks. When the server receives this data from the client it sends this data out to all connected players within the lobby.

You might also note that the client object on the server have a few variables.

Code:
/// <summary>
/// Handles clients. Reads and writes data and stores client information.
/// </summary>
public class SocketHelper
{
    public string ClientIPAddress;
    public string ClientName;
    public string ClientRace;
    public int ClientNumber;
    public Lobby GameLobby;
    public bool IsSearching;
    public bool IsIngame;
}

These are also sorta specific to Crux, and are updated with the player information. This is especially useful for when the client disconnects, so you can handle what to do, such as remove the client from matchmaking or tell their opponent in the lobby that they have disconnected.

Code:
/// <summary>
/// Disconnects the client from the server and stops all threads for client.
/// </summary>
public void DisconnectClient()
{
    //Console Message.
    Console.WriteLine("Disconnecting: " + ClientIPAddress);

    //Check if client is ingame.
    if (IsIngame)
    {
        //Find opposing client.
        SocketHelper opponet = null;
        foreach (SocketHelper lobbyClient in GameLobby.LobbyClients)
        {
            if (lobbyClient != this)
            {
                opponet = lobbyClient;
            }
        }
 
        //Causes opponent to win.
        BufferStream buffer = new BufferStream(BufferSize, BufferAlignment);
        buffer.Seek(0);
        UInt16 constant_out = 1010;
        buffer.Write(constant_out);
        opponet.SendMessage(buffer);
        Console.WriteLine(ClientIPAddress + " is ingame. Granting win to opponent.");

        //Remove lobby from server.
        ParentServer.Lobbies.Remove(GameLobby);
        GameLobby = null;
        IsIngame = false;
    }

    //Removes client from server.
    ParentServer.Clients.Remove(this);
    if (IsSearching)
    {
        Console.WriteLine(ClientIPAddress + " was searching for a game. Stopped searching.");
        ParentServer.SearchingClients.Remove(this);
        IsSearching = false;
    }

    //Closes Stream.
    MscClient.Close();

    //Starts an abort thread.
    AbortThread = new Thread(new ThreadStart(delegate
    {
        Abort();
    }));
    Console.WriteLine("Aborting threads on client.");
    AbortThread.Start();
}

I hope this example/tutorial have been helpful for you. If so, please like and leave a comment. I might simplify it and make it more generalized in the future, or have a chat example instead of a more complex complete game. Who knows.

Also if you have the time, check out my game Crux, the example for which this was made.
 
Last edited by a moderator:
G

goodprogrammer

Guest
Thank you for your good example
But you use some funcion and obj NOT existe in your example, pleas discribe what these obj and funcion do?
And also if you can pleas upload a simply source on deropbox pleas
 

Yaazarai

Member
Hey, thanks for the reference here... I'm hoping to write a more comprehensive tutorial in the future. Sorry for the problems and lack of support.
 
Top