Search Unity

[Released] Master Server Kit: create your own UNET-based dedicated server

Discussion in 'Assets and Asset Store' started by gamevanilla, Oct 14, 2016.

Thread Status:
Not open for further replies.
  1. onesoftgames

    onesoftgames

    Joined:
    Dec 27, 2016
    Posts:
    6
    Hi Spelltwine!
    Thank for your answer. I just follow the link and learn a bit about UNET. So basically, we using MSK to host one of client instance that hold logic code (act as the server) ? And all other client code will not include the "logic code"?
    And if we using MSK, do we still need enable multiplayer service from unity, do we have any limit of ccu ?
    Thanks! I think MSK is really awesome!
     
  2. gamevanilla

    gamevanilla

    Joined:
    Dec 28, 2015
    Posts:
    968
    Thank you!
     
  3. gamevanilla

    gamevanilla

    Joined:
    Dec 28, 2015
    Posts:
    968
    You are welcome, and thank you for your kind words!

    When using Unity Services, one player always acts like the host of the game (meaning it is the server and a client at the same time). When using Master Server Kit, all players are exclusively clients and your dedicated server hosts a game server instance for the game (effectively containing the authoritative logic of the game).

    You do not need to enable Unity Services when using the kit and you have no artificial CCU limits (you will be able to host as many players as the combination of your game and server physically allows). You will also be responsible for managing your own servers.
     
  4. codyraymiller

    codyraymiller

    Joined:
    Jun 1, 2017
    Posts:
    10
    Spelltwine, I got it working. I still have no idea what the problem was but removing and then reimporting the MSK asset solved the issue. Everything works just great now with my Digital Ocean server.

    Thank you for all your help!
     
    gamevanilla likes this.
  5. gamevanilla

    gamevanilla

    Joined:
    Dec 28, 2015
    Posts:
    968
    Really happy to hear that! :)
     
  6. drewdough

    drewdough

    Joined:
    Oct 5, 2016
    Posts:
    57
    hi!

    First, let me say THANK YOU for creating this amazing asset. The code is super clean and the documentation is well made.

    I have been through other options such as Photon Server & Bolt (almost NO) documentation or guides for Authoritative Server stuff, MSK Master Server (code wasn't as clean IMO) and reviewed Forge, etc. This so far is the most promising option to me.

    I have a question as well as a comment/critique.

    First, my question. If I commit to building my game on top of this framework, I'll be moving away from some higher level options which might provide game level logic for the network synchronization. Such as physical movement. I'm wondering, can we still use the UNET HLAPI in conjunction with the low level code that's being used here, to do things like NetworkedAnimation? Or would that break the paradigm of the low level code we see here? Is there any other networking APIs which can work with this project which would empower us to synchronize movement and animations?

    Next, a critique. I want to first say that this code is SUPER clean. I've been a C# developer for over a decade and I'm a stickler for clean code. That being said - the database level code could use a little improvement IMO.

    1. You are opening a single connection and then NOT closing it. I could be totally wrong as I haven't been doing game dev for very long, but isn't this a bad practice as maintaining an open connection to a database will not work long term due to connectivity issues. Opening the connection for each transaction will be worth the cost of the overhead to do so and should not be avoided as that IMO is a premature optimization. If we find DB access is a bottleneck via profiling, there are other options to explore before maintaining an open connection to the SQL Server. One option would be adding a caching layer in a memory, the other would be to group together transactions into batches.
    2. The current version of the code doesn't cleanly make use of 'using' statements for the connection, command, and adapter. This means we aren't properly cleaning up the resources for these objects as Dispose() is never actually called manually. I do see you manually call Close() on the command, but this doesn't actually free up the resources fully as Dispose does. And since there's no try/catch wrapping, it also means an exception could even fire at some point and prevent the Close() from being called.
    3. Lots of usage of Coroutines with complex callback mechanisms to return information. Have you had a chance yet to look at UniRX? It allows us to remove the coroutines and allow a clean way for exceptions to propagate and values to be returned from the method, without using any callbacks. See below:

    A re-write of the IDatabaseProvider could look like this (no more callbacks and straight value returns and exceptions being raised appropriately)
    Code (CSharp):
    1. // Copyright (C) 2016-2017 Spelltwine Games. All rights reserved.
    2. // This code can only be used under the standard Unity Asset Store End User License Agreement,
    3. // a copy of which is available at http://unity3d.com/company/legal/as_terms.
    4.  
    5. using System;
    6. using System.Collections;
    7. using System.Threading.Tasks;
    8.  
    9. namespace MasterServerKit
    10. {
    11.     /// <summary>
    12.     /// Interface that defines a database provider to be used for storing the player data. By default,
    13.     /// SQLite, MongoDB and LiteDB implementations are provided with the kit.
    14.     /// </summary>
    15.     public interface IRXDatabaseProvider
    16.     {
    17.         /// <summary>
    18.         /// Performs any initialization-related logic.
    19.         /// </summary>
    20.         void InitializeDatabase();
    21.  
    22.         /// <summary>
    23.         /// Registers a new user with the specified properties in the system.
    24.         /// </summary>
    25.         /// <param name="email">The new user's email address.</param>
    26.         /// <param name="username">The new user's name.</param>
    27.         /// <param name="password">The new user's password.</param>
    28.         /// <param name="onSuccess">Delegate to be executed when the request is successful.</param>
    29.         /// <param name="onError">Delegate to be executed when the request is not successful.</param>
    30.         /// <returns>Async operation for the request.</returns>
    31.         RegistrationError Register(string email, string username, string password);
    32.  
    33.         /// <summary>
    34.         /// Logs the specified user in the system.
    35.         /// </summary>
    36.         /// <param name="username">The user's name.</param>
    37.         /// <param name="password">The user's password.</param>
    38.         /// <param name="onSuccess">Delegate to be executed when the request is successful.</param>
    39.         /// <param name="onError">Delegate to be executed when the request is not successful.</param>
    40.         /// <returns>Async operation for the remote request.</returns>
    41.         LoginError Login(string username, string password);
    42.  
    43.         /// <summary>
    44.         /// Gets the specified integer property from the specified user.
    45.         /// </summary>
    46.         /// <param name="username">The user's name.</param>
    47.         /// <param name="key">The property's key.</param>
    48.         /// <param name="onSuccess">Delegate to be executed when the request is successful.</param>
    49.         /// <param name="onError">Delegate to be executed when the request is not successful.</param>
    50.         /// <returns>Async operation for the remote request.</returns>
    51.         int GetIntProperty(string username, string key);
    52.  
    53.         /// <summary>
    54.         /// Sets the specified integer property from the specified user to the specified value.
    55.         /// </summary>
    56.         /// <param name="username">The user's name.</param>
    57.         /// <param name="key">The property's key.</param>
    58.         /// <param name="value">The property's value.</param>
    59.         /// <param name="onSuccess">Delegate to be executed when the request is successful.</param>
    60.         /// <param name="onError">Delegate to be executed when the request is not successful.</param>
    61.         /// <returns>Async operation for the remote request.</returns>
    62.         void SetIntProperty(string username, string key, int value);
    63.  
    64.         /// <summary>
    65.         /// Gets the specified string property from the specified user.
    66.         /// </summary>
    67.         /// <param name="username">The user's name.</param>
    68.         /// <param name="key">The property's key.</param>
    69.         /// <param name="onSuccess">Delegate to be executed when the request is successful.</param>
    70.         /// <param name="onError">Delegate to be executed when the request is not successful.</param>
    71.         /// <returns>Async operation for the remote request.</returns>
    72.         string GetStringProperty(string username, string key);
    73.  
    74.         /// <summary>
    75.         /// Sets the specified string property from the specified user to the specified value.
    76.         /// </summary>
    77.         /// <param name="username">The user's name.</param>
    78.         /// <param name="key">The property's key.</param>
    79.         /// <param name="value">The property's value.</param>
    80.         /// <param name="onSuccess">Delegate to be executed when the request is successful.</param>
    81.         /// <param name="onError">Delegate to be executed when the request is not successful.</param>
    82.         /// <returns>Async operation for the remote request.</returns>
    83.         void SetStringProperty(string username, string key, string value);
    84.     }
    85. }
    86.  
    The implementation looks like this: (beyond just using return values and exceptions, this version also makes use of 'using' statements which will properly dispose of resources as well as maintaining short connections):
    Code (CSharp):
    1. // Copyright (C) 2016-2017 Spelltwine Games. All rights reserved.
    2. // This code can only be used under the standard Unity Asset Store End User License Agreement,
    3. // a copy of which is available at http://unity3d.com/company/legal/as_terms.
    4.  
    5. using System;
    6. using System.Collections;
    7.  
    8. using Mono.Data.Sqlite;
    9. using System.Threading.Tasks;
    10.  
    11. namespace MasterServerKit
    12. {
    13.     /// <summary>
    14.     /// SQLite database provider implementation.
    15.     /// </summary>
    16.     public class RXSQLiteProvider : IRXDatabaseProvider
    17.     {
    18.         /// <summary>
    19.         /// Name of the SQLite database.
    20.         /// </summary>
    21.         private static readonly string databaseName = "database";
    22.         private static readonly string path = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
    23.         private static readonly string dbPath = "URI=file:" + path + "/" + databaseName + ".s3db";
    24.  
    25.         /// <summary>
    26.         /// Connection to the SQLite database.
    27.         /// </summary>
    28.        // private SqliteConnection connection;
    29.  
    30.         /// <summary>
    31.         /// Performs any initialization-related logic.
    32.         /// </summary>
    33.         public void InitializeDatabase()
    34.         {
    35.             using (var connection = new SqliteConnection(dbPath))
    36.             {
    37.  
    38.                 connection.Open();
    39.  
    40.                 using (var command = connection.CreateCommand())
    41.                 {
    42.                     var sql = @"CREATE TABLE IF NOT EXISTS User (
    43.                            id INTEGER PRIMARY KEY,
    44.                            username TEXT NOT NULL UNIQUE,
    45.                            email TEXT NOT NULL UNIQUE,
    46.                            password TEXT NOT NULL
    47.                        )";
    48.  
    49.                     command.CommandText = sql;
    50.                     command.ExecuteNonQuery();
    51.                 }
    52.  
    53.                 using (var command = connection.CreateCommand())
    54.                 {
    55.                     var sql = @"CREATE TABLE IF NOT EXISTS IntProperty (
    56.                                id INTEGER PRIMARY KEY,
    57.                                user_id INTEGER NOT NULL,
    58.                                key TEXT NOT NULL UNIQUE,
    59.                                value INTEGER NOT NULL,
    60.                                FOREIGN KEY(user_id) REFERENCES User(id)
    61.                            )";
    62.  
    63.                     command.CommandText = sql;
    64.                     command.ExecuteNonQuery();
    65.                 }
    66.  
    67.                 using (var command = connection.CreateCommand())
    68.                 {
    69.                     var sql = @"CREATE TABLE IF NOT EXISTS StringProperty (
    70.                        id INTEGER PRIMARY KEY,
    71.                        user_id INTEGER NOT NULL,
    72.                        key TEXT NOT NULL UNIQUE,
    73.                        value TEXT NOT NULL,
    74.                        FOREIGN KEY(user_id) REFERENCES User(id)
    75.                    )";
    76.                     command.CommandText = sql;
    77.                     command.ExecuteNonQuery();
    78.                 }
    79.             }
    80.         }
    81.  
    82.         /// <summary>
    83.         /// Registers a new user with the specified properties in the system.
    84.         /// </summary>
    85.         /// <param name="email">The new user's email address.</param>
    86.         /// <param name="username">The new user's name.</param>
    87.         /// <param name="password">The new user's password.</param>
    88.         /// <param name="onSuccess">Delegate to be executed when the request is successful.</param>
    89.         /// <param name="onError">Delegate to be executed when the request is not successful.</param>
    90.         /// <returns>Async operation for the request.</returns>
    91.         ///
    92.         public RegistrationError Register(string email, string username, string password)//Action<string> onSuccess, Action<RegistrationError> onError
    93.         {
    94.             if (email.IsNullOrWhitespace())
    95.             {
    96.                 return RegistrationError.MissingEmailAddress;
    97.             }
    98.  
    99.             if (username.IsNullOrWhitespace())
    100.             {
    101.                 return RegistrationError.MissingUsername;
    102.             }
    103.  
    104.             if (password.IsNullOrWhitespace())
    105.             {
    106.                 return RegistrationError.MissingPassword;
    107.             }
    108.  
    109.             using (var connection = new SqliteConnection(dbPath))
    110.             {
    111.                 connection.Open();
    112.  
    113.                 var command = connection.CreateCommand();
    114.                 var sql = "SELECT * from User WHERE email = @email";
    115.                 command.CommandText = sql;
    116.                 command.Parameters.AddWithValue("@email", email);
    117.  
    118.                 using (var reader = command.ExecuteReader())
    119.                 {
    120.                     if (reader.HasRows)
    121.                     {
    122.                         return RegistrationError.AlreadyExistingEmailAddress;
    123.                     }
    124.                 }
    125.  
    126.                 sql = "SELECT * from User WHERE username = @username";
    127.                 command.CommandText = sql;
    128.                 command.Parameters.Clear();
    129.                 command.Parameters.AddWithValue("@username", username);
    130.  
    131.                 using (var reader = command.ExecuteReader())
    132.                 {
    133.                     if (reader.HasRows)
    134.                     {
    135.                         return RegistrationError.AlreadyExistingUsername;
    136.                     }
    137.                 }
    138.  
    139.                 var passwordToStore = CryptoUtils.GenerateCombinedSaltAndHash(password);
    140.  
    141.                 sql = "INSERT INTO User (username, email, password) VALUES (@username, @email, @password)";
    142.                 command.CommandText = sql;
    143.                 command.Parameters.Clear();
    144.                 command.Parameters.AddWithValue("@username", username);
    145.                 command.Parameters.AddWithValue("@email", email);
    146.                 command.Parameters.AddWithValue("@password", passwordToStore);
    147.                 command.ExecuteNonQuery();
    148.  
    149.                 return RegistrationError.None;
    150.             }
    151.         }
    152.  
    153.         /// <summary>
    154.         /// Logs the specified user in the system.
    155.         /// </summary>
    156.         /// <param name="username">The user's name.</param>
    157.         /// <param name="password">The user's password.</param>
    158.         /// <param name="onSuccess">Delegate to be executed when the request is successful.</param>
    159.         /// <param name="onError">Delegate to be executed when the request is not successful.</param>
    160.         /// <returns>Async operation for the remote request.</returns>
    161.         public LoginError Login(string username, string password)
    162.         {
    163.             if (username.IsNullOrWhitespace())
    164.             {
    165.                 return LoginError.MissingUsername;
    166.             }
    167.  
    168.             if (password.IsNullOrWhitespace())
    169.             {
    170.                 return LoginError.MissingPassword;
    171.             }
    172.  
    173.             using (var connection = new SqliteConnection(dbPath))
    174.             {
    175.                 connection.Open();
    176.  
    177.                 var command = connection.CreateCommand();
    178.                 var sql = "SELECT * from User WHERE username = @username";
    179.                 command.CommandText = sql;
    180.                 command.Parameters.AddWithValue("@username", username);
    181.                 var reader = command.ExecuteReader();
    182.  
    183.                 if (!reader.HasRows)
    184.                 {
    185.                     return LoginError.NonexistingUser;
    186.                 }
    187.  
    188.                 reader.Read();
    189.  
    190.                 var storedPassword = reader["password"] as string;
    191.                 if (!CryptoUtils.CheckPasswordsAreEqual(password, storedPassword))
    192.                 {
    193.                     return LoginError.InvalidCredentials;
    194.                 }
    195.             }
    196.  
    197.             return LoginError.None;
    198.         }
    199.  
    200.         /// <summary>
    201.         /// Gets the specified integer property from the specified user.
    202.         /// </summary>
    203.         /// <param name="username">The user's name.</param>
    204.         /// <param name="key">The property's key.</param>
    205.         /// <param name="onSuccess">Delegate to be executed when the request is successful.</param>
    206.         /// <param name="onError">Delegate to be executed when the request is not successful.</param>
    207.         /// <returns>Async operation for the remote request.</returns>
    208.         public int GetIntProperty(string username, string key)
    209.         {
    210.             int returnValue;
    211.  
    212.             using (var connection = new SqliteConnection(dbPath))
    213.             {
    214.                 connection.Open();
    215.  
    216.                 int userId;
    217.                 var command = connection.CreateCommand();
    218.                 var sql = "SELECT * from User WHERE username = @username";
    219.                 command.CommandText = sql;
    220.                 command.Parameters.AddWithValue("@username", username);
    221.                 using (var reader = command.ExecuteReader())
    222.                 {
    223.                     if (!reader.HasRows)
    224.                     {
    225.                         throw new ArgumentException("This user does not exist.", username);
    226.                     }
    227.  
    228.                     reader.Read();
    229.                     userId = reader.GetInt32(reader.GetOrdinal("id"));
    230.                 }
    231.  
    232.                 sql = "SELECT * from IntProperty WHERE user_id = @user_id AND key = @key";
    233.                 command.CommandText = sql;
    234.                 command.Parameters.AddWithValue("@user_id", userId);
    235.                 command.Parameters.AddWithValue("@key", key);
    236.  
    237.                 using (var reader = command.ExecuteReader())
    238.                 {
    239.                     if (!reader.HasRows)
    240.                     {
    241.                         throw new ArgumentException("This property does not exist.", key);
    242.                     }
    243.  
    244.                     returnValue = Convert.ToInt32(reader["value"]);
    245.                 }
    246.             }
    247.  
    248.             return returnValue;
    249.         }
    250.  
    251.  
    252.         /// <summary>
    253.         /// Sets the specified integer property from the specified user to the specified value.
    254.         /// </summary>
    255.         /// <param name="username">The user's name.</param>
    256.         /// <param name="key">The property's key.</param>
    257.         /// <param name="value">The property's value.</param>
    258.         /// <param name="onSuccess">Delegate to be executed when the request is successful.</param>
    259.         /// <param name="onError">Delegate to be executed when the request is not successful.</param>
    260.         /// <returns>Async operation for the remote request.</returns>
    261.         public void SetIntProperty(string username, string key, int value)
    262.         {
    263.             using (var connection = new SqliteConnection(dbPath))
    264.             {
    265.                 int userId;
    266.  
    267.                 using (var command = connection.CreateCommand())
    268.                 {
    269.                     connection.Open();
    270.  
    271.                     var sql = "SELECT * from User WHERE username = @username";
    272.                     command.CommandText = sql;
    273.                     command.Parameters.AddWithValue("@username", username);
    274.                     using (var reader = command.ExecuteReader())
    275.                     {
    276.                         if (!reader.HasRows)
    277.                         {
    278.                             throw new ArgumentException("This user does not exist.", username);
    279.                         }
    280.  
    281.                         reader.Read();
    282.                         userId = reader.GetInt32(reader.GetOrdinal("id"));
    283.                     }
    284.  
    285.                     sql = "SELECT * from IntProperty WHERE user_id = @user_id AND key = @key";
    286.                     command.CommandText = sql;
    287.                     command.Parameters.AddWithValue("@user_id", userId);
    288.                     command.Parameters.AddWithValue("@key", key);
    289.  
    290.                     using (var reader = command.ExecuteReader())
    291.                     {
    292.                         if (!reader.HasRows)
    293.                         {
    294.                             sql = "INSERT INTO IntProperty (user_id, key, value) VALUES (@user_id, @key, @value)";
    295.                             command.CommandText = sql;
    296.                             command.Parameters.Clear();
    297.                             command.Parameters.AddWithValue("@user_id", userId);
    298.                             command.Parameters.AddWithValue("@key", key);
    299.                             command.Parameters.AddWithValue("@value", value);
    300.                             command.ExecuteNonQuery();
    301.                         }
    302.                     }
    303.  
    304.                     sql = "UPDATE IntProperty SET value = @value WHERE user_id = @user_id AND key = @key";
    305.                     command.CommandText = sql;
    306.                     command.Parameters.Clear();
    307.                     command.Parameters.AddWithValue("@value", value);
    308.                     command.Parameters.AddWithValue("@user_id", userId);
    309.                     command.Parameters.AddWithValue("@key", key);
    310.                     command.ExecuteNonQuery();
    311.                 }
    312.             }
    313.         }
    314.  
    315.         /// <summary>
    316.         /// Gets the specified string property from the specified user.
    317.         /// </summary>
    318.         /// <param name="username">The user's name.</param>
    319.         /// <param name="key">The property's key.</param>
    320.         /// <param name="onSuccess">Delegate to be executed when the request is successful.</param>
    321.         /// <param name="onError">Delegate to be executed when the request is not successful.</param>
    322.         /// <returns>Async operation for the remote request.</returns>
    323.         public string GetStringProperty(string username, string key)
    324.         {
    325.             string returnValue;
    326.  
    327.             using (var connection = new SqliteConnection(dbPath))
    328.             {
    329.                 connection.Open();
    330.  
    331.                 using (var command = connection.CreateCommand())
    332.                 {
    333.                     int userId;
    334.                     var sql = "SELECT * from User WHERE username = @username";
    335.                     command.CommandText = sql;
    336.                     command.Parameters.AddWithValue("@username", username);
    337.  
    338.                     using (var reader = command.ExecuteReader())
    339.                     {
    340.                         if (!reader.HasRows)
    341.                         {
    342.                             throw new ArgumentException("This user does not exist.", username);
    343.                         }
    344.  
    345.                         reader.Read();
    346.                         userId = reader.GetInt32(reader.GetOrdinal("id"));
    347.                     }
    348.  
    349.                     sql = "SELECT * from StringProperty WHERE user_id = @user_id AND key = @key";
    350.                     command.CommandText = sql;
    351.                     command.Parameters.AddWithValue("@user_id",userId);
    352.                     command.Parameters.AddWithValue("@key", key);
    353.  
    354.                     using (var reader = command.ExecuteReader())
    355.                     {
    356.                         if (!reader.HasRows)
    357.                         {
    358.                             throw new ArgumentException("This property does not exist.", key);
    359.                         }
    360.  
    361.                         returnValue = reader["value"] as string;
    362.                     }
    363.                 }
    364.  
    365.                 return returnValue;
    366.             }
    367.         }
    368.  
    369.         /// <summary>
    370.         /// Sets the specified string property from the specified user to the specified value.
    371.         /// </summary>
    372.         /// <param name="username">The user's name.</param>
    373.         /// <param name="key">The property's key.</param>
    374.         /// <param name="value">The property's value.</param>
    375.         /// <param name="onSuccess">Delegate to be executed when the request is successful.</param>
    376.         /// <param name="onError">Delegate to be executed when the request is not successful.</param>
    377.         /// <returns>Async operation for the remote request.</returns>
    378.         public void SetStringProperty(string username, string key, string value)
    379.         {
    380.             using (var connection = new SqliteConnection(dbPath))
    381.             {
    382.                 connection.Open();
    383.  
    384.                 using (var command = connection.CreateCommand())
    385.                 {
    386.                     int userId;
    387.  
    388.                     var sql = "SELECT * from User WHERE username = @username";
    389.                     command.CommandText = sql;
    390.                     command.Parameters.AddWithValue("@username", username);
    391.                     using (var reader = command.ExecuteReader())
    392.                     {
    393.  
    394.                         if (!reader.HasRows)
    395.                         {
    396.                             throw new ArgumentException("This user does not exist.", username);
    397.                         }
    398.  
    399.                         reader.Read();
    400.                         userId = reader.GetInt32(reader.GetOrdinal("id"));
    401.                     }
    402.          
    403.  
    404.                     sql = "SELECT * from StringProperty WHERE user_id = @user_id AND key = @key";
    405.                     command.CommandText = sql;
    406.                     command.Parameters.AddWithValue("@user_id", userId);
    407.                     command.Parameters.AddWithValue("@key", key);
    408.  
    409.                     using (var reader = command.ExecuteReader())
    410.                     {
    411.                         if (!reader.HasRows)
    412.                         {
    413.                             reader.Close();
    414.  
    415.                             sql = "INSERT INTO StringProperty (user_id, key, value) VALUES (@user_id, @key, @value)";
    416.                             command.CommandText = sql;
    417.                             command.Parameters.Clear();
    418.                             command.Parameters.AddWithValue("@user_id", userId);
    419.                             command.Parameters.AddWithValue("@key", key);
    420.                             command.Parameters.AddWithValue("@value", value);
    421.                             command.ExecuteNonQuery();
    422.                         }
    423.  
    424.                         reader.Close();
    425.                         sql = "UPDATE StringProperty SET value = @value WHERE user_id = @user_id AND key = @key";
    426.                         command.CommandText = sql;
    427.                         command.Parameters.Clear();
    428.                         command.Parameters.AddWithValue("@value", value);
    429.                         command.Parameters.AddWithValue("@user_id", userId);
    430.                         command.Parameters.AddWithValue("@key", key);
    431.                         command.ExecuteNonQuery();
    432.                     }
    433.                 }
    434.             }
    435.         }
    436.     }
    437. }
    The way we now make use of the more straight-forward and simplified DB classes is via UniRX Observable. To see an example of how that's done, check out the new code I have for OnPlayerLoginRequested:

    Code (CSharp):
    1.         protected virtual void OnPlayerLoginRequested(NetworkMessage netMsg)
    2.         {
    3.             var masterServer = baseServer as MasterServer;
    4.  
    5.             var msg = netMsg.ReadMessage<RequestPlayerLoginMessage>();
    6.  
    7.             var responseMsg = IsJoinAllowed(msg);
    8.             if (responseMsg.success == false)
    9.             {
    10.                 netMsg.conn.Send(AuthenticationNetworkProtocol.ResponsePlayerLogin, responseMsg);
    11.                 return;
    12.             }
    13.  
    14.             if (msg.isAnonymous)
    15.             {
    16.                 var player = new Player(netMsg.conn, masterServer.guestName);
    17.                 baseServer.players.Add(player);
    18.  
    19.                 responseMsg.success = true;
    20.                 responseMsg.username = player.name;
    21.                 netMsg.conn.Send(AuthenticationNetworkProtocol.ResponsePlayerLogin, responseMsg);
    22.             }
    23.             else
    24.             {
    25.  
    26.                 Observable.Start(() =>
    27.                 {
    28.                     // heavy method...
    29.                     var value = RXDatabaseService.Login(msg.username, msg.password);
    30.  
    31.                     if (value == LoginError.None)
    32.                     {
    33.                         responseMsg.success = true;
    34.                         responseMsg.username = msg.username;
    35.                         netMsg.conn.Send(AuthenticationNetworkProtocol.ResponsePlayerLogin, responseMsg);
    36.  
    37.                         var player = new Player(netMsg.conn, msg.username);
    38.                         baseServer.players.Add(player);
    39.                     }
    40.                     else
    41.                     {
    42.                         responseMsg.success = false;
    43.                         responseMsg.error = value;
    44.                         netMsg.conn.Send(AuthenticationNetworkProtocol.ResponsePlayerLogin, responseMsg);
    45.                     }
    46.                 }).Subscribe();
    47.  
    48.             }
    49.         }
    UniRX is SUPER bad ass. Now the code is JUST as clean and simple as before in the Handler, but we aren't forced into the Coroutines design and can have proper return values and error handling....

    Again, I hope you don't take my comments the wrong way.. I think this asset is CRAZY clean and well done. I am very grateful for you putting this together and sharing it and I'm happy to support in any way I can.. I hope my suggestions might help!

    Thanks again!
     
    Last edited: Jun 5, 2017
    gamevanilla likes this.
  7. gamevanilla

    gamevanilla

    Joined:
    Dec 28, 2015
    Posts:
    968
    Thank you for your purchase and your kind words! :)

    You can continue using the entirety of UNET's HLAPI when using the kit. In that regard, the kit does not make any assumptions about how you should write your game logic; you can use the HLAPI or go lower level if you want or need to. For example, in CCG Kit I use high-level constructs like the Network Manager, NetworkBehaviour, commands, SyncVars, etc. and the only change required when using Master Server Kit instead of Unity Services is in the matchmaking calls (meaning the game code works with no other changes). There is actually one assumption: that you are writing your game logic in UNET (although as far as I can tell there is nothing actually preventing you from using a different library for your game logic if you really want to).

    There is a step-by-step guide on how to integrate the kit on a game that uses the HLAPI here.

    No worries at all and thank you! I love your feedback and completely agree with you. I am obsessive when it comes to well-written, clean and simple code so this kind of feedback is honestly very much welcome and appreciated.

    1. You are completely right! I had always assumed that keeping a single connection open would be better for performance reasons, but it turns out that is not necessarily the case (as many connectors provide pooled connections) and this approach actually has problems with dropped connections and multithreading, as you rightly point out.

    2. Again, you are completely right!

    3. The use of coroutines in the database classes is something that has bothered me for quite some time as well. I was planning to research the use of promises as a cleaner way of handling the asynchronous code (the kit already uses the C-Sharp-Promise library internally to minimize callback hell in other scenarios). It looks like, similarly to promises, UniRx provides an alternative, cleaner way of handling asynchronous code. I will definitely look closely into it!

    I will improve all these areas in the next update based on your feedback and code. Again, thank you very much!
     
    drewdough likes this.
  8. codyraymiller

    codyraymiller

    Joined:
    Jun 1, 2017
    Posts:
    10
    Spelltwine,

    I have a MySQL database already set up with usernames and password on a web server (not the same server as my dedicated game server). How would I go about connecting MSK to this database? I read the database portion of the documentation provided with MSK but it didn't seem to address this type of setup.

    Thanks in advance!
     
  9. gamevanilla

    gamevanilla

    Joined:
    Dec 28, 2015
    Posts:
    968
    If you have an existing database, you will to need to either migrate the data to a new database with the format used by the kit or alternatively change the kit's implementation to comply with the structure of your database. For MySQL, you can find the relevant code in the MySQLProvider class.
     
  10. drewdough

    drewdough

    Joined:
    Oct 5, 2016
    Posts:
    57
    hi Spelltwine,

    Thanks for the reply! I think you will find Reactive Extensions very addictive :D.

    I had another question, I noticed that the Start() method of the Server will listen with 2 listeners using 2 different classes for the same IP and port. It's using the NetworkServerSimple as well as the NetworkServer.

    Is this strictly because you want to support WebGL sockets? If I don't want to have that second listener, can I completely remove that code which is referencing the 'webGLServer' variable?

    My second question - I noticed you configured to use 2 channels, unreliable and reliable ordered. I'm wondering though, do you ever make use of these? It seems we always call the default Send() which the docs say "uses chanel 0 or reliable chanel by default". BTW, that doesn't make any sense to me since QoSType with value 0 is Unreliable....

    Thanks again!
     
    Last edited: Jun 6, 2017
  11. gamevanilla

    gamevanilla

    Joined:
    Dec 28, 2015
    Posts:
    968
    You are welcome! :)

    Yes, the code is unfortunately needed in order to support WebGL clients and you can remove it if you are not interested in the platform. It would be amazing if UNET provided automatic WebGL support without writing additional code in the future.

    The reasoning behind the code is that, because I need to manually configure the server in order to support a custom (higher than the default) number of players, I also need to specify a custom connection configuration. I decided to go with the default configuration used internally by the NetworkServerSimple class (after looking at its source code) as a reasonable baseline.

    Please note the channel number is a different thing than the QoSType. Send() will send the message on channel 0, which in our configuration is the reliable sequenced channel.
     
  12. drewdough

    drewdough

    Joined:
    Oct 5, 2016
    Posts:
    57
    hi spelltwine,

    Thanks for clarifying. I have 2 more questions, I hope it's OK.

    1) Have you load tested the Master Server to know how many connections it can handle at once? If not, do you have any estimates? Is it 100,1000,10,000? It seems like the Master Server should scale horizontally without much effort, no? Basically, put a load balancer in front of the Master Server and have that LB pick which MS to connect a client to. The Chat Server wouldn't work across Master Servers (without a shared cache like Redis), but I think everything else out of the box should work fine?

    2) I am still chugging through all of the code to understand how it works. I noticed the Zone Server will actually Start() a new process and that GameServer will wake up and register with the rest of the system. I saw this same design pattern in the other MSK in the asset store. I'm trying to understand why this design is chosen. Won't it be heavy to spawn a new process for every single game session? Why not have one process which managed multiple sessions within it? I would bet a single process for say a card game could probably manage 50+ games in that process. But in this design, I'd need to spawn 50 separate processes..... Seems like a lot?

    Also, is there a race condition for when the Game Server Starts()? IT seems like we Start() that process and then immediately try to Connect() to it on the network. What if there is a delay in that process fully loading? Maybe some error handling here with a rolling backoff/Sleep to make sure the GameServer fully starts before we fail on connecting?
     
    Last edited: Jun 6, 2017
  13. gamevanilla

    gamevanilla

    Joined:
    Dec 28, 2015
    Posts:
    968
    The exact numbers will always depend on the characteristics of your game and your server hardware, so there really is no way of providing an accurate estimate without testing a specific scenario in the real world.

    At the master server level, yes, you could have different instances if really needed without too much trouble. But if you need to scale, you will probably need to do so at the game server level, where the kit provides a solution via the zone servers (which you can distribute across different machines to implement regions or similar schemes).

    There is no single right or wrong answer here, but having an independent process per game session offers several advantages in terms of reliability and management (a failing process will not bring down the entire server, processes can be independently started and stopped, you can trivially distribute multiple processes across different machines). A monolithic game server will fall short as soon as you try to do something more complex or closer to real-time (where you will need to introduce different threads to handle different games, with all the traditional caveats of multi-threaded programming). All this, combined with the fact that I wanted to provide full compatibility with existing HLAPI, Unity Services-powered games, led to the current design of the kit.

    There is definitely an added cost in opening a Unity instance for a new game; hopefully we will be able to launch a standalone console server in the future (with UNET's server library).

    Having said that, the kit does not necessarily force you into a single process per game session paradigm. You are responsible for the game server side, so you can write it in a way that allows multiple game sessions per process if it makes more sense for your project.

    Improving the error handling in that part of the code is something that is already on my list. Definitely a good point, thank you!
     
  14. GXMark

    GXMark

    Joined:
    Oct 13, 2012
    Posts:
    514
    Just purchased MSK and am delighted how its been architected. Awsome!

    I have question related to integrating with my own project. I have followed your integrating into a Network Manager but still am having difficulty following some specifics.

    Firstly, i have successfully configured MySQL and able to register/login clients from a login scene.

    Question 1. How to get list of game server ip:ports to choose from ?

    Question 2. What is the process to get from the login scene to the client game scene?

    Does network manager component have to be part of the login scene?
    Do i have to join server on the login scene?
    Do i have to explicitly load a new scene from login scene, or does join server do that?

    Do i need the clientAPI component on both login scene and client scene ?
    Do i need the network manager component on both login and game client scene or only the login or game client?

    These are the confusing parts i need help on?

    Question 3. Do i derive the game server network manager from the master network manager class?

    your help on this would be much appreciated?
     
  15. GXMark

    GXMark

    Joined:
    Oct 13, 2012
    Posts:
    514
    How do i access the int and string database properties from the client ?

    I see it implemented in the database providers but dont see any clientAPI access ?
     
  16. gamevanilla

    gamevanilla

    Joined:
    Dec 28, 2015
    Posts:
    968
    Thank you for your purchase and your kind words!

    The FindGameRooms method of the matchmaking API will return the list of available game rooms together with their connection data. You have usage examples of the entire API in the accompanying demo.

    The ClientAPI and Network Manager objects on the client side will be kept alive through the entire lifetime of the application, so you only need them in your initial scene. As long as you properly configure the Network Manager with appropriate offline and online scenes, the transitions from the lobby scene to the game scene and vice versa will happen automatically just like with Unity Services. The transition from the login scene to the lobby scene is performed manually after successful authentication into the master server; you can see how this works in the accompanying demo.

    Yes. The Network Manager on the game server side needs to be derived from Master Server Network Manager in order for it to work in concert with the rest of the kit.
     
  17. gamevanilla

    gamevanilla

    Joined:
    Dec 28, 2015
    Posts:
    968
    This is precisely on my list of things to implement for the next update! :)
     
  18. drewdough

    drewdough

    Joined:
    Oct 5, 2016
    Posts:
    57
    hi Spelltwine,

    I guess I was asking if there was any load testing done on the Master Server in its current state. E.G, if the functionality remained as it currently is - just routing players to the games and handling chat + auth, what kind of load could it handle as is.

    I'm continuing to dig through all of the code to understand it. I'm also putting together a sequencing diagram that I will share here later since I think it may help someone who's trying to understand the flow of the connections.

    I did have some follow up questions. I noticed that the password the client sends is in plain text. Does this means that is vulnerable to a man in the middle attack, where anyone listening on the network (or anywhere in between) could determine the user's password? Do you have any plans or guidance for implementing secure logins using a TLS connection?

    Secondly, I also noticed that the GameServer does no validation when receiving connections from a client. It basically just starts Listening and accepting commands. Shouldn't the GameServer check a security token sent by the client, to ensure that this is an authorized client connection? The master server could generate this token after authentication and send it up the chain (to ZoneServer, then to GameServer) and the GameServer would know to expect a client to connect with that token value. The Master Server would give that token to the user when the logged in.

    Without this in place, the physical machine which hosts Game Servers is vulnerable to DDoS attacks as well as client injection. Since a malicious user can see what port + ip range he is connecting to when he plays the game, he could inject himself into other games by trying each port until he connected.

    I will probably implement this myself if I move forward with using this framework as my baseline for the solution, but wanted to make sure I'm not missing something you already covered?

    Thank you again!
     
  19. gamevanilla

    gamevanilla

    Joined:
    Dec 28, 2015
    Posts:
    968
    My answer would be the same as before. :)

    The exact numbers even without taking the game side into account will depend on the characteristics of your server hardware. I am not sure how useful reference numbers that are highly context-dependent can be, specially considering they will certainly change when taking the game side into account.

    That sounds like an excellent idea!

    That is right. UDP (which UNET uses) is not a secure protocol, so the only option to improve this until Unity implements the ability to encrypt traffic would be to use an HTTPS web server on top of it for handling the authentication process.

    This is an interesting thing to consider and it should be pretty doable to perform an additional check in the process. I will certainly add this to my list of things to implement.

    Again, thank you for all your excellent feedback! It is very much appreciated. :)
     
  20. drewdough

    drewdough

    Joined:
    Oct 5, 2016
    Posts:
    57
    hey,

    Thanks for the reply. I'm still working out the Auth situation.

    The problem is that I can't find a TLS implementation for UDP in Unity (DTLS). I think some older version of UNet had this feature, but haven't seen it yet. I might be missing something obvious though.

    I plan on moving Authentication out of the Master Server and implementing it in an ASP.NET MVC web application hosted on a TLS secured IIS instance. The Auth Service will manage the generation of one use tokens for the game client to hand over to the Master Server and Game Server. Any time a disconnect occurs, the client will re-authenticate via the Auth Service. I haven't worked out which style of token to use yet, but I'm leaning towards just generating a cryptographically unique 128 bit number. The alternative is an encrypted/hashed timestamped signature which could contain the users ID and such. The advantage to this is that you wouldn't need to do any lookups to map the userID to the token, since it would be directly embedded into the token itself. The disadvantage is slight complexity of managing private keys on the game client.

    I will also only implement account level activities in the Auth Service and not in the Game or Master servers. Activities like account creation, purchasing goods, consuming purchasable goods, and adding friends and maybe even messaging. This is because any non-TLS connection is subject to a man in the middle attack. That will be OK for in-game movements and such, but won't be acceptable for account level actions such as spending real life money $.

    I have a pretty good sequencing diagram setup, it's in LucidChart showing the sequence of a client joining the game in the current version of the code. I'm happy to forward to you the original document if you'd like to use it. It looks like this:

    upload_2017-6-13_21-12-4.png

    The other security hole right now is interserver communication. Right now Master Server listens for Zone and Game Server registrations. Since Zone Server does not accept any client interactions, that's an easy thing to secure by putting it behind a firewall. The Master Server is a different story. Right now I believe the code is written to listen for all of this on the same open port. A malicious user could invent his own game servers and leech bandwidth from my Master and troll users (and mess up a potentially successful business). I need to do more research, but I'm thinking that it might be good enough to just listen on different ports for the Game and Zone server registrations and keep those ports secured at the firewall level, as long as the Master Server lives on the same private network as the Zone and Game Servers I think this will be OK. If not, some sort of private key style auth (like OAuth) routing through the Auth Service will be needed.

    Any thoughts?

    Peace!

    Drew
     

    Attached Files:

    Last edited: Jun 14, 2017
    gamevanilla likes this.
  21. gamevanilla

    gamevanilla

    Joined:
    Dec 28, 2015
    Posts:
    968
    There is none available as far as I know. It would definitely be great to have a Network.InitializeSecurity equivalent in UNET.

    That sounds like an excellent idea. I will do more research about whether there are plans to add encrypted connections to UNET in the near/mid future. If there are not, it may be a good idea to provide an optional web service to handle the authentication process in the kit in the future. The login process would look something like this:

    1. The user sends his credentials to the authentication web service.
    2. The web service generates a short-lived, one-use unique token and returns it to the user.
    3. The user sends the token to the master server.
    4. The master server verifies the token with the web service and authorizes/rejects the user accordingly.

    As long as the web service is running over HTTPS, this should be a pretty straightforward approach to a more secure login.

    This is awesome, thank you! I am definitely interested in having the original document and will add the diagram to the official documentation.

    Another user reached me a few days ago asking about this. He suggested adding the possibility of defining a unique identifier in the master and zone servers that would allow to reject any game servers trying to register themselves into the system without an appropriate identifier. I believe it is a great idea and it is on my list of things to implement.

    Thank you again! :)
     
  22. drewdough

    drewdough

    Joined:
    Oct 5, 2016
    Posts:
    57
    What's your email address? I can send over the Lucidchart.

    As far as a shared secret for the Master server registrations, this may be OK or may not be enough. The problem with a shared secret alone is subject to MITM attack. For example, someone sitting on the wire between the 2 servers could snoop that shared secret and now register their own servers with the master. If the servers are firewalled off and block on those ports from the physical layer, this might be OK. I will research this more, but my initial thought is to use an encrypted key - master server has the public key and the each sub server has private keys. Registration would be validated via creating a signature, signing it with the key and sending it over. Signature would include the ServerID and timestamp. I'm not sure how great this is without TLS though. On that note, another thought is routing these connections through the Auth Service. The Game and Zone Servers could exchange private keys (using something like OAuth 2.0 protocol) to the Auth Service. The Auth Service would generate the 1-time use 'registration' token to allow the servers to connect. That to me actually sounds the most rock solid and fits in line with the Auth Service concept. This hosted version of the Auth Service could also be firewalled off so that only in-network services could connect to that endpoint to add additional security. However it would have the flexabiblity to be hosted on a public network outside of the domain (not sure if that's reasonably needed though). I am still in the midst of fleshing out all of these designs and will let you know when I come up with a finalized design.

    I do see that Photon Server provides TLS connections. The problem with Photon Server is the documentation and examples SUCK. The ones from this MSK kit are much cleaner and easier to follow!
     
  23. gamevanilla

    gamevanilla

    Joined:
    Dec 28, 2015
    Posts:
    968
    The support email address is available at my Asset Store publisher page, but feel free to send it over a private message on the forums if you prefer. Thank you!

    That should never happen though, as far as I can tell. The secret would be passed between servers you are in complete control of that would be locked to the outside via a firewall policy as you rightly point out.

    Yes, that is exactly the problem with any encryption-based approach running over a non-secure connection: you will eventually need to pass a key somewhere, which can be potentially intercepted, leading to the original issue of sending plain, non-encrypted data.

    The authentication service idea sounds more solid, but I still wonder if the additional complexity is warranted (considering my previous point).

    I asked one of the UNET developers about this earlier today and will let you know once I have an official answer.

    Thank you for the kind words! :)
     
  24. IAMBATMAN

    IAMBATMAN

    Joined:
    Aug 14, 2015
    Posts:
    272
    Hey looking for some info on this.

    If I use this with UNet, can a player host a server, port forward there router, and it'll show up in this master server list?
     
  25. GXMark

    GXMark

    Joined:
    Oct 13, 2012
    Posts:
    514
    I use a separate client and game server project for my game. I can't get the client to switch between the offline and online scene. I have both in my build settings and included in the network manager. I have the game server project setup using game server settings for a standalone. Can you shed some light on the reason it does not work. From what i can tell the JoinGameServer(ip,port) actually connects to the game server but it does not switch scenes where i have a public override void OnClientSceneChanged(NetworkConnection conn) {}
     
  26. gamevanilla

    gamevanilla

    Joined:
    Dec 28, 2015
    Posts:
    968
    If you are using a Network Manager, the offline-to-online scene switching (and vice versa) happens automatically and is performed under-the-hood by the manager if everything is properly configured; you do not need to write any code to perform the transition yourself. Did you follow the official guide on how to integrate the kit with a game that uses the Network Manager? In case you did and are still having issues, please reach me at the official support email with a sample project demonstrating the problem and I will be happy to investigate further.
     
  27. gamevanilla

    gamevanilla

    Joined:
    Dec 28, 2015
    Posts:
    968
    Hello! I have just submitted version 1.13 of the kit to the Asset Store. These are the release notes:
    • Implemented new methods in the client API that allow players to retrieve the values of properties from the database.
    • Improved the SQL database implementations by not caching the connections and properly disposing all related resources.
    • Added a 'game server registration id' field to the master server that can be used to prevent illegal game servers from being registered into the system.
    • Implemented a centralized way of providing a custom ConnectionConfig for all the servers and clients in the kit.
    • Removed the DisallowMultipleComponent attribute from the server addons so that user subclasses can be created.
    • Fixed wrong UNIQUE constraints for player properties in the SQL database implementations.
    All these features are based on suggestions made by the community (and more are coming in future updates), so thank you! As always, you can grab the update from the official private repository or, alternatively, wait a few days until it is officially approved on the store.
     
  28. Paradoks

    Paradoks

    Joined:
    Oct 13, 2009
    Posts:
    436
    Hi,

    Before i get onboard i got a few questions(more):
    - Do you have a roadmap ?
    - For now the game server join/creation is related to direct client request, you can create a game or join a game you select.
    But in recent games like dota, hots, lol, street fighter etc...The client only has to click on a "play" button, and the server handles the game server selection, you told me that it was possible to make it with the actual code, but ( and here is my question) could you add this feature ?

    I would need that with blind game server join too, player click on join existing game, and server handles.

    If you tell me yes, i buy now :D
     
  29. gamevanilla

    gamevanilla

    Joined:
    Dec 28, 2015
    Posts:
    968
    Not a roadmap per se, but I keep track of all the suggestions made by the community and they are my focus for future updates.

    The matchmaking API also contains a PlayNow method which automatically joins an existing available game or alternatively creates a new one (if none is available).

    I hope this helps!
     
  30. Paradoks

    Paradoks

    Joined:
    Oct 13, 2009
    Posts:
    436
    It helps!

    But, what i tought about the simple play button, the first one, goes like that:
    - client click play.
    - server get the clients request, and keep it in a list.
    - the server waits until 10 players are in the list.
    - when the list is full, the server ask for a confirmation ( could be an option, it is like that in Dota but not in Hots).
    - when all 10 confirmations comes, the server make the game server, and send the clients to this server.

    I really need that for my game, as i said i could maybe make it myself with your tool, but never well enough like you would, and also i would like to say that it is an important feature as many games use this way for match making.

    The other point i want to cover, is the "MasterServerNetworkManager", you say that NetworkManager should derive from MasterServerNetworkManager, but what if i got allready a custom NetworkManager ?
     
  31. gamevanilla

    gamevanilla

    Joined:
    Dec 28, 2015
    Posts:
    968
    Oh, I see what you mean. That should be doable; I will add it to my list of things to implement in future updates.

    You can still use your custom Network Manager, but you will need to change it so that it derives from the Master Server Network Manager (please note that this is only a requirement on the game server side; you can use your Network Manager as is on the client side just fine).
     
  32. gamevanilla

    gamevanilla

    Joined:
    Dec 28, 2015
    Posts:
    968
    Hello! Version 1.13 of the kit is now available on the Asset Store. These are the release notes:
    • Implemented new methods in the client API that allow players to retrieve the values of properties from the database.
    • Improved the SQL database implementations by not caching the connections and properly disposing all related resources.
    • Added a 'game server registration id' field to the master server that can be used to prevent illegal game servers from being registered into the system.
    • Implemented a centralized way of providing a custom ConnectionConfig for all the servers and clients in the kit.
    • Removed the DisallowMultipleComponent attribute from the server addons so that user subclasses can be created.
    • Fixed wrong UNIQUE constraints for player properties in the SQL database implementations.
     
  33. Paradoks

    Paradoks

    Joined:
    Oct 13, 2009
    Posts:
    436
    Hey,

    I am playing a little with the kit, to get familiar, and before i ask my question i want to say that is it very well made !

    questions:

    - When i use the registration, where are the informations ( login-pass) saved ? i see that they are but dont see where.
    - When trying to launch the demo, i must launch the zone server, but if it is mandatory , could you auto launch it at server creation ? to avoid problems ?
    - Could you explain a little bit more the difference between gameServer and the client gameServer ? this is not clear to me, i cant find an example where the both are not the same.

    thx
     
  34. gamevanilla

    gamevanilla

    Joined:
    Dec 28, 2015
    Posts:
    968
    Thank you! :)

    They are stored in a database. The kit provides database implementations for SQLite, MySQL, MongoDB and LiteDB (SQLite is used by default) and you can find more information about how they work here.

    The zone server is only needed when you want players to be able to dynamically create new game servers, which is particularly useful in room-based games. But it is not strictly required; you can also launch standalone game servers, which is more useful in world-based games.

    There is no game server on the client side with Master Server Kit; there is only an authoritative game server that you control on the server side and that manages the logic of the game for all its connected players (which act exclusively as clients). This is different than when using Unity Services, where there is no authoritative server and one player (the host) acts like the server and a client at the same time.

    I hope this helps!
     
  35. Paradoks

    Paradoks

    Joined:
    Oct 13, 2009
    Posts:
    436
    In fact, i can't actually find where the pass-logins are stored when i compile the demo.
    I get lost in the code, could you point me out, where in the code it writes, and where in the compiled files it is stored please ? thx
     
  36. gamevanilla

    gamevanilla

    Joined:
    Dec 28, 2015
    Posts:
    968
    You want to look at the Register and Login methods of the DatabaseService class and the equivalent methods in the database provider you are interested in (for example, SQLiteProvider if you are interested in the SQLite implementation).
     
  37. Paradoks

    Paradoks

    Joined:
    Oct 13, 2009
    Posts:
    436
    Found it, it was in fact pretty clear, for those wondering:
    C:\Users\[USER]\AppData\Roaming
    Is the path.

    I am adapting the register system to my game, and was wondering.
    Do you plan to make:
    1 - registering email confirmation
    2 - system for password recovery

    thx
     
  38. Paradoks

    Paradoks

    Joined:
    Oct 13, 2009
    Posts:
    436
    Me again, is there a way to get the players list connected to the lobby ?
    I need it for the chat, so people know who is there.
     
  39. gamevanilla

    gamevanilla

    Joined:
    Dec 28, 2015
    Posts:
    968
    That would be nice to have in the future, thank you for your suggestion! It would require an additional server outside Unity, though.
     
  40. gamevanilla

    gamevanilla

    Joined:
    Dec 28, 2015
    Posts:
    968
    There is no built-in way to do it, but it should be pretty easy to write a custom addon providing that functionality. I would generally advise against doing such a thing though; it would not scale very well with many players.
     
  41. Paradoks

    Paradoks

    Joined:
    Oct 13, 2009
    Posts:
    436
    It would be temporary, there should not be that much players for a while.
    I just dont know where to begin.

    For my beta i just need the "chat player list"+ the "full game client join at once" i told you about. I will wait for that anyway.

    Next step: integrate the actual game with the framework.
    Then i launch a beta!

    For now, everything is going pretty easily with the kit.
     
  42. gamevanilla

    gamevanilla

    Joined:
    Dec 28, 2015
    Posts:
    968
    I am happy to hear that! :)

    I will add a guide about how to create a custom server addon to the official documentation in the next few days that you will be able to use as a starting point.
     
  43. Paradoks

    Paradoks

    Joined:
    Oct 13, 2009
    Posts:
    436
    Hello,

    Todays questions:

    1- In this page, http://www.spelltwinegames.com/masterserverkit/documentation/guides/
    under : Integrating Master Server Kit with a game using the Network Manager component

    you say:
    but you forget to say to put "using MasterServerKit;"

    2-i dont find what is that refering to:
    what is this "network Manager "field ? you mean "Manager" field ?

    3- lastly, when i test my gameScene, wich is the same as the gameServer scene, i got this error message:
    I need to be able to test my gameScene/gameServerScene like i did before i integrated the kit, without passing by the kit.

    I hope it helps!

    [EDIT]

    Is it possible to launch the gameScene from the " ClientAPI.JoinGameServer" with arguments or something, so i can keep having the same scene being both GameServer and clientGameScene- for now i dont know how to differentiate them other than with the plateform - wich is the same when i developp - the problem will disapear when i host btw.

    [EDIT]

    I dont know what i do wrong, but when i click on "onPlayNow" wich triggers this:
    Code (CSharp):
    1.     public void OnPlayNowButtonPressed()
    2.     {
    3.         ClientAPI.PlayNow((ip, port) =>
    4.             {
    5.                 WindowUtils.OpenLoadingDialog("Joining game...");
    6.                 JoinGameServer(ip, port);
    7.             },
    8.             (ip, port) =>
    9.             {
    10.                 WindowUtils.OpenLoadingDialog("Creating game...");
    11.                 JoinGameServer(ip, port);
    12.             },
    13.             error =>
    14.             {
    15.                 WindowManager.Instance.CloseWindow("GameClient_LoadingDialog");
    16.                 WindowUtils.OpenAlertDialog("ERROR", "No available games.");
    17.             });
    18.     }
    19.  
    20.     private void JoinGameServer(string ip, int port)
    21.     {
    22.         if (ClientAPI.useNetworkManager)
    23.         {
    24.             ClientAPI.JoinGameServer(ip, port);
    25.         }
    26.         else
    27.         {
    28.             ClientAPI.JoinGameServer(ip, port, () =>
    29.                 {
    30.                     SceneManager.LoadScene("GameScene");
    31.                 });
    32.         }
    33.     }
    The master server launch the gameScene, but no the client.
    The client stays in the client scene.
     
    Last edited: Jul 11, 2017
  44. gamevanilla

    gamevanilla

    Joined:
    Dec 28, 2015
    Posts:
    968
    The guide you mention does not discuss code at any moment, so I am not entirely sure how a comment about the kit's namespace would be relevant in that context.

    Sorry about that! That was true in the past, but now only the 'Use Network Manager' field is needed. I will remove that sentence from the documentation.

    It sounds like there are a couple of things wrong with your setup. Mainly, the game server scene should definitely not be the same as the game scene. I have sent you a project containing the first parts of the official Unity multiplayer tutorials integrated into the kit privately, so you can have a starting point into how a game that uses the Network Manager works in the context of the kit.
     
  45. Paradoks

    Paradoks

    Joined:
    Oct 13, 2009
    Posts:
    436
    My error then, i thought that it was possible to have the client game scene and the server game scene all in one.
    btw, it may lead to problem while developping, because most of the time the network code/scene is the same for both scenes.
    For my case actually everything i do, every prefab i move or script i tweak must be the same for server game and client game scene.
    Is there a way to keep the two scenes the same or will i have to save the client one over the master one, every time i compile ?
     
  46. gamevanilla

    gamevanilla

    Joined:
    Dec 28, 2015
    Posts:
    968
    I do not know the specifics of your game but, generally speaking, if you are using UNET and the Network Manager you have two scenes: the offline scene and the online scene. There is an implicit assumption/requirement that the online scene is the same for both the server and the clients, and that is still the case when using Master Server Kit. Of course, you will usually also have server-only or client-only code and/or objects, but that is an orthogonal issue to that of the scenes being the same on both sides.

    Basically, the online scene you reference in the game server's Network Manager should be exactly the same online scene you reference in the game client's Network Manager. The game server scene is a completely different scene from the game scene with the only purpose of running a Network Manager as a server and transitioning to the online game scene, where your actual game logic happens.
     
  47. denisebr

    denisebr

    Joined:
    Jun 19, 2014
    Posts:
    6
    I f I buy this asset and use on my game, and players create his own rooms will they connect direct to my dedicated or they'll need to open doors to play?
     
  48. gamevanilla

    gamevanilla

    Joined:
    Dec 28, 2015
    Posts:
    968
    They will connect directly to your dedicated server, where the game server instances will be created.
     
  49. Paradoks

    Paradoks

    Joined:
    Oct 13, 2009
    Posts:
    436
    Hello, little questions:

    1 - How do you get the player's name ? -
    ex: i just logged in as "Paradoks", i want to write "hello paradoks", but want to get the paradoks name from the server, how do i do ? i used " ClientAPI.gameClient.username;" dont seems to work, do i miss something ?

    2 - for now i only connect with "PlayNow", but i need a max player number limitation, is there a way to limit the max players with play now ?

    Btw, excellent asset so far ! keep the great work !
    [EDIT] Can we know what is planned for the next release ? Or is it secret :D
     
  50. gamevanilla

    gamevanilla

    Joined:
    Dec 28, 2015
    Posts:
    968
    ClientAPI.masterServerClient.username if you are in the lobby and ClientAPI.gameClient.username if you are in a game.

    That sounds like a good idea. I will make this configurable in the next update.

    Thank you for your kind words! Next on my list is improving the logging facilities in the kit.
     
Thread Status:
Not open for further replies.