From 7b4d450b626031e977f56804f4e6e611a31bb829 Mon Sep 17 00:00:00 2001 From: Petr Tsap Date: Tue, 13 May 2025 09:24:29 +0200 Subject: [PATCH 1/4] =?UTF-8?q?=D0=9D=D0=B0=D1=87=D0=B0=D0=BB=20=D0=B4?= =?UTF-8?q?=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D1=82=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Docs/Core/Client.md | 167 +++++++++++++++ Docs/Core/Database.md | 204 ++++++++++++++++++ Docs/Core/Events.md | 272 ++++++++++++++++++++++++ Docs/Core/Keys.md | 307 +++++++++++++++++++++++++++ Docs/Core/Localization.md | 376 +++++++++++++++++++++++++++++++++ Docs/Core/Logger.md | 227 ++++++++++++++++++++ Docs/Core/Mail.md | 118 +++++++++++ Docs/Core/MasterServer.md | 88 ++++++++ Docs/Core/Server.md | 251 ++++++++++++++++++++++ Docs/Modules/Authentication.md | 263 +++++++++++++++++++++++ Docs/Modules/Chat.md | 278 ++++++++++++++++++++++++ Docs/Modules/Lobbies.md | 327 ++++++++++++++++++++++++++++ Docs/Modules/Profiles.md | 276 ++++++++++++++++++++++++ Docs/Modules/Rooms.md | 355 +++++++++++++++++++++++++++++++ 14 files changed, 3509 insertions(+) create mode 100644 Docs/Core/Client.md create mode 100644 Docs/Core/Database.md create mode 100644 Docs/Core/Events.md create mode 100644 Docs/Core/Keys.md create mode 100644 Docs/Core/Localization.md create mode 100644 Docs/Core/Logger.md create mode 100644 Docs/Core/Mail.md create mode 100644 Docs/Core/MasterServer.md create mode 100644 Docs/Core/Server.md create mode 100644 Docs/Modules/Authentication.md create mode 100644 Docs/Modules/Chat.md create mode 100644 Docs/Modules/Lobbies.md create mode 100644 Docs/Modules/Profiles.md create mode 100644 Docs/Modules/Rooms.md diff --git a/Docs/Core/Client.md b/Docs/Core/Client.md new file mode 100644 index 0000000..6fb4212 --- /dev/null +++ b/Docs/Core/Client.md @@ -0,0 +1,167 @@ +# Master Server Toolkit - Client + +## Описание +Система клиентов для подключения к Master Server. Состоит из базовых классов и помощников подключения. + +## BaseClientBehaviour + +Базовый класс для создания клиентских компонентов. + +### Основные свойства: +```csharp +// Текущее подключение +public IClientSocket Connection { get; protected set; } + +// Проверка подключения +public bool IsConnected => Connection != null && Connection.IsConnected; + +// Логгер модуля +public Logger Logger { get; set; } +``` + +### Пример использования: +```csharp +public class MyClientModule : BaseClientBehaviour +{ + protected override void OnInitialize() + { + // Инициализация при запуске + Logger.Info("Module started"); + } + + protected override void OnConnectionStatusChanged(ConnectionStatus status) + { + Logger.Info($"Connection status: {status}"); + } +} +``` + +### Основные методы: +```csharp +// Регистрация обработчика сообщений +RegisterMessageHandler(IPacketHandler handler); +RegisterMessageHandler(ushort opCode, IncommingMessageHandler handler); + +// Изменение подключения +ChangeConnection(IClientSocket connection, bool clearHandlers = false); + +// Очистка подключения +ClearConnection(bool clearHandlers = true); +``` + +## BaseClientModule + +Базовый класс для клиентских модулей. + +### Пример: +```csharp +public class GameStatisticsModule : BaseClientModule +{ + public override void OnInitialize(BaseClientBehaviour parentBehaviour) + { + base.OnInitialize(parentBehaviour); + + // Регистрация обработчиков + parentBehaviour.RegisterMessageHandler(OpCodes.Statistics, HandleStatistics); + } + + private void HandleStatistics(IIncommingMessage message) + { + // Обработка статистики + } +} +``` + +## ClientToMasterConnector + +Компонент для автоматического подключения к Master Server. + +### Настройка: +```csharp +// Добавить на GameObject +var connector = GetComponent(); + +// Настройка через Inspector или код +connector.serverIp = "192.168.1.100"; +connector.serverPort = 5000; +connector.connectOnStart = true; +``` + +### Аргументы командной строки: +```bash +# Автоматическая настройка IP и порта +./Client.exe -masterip 192.168.1.100 -masterport 5000 +``` + +## ConnectionHelper + +Базовый помощник для создания подключений с автоматическими попытками. + +### Основные настройки: +```csharp +// Количество попыток подключения +[SerializeField] protected int maxAttemptsToConnect = 5; + +// Тайм-аут подключения +[SerializeField] protected float timeout = 5f; + +// Автоматическое подключение +[SerializeField] protected bool connectOnStart = true; + +// Безопасное подключение +[SerializeField] protected bool useSecure = false; +``` + +### События: +```csharp +// Успешное подключение +OnConnectedEvent + +// Неудачное подключение +OnFailedConnectEvent + +// Отключение +OnDisconnectedEvent +``` + +### Пример кастомного коннектора: +```csharp +public class MyCustomConnector : ConnectionHelper +{ + protected override void Start() + { + // Кастомная логика перед подключением + base.Start(); + } + + protected override void OnConnectedEventHandler(IClientSocket client) + { + base.OnConnectedEventHandler(client); + // Дополнительная логика после подключения + } +} +``` + +## Архитектура модулей + +### Иерархия модулей: +1. BaseClientBehaviour - основной компонент +2. BaseClientModule - дочерние модули +3. Автоматическая инициализация модулей при запуске + +### Пример структуры: +``` +GameObject +├── MyClientBehaviour (BaseClientBehaviour) +└── ChildModules + ├── AuthModule (BaseClientModule) + ├── ChatModule (BaseClientModule) + └── ProfileModule (BaseClientModule) +``` + +## Лучшие практики +1. Используйте ConnectionHelper для управления подключением +2. Наследуйтесь от BaseClientBehaviour для основных компонентов +3. Создавайте специализированные модули через BaseClientModule +4. Регистрируйте обработчики в OnInitialize +5. Очищайте ресурсы в OnDestroy diff --git a/Docs/Core/Database.md b/Docs/Core/Database.md new file mode 100644 index 0000000..b05e7db --- /dev/null +++ b/Docs/Core/Database.md @@ -0,0 +1,204 @@ +# Master Server Toolkit - Database + +## Описание +Система доступа к базам данных и API. Предоставляет абстракцию для работы с различными типами хранилищ данных. + +## IDatabaseAccessor + +Базовый интерфейс для всех аксессоров базы данных. + +```csharp +public interface IDatabaseAccessor : IDisposable +{ + MstProperties CustomProperties { get; } + Logger Logger { get; set; } +} +``` + +### Реализация собственного аксессора: +```csharp +public class MySQLAccessor : IDatabaseAccessor +{ + public MstProperties CustomProperties { get; } = new MstProperties(); + public Logger Logger { get; set; } + + // Реализация методов работы с MySQL + public async Task GetUserById(string userId) + { + // Логика получения пользователя + } + + public void Dispose() + { + // Освобождение ресурсов + } +} +``` + +## MstDbAccessor + +Менеджер для работы с различными аксессорами базы данных. + +### Основные методы: +```csharp +// Добавление аксессора +AddAccessor(IDatabaseAccessor access); + +// Получение аксессора по типу +T GetAccessor() where T : class, IDatabaseAccessor; +``` + +### Пример использования: +```csharp +// Создание центрального менеджера БД +var dbManager = new MstDbAccessor(); + +// Добавление различных аксессоров +dbManager.AddAccessor(new MongoDbAccessor()); +dbManager.AddAccessor(new RedisAccessor()); +dbManager.AddAccessor(new MySQLAccessor()); + +// Получение нужного аксессора +var mongoDb = dbManager.GetAccessor(); +var redis = dbManager.GetAccessor(); + +// Использование аксессора +var user = await mongoDb.GetUserById("user123"); +await redis.SetCache("key", data, TimeSpan.FromMinutes(30)); +``` + +## DatabaseAccessorFactory + +Абстрактная фабрика для создания аксессоров базы данных. + +### Пример реализации: +```csharp +public class GameDatabaseFactory : DatabaseAccessorFactory +{ + [Header("Database Settings")] + [SerializeField] private string connectionString; + [SerializeField] private DatabaseType dbType; + + public override void CreateAccessors() + { + switch (dbType) + { + case DatabaseType.MongoDB: + var mongoDb = new MongoDbAccessor(connectionString); + mongoDb.Logger = logger; + Mst.Server.DbAccessors.AddAccessor(mongoDb); + break; + + case DatabaseType.MySQL: + var mysql = new MySQLAccessor(connectionString); + mysql.Logger = logger; + Mst.Server.DbAccessors.AddAccessor(mysql); + break; + + case DatabaseType.Redis: + var redis = new RedisAccessor(connectionString); + redis.Logger = logger; + Mst.Server.DbAccessors.AddAccessor(redis); + break; + } + + logger.Info("Database accessors created successfully"); + } +} +``` + +### Настройка через Inspector: +1. Создать GameObject +2. Добавить компонент наследника DatabaseAccessorFactory +3. Настроить строку подключения и тип БД +4. Аксессоры будут созданы автоматически при запуске + +## Примеры аксессоров + +### Redis аксессор: +```csharp +public class RedisAccessor : IDatabaseAccessor +{ + private ConnectionMultiplexer _redis; + private IDatabase _db; + + public Logger Logger { get; set; } + public MstProperties CustomProperties { get; } = new MstProperties(); + + public RedisAccessor(string connectionString) + { + _redis = ConnectionMultiplexer.Connect(connectionString); + _db = _redis.GetDatabase(); + } + + public async Task SetCache(string key, string value, TimeSpan? expiry = null) + { + await _db.StringSetAsync(key, value, expiry); + } + + public async Task GetCache(string key) + { + return await _db.StringGetAsync(key); + } + + public void Dispose() + { + _redis?.Dispose(); + } +} +``` + +### MongoDB аксессор: +```csharp +public class MongoDbAccessor : IDatabaseAccessor +{ + private IMongoClient _client; + private IMongoDatabase _database; + + public Logger Logger { get; set; } + public MstProperties CustomProperties { get; } = new MstProperties(); + + public MongoDbAccessor(string connectionString) + { + _client = new MongoClient(connectionString); + _database = _client.GetDatabase("gamedb"); + } + + public async Task GetUserById(string userId) + { + var collection = _database.GetCollection("users"); + return await collection.Find(u => u.Id == userId).FirstOrDefaultAsync(); + } + + public void Dispose() + { + // MongoDB driver handles disposal automatically + } +} +``` + +## Лучшие практики + +1. **Один аксессор - одна ответственность**: Каждый аксессор должен работать с одним типом хранилища +2. **Используйте фабрику**: Создавайте аксессоры через DatabaseAccessorFactory +3. **Логгирование**: Всегда устанавливайте Logger для аксессоров +4. **Освобождение ресурсов**: Реализуйте Dispose правильно +5. **Асинхронность**: Используйте async/await для операций с БД + +## Структура для масштабирования + +``` +DatabaseAccessors/ +├── SQL/ +│ ├── MySQLAccessor.cs +│ └── PostgreSQLAccessor.cs +├── NoSQL/ +│ ├── MongoDbAccessor.cs +│ └── CassandraAccessor.cs +├── Cache/ +│ ├── RedisAccessor.cs +│ └── MemcachedAccessor.cs +└── Factories/ + ├── MainDatabaseFactory.cs + └── CacheDatabaseFactory.cs +``` diff --git a/Docs/Core/Events.md b/Docs/Core/Events.md new file mode 100644 index 0000000..7dd3c42 --- /dev/null +++ b/Docs/Core/Events.md @@ -0,0 +1,272 @@ +# Master Server Toolkit - Events + +## Описание +Система событий для коммуникации между компонентами без жестких связей. Включает каналы событий и типизированные сообщения. + +## MstEventsChannel + +Канал для отправки и получения событий. + +### Создание канала: +```csharp +// Создание канала с обработкой исключений +var defaultChannel = new MstEventsChannel("default", true); + +// Создание простого канала +var gameChannel = new MstEventsChannel("game"); + +// Использование глобального канала +var globalChannel = Mst.Events; +``` + +### Основные методы: +```csharp +// Отправка события без данных +channel.Invoke("playerDied"); + +// Отправка события с данными +channel.Invoke("scoreUpdated", 100); +channel.Invoke("playerJoined", new Player("John")); + +// Подписка на события +channel.AddListener("gameStarted", OnGameStarted); +channel.AddListener("levelCompleted", OnLevelCompleted, true); + +// Отписка от событий +channel.RemoveListener("gameStarted", OnGameStarted); +channel.RemoveAllListeners("playerDied"); +``` + +## EventMessage + +Контейнер для данных событий с типизированным доступом. + +### Создание и использование: +```csharp +// Создание пустого сообщения +var emptyMsg = EventMessage.Empty; + +// Создание с данными +var scoreMsg = new EventMessage(150); +var playerMsg = new EventMessage(new Player { Name = "Alex", Level = 10 }); + +// Получение данных +int score = scoreMsg.As(); +float damage = damageMsg.AsFloat(); +string text = textMsg.AsString(); +bool isWinner = resultMsg.AsBool(); + +// Проверка наличия данных +if (message.HasData()) +{ + var data = message.As(); +} +``` + +## Примеры использования + +### 1. Игровые события: +```csharp +public class GameManager : MonoBehaviour +{ + private MstEventsChannel gameEvents; + + void Start() + { + gameEvents = new MstEventsChannel("game"); + + // Подписка на события + gameEvents.AddListener("playerJoined", OnPlayerJoined); + gameEvents.AddListener("scoreChanged", OnScoreChanged); + gameEvents.AddListener("gameOver", OnGameOver); + } + + private void OnPlayerJoined(EventMessage msg) + { + var player = msg.As(); + Debug.Log($"Player {player.Name} joined the game"); + + // Уведомляем других игроков + gameEvents.Invoke("playerListUpdated", GetPlayerList()); + } + + private void OnScoreChanged(EventMessage msg) + { + int newScore = msg.AsInt(); + UpdateScoreUI(newScore); + + // Проверка рекорда + if (newScore > highScore) + { + gameEvents.Invoke("newRecord", newScore); + } + } +} +``` + +### 2. UI события: +```csharp +public class UIManager : MonoBehaviour +{ + void Start() + { + // Подписка на глобальные события + Mst.Events.AddListener("connectionLost", OnConnectionLost); + Mst.Events.AddListener("dataLoaded", OnDataLoaded); + Mst.Events.AddListener("errorOccurred", OnError); + } + + private void OnConnectionLost(EventMessage msg) + { + ShowReconnectDialog(); + } + + private void OnDataLoaded(EventMessage msg) + { + var data = msg.As(); + UpdateUI(data); + HideLoadingScreen(); + } + + private void OnError(EventMessage msg) + { + string errorText = msg.AsString(); + ShowErrorDialog(errorText); + } +} +``` + +### 3. Межкомпонентная коммуникация: +```csharp +public class InventorySystem : MonoBehaviour +{ + private MstEventsChannel inventoryEvents; + + void Start() + { + inventoryEvents = new MstEventsChannel("inventory"); + + // Подписка на игровые события + Mst.Events.AddListener("itemDropped", OnItemDropped); + Mst.Events.AddListener("playerDied", OnPlayerDied); + } + + public void AddItem(Item item) + { + if (CanAddItem(item)) + { + // Добавляем предмет + items.Add(item); + + // Уведомляем о изменении инвентаря + inventoryEvents.Invoke("itemAdded", item); + + // Проверяем квесты + if (IsQuestItem(item)) + { + Mst.Events.Invoke("questItemObtained", item); + } + } + else + { + inventoryEvents.Invoke("inventoryFull", item); + } + } +} +``` + +## Именованные каналы + +### Создание специализированных каналов: +```csharp +// Канал для боевой системы +var combatChannel = new MstEventsChannel("combat"); +combatChannel.AddListener("enemySpotted", OnEnemySpotted); +combatChannel.AddListener("damageDealt", OnDamageDealt); + +// Канал для социальной системы +var socialChannel = new MstEventsChannel("social"); +socialChannel.AddListener("friendRequestReceived", OnFriendRequest); +socialChannel.AddListener("messageReceived", OnChatMessage); + +// Канал для экономики +var economyChannel = new MstEventsChannel("economy"); +economyChannel.AddListener("purchaseCompleted", OnPurchase); +economyChannel.AddListener("currencyChanged", OnCurrencyUpdate); +``` + +## Лучшие практики + +1. **Используйте осмысленные имена событий**: +```csharp +// Хорошо +"playerJoinedLobby" +"itemCraftingCompleted" +"achievementUnlocked" + +// Плохо +"event1" +"update" +"changed" +``` + +2. **Типизируйте данные событий**: +```csharp +// Определите структуры для сложных данных +public struct ScoreChangedData +{ + public int oldScore; + public int newScore; + public string reason; +} + +// Используйте их в событиях +channel.Invoke("scoreChanged", new ScoreChangedData +{ + oldScore = 100, + newScore = 150, + reason = "enemyKilled" +}); +``` + +3. **Отписывайтесь от событий**: +```csharp +void OnDestroy() +{ + // Отписка от всех событий + Mst.Events.RemoveListener("playerJoined", OnPlayerJoined); + channel.RemoveAllListeners(); +} +``` + +4. **Используйте каналы для логической группировки**: +- Игровые события → "game" +- UI события → "ui" +- Сетевые события → "network" +- Системные события → "system" + +5. **Обрабатывайте исключения**: +```csharp +// При создании канала включайте обработку исключений +var channel = new MstEventsChannel("game", true); +``` + +## Интеграция с другими системами + +```csharp +// Интеграция с аналитикой +Mst.Events.AddListener("levelCompleted", (msg) => { + var data = msg.As(); + Analytics.TrackLevelCompletion(data); +}); + +// Интеграция с сохранениями +Mst.Events.AddListener("gameStateChanged", (msg) => { + SaveSystem.SaveGameState(msg.As()); +}); + +// Интеграция с сетью +Mst.Events.AddListener("playerAction", (msg) => { + NetworkManager.SendPlayerAction(msg.As()); +}); +``` diff --git a/Docs/Core/Keys.md b/Docs/Core/Keys.md new file mode 100644 index 0000000..800a147 --- /dev/null +++ b/Docs/Core/Keys.md @@ -0,0 +1,307 @@ +# Master Server Toolkit - Keys + +## Описание +Центральный реестр ключей и кодов для сетевых сообщений, событий и словарных данных. Обеспечивает типизированные константы для всех операций в фреймворке. + +## MstOpCodes + +Операционные коды для сетевых сообщений между клиентом и сервером. + +### Базовые операции: +```csharp +// Ошибки и пинг +MstOpCodes.Error // Сообщения об ошибках +MstOpCodes.Ping // Проверка связи + +// Аутентификация +MstOpCodes.SignIn // Вход в аккаунт +MstOpCodes.SignUp // Регистрация +MstOpCodes.SignOut // Выход +``` + +### Пример использования: +```csharp +// Отправка сообщения с определенным OpCode +client.SendMessage(MstOpCodes.SignIn, loginData); + +// Регистрация обработчика для OpCode +client.RegisterMessageHandler(MstOpCodes.LobbyInfo, HandleLobbyInfo); + +// Создание кастомного OpCode +public static ushort MyCustomCode = "myCustomAction".ToUint16Hash(); +``` + +### Категории OpCodes: + +#### 1. Аутентификация и аккаунты: +```csharp +MstOpCodes.SignIn +MstOpCodes.SignUp +MstOpCodes.SignOut +MstOpCodes.GetPasswordResetCode +MstOpCodes.ConfirmEmail +MstOpCodes.ChangePassword +``` + +#### 2. Комнаты и спавны: +```csharp +MstOpCodes.RegisterRoomRequest +MstOpCodes.DestroyRoomRequest +MstOpCodes.GetRoomAccessRequest +MstOpCodes.SpawnProcessRequest +MstOpCodes.CompleteSpawnProcess +``` + +#### 3. Лобби: +```csharp +MstOpCodes.CreateLobby +MstOpCodes.JoinLobby +MstOpCodes.LeaveLobby +MstOpCodes.SetLobbyProperties +MstOpCodes.StartLobbyGame +``` + +#### 4. Чат: +```csharp +MstOpCodes.ChatMessage +MstOpCodes.JoinChannel +MstOpCodes.LeaveChannel +MstOpCodes.PickUsername +``` + +## MstEventKeys + +Ключи для системы событий, используемые для UI и игровой логики. + +### UI события: +```csharp +// Диалоги +MstEventKeys.showOkDialogBox +MstEventKeys.hideOkDialogBox +MstEventKeys.showYesNoDialogBox + +// Экраны +MstEventKeys.showSignInView +MstEventKeys.hideSignInView +MstEventKeys.showLobbyListView +``` + +### Пример использования: +```csharp +// Показать диалог +Mst.Events.Invoke(MstEventKeys.showOkDialogBox, "Добро пожаловать!"); + +// Подписка на событие +Mst.Events.AddListener(MstEventKeys.gameStarted, OnGameStarted); + +// Создание кастомных событий +public static string MyCustomEvent = "game.levelCompleted"; +``` + +### Категории событий: + +#### 1. Навигация: +```csharp +MstEventKeys.goToZone +MstEventKeys.leaveRoom +MstEventKeys.showLoadingInfo +``` + +#### 2. Игровые события: +```csharp +MstEventKeys.gameStarted +MstEventKeys.gameOver +MstEventKeys.playerStartedGame +MstEventKeys.playerFinishedGame +``` + +#### 3. Визуальные элементы: +```csharp +MstEventKeys.showLoadingInfo +MstEventKeys.hideLoadingInfo +MstEventKeys.showPickUsernameView +``` + +## MstDictKeys + +Ключи для словарных данных, передаваемых в сообщениях. + +### Пользовательские данные: +```csharp +MstDictKeys.USER_ID // "-userId" +MstDictKeys.USER_NAME // "-userName" +MstDictKeys.USER_EMAIL // "-userEmail" +MstDictKeys.USER_AUTH_TOKEN // "-userAuthToken" +``` + +### Пример использования: +```csharp +// Создание сообщения с данными +var userData = new MstProperties(); +userData.Set(MstDictKeys.USER_NAME, "Player1"); +userData.Set(MstDictKeys.USER_EMAIL, "player@game.com"); + +// Отправка данных +client.SendMessage(MstOpCodes.SignUp, userData); + +// Получение данных +string userName = message.AsString(MstDictKeys.USER_NAME); +``` + +### Категории ключей: + +#### 1. Комнаты: +```csharp +MstDictKeys.ROOM_ID +MstDictKeys.ROOM_CONNECTION_TYPE +MstDictKeys.WORLD_ZONE +``` + +#### 2. Лобби: +```csharp +MstDictKeys.LOBBY_FACTORY_ID +MstDictKeys.LOBBY_NAME +MstDictKeys.LOBBY_PASSWORD +MstDictKeys.LOBBY_TEAM +``` + +#### 3. Аутентификация: +```csharp +MstDictKeys.USER_ID +MstDictKeys.USER_PASSWORD +MstDictKeys.USER_AUTH_TOKEN +MstDictKeys.RESET_PASSWORD_CODE +``` + +## MstPeerPropertyCodes + +Коды для свойств пиров (подключенных клиентов/серверов). + +```csharp +// Базовые свойства +MstPeerPropertyCodes.Start + +// Зарегистрированные сущности +MstPeerPropertyCodes.RegisteredRooms +MstPeerPropertyCodes.RegisteredSpawners + +// Клиентские запросы +MstPeerPropertyCodes.ClientSpawnRequest +``` + +### Пример использования: +```csharp +// Установка свойства пира +peer.SetProperty(MstPeerPropertyCodes.RegisteredRooms, roomsList); + +// Получение свойства +var rooms = peer.GetProperty(MstPeerPropertyCodes.RegisteredRooms); +``` + +## Создание собственных ключей + +### Расширение OpCodes: +```csharp +public static class CustomOpCodes +{ + public static ushort GetPlayerStats = "getPlayerStats".ToUint16Hash(); + public static ushort UpdateInventory = "updateInventory".ToUint16Hash(); + public static ushort CraftItem = "craftItem".ToUint16Hash(); +} +``` + +### Расширение EventKeys: +```csharp +public static class GameEventKeys +{ + public static string itemCrafted = "game.itemCrafted"; + public static string achievementUnlocked = "game.achievementUnlocked"; + public static string questCompleted = "game.questCompleted"; +} +``` + +### Расширение DictKeys: +```csharp +public static class CustomDictKeys +{ + public const string PLAYER_LEVEL = "-playerLevel"; + public const string INVENTORY_DATA = "-inventoryData"; + public const string ACHIEVEMENT_ID = "-achievementId"; +} +``` + +## Лучшие практики + +1. **Используйте хеширование для OpCodes**: +```csharp +// Хорошо +public static ushort MyAction = "myAction".ToUint16Hash(); + +// Плохо - прямые числа +public static ushort MyAction = 1234; +``` + +2. **Делайте ключи описательными**: +```csharp +// Хорошо +MstDictKeys.USER_AUTH_TOKEN + +// Плохо +"-token" +``` + +3. **Группируйте по функциональности**: +```csharp +// Группировка по модулям +public struct ChatOpCodes { } +public struct LobbyOpCodes { } +public struct AuthOpCodes { } +``` + +4. **Документируйте кастомные ключи**: +```csharp +/// +/// Получает статистику игрока за текущий сезон +/// +public static ushort GetSeasonStats = "getSeasonStats".ToUint16Hash(); +``` + +## Интеграция с другими системами + +```csharp +// Создание сообщения с несколькими ключами +var message = new MstProperties(); +message.Set(MstDictKeys.USER_ID, userId); +message.Set(MstDictKeys.ROOM_ID, roomId); +message.Set(CustomDictKeys.PLAYER_LEVEL, level); + +// Отправка через сеть +Mst.Server.SendMessage(peer, CustomOpCodes.UpdatePlayerData, message); + +// Обработка с использованием событий +Mst.Events.AddListener(GameEventKeys.itemCrafted, (msg) => { + var itemData = msg.As(); + // Обработка +}); +``` + +## Отладка и мониторинг + +```csharp +// Логирование всех входящих OpCodes +Connection.OnMessageReceived += (msg) => { + Debug.Log($"Received OpCode: {msg.OpCode} ({GetOpCodeName(msg.OpCode)})"); +}; + +// Метод для получения имени OpCode +private string GetOpCodeName(ushort opCode) +{ + var fields = typeof(MstOpCodes).GetFields(BindingFlags.Public | BindingFlags.Static); + foreach (var field in fields) + { + if ((ushort)field.GetValue(null) == opCode) + return field.Name; + } + return "Unknown"; +} +``` diff --git a/Docs/Core/Localization.md b/Docs/Core/Localization.md new file mode 100644 index 0000000..bd20079 --- /dev/null +++ b/Docs/Core/Localization.md @@ -0,0 +1,376 @@ +# Master Server Toolkit - Localization + +## Описание +Система локализации для мультиязычной поддержки игры. Поддерживает загрузку переводов из текстовых файлов и динамическую смену языка. + +## MstLocalization + +Главный класс для работы с локализацией. + +### Основные свойства: +```csharp +// Текущий язык +string Lang { get; set; } + +// Получение перевода по ключу +string this[string key] { get; } + +// Событие смены языка +event Action LanguageChangedEvent; +``` + +### Доступ к локализации: +```csharp +// Через глобальный экземпляр +Mst.Localization.Lang = "ru"; +string welcomeText = Mst.Localization["welcome_message"]; + +// Создание собственного экземпляра +var localization = new MstLocalization(); +``` + +## Формат файлов локализации + +### Структура файла: +``` +# Комментарии начинаются с # +;key;en;ru;de + +# Сообщения UI +ui_welcome;Welcome!;Добро пожаловать!;Willkommen! +ui_loading;Loading...;Загрузка...;Wird geladen... +ui_error;Error occurred;Произошла ошибка;Fehler aufgetreten + +# Игровые сообщения +game_start;Game Started;Игра началась;Spiel gestartet +game_over;Game Over;Игра окончена;Spiel beendet + +# Кнопки +btn_ok;OK;ОК;OK +btn_cancel;Cancel;Отмена;Abbrechen +btn_save;Save;Сохранить;Speichern +``` + +### Расположение файлов: +``` +Resources/ +└── Localization/ + ├── localization.txt # Основной файл + └── custom_localization.txt # Кастомные переводы +``` + +## Использование в коде + +### Базовое использование: +```csharp +public class UIManager : MonoBehaviour +{ + [Header("UI Elements")] + public Text titleText; + public Text statusText; + public Button okButton; + + void Start() + { + // Подписка на смену языка + Mst.Localization.LanguageChangedEvent += OnLanguageChanged; + + // Установка начальных текстов + UpdateTexts(); + } + + void OnLanguageChanged(string newLang) + { + UpdateTexts(); + } + + void UpdateTexts() + { + titleText.text = Mst.Localization["game_title"]; + statusText.text = Mst.Localization["status_ready"]; + okButton.GetComponentInChildren().text = Mst.Localization["btn_ok"]; + } +} +``` + +### Создание компонента локализации: +```csharp +public class LocalizedText : MonoBehaviour +{ + [Header("Localization")] + public string key; + + private Text textComponent; + + void Awake() + { + textComponent = GetComponent(); + } + + void Start() + { + Mst.Localization.LanguageChangedEvent += UpdateText; + UpdateText(Mst.Localization.Lang); + } + + void OnDestroy() + { + Mst.Localization.LanguageChangedEvent -= UpdateText; + } + + void UpdateText(string lang) + { + if (textComponent && !string.IsNullOrEmpty(key)) + { + textComponent.text = Mst.Localization[key]; + } + } +} +``` + +### Смена языка с сохранением: +```csharp +public class LanguageSelector : MonoBehaviour +{ + [Header("Available Languages")] + public string[] availableLanguages = { "en", "ru", "de" }; + + public Dropdown languageDropdown; + + void Start() + { + // Загрузка сохраненного языка + string savedLang = PlayerPrefs.GetString("SelectedLanguage", "en"); + Mst.Localization.Lang = savedLang; + + // Настройка выпадающего списка + SetupDropdown(); + } + + void SetupDropdown() + { + languageDropdown.ClearOptions(); + + var options = new List(); + int selectedIndex = 0; + + for (int i = 0; i < availableLanguages.Length; i++) + { + string lang = availableLanguages[i]; + options.Add(new Dropdown.OptionData(GetLanguageName(lang))); + + if (lang == Mst.Localization.Lang) + selectedIndex = i; + } + + languageDropdown.AddOptions(options); + languageDropdown.value = selectedIndex; + languageDropdown.onValueChanged.AddListener(OnLanguageSelected); + } + + void OnLanguageSelected(int index) + { + string newLang = availableLanguages[index]; + + // Смена языка + Mst.Localization.Lang = newLang; + + // Сохранение выбора + PlayerPrefs.SetString("SelectedLanguage", newLang); + PlayerPrefs.Save(); + } + + string GetLanguageName(string lang) + { + switch (lang) + { + case "en": return "English"; + case "ru": return "Русский"; + case "de": return "Deutsch"; + default: return lang.ToUpper(); + } + } +} +``` + +## Динамическая локализация + +### Локализация с параметрами: +```csharp +// В файле локализации: +# player_score;Score: {0};Счет: {0};Punkte: {0} + +// В коде: +string scoreText = string.Format( + Mst.Localization["player_score"], + currentScore +); + +// Или с помощью хелпера: +public static string GetLocalizedFormat(string key, params object[] args) +{ + string template = Mst.Localization[key]; + return string.Format(template, args); +} + +// Использование: +string message = GetLocalizedFormat("welcome_player", playerName); +``` + +### Локализация перечислений: +```csharp +public enum GameMode +{ + Single, + Multiplayer, + Tournament +} + +public static string GetLocalizedEnum(T enumValue) where T : Enum +{ + string key = $"enum_{typeof(T).Name}_{enumValue}"; + return Mst.Localization[key]; +} + +// В файле локализации: +# enum_GameMode_Single;Single Player;Одиночная игра;Einzelspieler +# enum_GameMode_Multiplayer;Multiplayer;Многопользовательская игра;Mehrspieler +``` + +## Расширенные возможности + +### Кастомный формат файлов: +```csharp +public class CustomLocalizationLoader +{ + public static void LoadJson(string jsonPath) + { + var jsonText = Resources.Load(jsonPath).text; + var locData = JsonUtility.FromJson(jsonText); + + foreach (var entry in locData.entries) + { + foreach (var translation in entry.translations) + { + Mst.Localization.RegisterKey( + translation.lang, + entry.key, + translation.value + ); + } + } + } +} + +[System.Serializable] +public class LocalizationData +{ + public LocalizationEntry[] entries; +} + +[System.Serializable] +public class LocalizationEntry +{ + public string key; + public Translation[] translations; +} + +[System.Serializable] +public class Translation +{ + public string lang; + public string value; +} +``` + +### Локализация изображений: +```csharp +public class LocalizedSprite : MonoBehaviour +{ + [Header("Localized Sprites")] + public Sprite englishSprite; + public Sprite russianSprite; + public Sprite germanSprite; + + private Image imageComponent; + + void Start() + { + imageComponent = GetComponent(); + Mst.Localization.LanguageChangedEvent += UpdateSprite; + UpdateSprite(Mst.Localization.Lang); + } + + void UpdateSprite(string lang) + { + switch (lang) + { + case "en": imageComponent.sprite = englishSprite; break; + case "ru": imageComponent.sprite = russianSprite; break; + case "de": imageComponent.sprite = germanSprite; break; + } + } +} +``` + +## Лучшие практики + +1. **Используйте namespace для ключей**: +``` +ui_main_menu_title +ui_settings_volume +game_message_victory +error_network_timeout +``` + +2. **Храните длинные тексты отдельно**: +``` +# tutorial_step1;Press [WASD] to move;Нажмите [WASD] для передвижения;... +``` + +3. **Валидация переводов при старте**: +```csharp +void ValidateTranslations() +{ + string[] requiredKeys = { "ui_welcome", "btn_ok", "game_start" }; + string[] languages = { "en", "ru", "de" }; + + foreach (var key in requiredKeys) + { + foreach (var lang in languages) + { + var oldLang = Mst.Localization.Lang; + Mst.Localization.Lang = lang; + + if (Mst.Localization[key] == key) + { + Debug.LogWarning($"Missing translation for {key} in {lang}"); + } + + Mst.Localization.Lang = oldLang; + } + } +} +``` + +4. **Аргументы командной строки**: +```bash +# Установка языка при запуске +./Game.exe -defaultLanguage ru +``` + +## Интеграция с системой событий + +```csharp +// Уведомление о смене языка +Mst.Localization.LanguageChangedEvent += (lang) => { + Mst.Events.Invoke("languageChanged", lang); +}; + +// Уведомление об ошибках перевода +public static void ReportMissingTranslation(string key, string lang) +{ + Mst.Events.Invoke("translationMissing", new { key, lang }); + Debug.LogWarning($"Missing translation: {key} for {lang}"); +} +``` diff --git a/Docs/Core/Logger.md b/Docs/Core/Logger.md new file mode 100644 index 0000000..23c2308 --- /dev/null +++ b/Docs/Core/Logger.md @@ -0,0 +1,227 @@ +# Master Server Toolkit - Logger + +## Описание +Гибкая система логирования с поддержкой уровней, именованных логгеров и настраиваемых выводов. + +## Logger + +Основной класс для логирования сообщений. + +### Создание логгера: +```csharp +// Создание именованного логгера +Logger logger = Mst.Create.Logger("MyModule"); +logger.LogLevel = LogLevel.Info; + +// Получение логгера через LogManager +Logger networkLogger = LogManager.GetLogger("Network"); +``` + +### Уровни логирования: +```csharp +LogLevel.All // Все сообщения +LogLevel.Trace // Детальное трассирование +LogLevel.Debug // Отладочная информация +LogLevel.Info // Информационные сообщения +LogLevel.Warn // Предупреждения +LogLevel.Error // Ошибки +LogLevel.Fatal // Критические ошибки +LogLevel.Off // Отключить логирование +LogLevel.Global // Использовать глобальный уровень +``` + +### Методы логирования: +```csharp +// Базовые методы +logger.Trace("Entering method GetPlayer()"); +logger.Debug("Player position: {0}", playerPos); +logger.Info("Player connected successfully"); +logger.Warn("Connection latency is high"); +logger.Error("Failed to load game data"); +logger.Fatal("Critical server error"); + +// Условное логирование +logger.Debug(player != null, "Player found: " + player.Name); +logger.Log(LogLevel.Info, "Custom message"); +``` + +## LogManager + +Центральный менеджер для всех логгеров. + +### Инициализация: +```csharp +// Базовая инициализация +LogManager.Initialize( + new[] { LogAppenders.UnityConsoleAppender }, + LogLevel.Info +); + +// Инициализация с кастомными аппендерами +LogManager.Initialize(new LogHandler[] { + LogAppenders.UnityConsoleAppender, + CustomFileAppender, + NetworkLogAppender +}, LogLevel.Debug); +``` + +### Глобальные настройки: +```csharp +// Глобальный уровень (для всех логгеров) +LogManager.GlobalLogLevel = LogLevel.Warn; + +// Принудительный уровень (переопределяет все) +LogManager.LogLevel = LogLevel.Off; +``` + +## Logs + +Статический класс для быстрого логирования без создания логгера. + +```csharp +// Использование статических методов +Logs.Info("Server started"); +Logs.Error("Connection failed"); +Logs.Debug("Processing player data"); + +// Условное логирование +Logs.Warn(healthPoints < 10, "Player health is critical"); +``` + +## Кастомные аппендеры + +### Создание файлового аппендера: +```csharp +public static void FileAppender(Logger logger, LogLevel logLevel, object message) +{ + string logPath = Path.Combine(Application.persistentDataPath, "game.log"); + string logEntry = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{logLevel}] [{logger.Name}] {message}\n"; + + File.AppendAllText(logPath, logEntry); +} + +// Регистрация аппендера +LogManager.AddAppender(FileAppender); +``` + +## Примеры использования + +### Модульное логирование: +```csharp +public class NetworkManager : MonoBehaviour +{ + private Logger logger; + + void Awake() + { + logger = Mst.Create.Logger("Network"); + logger.LogLevel = LogLevel.Debug; + } + + public async Task ConnectToServer(string ip, int port) + { + logger.Info($"Attempting connection to {ip}:{port}"); + + try + { + logger.Debug("Creating socket..."); + // Код подключения + + logger.Info("Successfully connected to server"); + } + catch (Exception ex) + { + logger.Error($"Connection failed: {ex.Message}"); + throw; + } + } +} +``` + +## Настройка через аргументы + +```csharp +// При запуске приложения +void ConfigureLogging() +{ + // Получение уровня из аргументов + string logLevelArg = Mst.Args.AsString(Mst.Args.Names.LogLevel, "Info"); + LogLevel level = (LogLevel)Enum.Parse(typeof(LogLevel), logLevelArg); + + LogManager.GlobalLogLevel = level; + + // Настройка вывода в файл (если указано) + if (Mst.Args.AsBool(Mst.Args.Names.EnableFileLog, false)) + { + LogManager.AddAppender(FileAppender); + } +} + +// Пример запуска +// ./Game.exe -logLevel Debug -enableFileLog true +``` + +## Рекомендации + +1. **Именование логгеров**: Используйте иерархические имена +```csharp +Logger("Network.Client") +Logger("Network.Server") +Logger("Game.Player") +Logger("Game.UI") +``` + +2. **Уровни в production**: +- Сервер: Info и выше +- Клиент: Warn и выше +- Разработка: Debug + +3. **Производительность**: +```csharp +// Плохо - создает строку всегда +logger.Debug($"Processing {listItems.Count} items"); + +// Хорошо - проверяет уровень сначала +if (logger.IsLogging(LogLevel.Debug)) +{ + logger.Debug($"Processing {listItems.Count} items"); +} +``` + +4. **Структура сообщений**: +```csharp +// Последовательный формат +logger.Info("Player [P12345] joined room [R67890] at position (10, 20, 30)"); +``` + +5. **Конфиденциальность**: +```csharp +// Избегайте логирования чувствительных данных +logger.Info($"User logged in: {user.Id}"); // ✓ +logger.Debug($"Password hash: {password.Substring(0, 8)}***"); // ✓ +logger.Error($"Login failed for: {user.Password}"); // ✗ +``` + +## Интеграция с MST + +```csharp +// Использование с модулями +public class MyModule : BaseServerModule +{ + protected override void Initialize() + { + Logger.Info("Module initializing..."); + + // Подписка на события + Mst.Events.AddListener("playerConnected", msg => { + Logger.Debug($"Player connected event: {msg}"); + }); + } + + protected override void OnDestroy() + { + Logger.Info("Module shutting down..."); + base.OnDestroy(); + } +} +``` diff --git a/Docs/Core/Mail.md b/Docs/Core/Mail.md new file mode 100644 index 0000000..a3cb664 --- /dev/null +++ b/Docs/Core/Mail.md @@ -0,0 +1,118 @@ +# Master Server Toolkit - Mail + +## Описание +Система отправки электронной почты через SMTP протокол с поддержкой HTML шаблонов. + +## SmtpMailer + +Основной класс для отправки почты через SMTP. + +### Настройка: +```csharp +[Header("E-mail settings")] +public string smtpHost = "smtp.gmail.com"; +public string smtpUsername = "your-email@gmail.com"; +public string smtpPassword = "your-app-password"; +public int smtpPort = 587; +public bool enableSsl = true; +public string mailFrom = "noreply@yourgame.com"; +public string senderDisplayName = "Your Game Name"; + +[Header("E-mail template")] +[SerializeField] protected TextAsset emailBodyTemplate; +``` + +### Пример использования: +```csharp +var mailer = GetComponent(); + +// Отправка простого письма +await mailer.SendMailAsync("player@example.com", "Welcome!", "Thank you!"); + +// С HTML шаблоном +await mailer.SendMailAsync(email, "Code: " + code, emailBody); +``` + +## Шаблоны писем + +### HTML шаблон: +```html +

#{MESSAGE_SUBJECT}

+
#{MESSAGE_BODY}
+
© #{MESSAGE_YEAR} Game Name
+``` + +### Токены замены: +- `#{MESSAGE_SUBJECT}` - Заголовок +- `#{MESSAGE_BODY}` - Тело письма +- `#{MESSAGE_YEAR}` - Текущий год + +## Настройка SMTP провайдеров + +### Gmail: +```csharp +smtpHost = "smtp.gmail.com"; +smtpPort = 587; +enableSsl = true; +// Используйте App Password +``` + +### SendGrid: +```csharp +smtpHost = "smtp.sendgrid.net"; +smtpPort = 587; +smtpUsername = "apikey"; +smtpPassword = "your-sendgrid-api-key"; +``` + +## Интеграция примеры + +### Подтверждение email: +```csharp +public async Task SendConfirmationCode(string email, string code) +{ + string subject = "Confirm your email"; + string body = $"Your code: {code}"; + + return await mailer.SendMailAsync(email, subject, body); +} +``` + +### Сброс пароля: +```csharp +public async Task SendPasswordReset(string email, string resetLink) +{ + string subject = "Password Reset"; + string body = $"Reset Password"; + + return await mailer.SendMailAsync(email, subject, body); +} +``` + +## Настройка через аргументы + +```bash +# При запуске +./Server.exe -smtpHost smtp.gmail.com -smtpUsername game@gmail.com -smtpPassword app-password +``` + +## Аргументы проверяются автоматически: +```csharp +smtpHost = Mst.Args.AsString(Mst.Args.Names.SmtpHost, smtpHost); +smtpUsername = Mst.Args.AsString(Mst.Args.Names.SmtpUsername, smtpUsername); +smtpPassword = Mst.Args.AsString(Mst.Args.Names.SmtpPassword, smtpPassword); +``` + +## Важные замечания +- Не работает в WebGL +- Ошибки логируются асинхронно +- Используйте App Password для Gmail +- Проверяйте настройки брандмауэра +- HTML шаблоны загружаются из Resources + +## Лучшие практики +1. Храните SMTP пароли в безопасном месте +2. Используйте асинхронные вызовы +3. Проверяйте email на валидность +4. Внедряйте ограничения на отправку +5. Тестируйте с разными провайдерами diff --git a/Docs/Core/MasterServer.md b/Docs/Core/MasterServer.md new file mode 100644 index 0000000..48dfb83 --- /dev/null +++ b/Docs/Core/MasterServer.md @@ -0,0 +1,88 @@ +# Master Server Toolkit - Ядро + +## Описание +Master Server Toolkit (версия 4.20.0) - это система для создания выделенных серверов и мультиплеерных игр. Ядро состоит из двух основных компонентов: класса `Mst` и `MasterServerBehaviour`. + +## Класс Mst +Центральный класс фреймворка, предоставляющий доступ ко всем основным системам. + +### Основные свойства: +```csharp +// Версия и название фреймворка +Mst.Version // "4.20.0" +Mst.Name // "Master Server Toolkit" + +// Основное подключение к master серверу +Mst.Connection // IClientSocket + +// Расширенные настройки фреймворка +Mst.Settings // MstAdvancedSettings +``` + +### Основные компоненты: +```csharp +// Клиентские методы +Mst.Client // MstClient - для игровых клиентов +Mst.Server // MstServer - для игровых серверов + +// Утилиты и хелперы +Mst.Helper // Вспомогательные методы +Mst.Security // Безопасность и шифрование +Mst.Create // Создание сокетов и сообщений +Mst.Concurrency // Работа с потоками + +// Системы +Mst.Events // Канал событий +Mst.Runtime // Работа с данными времени выполнения +Mst.Args // Аргументы командной строки +``` + +## Класс MasterServerBehaviour + +Синглтон для управления работой master сервера в Unity. + +### Пример использования: +```csharp +// Получение экземпляра +var masterServer = MasterServerBehaviour.Instance; + +// События +MasterServerBehaviour.OnMasterStartedEvent += (server) => { + Debug.Log("Master Server запущен!"); +}; + +MasterServerBehaviour.OnMasterStoppedEvent += (server) => { + Debug.Log("Master Server остановлен!"); +}; +``` + +### Ключевые особенности: +- Автоматический запуск при старте сцены +- Singleton pattern для единственного экземпляра +- Поддержка аргументов командной строки для IP и порта +- События для отслеживания состояния сервера + +### Аргументы командной строки: +```csharp +// IP адрес master сервера +Mst.Args.AsString(Mst.Args.Names.MasterIp, defaultIP); + +// Порт master сервера +Mst.Args.AsInt(Mst.Args.Names.MasterPort, defaultPort); +``` + +## Быстрый старт + +1. Добавить MasterServerBehaviour на сцену +2. Настроить IP и порт +3. Запустить сцену или установить аргументы командной строки + +```bash +# Пример запуска с аргументами +./MyGameServer.exe -masterip 127.0.0.1 -masterport 5000 +``` + +## Важные замечания +- Каждый master сервер использует единственный экземпляр MasterServerBehaviour +- Все компоненты инициализируются автоматически при первом использовании Mst класса +- События OnMasterStarted/Stopped должны быть подписаны до запуска сервера diff --git a/Docs/Core/Server.md b/Docs/Core/Server.md new file mode 100644 index 0000000..2f06952 --- /dev/null +++ b/Docs/Core/Server.md @@ -0,0 +1,251 @@ +# Master Server Toolkit - Server + +## Описание +Базовая инфраструктура для создания серверных приложений. Включает ServerBehaviour для сетевого сервера и BaseServerModule для модулей. + +## ServerBehaviour + +Базовый класс для создания серверных приложений с автоматическим управлением подключениями и модулями. + +### Основные свойства: +```csharp +[Header("Server Settings")] +public string serverIp = "localhost"; +public int serverPort = 5000; +public ushort maxConnections = 0; +public string service = "mst"; + +[Header("Security Settings")] +public bool useSecure = false; +public string password = "mst"; +``` + +### Управление сервером: +```csharp +// Запуск сервера +server.StartServer(); +server.StartServer(8080); +server.StartServer("192.168.1.100", 8080); + +// Остановка сервера +server.StopServer(); + +// Проверка статуса +bool isRunning = server.IsRunning; +int connectedClients = server.PeersCount; +``` + +### События сервера: +```csharp +// Подключение клиента +server.OnPeerConnectedEvent += (peer) => { + Debug.Log($"Client {peer.Id} connected"); +}; + +// Отключение клиента +server.OnPeerDisconnectedEvent += (peer) => { + Debug.Log($"Client {peer.Id} disconnected"); +}; + +// Старт/остановка сервера +server.OnServerStartedEvent += () => Debug.Log("Server started"); +server.OnServerStoppedEvent += () => Debug.Log("Server stopped"); +``` + +### Регистрация обработчиков: +```csharp +// Регистрация обработчика сообщений +server.RegisterMessageHandler(MstOpCodes.SignIn, HandleSignIn); + +// Асинхронный обработчик +server.RegisterMessageHandler(CustomOpCodes.GetData, async (message) => { + var data = await LoadDataAsync(); + message.Respond(data, ResponseStatus.Success); +}); +``` + +## BaseServerModule + +Базовый класс для создания модульных компонентов сервера. + +### Создание модуля: +```csharp +public class AccountsModule : BaseServerModule +{ + protected override void Awake() + { + base.Awake(); + // Добавление зависимостей + AddDependency(); + AddOptionalDependency(); + } + + public override void Initialize(IServer server) + { + // Регистрация обработчиков + server.RegisterMessageHandler(MstOpCodes.SignIn, HandleSignIn); + server.RegisterMessageHandler(MstOpCodes.SignUp, HandleSignUp); + } + + private void HandleSignIn(IIncomingMessage message) + { + // Логика входа + } +} +``` + +### Зависимости модулей: +```csharp +// Обязательные зависимости +AddDependency(); +AddDependency(); + +// Опциональные зависимости +AddOptionalDependency(); +AddOptionalDependency(); + +// Получение других модулей +var dbModule = Server.GetModule(); +var emailModule = Server.GetModule(); +``` + +## Расширенные возможности + +### Кастомный ServerBehaviour: +```csharp +public class GameServerBehaviour : ServerBehaviour +{ + protected override void OnPeerConnected(IPeer peer) + { + base.OnPeerConnected(peer); + // Кастомная логика для нового игрока + NotifyOtherPlayers(peer); + } + + protected override void ValidateConnection(ProvideServerAccessCheckPacket packet, SuccessCallback callback) + { + // Кастомная валидация подключения + if (CheckServerPassword(packet.Password) && CheckGameVersion(packet.Version)) + { + callback.Invoke(true, string.Empty); + } + else + { + callback.Invoke(false, "Access denied"); + } + } +} +``` + +### Статистика и мониторинг: +```csharp +// Получение информации о сервере +MstProperties info = server.Info(); +MstJson jsonInfo = server.JsonInfo(); + +// Статистика подключений +Debug.Log($"Active clients: {server.PeersCount}"); +Debug.Log($"Total clients: {server.Info().Get("Total clients")}"); +Debug.Log($"Highest clients: {server.Info().Get("Highest clients")}"); +``` + +## Аргументы командной строки + +```bash +# Основные параметры +./Server.exe -masterip 192.168.1.100 -masterport 5000 + +# Безопасность +./Server.exe -mstUseSecure true -certificatePath cert.pfx -certificatePassword pass + +# Производительность +./Server.exe -targetFrameRate 60 -clientInactivityTimeout 30 +``` + +### Автоматическая настройка из аргументов: +```csharp +// Адрес и порт +serverIp = Mst.Args.AsString(Mst.Args.Names.MasterIp, serverIp); +serverPort = Mst.Args.AsInt(Mst.Args.Names.MasterPort, serverPort); + +// Безопасность +useSecure = Mst.Args.AsBool(Mst.Args.Names.UseSecure, useSecure); +certificatePath = Mst.Args.AsString(Mst.Args.Names.CertificatePath, certificatePath); + +// Таймауты +inactivityTimeout = Mst.Args.AsFloat(Mst.Args.Names.ClientInactivityTimeout, inactivityTimeout); +``` + +## Работа с подключениями + +### Управление пирами: +```csharp +// Получение пира по ID +IPeer peer = server.GetPeer(peerId); + +// Отключение всех клиентов +foreach (var peer in connectedPeers.Values) +{ + peer.Disconnect("Server maintenance"); +} + +// Проверка подлинности +var securityInfo = peer.GetExtension(); +int permissionLevel = securityInfo.PermissionLevel; +``` + +### Права доступа: +```csharp +[SerializeField] +private List permissions = new List +{ + new PermissionEntry { key = "admin", permissionLevel = 100 }, + new PermissionEntry { key = "moderator", permissionLevel = 50 } +}; + +// Проверка прав +if (peer.HasPermission(50)) +{ + // Разрешено для модераторов и выше +} +``` + +## Лучшие практики + +1. **Используйте модули для логической группировки**: + - AuthModule - аутентификация + - GameModule - игровая логика + - ChatModule - чат + - DatabaseModule - работа с БД + +2. **Управляйте зависимостями**: + - Объявляйте зависимости в Awake() + - Используйте опциональные зависимости для дополнительных функций + +3. **Обрабатывайте ошибки**: +```csharp +protected override void OnMessageReceived(IIncomingMessage message) +{ + try + { + base.OnMessageReceived(message); + } + catch (Exception ex) + { + logger.Error($"Message error: {ex.Message}"); + message.Respond(ResponseStatus.Error); + } +} +``` + +4. **Оптимизируйте производительность**: + - Ограничивайте FPS сервера + - Настраивайте таймауты + - Используйте максимальное количество подключений + +5. **Логируйте важные события**: +```csharp +logger.Info($"Server started on {Address}:{Port}"); +logger.Debug($"Module {GetType().Name} initialized"); +logger.Error($"Failed to handle message: {ex.Message}"); +``` diff --git a/Docs/Modules/Authentication.md b/Docs/Modules/Authentication.md new file mode 100644 index 0000000..823e68e --- /dev/null +++ b/Docs/Modules/Authentication.md @@ -0,0 +1,263 @@ +# Master Server Toolkit - Authentication + +## Описание +Модуль аутентификации для регистрации, входа в систему, восстановления паролей и управления пользователями. + +## AuthModule + +Основной класс модуля аутентификации. + +### Настройка: +```csharp +[Header("Settings")] +[SerializeField] private int usernameMinChars = 4; +[SerializeField] private int usernameMaxChars = 12; +[SerializeField] private int userPasswordMinChars = 8; +[SerializeField] private bool emailConfirmRequired = true; + +[Header("Guest Settings")] +[SerializeField] private bool allowGuestLogin = true; +[SerializeField] private string guestPrefix = "user_"; + +[Header("Security")] +[SerializeField] private string tokenSecret = "your-secret-key"; +[SerializeField] private int tokenExpiresInDays = 7; +``` + +### Типы аутентификации: + +#### 1. По имени и паролю: +```csharp +// Клиент отправляет +var credentials = new MstProperties(); +credentials.Set(MstDictKeys.USER_NAME, "playerName"); +credentials.Set(MstDictKeys.USER_PASSWORD, "password123"); +credentials.Set(MstDictKeys.USER_DEVICE_ID, "device123"); +credentials.Set(MstDictKeys.USER_DEVICE_NAME, "iPhone 12"); + +// Шифрование и отправка +var encryptedData = Mst.Security.EncryptAES(credentials.ToBytes(), aesKey); +Mst.Client.Connection.SendMessage(MstOpCodes.SignIn, encryptedData); +``` + +#### 2. По email: +```csharp +// Отправляет пароль на email +var credentials = new MstProperties(); +credentials.Set(MstDictKeys.USER_EMAIL, "player@game.com"); +Mst.Client.Connection.SendMessage(MstOpCodes.SignIn, credentials); +``` + +#### 3. По токену: +```csharp +// Автоматический вход +var credentials = new MstProperties(); +credentials.Set(MstDictKeys.USER_AUTH_TOKEN, savedToken); +Mst.Client.Connection.SendMessage(MstOpCodes.SignIn, credentials); +``` + +#### 4. Гостевой вход: +```csharp +var credentials = new MstProperties(); +credentials.Set(MstDictKeys.USER_IS_GUEST, true); +credentials.Set(MstDictKeys.USER_DEVICE_ID, "device123"); +Mst.Client.Connection.SendMessage(MstOpCodes.SignIn, credentials); +``` + +## Регистрация + +```csharp +// Создание аккаунта +var credentials = new MstProperties(); +credentials.Set(MstDictKeys.USER_NAME, "newPlayer"); +credentials.Set(MstDictKeys.USER_PASSWORD, "securePassword"); +credentials.Set(MstDictKeys.USER_EMAIL, "new@player.com"); + +// Шифрование +var encrypted = Mst.Security.EncryptAES(credentials.ToBytes(), aesKey); +Mst.Client.Connection.SendMessage(MstOpCodes.SignUp, encrypted); +``` + +## Управление пользователями + +### Работа с залогиненными пользователями: +```csharp +// Получить пользователя +var user = authModule.GetLoggedInUserByUsername("playerName"); +var user = authModule.GetLoggedInUserById("userId"); +var user = authModule.GetLoggedInUserByEmail("email@game.com"); + +// Проверить залогинен ли +bool isOnline = authModule.IsUserLoggedInByUsername("playerName"); + +// Получить всех онлайн +var allOnline = authModule.LoggedInUsers; + +// Разлогинить +authModule.SignOut("playerName"); +authModule.SignOut(userExtension); +``` + +### События: +```csharp +// Подписка на события +authModule.OnUserLoggedInEvent += (user) => { + Debug.Log($"User {user.Username} logged in"); +}; + +authModule.OnUserLoggedOutEvent += (user) => { + Debug.Log($"User {user.Username} logged out"); +}; + +authModule.OnUserRegisteredEvent += (peer, account) => { + Debug.Log($"New user registered: {account.Username}"); +}; +``` + +## Восстановление пароля + +### Запрос кода: +```csharp +// Клиент запрашивает код +Mst.Connection.SendMessage(MstOpCodes.GetPasswordResetCode, "email@game.com"); + +// Сервер отправляет код на email +``` + +### Смена пароля: +```csharp +var data = new MstProperties(); +data.Set(MstDictKeys.RESET_PASSWORD_EMAIL, "email@game.com"); +data.Set(MstDictKeys.RESET_PASSWORD_CODE, "123456"); +data.Set(MstDictKeys.RESET_PASSWORD, "newPassword"); + +Mst.Connection.SendMessage(MstOpCodes.ChangePassword, data); +``` + +## Подтверждение email + +```csharp +// Запрос кода подтверждения +Mst.Connection.SendMessage(MstOpCodes.GetEmailConfirmationCode); + +// Подтверждение email +Mst.Connection.SendMessage(MstOpCodes.ConfirmEmail, "confirmationCode"); +``` + +## Дополнительные свойства + +```csharp +// Привязка дополнительных данных +var extraProps = new MstProperties(); +extraProps.Set("playerLevel", 10); +extraProps.Set("gameClass", "warrior"); + +Mst.Connection.SendMessage(MstOpCodes.BindExtraProperties, extraProps); +``` + +## Настройка базы данных + +```csharp +// Реализация IAccountsDatabaseAccessor +public class MyDatabaseAccessor : IAccountsDatabaseAccessor +{ + public async Task GetAccountByUsernameAsync(string username) + { + // Получение аккаунта из БД + } + + public async Task InsertAccountAsync(IAccountInfoData account) + { + // Сохранение нового аккаунта + } + + public async Task UpdateAccountAsync(IAccountInfoData account) + { + // Обновление аккаунта + } +} + +// Создание фабрики +public class DatabaseFactory : DatabaseAccessorFactory +{ + public override void CreateAccessors() + { + var accessor = new MyDatabaseAccessor(); + Mst.Server.DbAccessors.AddAccessor(accessor); + } +} +``` + +## Проверка прав доступа + +```csharp +// Получение информации о пире +Mst.Connection.SendMessage(MstOpCodes.GetAccountInfoByPeer, peerId); +Mst.Connection.SendMessage(MstOpCodes.GetAccountInfoByUsername, "playerName"); + +// Требует минимальный уровень прав +authModule.getPeerDataPermissionsLevel = 10; +``` + +## Кастомизация валидации + +```csharp +// Переопределение в наследнике AuthModule +protected override bool IsUsernameValid(string username) +{ + if (!base.IsUsernameValid(username)) + return false; + + // Дополнительные проверки + if (username.Contains("admin")) + return false; + + return true; +} + +protected override bool IsPasswordValid(string password) +{ + if (!base.IsPasswordValid(password)) + return false; + + // Проверка на сложность + bool hasUpper = password.Any(char.IsUpper); + bool hasNumber = password.Any(char.IsDigit); + + return hasUpper && hasNumber; +} +``` + +## Интеграция с email + +```csharp +// Настройка mailer компонента +[SerializeField] protected Mailer mailer; + +// Настройка Smtp (в SmtpMailer) +smtpHost = "smtp.gmail.com"; +smtpPort = 587; +smtpUsername = "yourgame@gmail.com"; +smtpPassword = "app-password"; +mailFrom = "noreply@yourgame.com"; +``` + +## Аргументы командной строки + +```bash +# Токен настройки +./Server.exe -tokenSecret mysecret -tokenExpires 30 + +# Настройка issuer/audience +./Server.exe -tokenIssuer http://mygame.com -tokenAudience http://mygame.com/users +``` + +## Лучшие практики + +1. **Всегда шифруйте пароли перед отправкой** +2. **Используйте токены для автовхода** +3. **Храните конфиденциальные данные в базе** +4. **Настройте email для восстановления паролей** +5. **Проверяйте имена на цензуру с помощью CensorModule** +6. **Ограничивайте количество попыток входа** +7. **Используйте HTTPS для production** diff --git a/Docs/Modules/Chat.md b/Docs/Modules/Chat.md new file mode 100644 index 0000000..533e0a1 --- /dev/null +++ b/Docs/Modules/Chat.md @@ -0,0 +1,278 @@ +# Master Server Toolkit - Chat + +## Описание +Модуль для создания чат-системы с поддержкой каналов, приватных сообщений и проверки нецензурных слов. + +## ChatModule + +Основной класс для управления чатом. + +### Настройка: +```csharp +[Header("General Settings")] +[SerializeField] protected bool useAuthModule = true; +[SerializeField] protected bool useCensorModule = true; +[SerializeField] protected bool allowUsernamePicking = true; + +[SerializeField] protected bool setFirstChannelAsLocal = true; +[SerializeField] protected bool setLastChannelAsLocal = true; + +[SerializeField] protected int minChannelNameLength = 5; +[SerializeField] protected int maxChannelNameLength = 25; +``` + +## Работа с каналами + +### Создание/получение канала: +```csharp +// Получить или создать канал +var channel = chatModule.GetOrCreateChannel("general"); + +// Проверка на запрещенные слова +var channel = chatModule.GetOrCreateChannel("badword", true); // игнорировать проверку +``` + +### Присоединение к каналу: +```csharp +// Отправка запроса на сервер +Mst.Client.Connection.SendMessage(MstOpCodes.JoinChannel, "general"); + +// Получение текущих каналов +Mst.Client.Connection.SendMessage(MstOpCodes.GetCurrentChannels); +``` + +### Управление каналами: +```csharp +// Покинуть канал +Mst.Client.Connection.SendMessage(MstOpCodes.LeaveChannel, "general"); + +// Установить канал по умолчанию +Mst.Client.Connection.SendMessage(MstOpCodes.SetDefaultChannel, "global"); + +// Получить список пользователей в канале +Mst.Client.Connection.SendMessage(MstOpCodes.GetUsersInChannel, "general"); +``` + +## Отправка сообщений + +### Типы сообщений: +```csharp +public enum ChatMessageType : byte +{ + ChannelMessage, // Сообщение в канал + PrivateMessage // Приватное сообщение +} +``` + +### Отправка сообщений: +```csharp +// Сообщение в канал +var channelMsg = new ChatMessagePacket +{ + MessageType = ChatMessageType.ChannelMessage, + Receiver = "general", // имя канала + Message = "Hello everyone!" +}; +Mst.Client.Connection.SendMessage(MstOpCodes.ChatMessage, channelMsg); + +// Сообщение в личный канал (без указания получателя) +var localMsg = new ChatMessagePacket +{ + MessageType = ChatMessageType.ChannelMessage, + Receiver = null, // отправится в DefaultChannel + Message = "Hello local channel!" +}; + +// Приватное сообщение +var privateMsg = new ChatMessagePacket +{ + MessageType = ChatMessageType.PrivateMessage, + Receiver = "username", // имя пользователя + Message = "Hello privately!" +}; +``` + +## Управление пользователями + +### Установка имени пользователя: +```csharp +// Если allowUsernamePicking = true +Mst.Client.Connection.SendMessage(MstOpCodes.PickUsername, "myUsername"); +``` + +### Работа с ChatUserPeerExtension: +```csharp +// Получить расширение пользователя +var chatUser = peer.GetExtension(); + +// Изменить имя пользователя +chatModule.ChangeUsername(peer, "newUsername", true); // сохранить каналы + +// Доступ к каналам пользователя +var channels = chatUser.CurrentChannels; +var defaultChannel = chatUser.DefaultChannel; +``` + +## Интеграция с другими модулями + +### Интеграция с AuthModule: +```csharp +// При useAuthModule = true +authModule.OnUserLoggedInEvent += OnUserLoggedInEventHandler; +authModule.OnUserLoggedOutEvent += OnUserLoggedOutEventHandler; + +// Автоматическое создание ChatUser при входе +``` + +### Интеграция с CensorModule: +```csharp +// При useCensorModule = true +// Автоматическая проверка сообщений на нецензурные слова +// Замена запрещенного сообщения на предупреждение +``` + +## Кастомизация + +### Переопределение обработки сообщений: +```csharp +protected override bool TryHandleChatMessage(ChatMessagePacket chatMessage, ChatUserPeerExtension sender, IIncomingMessage message) +{ + // Кастомная логика обработки + if (chatMessage.Message.StartsWith("/")) + { + HandleCommand(chatMessage); + return true; + } + + return base.TryHandleChatMessage(chatMessage, sender, message); +} +``` + +### Создание кастомных пользователей: +```csharp +protected override ChatUserPeerExtension CreateChatUser(IPeer peer, string username) +{ + // Создание расширенного пользователя + return new MyChatUserExtension(peer, username); +} +``` + +## Клиентский код (MstChatClient) + +### Подписка на события: +```csharp +// Подключение к чат-модулю +Mst.Client.Chat.Connection = connection; + +// События +Mst.Client.Chat.OnMessageReceivedEvent += (message) => { + Debug.Log($"[{message.Sender}]: {message.Message}"); +}; + +Mst.Client.Chat.OnLeftChannelEvent += (channel) => { + Debug.Log($"Left channel: {channel}"); +}; + +Mst.Client.Chat.OnJoinedChannelEvent += (channel) => { + Debug.Log($"Joined channel: {channel}"); +}; +``` + +### Отправка сообщений с клиента: +```csharp +// В канал +Mst.Client.Chat.SendToChannel("general", "Hello world!"); + +// Приватное сообщение +Mst.Client.Chat.SendToUser("username", "Secret message"); + +// В локальный канал +Mst.Client.Chat.SendToLocalChannel("Hello local!"); +``` + +## Примеры использования + +### Создание игрового чата: +```csharp +public class GameChatUI : MonoBehaviour +{ + [Header("UI")] + public InputField messageInput; + public Text chatLog; + public Dropdown channelDropdown; + + void Start() + { + // Присоединиться к общему каналу + Mst.Client.Chat.JoinChannel("general"); + Mst.Client.Chat.SetDefaultChannel("general"); + + // Событие получения сообщения + Mst.Client.Chat.OnMessageReceivedEvent += OnMessageReceived; + + // События каналов + Mst.Client.Chat.OnChannelUsersChanged += OnChannelUsersChanged; + } + + private void OnMessageReceived(ChatMessagePacket message) + { + string formattedMsg = $"{message.Sender}: {message.Message}\n"; + chatLog.text += formattedMsg; + } + + public void SendMessage() + { + if (!string.IsNullOrEmpty(messageInput.text)) + { + Mst.Client.Chat.SendToDefaultChannel(messageInput.text); + messageInput.text = ""; + } + } +} +``` + +### Система команд в чате: +```csharp +protected override bool TryHandleChatMessage(ChatMessagePacket chatMessage, ChatUserPeerExtension sender, IIncomingMessage message) +{ + if (chatMessage.Message.StartsWith("/")) + { + var command = chatMessage.Message.Split(' ')[0].Substring(1); + var args = chatMessage.Message.Split(' ').Skip(1).ToArray(); + + switch (command) + { + case "whisper": + if (args.Length >= 2) + { + var targetUser = args[0]; + var privateMsg = string.Join(" ", args.Skip(1)); + // Отправить приватное сообщение + } + break; + + case "join": + if (args.Length > 0) + { + // Присоединиться к каналу + } + break; + } + + message.Respond(ResponseStatus.Success); + return true; + } + + return base.TryHandleChatMessage(chatMessage, sender, message); +} +``` + +## Лучшие практики + +1. **Всегда используйте AuthModule** для автоматической настройки пользователей +2. **Настройте CensorModule** для фильтрации нецензурных слов +3. **Ограничивайте длину имени канала** для предотвращения злоупотреблений +4. **Используйте приватные сообщения** для чувствительной информации +5. **Создавайте различные каналы** для разных целей (общий, торговля, гильдия) +6. **Очищайте пустые каналы** для оптимизации ресурсов +7. **Логируйте все сообщения** для модерации и анализа \ No newline at end of file diff --git a/Docs/Modules/Lobbies.md b/Docs/Modules/Lobbies.md new file mode 100644 index 0000000..98d2865 --- /dev/null +++ b/Docs/Modules/Lobbies.md @@ -0,0 +1,327 @@ +# Master Server Toolkit - Lobbies + +## Описание +Модуль для создания и управления игровыми лобби, включая создание групп игроков, управление готовностью и запуск игр. + +## LobbiesModule + +Основной класс для управления игровыми лобби. + +### Настройка: +```csharp +[Header("Configuration")] +public int createLobbiesPermissionLevel = 0; +public bool dontAllowCreatingIfJoined = true; +public int joinedLobbiesLimit = 1; +``` + +## Создание лобби + +### Регистрация фабрики лобби: +```csharp +// Создание фабрики +var factory = new LobbyFactoryAnonymous("Game_Lobby", lobbyModule); +factory.CreateNewLobbyHandler = (properties, user) => +{ + var config = new LobbyConfig + { + Name = properties.AsString("name", "Game Lobby"), + IsPublic = properties.AsBool("isPublic", true), + MaxPlayers = properties.AsInt("maxPlayers", 10), + MaxWaitTime = properties.AsInt("maxWaitTime", 60000), + Teams = new List + { + new LobbyTeam("Team A") { MaxPlayers = 5 }, + new LobbyTeam("Team B") { MaxPlayers = 5 } + } + }; + + return new GameLobby(lobbyModule.NextLobbyId(), config, lobbyModule); +}; + +// Регистрация фабрики +lobbyModule.AddFactory(factory); +``` + +### Создание лобби (клиент): +```csharp +// Создание лобби +var properties = new MstProperties(); +properties.Set(MstDictKeys.LOBBY_FACTORY_ID, "Game_Lobby"); +properties.Set("name", "Epic Battle Room"); +properties.Set("isPublic", true); +properties.Set("maxPlayers", 8); + +Mst.Client.Connection.SendMessage(MstOpCodes.CreateLobby, properties, (status, response) => +{ + if (status == ResponseStatus.Success) + { + int lobbyId = response.AsInt(); + Debug.Log($"Lobby created: {lobbyId}"); + } +}); +``` + +## Присоединение к лобби + +### Присоединение (клиент): +```csharp +// Присоединение к лобби +Mst.Client.Connection.SendMessage(MstOpCodes.JoinLobby, lobbyId, (status, response) => +{ + if (status == ResponseStatus.Success) + { + var lobbyData = response.AsPacket(); + Debug.Log($"Joined lobby: {lobbyData.Name}"); + + // Подписка на события лобби + SubscribeToLobbyEvents(); + } +}); + +// Покинуть лобби +Mst.Client.Connection.SendMessage(MstOpCodes.LeaveLobby, lobbyId); +``` + +## События лобби + +### Подписка на события: +```csharp +// Подписка на изменения +Mst.Client.Connection.RegisterMessageHandler(MstOpCodes.LobbyMemberJoined, OnMemberJoined); +Mst.Client.Connection.RegisterMessageHandler(MstOpCodes.LobbyMemberLeft, OnMemberLeft); +Mst.Client.Connection.RegisterMessageHandler(MstOpCodes.LobbyStateChange, OnLobbyStateChange); +Mst.Client.Connection.RegisterMessageHandler(MstOpCodes.LobbyMemberReadyStatusChange, OnMemberReadyChange); +Mst.Client.Connection.RegisterMessageHandler(MstOpCodes.LobbyChatMessage, OnChatMessage); + +// Обработчики +private void OnMemberJoined(IIncomingMessage message) +{ + var memberData = message.AsPacket(); + Debug.Log($"Player {memberData.Username} joined the lobby"); +} + +private void OnLobbyStateChange(IIncomingMessage message) +{ + var state = (LobbyState)message.AsInt(); + Debug.Log($"Lobby state changed to: {state}"); +} +``` + +## Управление свойствами + +### Свойства лобби: +```csharp +// Установка свойств лобби (только владелец) +var properties = new MstProperties(); +properties.Set("map", "forest"); +properties.Set("gameMode", "battle"); +properties.Set("maxPlayers", 12); + +var packet = new LobbyPropertiesSetPacket +{ + LobbyId = currentLobbyId, + Properties = properties +}; + +Mst.Client.Connection.SendMessage(MstOpCodes.SetLobbyProperties, packet); +``` + +### Свойства игрока: +```csharp +// Установка своих свойств +var myProperties = new Dictionary +{ + { "character", "warrior" }, + { "level", "25" }, + { "color", "blue" } +}; + +Mst.Client.Connection.SendMessage(MstOpCodes.SetMyProperties, myProperties.ToBytes()); +``` + +## Команды + +### Готовность игрока: +```csharp +// Установить себя готовым +Mst.Client.Connection.SendMessage(MstOpCodes.SetLobbyAsReady, 1); + +// Отменить готовность +Mst.Client.Connection.SendMessage(MstOpCodes.SetLobbyAsReady, 0); +``` + +### Присоединение к команде: +```csharp +// Присоединиться к команде +var joinTeamPacket = new LobbyJoinTeamPacket +{ + LobbyId = currentLobbyId, + TeamName = "Team A" +}; + +Mst.Client.Connection.SendMessage(MstOpCodes.JoinLobbyTeam, joinTeamPacket); +``` + +## Чат в лобби + +### Отправка сообщений: +```csharp +// Отправка сообщения в чат лобби +string chatMessage = "Hello everyone!"; +Mst.Client.Connection.SendMessage(MstOpCodes.SendMessageToLobbyChat, chatMessage); + +// Получение сообщений +private void OnChatMessage(IIncomingMessage message) +{ + var chatData = message.AsPacket(); + Debug.Log($"{chatData.Sender}: {chatData.Message}"); +} +``` + +## Запуск игры + +### Ручной запуск: +```csharp +// Начать игру (только владелец лобби) +Mst.Client.Connection.SendMessage(MstOpCodes.StartLobbyGame, (status, response) => +{ + if (status == ResponseStatus.Success) + { + Debug.Log("Game started!"); + } +}); +``` + +### Автоматический запуск: +```csharp +// Настройка автозапуска +var config = new LobbyConfig +{ + EnableReadySystem = true, + MinPlayersToStart = 2, + MaxWaitTime = 30000 // 30 секунд +}; + +// Игра начнется автоматически когда: +// 1. Минимум игроков готовы +// 2. Истекло время ожидания +``` + +## Реализация пользовательского лобби + +```csharp +public class RankedGameLobby : BaseLobby +{ + public RankedGameLobby(int lobbyId, ILobbyFactory factory, + LobbiesModule module) : base(lobbyId, factory, module) + { + // Кастомные настройки + EnableTeamSwitching = false; + EnableReadySystem = true; + MaxPlayers = 10; + } + + public override bool AddPlayer(LobbyUserPeerExtension playerExt, out string error) + { + // Проверка рейтинга игрока + var playerProfile = GetPlayerProfile(playerExt); + if (playerProfile.Rating < RequiredRating) + { + error = "Rating too low for this lobby"; + return false; + } + + // Автоматическое распределение по командам + AutoBalanceTeams(playerExt); + + return base.AddPlayer(playerExt, out error); + } + + protected override void OnGameStart() + { + // Кастомная логика старта + CalculateTeamBalance(); + ApplyRatingModifiers(); + + base.OnGameStart(); + } +} +``` + +## Интеграция с другими модулями + +### С Spawner: +```csharp +// Автоматическое создание сервера при старте игры +protected override void OnGameStart() +{ + var spawnOptions = new MstProperties(); + spawnOptions.Set("map", Properties["map"]); + spawnOptions.Set("gameMode", Properties["gameMode"]); + + SpawnersModule.Spawn(spawnOptions, (spawner, data) => + { + GamePort = data.RoomPort; + GameIp = data.RoomIp; + // Игроки получат доступ к серверу + }); +} +``` + +### С Matchmaker: +```csharp +// Интеграция с матчмейкингом +var factory = new LobbyFactoryAnonymous("Matchmaking_Lobby", lobbyModule); +factory.CreateNewLobbyHandler = (properties, user) => +{ + var matchmakingData = properties.AsPacket(); + var lobby = new MatchmakingLobby(); + + // Настройка на основе данных матчмейкинга + lobby.SetupMatchmaking(matchmakingData); + + return lobby; +}; +``` + +## Лучшие практики + +1. **Используйте фабрики** для создания разных типов лобби +2. **Валидируйте свойства** перед добавлением игроков +3. **Настройте автобаланс** для справедливой игры +4. **Обрабатывайте события** для создания отзывчивого UI +5. **Очищайте пустые лобби** для освобождения ресурсов +6. **Логируйте действия** для отладки и аналитики +7. **Защищайте от злоупотреблений** с помощью лимитов + +## Примеры UI + +### Простой список лобби: +```csharp +public class LobbyListUI : MonoBehaviour +{ + [Header("UI References")] + public GameObject lobbyItemPrefab; + public Transform lobbyList; + + void Start() + { + RefreshLobbyList(); + } + + void RefreshLobbyList() + { + // Получение списка публичных лобби + Mst.Client.Lobbies.GetPublicLobbies((lobbies) => + { + foreach (var lobby in lobbies) + { + var item = Instantiate(lobbyItemPrefab, lobbyList); + var itemComponent = item.GetComponent(); + itemComponent.Setup(lobby); + } + }); + } +} +``` \ No newline at end of file diff --git a/Docs/Modules/Profiles.md b/Docs/Modules/Profiles.md new file mode 100644 index 0000000..850e5b4 --- /dev/null +++ b/Docs/Modules/Profiles.md @@ -0,0 +1,276 @@ +# Master Server Toolkit - Profiles + +## Описание +Модуль профилей для управления пользовательскими данными, наблюдения за изменениями и синхронизации между клиентами и серверами. + +## ProfilesModule + +Основной класс для управления профилями пользователей. + +### Настройка: +```csharp +[Header("General Settings")] +[SerializeField] protected int unloadProfileAfter = 20; +[SerializeField] protected int saveProfileDebounceTime = 1; +[SerializeField] protected int clientUpdateDebounceTime = 1; +[SerializeField] protected int editProfilePermissionLevel = 0; +[SerializeField] protected int maxUpdateSize = 1048576; + +[Header("Timeout Settings")] +[SerializeField] protected int profileLoadTimeoutSeconds = 10; + +[Header("Database")] +public DatabaseAccessorFactory databaseAccessorFactory; +[SerializeField] private ObservablePropertyPopulatorsDatabase populatorsDatabase; +``` + +## Свойства профиля + +### Создание системы свойств: +```csharp +// Создание популятора +public class PlayerStatsPopulator : IObservablePropertyPopulator +{ + public IProperty Populate() + { + var properties = new ObservableBase(); + + // Базовые статы + properties.Set("playerLevel", new ObservableInt(1)); + properties.Set("experience", new ObservableInt(0)); + properties.Set("coins", new ObservableInt(0)); + + // Словарь для инвентаря + var inventory = new ObservableDictStringInt(); + inventory.Add("sword", 1); + inventory.Add("potion", 5); + properties.Set("inventory", inventory); + + return properties; + } +} +``` + +## Работа с профилями + +### Доступ к профилю (клиент): +```csharp +// Запрос профиля +Mst.Client.Connection.SendMessage(MstOpCodes.ClientFillInProfileValues); + +// Подписка на обновления +Mst.Client.Connection.RegisterMessageHandler(MstOpCodes.UpdateClientProfile, OnProfileUpdated); + +// Обработка ответа +private void OnProfileUpdated(IIncomingMessage message) +{ + var profile = new ObservableProfile(); + profile.FromBytes(message.AsBytes()); + + // Доступ к свойствам + int level = profile.GetProperty("playerLevel").Value; + int coins = profile.GetProperty("coins").Value; +} +``` + +### Доступ к профилю (сервер): +```csharp +// Получение профиля по Id +var profile = profilesModule.GetProfileByUserId(userId); + +// Получение профиля по Peer +var profile = profilesModule.GetProfileByPeer(peer); + +// Изменение профиля +profile.GetProperty("playerLevel").Add(1); +profile.GetProperty("coins").Set(100); +``` + +## События профиля + +### Подписка на изменения: +```csharp +// На сервере +profilesModule.OnProfileCreated += OnProfileCreated; +profilesModule.OnProfileLoaded += OnProfileLoaded; + +// В профиле +profile.OnModifiedInServerEvent += OnProfileChanged; + +// Конкретное свойство +profile.GetProperty("playerLevel").OnDirtyEvent += OnLevelChanged; +``` + +## Синхронизация с базой данных + +### Реализация IProfilesDatabaseAccessor: +```csharp +public class ProfilesDatabaseAccessor : IProfilesDatabaseAccessor +{ + public async Task RestoreProfileAsync(ObservableServerProfile profile) + { + // Загрузка из БД + var data = await LoadProfileDataFromDB(profile.UserId); + if (data != null) + { + profile.FromBytes(data); + } + } + + public async Task UpdateProfilesAsync(List profiles) + { + // Batch сохранение в БД + foreach (var profile in profiles) + { + await SaveProfileToDB(profile.UserId, profile.ToBytes()); + } + } +} +``` + +## Типы наблюдаемых свойств + +### Базовые типы: +```csharp +// Числовые +ObservableInt level = new ObservableInt(10); +ObservableFloat health = new ObservableFloat(100.0f); + +// Строки +ObservableString name = new ObservableString("Player"); + +// Списки +ObservableListInt scores = new ObservableListInt(); +scores.Add(100); + +// Словари +ObservableDictStringInt items = new ObservableDictStringInt(); +items.Add("sword", 1); +``` + +## Серверные обновления + +### Отправка обновлений с игрового сервера: +```csharp +// Создание пакета обновлений +var updates = new ProfileUpdatePacket(); +updates.UserId = userId; +updates.Properties = new MstProperties(); +updates.Properties.Set("playerLevel", 15); +updates.Properties.Set("experience", 1500); + +// Отправка на master server +Mst.Server.Connection.SendMessage(MstOpCodes.ServerUpdateProfileValues, updates); +``` + +## Производительность и оптимизация + +### Debounce настройки: +```csharp +// Задержка сохранения в БД (секунды) +saveProfileDebounceTime = 1; + +// Задержка отправки обновлений клиенту +clientUpdateDebounceTime = 0.5f; + +// Время до выгрузки профиля после выхода +unloadProfileAfter = 20; +``` + +### Ограничения: +```csharp +// Максимальный размер обновления +maxUpdateSize = 1048576; + +// Тайм-аут загрузки профиля +profileLoadTimeoutSeconds = 10; +``` + +## Примеры использования + +### Игровая статистика: +```csharp +public class PlayerStats +{ + public ObservableInt Level { get; private set; } + public ObservableInt Experience { get; private set; } + public ObservableFloat Health { get; private set; } + public ObservableDictStringInt Inventory { get; private set; } + + public PlayerStats(ObservableServerProfile profile) + { + Level = profile.GetProperty("playerLevel"); + Experience = profile.GetProperty("experience"); + Health = profile.GetProperty("health"); + Inventory = profile.GetProperty("inventory"); + } + + public void AddExperience(int amount) + { + Experience.Add(amount); + + if (Experience.Value >= GetExperienceForNextLevel()) + { + LevelUp(); + } + } + + private void LevelUp() + { + Level.Add(1); + Health.Set(100.0f); // Восстановление здоровья при уровне + Experience.Set(0); + } +} +``` + +### Клиентский профиль: +```csharp +public class ProfileUI : MonoBehaviour +{ + [Header("UI References")] + public Text levelText; + public Text coinsText; + public Text healthText; + + private ObservableProfile profile; + + void Start() + { + // Запрос профиля + Mst.Client.Connection.SendMessage(MstOpCodes.ClientFillInProfileValues); + + // Регистрация обработчика обновлений + Mst.Client.Connection.RegisterMessageHandler(MstOpCodes.UpdateClientProfile, OnProfileUpdate); + } + + private void OnProfileUpdate(IIncomingMessage message) + { + if (profile == null) + profile = new ObservableProfile(); + + profile.ApplyUpdates(message.AsBytes()); + + // Обновление UI + UpdateUI(); + } + + private void UpdateUI() + { + levelText.text = $"Level: {profile.GetProperty("playerLevel").Value}"; + coinsText.text = $"Coins: {profile.GetProperty("coins").Value}"; + healthText.text = $"Health: {profile.GetProperty("health").Value}"; + } +} +``` + +## Лучшие практики + +1. **Используйте популяторы** для инициализации профилей +2. **Группируйте обновления** для снижения нагрузки +3. **Настройте debounce** для оптимизации производительности +4. **Проверяйте размер обновлений** для предотвращения атак +5. **Используйте типизированные свойства** для безопасности +6. **Подписывайтесь на события** для реактивного программирования +7. **Очищайте неиспользуемые профили** для освобождения памяти +8. **Реализуйте резервное копирование** для важных данных diff --git a/Docs/Modules/Rooms.md b/Docs/Modules/Rooms.md new file mode 100644 index 0000000..6c501a0 --- /dev/null +++ b/Docs/Modules/Rooms.md @@ -0,0 +1,355 @@ +# Master Server Toolkit - Rooms + +## Описание +Модуль для регистрации, управления и предоставления доступа к игровым комнатам. Позволяет создавать публичные и приватные комнаты, управлять их параметрами и контролировать доступ игроков. + +## RoomsModule + +Основной класс для управления комнатами на master server. + +### Настройка: +```csharp +[Header("Permissions")] +[SerializeField] protected int registerRoomPermissionLevel = 0; +``` + +## Регистрация комнаты + +### С игрового сервера: +```csharp +// Создание опций комнаты +var options = new RoomOptions +{ + Name = "Epic Battle Arena", + RoomIp = "192.168.1.100", + RoomPort = 7777, + MaxConnections = 16, + IsPublic = true, + Password = "", // пустой для публичной комнаты + Region = "RU", + CustomOptions = new MstProperties() +}; + +// Добавляем кастомные свойства +options.CustomOptions.Set("map", "forest_arena"); +options.CustomOptions.Set("gameMode", "battle_royale"); +options.CustomOptions.Set("difficulty", "hard"); + +// Регистрация на master server +Mst.Server.Rooms.RegisterRoom(options, (room, error) => +{ + if (room != null) + { + Debug.Log($"Room registered with ID: {room.RoomId}"); + } + else + { + Debug.LogError($"Failed to register room: {error}"); + } +}); +``` + +### Автоматическая регистрация: +```csharp +public class GameServerManager : MonoBehaviour +{ + [Header("Room Settings")] + public string roomName = "Game Room"; + public int maxPlayers = 10; + public bool isPublic = true; + + void Start() + { + RegisterGameRoom(); + } + + private void RegisterGameRoom() + { + var options = new RoomOptions + { + Name = roomName, + RoomIp = GetServerIp(), + RoomPort = NetworkManager.singleton.networkPort, + MaxConnections = maxPlayers, + IsPublic = isPublic + }; + + Mst.Server.Rooms.RegisterRoom(options); + } +} +``` + +## Управление комнатой + +### Обновление параметров: +```csharp +// Изменение опций комнаты +var newOptions = room.Options; +newOptions.MaxConnections = 20; +newOptions.CustomOptions.Set("gameState", "playing"); + +Mst.Server.Rooms.SaveRoomOptions(room.RoomId, newOptions); + +// Управление игроками +room.AddPlayer(peerId, peer); +room.RemovePlayer(peerId); + +// Получение списка игроков +var players = room.Players.Values; +``` + +### Уничтожение комнаты: +```csharp +// При завершении игры +Mst.Server.Rooms.DestroyRoom(room.RoomId, (successful, error) => +{ + if (successful) + { + Debug.Log("Room destroyed successfully"); + } +}); + +// Автоматическое уничтожение при отключении +void OnApplicationQuit() +{ + if (registeredRoom != null) + { + Mst.Server.Rooms.DestroyRoom(registeredRoom.RoomId); + } +} +``` + +## Подключение к комнате + +### Поиск и подключение: +```csharp +// Поиск публичных комнат +Mst.Client.Matchmaker.FindGames((games) => +{ + foreach (var game in games) + { + Debug.Log($"Room: {game.Name}, Players: {game.OnlinePlayers}/{game.MaxPlayers}"); + } +}); + +// Подключение к конкретной комнате +var roomId = 12345; +Mst.Client.Rooms.GetAccess(roomId, "", (access, error) => +{ + if (access != null) + { + ConnectToGameServer(access.RoomIp, access.RoomPort, access.Token); + } +}); +``` + +### Подключение с паролем: +```csharp +// Подключение к приватной комнате +Mst.Client.Rooms.GetAccess(roomId, "secret_password", (access, error) => +{ + if (access != null) + { + Debug.Log("Access granted!"); + JoinGameServer(access.RoomIp, access.RoomPort, access.Token); + } + else + { + Debug.LogError("Invalid password"); + } +}); +``` + +## Интеграция с игровым сервером + +### RoomServerManager: +```csharp +public class RoomServerManager : MonoBehaviour +{ + [Header("Refs")] + public NetworkManager networkManager; + + private RegisteredRoom currentRoom; + + void Start() + { + // Запуск сервера + networkManager.StartServer(); + + // Регистрация комнаты + RegisterRoom(); + } + + public override void OnServerConnect(NetworkConnectionToClient conn) + { + // Проверка токена доступа + ValidatePlayerAccess(conn); + } + + private void ValidatePlayerAccess(NetworkConnectionToClient conn) + { + // Получение токена от клиента + string accessToken = GetTokenFromClient(conn); + + // Проверка на master server + Mst.Server.Rooms.ValidateAccess(currentRoom.RoomId, accessToken, (userData, error) => + { + if (userData != null) + { + // Доступ разрешен + acceptedPlayers[conn.connectionId] = userData; + Debug.Log($"Player {userData.Username} validated"); + } + else + { + // Отклонить подключение + conn.Disconnect(); + } + }); + } +} +``` + +## Фильтрация комнат + +### Поиск по критериям: +```csharp +// Создание фильтров +var filters = new MstProperties(); +filters.Set("map", "forest"); +filters.Set("gameMode", "pvp"); +filters.Set("maxPlayers", 10); + +// Поиск комнат с фильтрами +Mst.Client.Matchmaker.FindGames(filters, (games) => +{ + var filteredRooms = games.Where(g => + g.Type == GameInfoType.Room && + g.OnlinePlayers < g.MaxPlayers && + !g.IsPasswordProtected + ); +}); +``` + +### Кастомная фильтрация: +```csharp +// На стороне сервера - переопределение GetPublicRoomOptions +public override MstProperties GetPublicRoomOptions(IPeer player, RegisteredRoom room, MstProperties playerFilters) +{ + var roomData = base.GetPublicRoomOptions(player, room, playerFilters); + + // Добавляем дополнительную информацию + roomData.Set("serverVersion", "1.2.3"); + roomData.Set("ping", CalculatePing(player, room)); + + // Скрываем некоторые данные + if (!IsAdminPlayer(player)) + { + roomData.Remove("adminPort"); + } + + return roomData; +} +``` + +## События комнат + +### Подписка на события: +```csharp +// На мастер сервере +roomsModule.OnRoomRegisteredEvent += (room) => +{ + Debug.Log($"New room registered: {room.Options.Name}"); + NotifyMatchmakingSystem(room); +}; + +roomsModule.OnRoomDestroyedEvent += (room) => +{ + Debug.Log($"Room destroyed: {room.RoomId}"); + CleanupResources(room); +}; + +// В игровой комнате +room.OnPlayerJoinedEvent += (player) => +{ + SendWelcomeMessage(player); + UpdatePlayerCount(); +}; + +room.OnPlayerLeftEvent += (player) => +{ + SavePlayerProgress(player); + CheckIfRoomShouldClose(); +}; +``` + +## UI для списка комнат + +```csharp +public class RoomListUI : MonoBehaviour +{ + [Header("UI")] + public GameObject roomItemPrefab; + public Transform roomList; + public Button refreshButton; + + void Start() + { + refreshButton.onClick.AddListener(RefreshRoomList); + RefreshRoomList(); + } + + void RefreshRoomList() + { + // Очистка списка + foreach (Transform child in roomList) + { + Destroy(child.gameObject); + } + + // Получение комнат + Mst.Client.Matchmaker.FindGames((games) => + { + foreach (var game in games) + { + if (game.Type == GameInfoType.Room) + { + CreateRoomItem(game); + } + } + }); + } + + void CreateRoomItem(GameInfoPacket room) + { + var item = Instantiate(roomItemPrefab, roomList); + var roomUI = item.GetComponent(); + + roomUI.Setup(room); + roomUI.OnJoinClicked = () => JoinRoom(room.Id); + } + + void JoinRoom(int roomId) + { + Mst.Client.Rooms.GetAccess(roomId, (access, error) => + { + if (access != null) + { + NetworkManager.singleton.networkAddress = access.RoomIp; + NetworkManager.singleton.StartClient(); + } + }); + } +} +``` + +## Лучшие практики + +1. **Всегда проверяйте токены доступа** на игровом сервере +2. **Используйте кастомные свойства** для гибкой настройки комнат +3. **Регистрируйте комнаты после старта сервера** +4. **Обновляйте количество игроков** в реальном времени +5. **Очищайте ресурсы** при уничтожении комнаты +6. **Используйте пароли** для приватных комнат +7. **Мониторьте статус комнат** для оптимизации ресурсов +8. **Применяйте фильтры** для лучшего UX поиска комнат From 88dab8e2d0ace48ca5d2528c1a0a99902ec418f1 Mon Sep 17 00:00:00 2001 From: Petr Tsap Date: Tue, 13 May 2025 10:46:08 +0200 Subject: [PATCH 2/4] =?UTF-8?q?=D0=97=D0=B0=D0=BA=D0=BE=D0=BD=D1=87=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BF=D0=B5=D1=80=D0=B2=D1=83=D1=8E=20=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D1=81=D0=B8=D1=8E=20=D1=80=D1=83=D1=81=D1=81=D0=BA=D0=BE?= =?UTF-8?q?=D1=8F=D0=B7=D1=8B=D1=87=D0=BD=D1=8B=D1=85=20=D0=B4=D0=BE=D0=BA?= =?UTF-8?q?=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Docs/Core/README.md | 133 ++++++ Docs/DebounceDispatcher Documentation.md | 356 -------------- Docs/Modules/Achievements.md | 305 ++++++++++++ Docs/Modules/AnalyticsModule.md | 237 ++++++++++ Docs/Modules/Censor.md | 233 +++++++++ Docs/Modules/Matchmaker.md | 150 ++++++ Docs/Modules/Notification.md | 358 ++++++++++++++ Docs/Modules/Ping.md | 385 +++++++++++++++ Docs/Modules/QuestsModule.md | 192 ++++++++ Docs/Modules/Spawner.md | 245 ++++++++++ Docs/Modules/WebServer.md | 579 +++++++++++++++++++++++ Docs/Modules/WorldRooms.md | 309 ++++++++++++ Docs/Networking.md | 255 ++++++++++ Docs/ProfilesModule Documentation.md | 578 ---------------------- Docs/README.md | 86 ++++ Docs/ThrottleDispatcher Documentation.md | 389 --------------- Docs/Tools/Attributes.md | 141 ++++++ Docs/Tools/DebounceThrottle.md | 195 ++++++++ Docs/Tools/README.md | 31 ++ Docs/Tools/Terminal.md | 261 ++++++++++ Docs/Tools/Tweener.md | 299 ++++++++++++ Docs/Tools/UI/Components.md | 213 +++++++++ Docs/Tools/UI/README.md | 57 +++ Docs/Tools/UI/Validation.md | 315 ++++++++++++ Docs/Tools/UI/Views.md | 291 ++++++++++++ Docs/Tools/Utilities.md | 469 ++++++++++++++++++ Docs/Tools/WebGL.md | 334 +++++++++++++ 27 files changed, 6073 insertions(+), 1323 deletions(-) create mode 100644 Docs/Core/README.md delete mode 100644 Docs/DebounceDispatcher Documentation.md create mode 100644 Docs/Modules/Achievements.md create mode 100644 Docs/Modules/AnalyticsModule.md create mode 100644 Docs/Modules/Censor.md create mode 100644 Docs/Modules/Matchmaker.md create mode 100644 Docs/Modules/Notification.md create mode 100644 Docs/Modules/Ping.md create mode 100644 Docs/Modules/QuestsModule.md create mode 100644 Docs/Modules/Spawner.md create mode 100644 Docs/Modules/WebServer.md create mode 100644 Docs/Modules/WorldRooms.md create mode 100644 Docs/Networking.md delete mode 100644 Docs/ProfilesModule Documentation.md create mode 100644 Docs/README.md delete mode 100644 Docs/ThrottleDispatcher Documentation.md create mode 100644 Docs/Tools/Attributes.md create mode 100644 Docs/Tools/DebounceThrottle.md create mode 100644 Docs/Tools/README.md create mode 100644 Docs/Tools/Terminal.md create mode 100644 Docs/Tools/Tweener.md create mode 100644 Docs/Tools/UI/Components.md create mode 100644 Docs/Tools/UI/README.md create mode 100644 Docs/Tools/UI/Validation.md create mode 100644 Docs/Tools/UI/Views.md create mode 100644 Docs/Tools/Utilities.md create mode 100644 Docs/Tools/WebGL.md diff --git a/Docs/Core/README.md b/Docs/Core/README.md new file mode 100644 index 0000000..288955c --- /dev/null +++ b/Docs/Core/README.md @@ -0,0 +1,133 @@ +# Master Server Toolkit - Ядро системы + +## Общий обзор +Ядро Master Server Toolkit состоит из базовых компонентов, которые обеспечивают фундаментальную функциональность для построения многопользовательских игр. Эти компоненты тесно взаимодействуют между собой, создавая гибкую и расширяемую архитектуру. + +## Структура ядра + +### [MasterServer](MasterServer.md) +Центральный компонент, предоставляющий доступ ко всем системам через класс `Mst`. Содержит основные настройки и конфигурацию сервера. + +### [Client](Client.md) +Клиентская часть системы для подключения к серверу, отправки запросов и обработки ответов. Включает базовый класс для клиентских модулей. + +### [Server](Server.md) +Серверная часть системы для обработки подключений, регистрации обработчиков сообщений и управления модулями. + +### [Database](Database.md) +Абстракция для работы с различными базами данных. Позволяет использовать разные хранилища данных без изменения основного кода. + +### [Events](Events.md) +Система событий для обмена сообщениями между компонентами без жестких зависимостей. Основа для слабосвязанной архитектуры. + +### [Keys](Keys.md) +Система констант и ключей для унификации доступа к данным. Предотвращает ошибки из-за опечаток при работе со строковыми идентификаторами. + +### [Localization](Localization.md) +Система локализации для поддержки множества языков. Позволяет легко добавлять и менять переводы. + +### [Logger](Logger.md) +Система логирования для отслеживания работы приложения. Поддерживает различные уровни логирования и форматирование. + +### [Mail](Mail.md) +Система для отправки email сообщений. Используется для подтверждения регистрации, восстановления пароля и других целей. + +## Взаимодействие компонентов + +![Архитектура ядра](../Images/core_architecture.png) + +### Основные взаимосвязи: + +1. **MasterServer** содержит центральный класс `Mst`, который предоставляет доступ ко всем другим компонентам: + ```csharp + // Доступ к клиенту + Mst.Client + + // Доступ к серверу + Mst.Server + + // Доступ к системе событий + Mst.Events + ``` + +2. **Client и Server** используют общую систему сетевых сообщений, но работают на разных сторонах соединения: + ```csharp + // На стороне клиента + Mst.Client.Connection.SendMessage(MstOpCodes.SignIn, credentials); + + // На стороне сервера + server.RegisterMessageHandler(MstOpCodes.SignIn, HandleSignIn); + ``` + +3. **Database** используется серверными модулями для доступа к данным: + ```csharp + // Получение аксессора базы данных + var dbAccessor = Mst.Server.DbAccessors.GetAccessor(); + + // Использование аксессора + var account = await dbAccessor.GetAccountByUsername(username); + ``` + +4. **Events** используется всеми компонентами для слабосвязанного обмена информацией: + ```csharp + // Отправка события + Mst.Events.Invoke("userLoggedIn", userId); + + // Подписка на событие + Mst.Events.AddListener("userLoggedIn", OnUserLoggedIn); + ``` + +5. **Logger** используется повсеместно для отладки и мониторинга: + ```csharp + // Логирование событий + Mst.Logger.Debug("Connection established"); + Mst.Logger.Error($"Failed to connect: {error}"); + ``` + +6. **Keys** используется для стандартизации доступа к данным: + ```csharp + // Использование ключей + properties.Set(MstDictKeys.USER_ID, userId); + properties.Set(MstDictKeys.USER_NAME, username); + ``` + +7. **Localization** обеспечивает мультиязычность: + ```csharp + // Получение локализованной строки + string welcomeText = Mst.Localization.GetString("welcome_message"); + ``` + +8. **Mail** используется серверными модулями для коммуникации с пользователями: + ```csharp + // Отправка письма + await Mst.Server.Mail.SendEmailAsync(email, subject, body); + ``` + +## Процесс инициализации + +1. Создание экземпляра `MasterServerBehaviour` +2. Определение настроек подключения (IP, порт, безопасность) +3. Регистрация необходимых модулей +4. Запуск сервера или подключение клиента +5. Инициализация всех зарегистрированных модулей +6. Установка необходимых обработчиков сообщений + +## Принципы построения модулей + +Ядро предоставляет базовые классы для создания модулей: +- `BaseServerModule` - Для серверных модулей +- `BaseClientModule` - Для клиентских модулей + +Модули следуют следующим принципам: +1. **Единственная ответственность** - Каждый модуль отвечает за одну функциональность +2. **Слабая связность** - Общение через события и общий интерфейс +3. **Расширяемость** - Возможность наследования и переопределения базового поведения +4. **Зависимость от абстракций** - Использование интерфейсов вместо конкретных реализаций + +## Рекомендации по работе с ядром + +1. **Используйте события** вместо прямых вызовов для уменьшения связности +2. **Инкапсулируйте логику** в специализированные модули +3. **Следуйте архитектуре** базовых компонентов при создании своих +4. **Используйте базовые классы** вместо создания с нуля +5. **Документируйте интерфейсы** для облегчения дальнейшего сопровождения diff --git a/Docs/DebounceDispatcher Documentation.md b/Docs/DebounceDispatcher Documentation.md deleted file mode 100644 index 7f0ce0a..0000000 --- a/Docs/DebounceDispatcher Documentation.md +++ /dev/null @@ -1,356 +0,0 @@ -# DebounceDispatcher Documentation - -## Introduction - -`DebounceDispatcher` is a class that implements the "debounce" pattern for C#, which allows delaying the execution of a function until a specified time interval has passed since the last call. This is useful for optimizing performance in scenarios with frequent calls, such as processing user input, API requests, or UI updates. - -## Contents - -- Overview of Classes -- Installation -- Usage Examples - - Basic Debounce with Action - - Asynchronous Debounce - - Using Cancellation - - Getting Results with DebounceDispatcher\ -- Advanced Scenarios -- Usage Recommendations - -## Overview of Classes - -The library consists of two main classes: - -1. **DebounceDispatcher\** - The main generic class that allows you to specify the return value type. -2. **DebounceDispatcher** - A simplified version that inherits from `DebounceDispatcher` and provides convenient methods for actions without return values. - -## Installation - -Add the `DebounceDispatcher.cs` and `DebounceDispatcherGeneric.cs` files to your project and make sure you're using the correct namespace: - -```csharp -using MasterServerToolkit.DebounceThrottle; -``` - -## Usage Examples - -### Basic Debounce with Action - -```csharp -// Create a debounce dispatcher with a 500ms interval -var debouncer = new DebounceDispatcher(500); - -// Imagine this method is called frequently, for example during text input -void HandleTextChanged(string text) -{ - // Wrap the handler in Debounce, so it's called only after a 500ms pause - debouncer.Debounce(() => { - Console.WriteLine($"Performing search for text: {text}"); - // Here would be code that performs a search or other expensive operation - }); -} - -// Simulation of rapid sequential calls -HandleTextChanged("a"); -HandleTextChanged("ab"); -HandleTextChanged("abc"); -HandleTextChanged("abcd"); - -// Only the last call with text "abcd" will be executed after 500ms -``` - -### Asynchronous Debounce - -```csharp -// Create a debounce dispatcher with a 300ms interval -var debouncer = new DebounceDispatcher(300); - -// Asynchronous method that is called as the user types -async Task HandleSearchAsync(string query) -{ - // Use asynchronous debounce - await debouncer.DebounceAsync(async () => - { - Console.WriteLine($"Performing asynchronous search for query: {query}"); - - // Simulating an asynchronous operation, such as an API request - await Task.Delay(1000); // Simulating network operation - - Console.WriteLine($"Search for query '{query}' completed"); - }); -} - -// Use in an asynchronous method -async Task RunSearchExample() -{ - // Simulation of sequential requests - await HandleSearchAsync("progra"); - await HandleSearchAsync("programm"); - await HandleSearchAsync("programming"); - - // Only the search for "programming" will be executed -} -``` - -### Using Cancellation - -```csharp -// Create a debounce dispatcher with a 1000ms interval -var debouncer = new DebounceDispatcher(1000); - -// Cancellation token source -using var cts = new CancellationTokenSource(); - -// Method with cancellation support -async Task PerformOperationWithCancellation() -{ - try - { - await debouncer.DebounceAsync(async () => - { - Console.WriteLine("Starting a long operation..."); - - // Simulating long-running work - for (int i = 0; i < 10; i++) - { - // Check for cancellation token in the loop - if (cts.Token.IsCancellationRequested) - { - Console.WriteLine("Operation was cancelled during execution"); - return; - } - - await Task.Delay(500); // Simulating an operation step - Console.WriteLine($"Step {i + 1} of 10"); - } - - Console.WriteLine("Operation completed successfully"); - }, cts.Token); - } - catch (OperationCanceledException) - { - Console.WriteLine("Operation was cancelled before execution started"); - } -} - -// Example of using with cancellation -async Task CancellationExample() -{ - // Start the operation - var task = PerformOperationWithCancellation(); - - // After 1500ms cancel the operation - await Task.Delay(1500); - cts.Cancel(); - - // Wait for the task to complete - await task; -} -``` - -### Getting Results with DebounceDispatcher\ - -```csharp -// Create a generic dispatcher to get a string result -var resultDebouncer = new DebounceDispatcher(800); - -// Function that will return a result -async Task FetchDataWithResultAsync(string query) -{ - return await resultDebouncer.DebounceAsync(async () => - { - Console.WriteLine($"Fetching data for: {query}"); - - // Simulating an API request - await Task.Delay(1000); - - return $"Results for query '{query}': found 42 matches"; - }); -} - -// Usage -async Task ResultExample() -{ - // Call sequentially, but only the last one will execute - Task task1 = FetchDataWithResultAsync("C#"); - Task task2 = FetchDataWithResultAsync("C# debounce"); - Task task3 = FetchDataWithResultAsync("C# debounce pattern"); - - // Each of these variables will get the result of the last call - string result1 = await task1; - string result2 = await task2; - string result3 = await task3; - - // All three variables will contain the same result: - // "Results for query 'C# debounce pattern': found 42 matches" - Console.WriteLine($"result1: {result1}"); - Console.WriteLine($"result2: {result2}"); - Console.WriteLine($"result3: {result3}"); -} -``` - -## Advanced Scenarios - -### Combination with Throttle - -If you need a combination of debounce and throttle patterns, you can implement the following approach: - -```csharp -public class DebouncedSearchService -{ - private readonly DebounceDispatcher _debouncer; - private readonly SemaphoreSlim _throttleSemaphore; - private readonly int _throttleInterval; - private DateTime _lastExecutionTime = DateTime.MinValue; - - public DebouncedSearchService(int debounceInterval, int throttleInterval, int maxConcurrent = 1) - { - _debouncer = new DebounceDispatcher(debounceInterval); - _throttleSemaphore = new SemaphoreSlim(maxConcurrent); - _throttleInterval = throttleInterval; - } - - public async Task SearchAsync(string query) - { - await _debouncer.DebounceAsync(async () => - { - // Apply throttle through semaphore - await _throttleSemaphore.WaitAsync(); - try - { - // Check if enough time has passed since the last execution - var elapsed = (DateTime.UtcNow - _lastExecutionTime).TotalMilliseconds; - if (elapsed < _throttleInterval) - { - // Wait for the remaining time until throttle interval - await Task.Delay((int)(_throttleInterval - elapsed)); - } - - // Perform the operation - Console.WriteLine($"Performing search for query: {query}"); - await Task.Delay(1000); // Simulating operation - - // Update last execution time - _lastExecutionTime = DateTime.UtcNow; - } - finally - { - _throttleSemaphore.Release(); - } - }); - } -} -``` - -### Binding to UI Events (WPF) - -```csharp -public class SearchViewModel : INotifyPropertyChanged -{ - private readonly DebounceDispatcher _debouncer; - private string _searchText; - - public event PropertyChangedEventHandler PropertyChanged; - - public string SearchText - { - get => _searchText; - set - { - _searchText = value; - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SearchText))); - - // Debounce search when text changes - _debouncer.Debounce(PerformSearch); - } - } - - public ObservableCollection SearchResults { get; } = new ObservableCollection(); - - public SearchViewModel() - { - _debouncer = new DebounceDispatcher(300); - } - - private void PerformSearch() - { - // Executes in the UI thread after debounce - SearchResults.Clear(); - - if (string.IsNullOrWhiteSpace(_searchText)) - return; - - // In a real application, this would be a database or API query - for (int i = 0; i < 5; i++) - { - SearchResults.Add($"Result {i + 1} for '{_searchText}'"); - } - } -} -``` - -## Usage Recommendations - -1. **Choose the optimal debounce interval**: - - For UI events (text input): 200-500 ms - - For API requests: 500-1000 ms - - For heavy operations: 1000+ ms - -2. **Always handle exceptions**: - ```csharp - try - { - await debouncer.DebounceAsync(async () => { /* code */ }); - } - catch (OperationCanceledException) - { - // Handle cancellation - } - catch (Exception ex) - { - // Handle other exceptions - } - ``` - -3. **Don't forget to call Dispose**: - ```csharp - public class MyService : IDisposable - { - private readonly DebounceDispatcher _debouncer = new DebounceDispatcher(500); - - // Using _debouncer... - - public void Dispose() - { - _debouncer.Dispose(); - } - } - ``` - -4. **Use cancellation to avoid resource leaks**: - ```csharp - private CancellationTokenSource _searchCts; - - private void OnSearchTextChanged(string text) - { - // Cancel previous search - _searchCts?.Cancel(); - _searchCts = new CancellationTokenSource(); - - _debouncer.Debounce(() => PerformSearch(text), _searchCts.Token); - } - ``` - -5. **Use the generic version to get results**: - Instead of: - ```csharp - var result = ""; - debouncer.Debounce(() => { result = GetResult(); }); - ``` - - Better: - ```csharp - var resultDebouncer = new DebounceDispatcher(500); - var result = await resultDebouncer.DebounceAsync(() => GetResultAsync()); - ``` - -This documentation covers the basic usage of `DebounceDispatcher` and `DebounceDispatcher` and should help you effectively apply these classes in your project. \ No newline at end of file diff --git a/Docs/Modules/Achievements.md b/Docs/Modules/Achievements.md new file mode 100644 index 0000000..bff127b --- /dev/null +++ b/Docs/Modules/Achievements.md @@ -0,0 +1,305 @@ +# Master Server Toolkit - Achievements + +## Описание +Модуль достижений для создания, отслеживания и разблокировки достижений игроков. Интегрируется с профилями пользователей для сохранения прогресса. + +## AchievementsModule + +Основной класс модуля достижений. + +### Настройка: +```csharp +[Header("Permission")] +[SerializeField] protected bool clientCanUpdateProgress = false; + +[Header("Settings")] +[SerializeField] protected AchievementsDatabase achievementsDatabase; +``` + +### Зависимости: +Модуль требует наличия: +- AuthModule - для аутентификации пользователей +- ProfilesModule - для сохранения прогресса достижений + +## Создание базы данных достижений + +### AchievementsDatabase: +```csharp +// Создание ScriptableObject с достижениями +[CreateAssetMenu(menuName = "Master Server Toolkit/Achievements Database")] +public class AchievementsDatabase : ScriptableObject +{ + [SerializeField] private List achievements = new List(); + + public IReadOnlyCollection Achievements => achievements; +} +``` + +### Определение достижений: +```csharp +// Создание в Unity Editor +var database = ScriptableObject.CreateInstance(); + +// Добавление достижений +database.Add(new AchievementData +{ + Key = "first_victory", + Title = "First Victory", + Description = "Win your first match", + Type = AchievementType.Normal, + MaxProgress = 1, + Unlockable = true +}); + +database.Add(new AchievementData +{ + Key = "win_streak", + Title = "Win Streak", + Description = "Win 10 matches in a row", + Type = AchievementType.Incremental, + MaxProgress = 10, + Unlockable = true +}); + +// Сохранение базы достижений +AssetDatabase.CreateAsset(database, "Assets/Resources/AchievementsDatabase.asset"); +AssetDatabase.SaveAssets(); +``` + +## Типы достижений + +### AchievementType: +```csharp +public enum AchievementType +{ + Normal, // Обычное достижение (0/1) + Incremental, // Постепенное достижение (0/N) + Infinite // Бесконечное достижение (трекинг без разблокировки) +} +``` + +### Структура данных достижения: +```csharp +[Serializable] +public class AchievementData +{ + public string Key; + public string Title; + public string Description; + public AchievementType Type; + public int MaxProgress; + public bool Unlockable; + public Sprite Icon; + public AchievementExtraData[] ResultCommands; + + [Serializable] + public class AchievementExtraData + { + public string CommandKey; + public string[] CommandValues; + } +} +``` + +## Обновление прогресса достижений + +### Со стороны сервера: +```csharp +// Получить модуль +var achievementsModule = Mst.Server.Modules.GetModule(); + +// Обновить прогресс достижения +void UpdateAchievement(string userId, string achievementKey, int progress) +{ + var packet = new UpdateAchievementProgressPacket + { + userId = userId, + key = achievementKey, + progress = progress + }; + + // Отправить пакет обновления + Mst.Server.SendMessage(MstOpCodes.ServerUpdateAchievementProgress, packet); +} + +// Пример использования +UpdateAchievement(user.UserId, "first_victory", 1); +``` + +### Со стороны клиента (если разрешено): +```csharp +// Клиентский запрос на обновление прогресса +void UpdateAchievement(string achievementKey, int progress) +{ + var packet = new UpdateAchievementProgressPacket + { + key = achievementKey, + progress = progress + }; + + Mst.Client.Connection.SendMessage(MstOpCodes.ClientUpdateAchievementProgress, packet, (status, response) => + { + if (status == ResponseStatus.Success) + { + Debug.Log($"Achievement progress updated for {achievementKey}"); + } + }); +} +``` + +## Интеграция с профилями + +Достижения автоматически синхронизируются с профилями пользователей через свойство профиля: + +```csharp +// Регистрация свойства достижений в ProfilesModule +profilesModule.RegisterProperty(ProfilePropertyOpCodes.achievements, null, () => +{ + return new ObservableAchievements(); +}); + +// Получение достижений из профиля +void GetAchievements(ObservableServerProfile profile) +{ + if (profile.TryGet(ProfilePropertyOpCodes.achievements, out ObservableAchievements achievements)) + { + // Список всех достижений пользователя + var allAchievements = achievements.GetAll(); + + // Проверка разблокировано ли достижение + bool isUnlocked = achievements.IsUnlocked("first_victory"); + + // Получить прогресс достижения + int progress = achievements.GetProgress("win_streak"); + } +} +``` + +## Отслеживание событий разблокировки + +### Подписка на события клиента: +```csharp +// В клиентском классе +private void Start() +{ + Mst.Client.Connection.RegisterMessageHandler(MstOpCodes.ClientAchievementUnlocked, OnAchievementUnlocked); +} + +private void OnAchievementUnlocked(IIncomingMessage message) +{ + string achievementKey = message.AsString(); + Debug.Log($"Achievement unlocked: {achievementKey}"); + + // Показать интерфейс разблокировки достижения + ShowAchievementUnlockedUI(achievementKey); +} +``` + +## Настройка наград за достижения + +Модуль позволяет настроить команды, которые будут выполнены при разблокировке достижения: + +```csharp +// Настройка ResultCommands в достижении +var achievement = new AchievementData +{ + Key = "100_matches_played", + Title = "Veteran", + Description = "Play 100 matches", + Type = AchievementType.Incremental, + MaxProgress = 100, + Unlockable = true, + + // Команды для выполнения при разблокировке + ResultCommands = new[] + { + new AchievementExtraData + { + CommandKey = "add_currency", + CommandValues = new[] { "gold", "100" } + }, + new AchievementExtraData + { + CommandKey = "unlock_avatar", + CommandValues = new[] { "veteran_avatar" } + } + } +}; +``` + +### Обработка команд: +```csharp +// Расширение модуля для обработки команд +public class MyAchievementsModule : AchievementsModule +{ + protected override void OnAchievementResultCommand(IUserPeerExtension user, string key, AchievementExtraData[] resultCommands) + { + foreach (var command in resultCommands) + { + switch (command.CommandKey) + { + case "add_currency": + AddCurrency(user, command.CommandValues[0], int.Parse(command.CommandValues[1])); + break; + + case "unlock_avatar": + UnlockAvatar(user, command.CommandValues[0]); + break; + + // Другие команды + default: + logger.Error($"Unknown achievement command: {command.CommandKey}"); + break; + } + } + } + + private void AddCurrency(IUserPeerExtension user, string currencyType, int amount) + { + // Добавление валюты игроку + } + + private void UnlockAvatar(IUserPeerExtension user, string avatarId) + { + // Разблокировка аватара игроку + } +} +``` + +## Клиентская обертка + +### AchievementsModuleClient: +```csharp +// Пример использования +var client = Mst.Client.Modules.GetModule(); + +// Получение списка достижений +var achievements = client.GetAchievements(); + +// Отображение интерфейса +void ShowAchievementsUI() +{ + foreach (var achievement in achievements) + { + // Создать элемент интерфейса для достижения + var item = Instantiate(achievementItemPrefab, container); + + // Заполнить данными + item.SetTitle(achievement.Title); + item.SetDescription(achievement.Description); + item.SetIcon(achievement.Icon); + item.SetProgress(achievement.CurrentProgress, achievement.MaxProgress); + item.SetUnlocked(achievement.IsUnlocked); + } +} +``` + +## Лучшие практики + +1. **Используйте уникальные ключи** для каждого достижения +2. **Разделяйте одиночные и инкрементальные** достижения +3. **Внедряйте проверку на стороне сервера** для предотвращения читерства +4. **Выполняйте логику наград на сервере** +5. **Кэшируйте данные достижений** на клиенте для быстрого доступа +6. **Разблокируйте похожие достижения** автоматически (например, "Убить 10 монстров" автоматически разблокирует "Убить 5 монстров") +7. **Собирайте аналитику** по достижениям для оценки геймплея diff --git a/Docs/Modules/AnalyticsModule.md b/Docs/Modules/AnalyticsModule.md new file mode 100644 index 0000000..39194b7 --- /dev/null +++ b/Docs/Modules/AnalyticsModule.md @@ -0,0 +1,237 @@ +# Master Server Toolkit - Analytics + +## Описание +Модуль для сбора, хранения и анализа игровых событий и метрик с поддержкой пользовательских запросов и фильтрации данных. + +## Основные компоненты + +### AnalyticsModule (Сервер) +```csharp +// Настройки +[SerializeField] protected float saveDebounceTime = 5f; // Задержка перед сохранением в БД +[SerializeField] protected bool useAnalytics = true; // Включить/выключить аналитику +[SerializeField] protected DatabaseAccessorFactory databaseAccessorFactory; // Фабрика базы данных +``` + +### AnalyticsModuleClient (Клиент) +```csharp +// Отправка события (единоразовое) +void SendEvent(string key, string category, Dictionary data); + +// Отправка события сессии (только один раз за сессию) +void SendSessionEvent(string key, string category, Dictionary data); +``` + +## Структура событий + +### AnalyticsDataInfoPacket +```csharp +public class AnalyticsDataInfoPacket : IAnalyticsInfoData +{ + public string Id { get; set; } // Уникальный ID события + public string UserId { get; set; } // ID пользователя + public string Key { get; set; } // Ключ события + public string Category { get; set; } // Категория события + public DateTime Timestamp { get; set; } // Время события + public Dictionary Data { get; set; } // Данные события + public bool IsSessionEvent { get; set; } // Событие сессии +} +``` + +## Примеры использования + +### Отправка событий с клиента +```csharp +// Получение клиента +var analytics = Mst.Client.Analytics; + +// Простое событие игры +var data = new Dictionary +{ + { "level", "2" }, + { "difficulty", "normal" }, + { "time", "125.5" } +}; + +analytics.SendEvent("level_completed", "gameplay", data); + +// Событие сессии (отправляется только один раз за сессию) +var sessionData = new Dictionary +{ + { "device", SystemInfo.deviceModel }, + { "os", SystemInfo.operatingSystem }, + { "screen_resolution", $"{Screen.width}x{Screen.height}" } +}; + +analytics.SendSessionEvent("session_start", "system", sessionData); +``` + +### Система отслеживания игровых действий +```csharp +public class AnalyticsTracker : MonoBehaviour +{ + // Отслеживание предметов + public void TrackItemAcquired(string itemId, string source) + { + var data = new Dictionary + { + { "item_id", itemId }, + { "source", source }, + { "player_level", PlayerStats.Level.ToString() } + }; + + Mst.Client.Analytics.SendEvent("item_acquired", "economy", data); + } + + // Отслеживание боевой системы + public void TrackEnemyDefeated(string enemyType, string weaponUsed, int timeToKill) + { + var data = new Dictionary + { + { "enemy_type", enemyType }, + { "weapon", weaponUsed }, + { "time_to_kill", timeToKill.ToString() }, + { "player_health", PlayerStats.Health.ToString() } + }; + + Mst.Client.Analytics.SendEvent("enemy_defeated", "combat", data); + } + + // Отслеживание покупок + public void TrackPurchase(string productId, float price, string currency) + { + var data = new Dictionary + { + { "product_id", productId }, + { "price", price.ToString() }, + { "currency", currency }, + { "player_balance", PlayerStats.Balance.ToString() } + }; + + Mst.Client.Analytics.SendEvent("purchase", "economy", data); + } +} +``` + +### Реализация интерфейса доступа к базе данных +```csharp +public class MongoAnalyticsAccessor : IAnalyticsDatabaseAccessor +{ + private MongoClient client; + private IMongoDatabase database; + private IMongoCollection eventsCollection; + + public void Initialize(string connectionString) + { + client = new MongoClient(connectionString); + database = client.GetDatabase("game_analytics"); + eventsCollection = database.GetCollection("events"); + } + + public async Task Insert(IEnumerable eventsData) + { + var documents = eventsData.Cast().ToList(); + await eventsCollection.InsertManyAsync(documents); + } + + public async Task> GetByKey(string eventKey, int size, int page) + { + var filter = Builders.Filter.Eq(e => e.Key, eventKey); + var options = new FindOptions + { + Limit = size, + Skip = page * size + }; + + var cursor = await eventsCollection.FindAsync(filter, options); + return await cursor.ToListAsync(); + } + + // Реализация других методов интерфейса... +} +``` + +### Регистрация базы данных +```csharp +public class AnalyticsDatabaseFactory : DatabaseAccessorFactory +{ + [SerializeField] private string connectionString = "mongodb://localhost:27017"; + + public override void CreateAccessors() + { + var accessor = new MongoAnalyticsAccessor(); + accessor.Initialize(connectionString); + + Mst.Server.DbAccessors.AddAccessor(accessor); + } +} +``` + +## Запросы данных на сервере + +```csharp +// Получение модуля +var analyticsModule = Mst.Server.Modules.GetModule(); + +// Получение всех событий (с пагинацией) +var allEvents = await analyticsModule.GetAll(100, 0); // 100 событий, страница 0 + +// Получение событий по ключу +var levelEvents = await analyticsModule.GetByKey("level_completed", 100, 0); + +// Получение событий пользователя +var userEvents = await analyticsModule.GetByUserId(userId, 100, 0); + +// Получение событий в диапазоне дат +var dateStart = DateTime.UtcNow.AddDays(-7); +var dateEnd = DateTime.UtcNow; +var weekEvents = await analyticsModule.GetByTimestampRange(dateStart, dateEnd, 1000, 0); + +// Получение событий с пользовательским запросом +var customQuery = "{ $and: [ { 'Key': 'purchase' }, { 'Data.price': { $gt: '10' } } ] }"; +var expensivePurchases = await analyticsModule.GetWithQuery(customQuery, 100, 0); +``` + +## Интеграция с веб-панелью + +```csharp +// Контроллер для веб-панели аналитики +public class AnalyticsWebController : WebController +{ + private AnalyticsModule analyticsModule; + + public override void Initialize(WebServerModule server) + { + base.Initialize(server); + + analyticsModule = server.GetModule(); + + // API маршруты + WebServer.RegisterGetHandler("api/analytics/events", GetEventsHandler, true); + WebServer.RegisterGetHandler("api/analytics/users", GetUsersHandler, true); + WebServer.RegisterGetHandler("api/analytics/dashboard", GetDashboardHandler, true); + } + + private async Task GetEventsHandler(HttpListenerRequest request) + { + string eventType = request.QueryString["type"]; + int size = int.Parse(request.QueryString["size"] ?? "100"); + int page = int.Parse(request.QueryString["page"] ?? "0"); + + var events = await analyticsModule.GetByKey(eventType, size, page); + + return new JsonResult(events); + } + + // Другие обработчики... +} +``` + +## Лучшие практики + +1. **Используйте дебаунс** для пакетной обработки событий +2. **Группируйте события по категориям** для более удобного анализа +3. **Ограничивайте объем собираемых данных** - собирайте только необходимую информацию +4. **Документируйте ключи событий** для обеспечения согласованности +5. **Регулярно анализируйте данные** для выявления трендов +6. **Используйте уникальные идентификаторы** для событий для устранения дублирования diff --git a/Docs/Modules/Censor.md b/Docs/Modules/Censor.md new file mode 100644 index 0000000..2fe269c --- /dev/null +++ b/Docs/Modules/Censor.md @@ -0,0 +1,233 @@ +# Master Server Toolkit - Censor + +## Описание +Модуль цензуры для фильтрации нежелательного контента, такого как нецензурная лексика или оскорбления. Обеспечивает безопасную коммуникацию между игроками. + +## CensorModule + +Основной класс модуля цензуры. + +### Настройка: +```csharp +[Header("Settings")] +[SerializeField] private TextAsset[] wordsLists; +[SerializeField, TextArea(5, 10)] private string matchPattern = @"\b{0}\b"; +``` + +### Инициализация: +```csharp +// Добавление модуля на сцену +var censorModule = gameObject.AddComponent(); + +// Настройка списков запрещенных слов +public TextAsset[] wordsLists; +wordsLists = new TextAsset[] { forbiddenWordsAsset }; +``` + +### Формат файла словаря: +Словарь представляет собой текстовый файл с запрещенными словами, разделенными запятыми: +``` +bad,words,list,here,separated,by,commas +``` + +## Использование в коде + +### Проверка текста: +```csharp +// Получение экземпляра модуля +var censorModule = Mst.Server.Modules.GetModule(); + +// Проверка текста на наличие запрещенных слов +bool hasBadWord = censorModule.HasCensoredWord("Text to check"); + +if (hasBadWord) +{ + // Текст содержит запрещенные слова + Debug.Log("Text contains censored words"); +} +else +{ + // Текст безопасен + Debug.Log("Text is clean"); +} +``` + +### Интеграция с чатом: +```csharp +// В обработчике сообщений чата +void HandleChatMessage(string message, IPeer sender) +{ + var censorModule = Mst.Server.Modules.GetModule(); + + if (censorModule.HasCensoredWord(message)) + { + // Отклонить сообщение + sender.SendMessage(MstOpCodes.MessageRejected, "Message contains forbidden words"); + return; + } + + // Отправить сообщение всем пользователям + BroadcastMessage(message); +} +``` + +### Проверка имени пользователя: +```csharp +// При регистрации в AuthModule +protected override bool IsUsernameValid(string username) +{ + if (!base.IsUsernameValid(username)) + return false; + + var censorModule = Mst.Server.Modules.GetModule(); + + // Проверка имени на запрещенные слова + if (censorModule.HasCensoredWord(username)) + return false; + + return true; +} +``` + +## Настройка паттерна проверки + +Параметр `matchPattern` определяет, как именно будут проверяться запрещенные слова: + +```csharp +// По умолчанию: Целые слова (используя границы слов) +matchPattern = @"\b{0}\b"; + +// Более строгая проверка: включая частичные совпадения +matchPattern = @"{0}"; + +// С разделителями: проверяет только слова, разделенные пробелами +matchPattern = @"(\s|^){0}(\s|$)"; +``` + +## Расширение модуля + +Вы можете расширить базовую функциональность, создав наследника CensorModule: + +```csharp +public class EnhancedCensorModule : CensorModule +{ + [SerializeField] private bool useRegexPatterns = false; + [SerializeField] private TextAsset regexPatterns; + + private List patterns = new List(); + + protected override void ParseTextFiles() + { + base.ParseTextFiles(); + + // Загрузка дополнительных регулярных выражений + if (useRegexPatterns && regexPatterns != null) + { + var patternLines = regexPatterns.text.Split('\n'); + foreach (var pattern in patternLines) + { + if (!string.IsNullOrEmpty(pattern)) + { + patterns.Add(new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled)); + } + } + } + } + + public override bool HasCensoredWord(string text) + { + // Проверка базовым методом + if (base.HasCensoredWord(text)) + return true; + + // Проверка с помощью регулярных выражений + foreach (var regex in patterns) + { + if (regex.IsMatch(text)) + return true; + } + + return false; + } + + // Дополнительный метод для получения замаскированного текста + public string GetCensoredText(string text, char maskChar = '*') + { + string result = text; + + // Замена запрещенных слов маской + foreach (var word in censoredWords) + { + string pattern = string.Format(matchPattern, Regex.Escape(word)); + string replacement = new string(maskChar, word.Length); + result = Regex.Replace(result, pattern, replacement, RegexOptions.IgnoreCase); + } + + return result; + } +} +``` + +## Лучшие практики + +1. **Регулярно обновляйте словари** запрещенных слов +2. **Используйте границы слов** (`\b{0}\b`) для избежания ложных срабатываний +3. **Интегрируйте с системой чата** для автоматической фильтрации сообщений +4. **Добавляйте преобразование текста** перед проверкой для обхода простых попыток обмана (напр. "b@d w0rd") +5. **Включайте многоязычную поддержку** для интернациональных проектов +6. **Учитывайте контекст** при фильтрации - некоторые слова могут быть нормальными в одном контексте, но нежелательными в другом +7. **Используйте локализованные словари** для разных регионов + +## Примеры интеграции + +### Система автоматического предупреждения: +```csharp +public class ChatFilterSystem : MonoBehaviour +{ + [SerializeField] private int maxViolations = 3; + private Dictionary violations = new Dictionary(); + + public void CheckMessage(string username, string message) + { + var censorModule = Mst.Server.Modules.GetModule(); + + if (censorModule.HasCensoredWord(message)) + { + if (!violations.ContainsKey(username)) + violations[username] = 0; + + violations[username]++; + + if (violations[username] >= maxViolations) + { + // Временный бан пользователя + BanUser(username, TimeSpan.FromMinutes(10)); + } + } + } +} +``` + +### Интеграция с пользовательским контентом: +```csharp +// Проверка пользовательских названий +public bool ValidateUserContent(string text) +{ + var censorModule = Mst.Server.Modules.GetModule(); + return !censorModule.HasCensoredWord(text); +} + +// Использование при создании предметов, кланов и т.д. +public bool CreateClan(string clanName, string clanTag) +{ + if (!ValidateUserContent(clanName) || !ValidateUserContent(clanTag)) + { + return false; + } + + // Создание клана + // ... + + return true; +} +``` \ No newline at end of file diff --git a/Docs/Modules/Matchmaker.md b/Docs/Modules/Matchmaker.md new file mode 100644 index 0000000..19882cf --- /dev/null +++ b/Docs/Modules/Matchmaker.md @@ -0,0 +1,150 @@ +# Master Server Toolkit - Matchmaker + +## Описание +Модуль для поиска игр, комнат и лобби по критериям, а также информации о доступных регионах серверов. + +## Основные компоненты + +### MatchmakerModule (Сервер) +```csharp +// Зависимости +AddOptionalDependency(); +AddOptionalDependency(); +AddOptionalDependency(); + +// Основные методы +public void AddProvider(IGamesProvider provider) // Добавить провайдер игр +``` + +### MstMatchmakerClient (Клиент) +```csharp +// Поиск игр (без фильтров) +matchmaker.FindGames((games) => { + Debug.Log($"Found {games.Count} games"); +}); + +// Поиск игр с фильтрами +var filters = new MstProperties(); +filters.Set("minPlayers", 2); +matchmaker.FindGames(filters, (games) => { }); + +// Получение регионов +matchmaker.GetRegions((regions) => { + // regions[0].Name, regions[0].Ip, regions[0].PingTime +}); +``` + +## Ключевые структуры данных + +### GameInfoPacket +```csharp +public class GameInfoPacket +{ + public int Id { get; set; } // ID игры + public string Address { get; set; } // Адрес подключения + public GameInfoType Type { get; set; } // Тип (Room, Lobby, Custom) + public string Name { get; set; } // Название + public string Region { get; set; } // Регион + public bool IsPasswordProtected { get; set; } // Требуется пароль + public int MaxPlayers { get; set; } // Максимум игроков + public int OnlinePlayers { get; set; } // Текущее число игроков + public List OnlinePlayersList { get; } // Список игроков + public MstProperties Properties { get; set; } // Доп. свойства +} +``` + +### RegionInfo +```csharp +public class RegionInfo +{ + public string Name { get; set; } // Название региона + public string Ip { get; set; } // IP-адрес + public int PingTime { get; set; } // Пинг (мс) +} +``` + +## Пользовательские провайдеры игр + +### Интерфейс IGamesProvider +```csharp +public interface IGamesProvider +{ + IEnumerable GetPublicGames(IPeer peer, MstProperties filters); +} +``` + +### Пример минимальной реализации +```csharp +public class CustomGamesProvider : MonoBehaviour, IGamesProvider +{ + private List games = new List(); + + public IEnumerable GetPublicGames(IPeer peer, MstProperties filters) + { + // Фильтрация по региону + if (filters.Has("region")) + { + return games.Where(g => g.Region == filters.AsString("region")); + } + + return games; + } + + // API для добавления/удаления игр + public void AddGame(GameInfoPacket game) => games.Add(game); + public void RemoveGame(int gameId) => games.RemoveAll(g => g.Id == gameId); +} + +// Регистрация +matchmaker.AddProvider(gameObject.AddComponent()); +``` + +## Пример использования + +### Поиск игр с несколькими фильтрами +```csharp +var filters = new MstProperties(); +filters.Set("region", "eu-west"); // Только европейский регион +filters.Set("minPlayers", 1); // С минимум 1 игроком +filters.Set("gameMode", "deathmatch"); // Режим "deathmatch" + +matchmaker.FindGames(filters, (games) => +{ + // Найденные игры + foreach (var game in games) + { + Debug.Log($"{game.Name} - {game.OnlinePlayers}/{game.MaxPlayers}"); + + // Подключение к игре + Mst.Client.Rooms.JoinRoom(game.Id, "", (successful, roomAccess) => + { + if (successful) + ConnectToGameServer(roomAccess); + }); + } +}); +``` + +### Выбор региона с лучшим пингом +```csharp +matchmaker.GetRegions((regions) => +{ + if (regions.Count > 0) + { + // Сортировка по пингу + var bestRegion = regions.OrderBy(r => r.PingTime).First(); + PlayerPrefs.SetString("SelectedRegion", bestRegion.Name); + + Debug.Log($"Using region: {bestRegion.Name} ({bestRegion.PingTime}ms)"); + } +}); +``` + +## Лучшие практики + +1. **Используйте осмысленные имена свойств** для фильтрации +2. **Добавляйте категории** в Properties для лучшей организации фильтров +3. **Обрабатывайте случай отсутствия игр** в клиентском коде +4. **Сортируйте результаты** для улучшения пользовательского опыта +5. **Кэшируйте список регионов** и результаты поиска при необходимости +6. **Обновляйте список регионов** при запуске игры diff --git a/Docs/Modules/Notification.md b/Docs/Modules/Notification.md new file mode 100644 index 0000000..0dbeed0 --- /dev/null +++ b/Docs/Modules/Notification.md @@ -0,0 +1,358 @@ +# Master Server Toolkit - Notification + +## Описание +Модуль уведомлений для отправки сообщений от сервера клиентам, с возможностью уведомления отдельных пользователей, групп в комнатах или всех подключенных пользователей. + +## NotificationModule + +Основной серверный класс модуля уведомлений. + +### Настройка: +```csharp +[Header("General Settings")] +[SerializeField, Tooltip("If true, notification module will subscribe to auth module, and automatically setup recipients when they log in")] +protected bool useAuthModule = true; +[SerializeField, Tooltip("If true, notification module will subscribe to rooms module to be able to send notifications to room players")] +protected bool useRoomsModule = true; +[SerializeField, Tooltip("Permission level to be able to send notifications")] +protected int notifyPermissionLevel = 1; +[SerializeField] +private int maxPromisedMessages = 10; +``` + +### Зависимости: +- AuthModule (опционально) - для автоматического добавления пользователей в список получателей при входе в систему +- RoomsModule (опционально) - для отправки уведомлений игрокам в комнатах + +## Основные методы + +### Отправка уведомлений: +```csharp +// Получить модуль +var notificationModule = Mst.Server.Modules.GetModule(); + +// Отправить всем пользователям +notificationModule.NoticeToAll("Сервер будет перезагружен через 5 минут"); + +// Отправить всем и добавить в обещанные сообщения (новые пользователи также получат это уведомление при входе) +notificationModule.NoticeToAll("Добро пожаловать в наш мир!", true); + +// Отправить конкретному пользователю (по ID пира) +notificationModule.NoticeToRecipient(peerId, "Вы получили новое достижение"); + +// Отправить группе пользователей +List peerIds = new List { 123, 456, 789 }; +notificationModule.NoticeToRecipients(peerIds, "Новое групповое задание доступно"); + +// Отправить всем пользователям в комнате +notificationModule.NoticeToRoom(roomId, new List(), "Комната будет закрыта через 2 минуты"); + +// Отправить всем в комнате, кроме указанных пользователей +List ignorePeerIds = new List { 123 }; +notificationModule.NoticeToRoom(roomId, ignorePeerIds, "Игрок присоединился к комнате"); +``` + +### Управление получателями: +```csharp +// Проверить наличие получателя +bool hasUser = notificationModule.HasRecipient(userId); + +// Получить получателя +NotificationRecipient recipient = notificationModule.GetRecipient(userId); + +// Получить получателя безопасно +if (notificationModule.TryGetRecipient(userId, out NotificationRecipient recipient)) +{ + // Отправить уведомление + recipient.Notify("Персональное сообщение"); +} + +// Добавить получателя вручную +NotificationRecipient newRecipient = notificationModule.AddRecipient(userExtension); + +// Удалить получателя +notificationModule.RemoveRecipient(userId); +``` + +## Клиентская часть - MstNotificationClient + +```csharp +// Получить клиент +var notificationClient = Mst.Client.Notifications; + +// Подписаться на уведомления +notificationClient.Subscribe((isSuccess, error) => +{ + if (isSuccess) + { + Debug.Log("Успешно подписались на уведомления"); + } + else + { + Debug.LogError($"Ошибка подписки на уведомления: {error}"); + } +}); + +// Подписаться на событие получения уведомления +notificationClient.OnNotificationReceivedEvent += OnNotificationReceived; + +// Обработчик уведомлений +private void OnNotificationReceived(string message) +{ + // Показать уведомление пользователю + uiNotificationManager.ShowNotification(message); +} + +// Отписаться от уведомлений +notificationClient.Unsubscribe((isSuccess, error) => +{ + if (isSuccess) + { + Debug.Log("Успешно отписались от уведомлений"); + } + else + { + Debug.LogError($"Ошибка отписки от уведомлений: {error}"); + } +}); + +// Отписаться от события +notificationClient.OnNotificationReceivedEvent -= OnNotificationReceived; +``` + +## Пакеты и структуры + +### NotificationPacket: +```csharp +public class NotificationPacket : SerializablePacket +{ + public int RoomId { get; set; } = -1; // ID комнаты (если отправляется в комнату) + public string Message { get; set; } = string.Empty; // Текст уведомления + public List Recipients { get; set; } = new List(); // Список получателей + public List IgnoreRecipients { get; set; } = new List(); // Исключения +} +``` + +### NotificationRecipient: +```csharp +public class NotificationRecipient +{ + public string UserId { get; set; } + public IPeer Peer { get; set; } + + // Отправка уведомления конкретному получателю + public void Notify(string message) + { + Peer.SendMessage(MstOpCodes.Notification, message); + } +} +``` + +## Серверная реализация - Пользовательский модуль уведомлений + +Пример создания расширенного модуля уведомлений: + +```csharp +public class GameNotificationModule : NotificationModule +{ + // Форматированные уведомления + public void SendSystemNotification(string message) + { + string formattedMessage = $"[СИСТЕМА]: {message}"; + NoticeToAll(formattedMessage); + } + + public void SendAdminNotification(string message, string adminName) + { + string formattedMessage = $"[АДМИН - {adminName}]: {message}"; + NoticeToAll(formattedMessage, true); // Сохраняем как обещанное сообщение + } + + public void SendAchievementNotification(int peerId, string achievementTitle) + { + string formattedMessage = $"[ДОСТИЖЕНИЕ]: Вы получили '{achievementTitle}'!"; + NoticeToRecipient(peerId, formattedMessage); + } + + // Уведомления с json-данными + public void SendJSONNotification(int peerId, string type, object data) + { + var notification = new JSONNotification + { + Type = type, + Data = JsonUtility.ToJson(data) + }; + + string jsonMessage = JsonUtility.ToJson(notification); + NoticeToRecipient(peerId, jsonMessage); + } + + // Класс для json-уведомлений + [Serializable] + private class JSONNotification + { + public string Type; + public string Data; + } +} +``` + +## Интеграция с UI + +Пример обработки уведомлений в пользовательском интерфейсе: + +```csharp +public class NotificationUIManager : MonoBehaviour +{ + [SerializeField] private GameObject notificationPrefab; + [SerializeField] private Transform notificationsContainer; + [SerializeField] private float displayTime = 5f; + + private void Start() + { + // Получить клиент уведомлений + var notificationClient = Mst.Client.Notifications; + + // Подписаться на события + notificationClient.OnNotificationReceivedEvent += ShowNotification; + + // Подписаться на получение уведомлений от сервера + notificationClient.Subscribe((isSuccess, error) => + { + if (!isSuccess) + { + Debug.LogError($"Failed to subscribe to notifications: {error}"); + } + }); + } + + // Обработка простого текстового уведомления + public void ShowNotification(string message) + { + // Проверка на JSON + if (message.StartsWith("{") && message.EndsWith("}")) + { + try + { + // Пытаемся распарсить как JSON + JsonNotification notification = JsonUtility.FromJson(message); + + // Отобразить в зависимости от типа + switch (notification.Type) + { + case "achievement": + ShowAchievementNotification(notification.Data); + break; + case "system": + ShowSystemNotification(notification.Data); + break; + default: + CreateTextNotification(message); + break; + } + } + catch + { + // Если не JSON, отображаем как обычный текст + CreateTextNotification(message); + } + } + else + { + // Обычное текстовое сообщение + CreateTextNotification(message); + } + } + + // Создание уведомления в UI + private void CreateTextNotification(string text) + { + GameObject notification = Instantiate(notificationPrefab, notificationsContainer); + notification.GetComponentInChildren().text = text; + + // Автоматически уничтожить через время + Destroy(notification, displayTime); + } + + // Кастомные обработчики для специальных уведомлений + private void ShowAchievementNotification(string data) + { + // Кастомная логика для отображения уведомления о достижении + } + + private void ShowSystemNotification(string data) + { + // Кастомная логика для отображения системного уведомления + } + + [Serializable] + private class JsonNotification + { + public string Type; + public string Data; + } + + private void OnDestroy() + { + // Отписаться + var notificationClient = Mst.Client.Notifications; + if (notificationClient != null) + { + notificationClient.OnNotificationReceivedEvent -= ShowNotification; + } + } +} +``` + +## Пример использования из комнаты + +```csharp +public class RoomManager : MonoBehaviour, IRoomManager +{ + // Отправить уведомление всем игрокам в комнате, когда один из игроков готов + public void OnPlayerReadyStatusChanged(int peerId, bool isReady) + { + var player = Mst.Server.Rooms.GetPlayer(currentRoomId, peerId); + + if (player != null && isReady) + { + var username = player.GetExtension()?.Username ?? "Unknown"; + + // Создать пакет уведомления + var packet = new NotificationPacket + { + RoomId = currentRoomId, + Message = $"Игрок {username} готов к игре!", + IgnoreRecipients = new List { peerId } // Не отправлять самому игроку + }; + + // Отправить уведомление через сервер + Mst.Server.Connection.SendMessage(MstOpCodes.Notification, packet); + } + } +} +``` + +## Обещанные сообщения + +Особенность модуля уведомлений - возможность сохранять "обещанные сообщения", которые будут доставлены новым пользователям при входе в систему. Это полезно для системных объявлений, новостей, которые должны получить все игроки. + +```csharp +// Отправить всем и сохранить как обещанное сообщение +notificationModule.NoticeToAll("Новое обновление игры! Версия 1.2.0 доступна!", true); +``` + +Настройка количества сохраняемых обещанных сообщений: +```csharp +[SerializeField] private int maxPromisedMessages = 10; +``` + +## Лучшие практики + +1. **Разделяйте типы уведомлений** - используйте различные форматирование или префиксы для различных типов уведомлений +2. **Используйте JSON для сложных уведомлений** - когда требуется передать структурированные данные +3. **Управляйте уровнями доступа** - настройте `notifyPermissionLevel` для ограничения возможности отправки уведомлений +4. **Интегрируйте с другими модулями** - используйте события из других модулей для отправки уведомлений +5. **Создавайте фильтры уведомлений** на клиенте - позволяйте пользователям настраивать, какие уведомления им показывать +6. **Не злоупотребляйте обещанными сообщениями** - сохраняйте как обещанные только действительно важные системные сообщения +7. **Используйте локализацию** для интернациональных проектов diff --git a/Docs/Modules/Ping.md b/Docs/Modules/Ping.md new file mode 100644 index 0000000..3d6f5d2 --- /dev/null +++ b/Docs/Modules/Ping.md @@ -0,0 +1,385 @@ +# Master Server Toolkit - Ping + +## Описание +Модуль Ping для проверки соединения между клиентом и сервером, измерения задержки и тестирования доступности сервера. + +## PingModule + +Основной класс модуля Ping. + +### Настройка: +```csharp +[SerializeField, TextArea(3, 5)] +private string pongMessage = "Hello, Pong!"; +``` + +### Свойства: +```csharp +public string PongMessage { get; set; } +``` + +## Использование на сервере + +### Инициализация: +```csharp +// Модуль автоматически регистрирует обработчик сообщений Ping +public override void Initialize(IServer server) +{ + server.RegisterMessageHandler(MstOpCodes.Ping, OnPingRequestListener); +} + +// Обработчик пинг-запросов +private Task OnPingRequestListener(IIncomingMessage message) +{ + message.Respond(pongMessage, ResponseStatus.Success); + return Task.CompletedTask; +} +``` + +### Настройка сообщения Pong: +```csharp +// Получение модуля +var pingModule = Mst.Server.Modules.GetModule(); + +// Установка сообщения +pingModule.PongMessage = "Game server is running!"; +``` + +## Использование на клиенте + +### Отправка Ping: +```csharp +// Отправка пинг-запроса +Mst.Client.Connection.SendMessage(MstOpCodes.Ping, (status, response) => +{ + if (status == ResponseStatus.Success) + { + // Получаем сообщение от сервера + string pongMessage = response.AsString(); + + // Вычисляем время отклика (RTT - Round Trip Time) + float rtt = response.TimeElapsedSinceRequest; + + Debug.Log($"Ping successful. RTT: {rtt}ms. Message: {pongMessage}"); + } + else + { + Debug.LogError("Ping failed. Server might be unavailable."); + } +}); +``` + +### Реализация периодического пинга: +```csharp +public class PingTester : MonoBehaviour +{ + [SerializeField] private float pingInterval = 1f; + [SerializeField] private int maxFailedPings = 3; + + private float nextPingTime = 0f; + private int failedPingsCount = 0; + + private void Update() + { + if (Time.time >= nextPingTime && Mst.Client.Connection.IsConnected) + { + nextPingTime = Time.time + pingInterval; + SendPing(); + } + } + + private void SendPing() + { + Mst.Client.Connection.SendMessage(MstOpCodes.Ping, (status, response) => + { + if (status == ResponseStatus.Success) + { + // Сброс счётчика неудачных попыток + failedPingsCount = 0; + + // Обновление отображения пинга в UI + float rtt = response.TimeElapsedSinceRequest; + UpdatePingDisplay(rtt); + } + else + { + failedPingsCount++; + + if (failedPingsCount >= maxFailedPings) + { + // Обработка потери соединения + HandleConnectionLost(); + } + } + }); + } + + private void UpdatePingDisplay(float rtt) + { + // Пример обновления UI + // pingText.text = $"Ping: {Mathf.RoundToInt(rtt)}ms"; + } + + private void HandleConnectionLost() + { + Debug.LogWarning("Connection to server lost!"); + // Обработка потери соединения с сервером + } +} +``` + +## Расширенная реализация Ping + +### Обновление модуля для передачи дополнительной информации: + +```csharp +public class EnhancedPingModule : PingModule +{ + [SerializeField] private int serverCurrentLoad = 0; + [SerializeField] private int maxServerLoad = 100; + + private Task OnPingRequestListener(IIncomingMessage message) + { + // Создаем расширенный ответ с дополнительной информацией + var pingResponse = new PingResponseInfo + { + Message = PongMessage, + ServerTime = System.DateTime.UtcNow.Ticks, + ServerLoad = serverCurrentLoad, + MaxServerLoad = maxServerLoad, + OnlinePlayers = Mst.Server.ConnectionsCount + }; + + // Преобразуем в JSON + string jsonResponse = JsonUtility.ToJson(pingResponse); + + // Отправляем ответ + message.Respond(jsonResponse, ResponseStatus.Success); + return Task.CompletedTask; + } + + // Обновление информации о нагрузке + public void UpdateServerLoad(int currentLoad) + { + serverCurrentLoad = currentLoad; + } + + [System.Serializable] + private class PingResponseInfo + { + public string Message; + public long ServerTime; + public int ServerLoad; + public int MaxServerLoad; + public int OnlinePlayers; + } +} +``` + +### Клиентская обработка расширенного ответа: + +```csharp +// Отправка запроса +Mst.Client.Connection.SendMessage(MstOpCodes.Ping, (status, response) => +{ + if (status == ResponseStatus.Success) + { + string jsonResponse = response.AsString(); + + try + { + // Парсинг JSON-ответа + PingResponseInfo pingInfo = JsonUtility.FromJson(jsonResponse); + + // Использование информации + Debug.Log($"Server message: {pingInfo.Message}"); + Debug.Log($"Server time: {new System.DateTime(pingInfo.ServerTime)}"); + Debug.Log($"Server load: {pingInfo.ServerLoad}/{pingInfo.MaxServerLoad}"); + Debug.Log($"Online players: {pingInfo.OnlinePlayers}"); + + // Расчёт разницы во времени между клиентом и сервером + long clientTime = System.DateTime.UtcNow.Ticks; + TimeSpan timeDifference = new System.DateTime(pingInfo.ServerTime) - new System.DateTime(clientTime); + Debug.Log($"Time difference: {timeDifference.TotalMilliseconds}ms"); + } + catch + { + // Если ответ в старом формате, обрабатываем как строку + Debug.Log($"Server message: {jsonResponse}"); + } + } +}); +``` + +## Интеграция с другими системами + +### Мониторинг соединения: +```csharp +public class ConnectionMonitor : MonoBehaviour +{ + [SerializeField] private float pingInterval = 5f; + [SerializeField] private float maxPingTime = 500f; // ms + + private List pingsHistory = new List(); + private int historySize = 10; + + private void Start() + { + StartCoroutine(PingRoutine()); + } + + private IEnumerator PingRoutine() + { + while (true) + { + if (Mst.Client.Connection.IsConnected) + { + SendPing(); + } + + yield return new WaitForSeconds(pingInterval); + } + } + + private void SendPing() + { + Mst.Client.Connection.SendMessage(MstOpCodes.Ping, (status, response) => + { + if (status == ResponseStatus.Success) + { + float rtt = response.TimeElapsedSinceRequest; + + // Добавляем в историю + pingsHistory.Add(rtt); + + // Поддерживаем ограниченный размер истории + if (pingsHistory.Count > historySize) + { + pingsHistory.RemoveAt(0); + } + + // Проверка на высокий пинг + if (rtt > maxPingTime) + { + OnHighPingDetected(rtt); + } + + // Уведомление о среднем пинге + float averagePing = pingsHistory.Average(); + OnPingUpdated(rtt, averagePing); + } + else + { + OnPingFailed(); + } + }); + } + + // События для интеграции с игровыми системами + private void OnPingUpdated(float currentPing, float averagePing) + { + // Обновление UI или состояния игры + } + + private void OnHighPingDetected(float ping) + { + // Предупреждение игрока о плохом соединении + } + + private void OnPingFailed() + { + // Обработка неудачной попытки пинга + } +} +``` + +### Автоматическое переподключение: +```csharp +public class AutoReconnector : MonoBehaviour +{ + [SerializeField] private float checkInterval = 5f; + [SerializeField] private int maxFailedPings = 3; + [SerializeField] private int maxReconnectAttempts = 5; + + private int failedPingsCount = 0; + private int reconnectAttempts = 0; + + private void Start() + { + StartCoroutine(ConnectionCheckRoutine()); + } + + private IEnumerator ConnectionCheckRoutine() + { + while (true) + { + if (Mst.Client.Connection.IsConnected) + { + // Проверка соединения через пинг + CheckConnection(); + } + else if (reconnectAttempts < maxReconnectAttempts) + { + // Попытка переподключения + AttemptReconnect(); + } + + yield return new WaitForSeconds(checkInterval); + } + } + + private void CheckConnection() + { + Mst.Client.Connection.SendMessage(MstOpCodes.Ping, (status, response) => + { + if (status == ResponseStatus.Success) + { + // Соединение работает, сбрасываем счётчики + failedPingsCount = 0; + reconnectAttempts = 0; + } + else + { + failedPingsCount++; + + if (failedPingsCount >= maxFailedPings) + { + Debug.LogWarning("Connection lost. Attempting to reconnect..."); + AttemptReconnect(); + } + } + }); + } + + private void AttemptReconnect() + { + reconnectAttempts++; + + Debug.Log($"Reconnect attempt {reconnectAttempts}/{maxReconnectAttempts}"); + + // Попытка переподключения + Mst.Client.Connection.Connect(Mst.Client.Connection.ConnectionIp, Mst.Client.Connection.ConnectionPort, (isSuccessful, error) => + { + if (isSuccessful) + { + Debug.Log("Successfully reconnected"); + failedPingsCount = 0; + } + else + { + Debug.LogError($"Failed to reconnect: {error}"); + } + }); + } +} +``` + +## Лучшие практики + +1. **Используйте Ping для мониторинга состояния соединения** - периодические проверки помогают обнаружить проблемы раньше +2. **Реализуйте автоматическое переподключение** при обнаружении потери соединения +3. **Храните историю Ping** для анализа стабильности соединения +4. **Отображайте пинг в UI** для информирования игроков о качестве соединения +5. **Расширяйте базовый функционал** для передачи дополнительной информации о сервере +6. **Установите разумные интервалы пинга** - слишком частые запросы могут создать дополнительную нагрузку +7. **Адаптируйте геймплей** под текущее состояние соединения, например, уменьшайте количество отправляемых обновлений при высоком пинге diff --git a/Docs/Modules/QuestsModule.md b/Docs/Modules/QuestsModule.md new file mode 100644 index 0000000..b824727 --- /dev/null +++ b/Docs/Modules/QuestsModule.md @@ -0,0 +1,192 @@ +# Master Server Toolkit - Quests + +## Описание +Модуль для создания, отслеживания и выполнения игровых квестов с возможностью настройки цепочек заданий, временных ограничений и наград. + +## Основные структуры + +### QuestData (ScriptableObject) +```csharp +[CreateAssetMenu(menuName = "Master Server Toolkit/Quests/New Quest")] +public class QuestData : ScriptableObject +{ + // Основная информация + public string Key => key; // Уникальный ключ квеста + public string Title => title; // Название + public string Description => description; // Описание + public int RequiredProgress => requiredProgress; // Кол-во для завершения + public Sprite Icon => icon; // Иконка квеста + + // Сообщения для разных статусов + public string StartMessage => startMessage; // Сообщение при взятии квеста + public string ActiveMessage => activeMessage; // Сообщение во время выполнения + public string CompletedMessage => completedMessage; // Сообщение при завершении + public string CancelMessage => cancelMessage; // Сообщение при отмене + public string ExpireMessage => expireMessage; // Сообщение при истечении срока + + // Настройки + public bool IsOneTime => isOneTime; // Одноразовый квест + public int TimeToComplete => timeToComplete; // Время на выполнение (мин) + + // Связи с другими квестами + public QuestData ParentQuest => parentQuest; // Родительский квест + public QuestData[] ChildrenQuests => childrenQuests; // Дочерние квесты +} +``` + +### Статусы квестов +```csharp +public enum QuestStatus +{ + Inactive, // Квест неактивен + Active, // Квест активен + Completed, // Квест завершен + Canceled, // Квест отменен + Expired // Время квеста истекло +} +``` + +### Интерфейс IQuestInfo +```csharp +public interface IQuestInfo +{ + string Id { get; set; } // Уникальный ID + string Key { get; set; } // Ключ квеста + string UserId { get; set; } // ID пользователя + int Progress { get; set; } // Текущий прогресс + int Required { get; set; } // Требуемый прогресс + DateTime StartTime { get; set; } // Время начала + DateTime ExpireTime { get; set; } // Время истечения + DateTime CompleteTime { get; set; } // Время завершения + QuestStatus Status { get; set; } // Статус + string ParentQuestKey { get; set; } // Ключ родительского квеста + string ChildrenQuestsKeys { get; set; } // Ключи дочерних квестов + bool TryToComplete(int progress); // Метод для завершения квеста +} +``` + +## QuestsModule (Сервер) + +```csharp +// Зависимости +AddDependency(); +AddDependency(); + +// Настройки +[Header("Permission"), SerializeField] +protected bool clientCanUpdateProgress = false; // Может ли клиент обновлять прогресс + +[Header("Settings"), SerializeField] +protected QuestsDatabase[] questsDatabases; // Базы данных квестов +``` + +### Основные операции сервера +1. Получение списка квестов +2. Начало квеста +3. Обновление прогресса квеста +4. Отмена квеста +5. Проверка истечения срока квестов + +### Интеграция с профилями +```csharp +private void ProfilesModule_OnProfileLoaded(ObservableServerProfile profile) +{ + if (profile.TryGet(ProfilePropertyOpCodes.quests, out ObservableQuests property)) + { + // Инициализация квестов пользователя + } +} +``` + +## QuestsModuleClient (Клиент) + +```csharp +// Получение списка доступных квестов +questsClient.GetQuests((quests) => { + foreach (var quest in quests) + { + Debug.Log($"Quest: {quest.Title}, Status: {quest.Status}"); + } +}); + +// Начать квест +questsClient.StartQuest("quest_key", (isStarted, quest) => { + if (isStarted) + Debug.Log($"Started quest: {quest.Title}"); +}); + +// Обновить прогресс квеста +questsClient.UpdateQuestProgress("quest_key", 5, (isUpdated) => { + if (isUpdated) + Debug.Log("Quest progress updated"); +}); + +// Отменить квест +questsClient.CancelQuest("quest_key", (isCanceled) => { + if (isCanceled) + Debug.Log("Quest canceled"); +}); +``` + +## Пример создания квестов + +### Создание базы данных квестов +```csharp +// В редакторе Unity +[CreateAssetMenu(menuName = "Master Server Toolkit/Quests/QuestsDatabase")] +public class QuestsDatabase : ScriptableObject +{ + [SerializeField] + private List quests = new List(); + + public IReadOnlyCollection Quests => quests; +} + +// Создание базы данных +var database = ScriptableObject.CreateInstance(); +``` + +### Создание квеста +```csharp +// В редакторе Unity +var quest = ScriptableObject.CreateInstance(); +quest.name = "CollectResources"; +// Настройка квеста через инспектор + +// Программно +var questData = new QuestData +{ + Key = "collect_wood", + Title = "Collect Wood", + Description = "Collect 10 pieces of wood", + RequiredProgress = 10, + TimeToComplete = 60 // 60 минут на выполнение +}; +``` + +### Цепочки квестов +```csharp +// Создание зависимостей между квестами +var mainQuest = ScriptableObject.CreateInstance(); +mainQuest.name = "MainQuest"; + +var subQuest1 = ScriptableObject.CreateInstance(); +subQuest1.name = "SubQuest1"; +// Установка mainQuest как родительского для subQuest1 + +var subQuest2 = ScriptableObject.CreateInstance(); +subQuest2.name = "SubQuest2"; +// Установка mainQuest как родительского для subQuest2 + +// В родительском квесте указываем дочерние +// mainQuest.ChildrenQuests = new QuestData[] { subQuest1, subQuest2 }; +``` + +## Лучшие практики + +1. **Создавайте осмысленные ключи квестов** для лучшей идентификации +2. **Группируйте квесты** по тематическим базам данных +3. **Определяйте реалистичные сроки** выполнения квестов +4. **Используйте цепочки квестов** для создания сюжетных линий +5. **Обеспечьте отказоустойчивость** при обработке квестов +6. **Предоставляйте понятные сообщения** для каждого статуса квеста diff --git a/Docs/Modules/Spawner.md b/Docs/Modules/Spawner.md new file mode 100644 index 0000000..4907fa5 --- /dev/null +++ b/Docs/Modules/Spawner.md @@ -0,0 +1,245 @@ +# Master Server Toolkit - Spawner + +## Описание +Модуль для управления процессами запуска игровых серверов в различных регионах с поддержкой балансировки нагрузки и очередей. + +## Основные компоненты + +### SpawnersModule +```csharp +// Настройки +[SerializeField] protected int createSpawnerPermissionLevel = 0; // Минимальный уровень прав для регистрации спаунера +[SerializeField] protected float queueUpdateFrequency = 0.1f; // Частота обновления очередей +[SerializeField] protected bool enableClientSpawnRequests = true; // Разрешить запросы на спаун от клиентов + +// События +public event Action OnSpawnerRegisteredEvent; // При регистрации спаунера +public event Action OnSpawnerDestroyedEvent; // При удалении спаунера +public event SpawnedProcessRegistrationHandler OnSpawnedProcessRegisteredEvent; // При регистрации процесса +``` + +### RegisteredSpawner +```csharp +// Основные свойства +public int SpawnerId { get; } // Уникальный ID спаунера +public IPeer Peer { get; } // Подключение к спаунеру +public SpawnerOptions Options { get; } // Настройки спаунера +public int ProcessesRunning { get; } // Количество запущенных процессов +public int QueuedTasks { get; } // Количество задач в очереди + +// Методы +public bool CanSpawnAnotherProcess(); // Может ли запустить еще один процесс +public int CalculateFreeSlotsCount(); // Расчет количества свободных слотов +``` + +### SpawnerOptions +```csharp +// Настройки спаунера +public string MachineIp { get; set; } // IP машины спаунера +public int MaxProcesses { get; set; } = 5; // Максимальное количество процессов +public string Region { get; set; } = "Global"; // Регион спаунера +public Dictionary CustomOptions { get; set; } // Дополнительные настройки +``` + +### SpawnTask +```csharp +// Свойства +public int Id { get; } // ID задачи +public RegisteredSpawner Spawner { get; } // Спаунер, выполняющий задачу +public MstProperties Options { get; } // Настройки запуска +public SpawnStatus Status { get; } // Текущий статус +public string UniqueCode { get; } // Уникальный код для безопасности +public IPeer Requester { get; set; } // Запросивший клиент +public IPeer RegisteredPeer { get; private set; } // Зарегистрированный процесс + +// События +public event Action OnStatusChangedEvent; // При изменении статуса +``` + +## Статусы процесса SpawnStatus +```csharp +public enum SpawnStatus +{ + None, // Начальный статус + Queued, // В очереди + ProcessStarted, // Процесс запущен + ProcessRegistered, // Процесс зарегистрирован + Finalized, // Финализирован + Aborted, // Прерван + Killed // Убит +} +``` + +## Процесс работы + +### Регистрация спаунера +```csharp +// Клиент +var options = new SpawnerOptions +{ + MachineIp = "192.168.1.10", + MaxProcesses = 10, + Region = "eu-west", + CustomOptions = new Dictionary { + { "mapsList", "map1,map2,map3" } + } +}; + +Mst.Client.Spawners.RegisterSpawner(options, (successful, spawnerId) => +{ + if (successful) + Debug.Log($"Spawner registered with ID: {spawnerId}"); +}); + +// Сервер +RegisteredSpawner spawner = CreateSpawner(peer, options); +``` + +### Запрос на запуск игрового сервера +```csharp +// Клиент +var spawnOptions = new MstProperties(); +spawnOptions.Set(Mst.Args.Names.RoomName, "MyGame"); +spawnOptions.Set(Mst.Args.Names.RoomMaxPlayers, 10); +spawnOptions.Set(Mst.Args.Names.RoomRegion, "eu-west"); + +Mst.Client.Spawners.RequestSpawn(spawnOptions, (successful, spawnId) => +{ + if (successful) + { + Debug.Log($"Spawn request created with ID: {spawnId}"); + + // Подписка на изменения статуса + Mst.Client.Spawners.OnStatusChangedEvent += OnSpawnStatusChanged; + } +}); + +// Сервер +SpawnTask task = Spawn(options, region); +``` + +### Выбор спаунера для запуска +```csharp +// Фильтрация спаунеров по региону +var spawners = GetSpawnersInRegion(region); + +// Сортировка по доступным слотам +var availableSpawners = spawners + .OrderByDescending(s => s.CalculateFreeSlotsCount()) + .Where(s => s.CanSpawnAnotherProcess()) + .ToList(); + +// Выбор спаунера +if (availableSpawners.Count > 0) +{ + return availableSpawners[0]; +} +``` + +### Завершение процесса спауна +```csharp +// На стороне процесса +var finalizationData = new MstProperties(); +finalizationData.Set("roomId", 12345); +finalizationData.Set("connectionAddress", "192.168.1.10:12345"); + +// Отправка данных о завершении +Mst.Client.Spawners.FinalizeSpawn(spawnTaskId, finalizationData); + +// На стороне клиента +Mst.Client.Spawners.GetFinalizationData(spawnId, (successful, data) => +{ + if (successful) + { + string connectionAddress = data.AsString("connectionAddress"); + int roomId = data.AsInt("roomId"); + + // Подключение к комнате + ConnectToRoom(connectionAddress, roomId); + } +}); +``` + +## Управление регионами + +```csharp +// Получение всех регионов +List regions = spawnersModule.GetRegions(); + +// Получение спаунеров в конкретном регионе +List regionalSpawners = spawnersModule.GetSpawnersInRegion("eu-west"); + +// Создание спаунера в регионе +var options = new SpawnerOptions { Region = "eu-west" }; +var spawner = spawnersModule.CreateSpawner(peer, options); +``` + +## Балансировка нагрузки + +```csharp +// Пример балансировки по наименее загруженным серверам +public RegisteredSpawner GetLeastBusySpawner(string region) +{ + var spawners = GetSpawnersInRegion(region); + + // Если нет спаунеров в регионе, используем все доступные + if (spawners.Count == 0) + spawners = GetSpawners(); + + // Сортировка по загруженности + return spawners + .OrderByDescending(s => s.CalculateFreeSlotsCount()) + .FirstOrDefault(s => s.CanSpawnAnotherProcess()); +} + +// Расширенная логика выбора спаунера +public RegisteredSpawner GetOptimalSpawner(MstProperties options) +{ + string region = options.AsString(Mst.Args.Names.RoomRegion, ""); + string gameMode = options.AsString("gameMode", ""); + + // Фильтрация по региону и игровому режиму + var filtered = spawnersList.Values + .Where(s => (string.IsNullOrEmpty(region) || s.Options.Region == region) && + (string.IsNullOrEmpty(gameMode) || + s.Options.CustomOptions.ContainsKey("gameModes") && + s.Options.CustomOptions["gameModes"].Contains(gameMode))) + .ToList(); + + return filtered + .OrderByDescending(s => s.CalculateFreeSlotsCount()) + .FirstOrDefault(s => s.CanSpawnAnotherProcess()); +} +``` + +## Практический пример + +### Настройка системы: +```csharp +// 1. Регистрация спаунеров +RegisterSpawner("EU", "10.0.0.1", 10); +RegisterSpawner("US", "10.0.1.1", 15); +RegisterSpawner("ASIA", "10.0.2.1", 8); + +// 2. Клиентский запрос на создание игрового сервера +var options = new MstProperties(); +options.Set(Mst.Args.Names.RoomName, "CustomGame"); +options.Set(Mst.Args.Names.RoomMaxPlayers, 16); +options.Set(Mst.Args.Names.RoomRegion, "EU"); +options.Set("gameMode", "deathmatch"); + +// 3. Обработка запроса, выбор спаунера и запуск процесса +SpawnTask task = spawnersModule.Spawn(options, "EU"); + +// 4. Клиент ожидает завершения процесса +// 5. Созданный процесс регистрируется и отправляет данные для подключения +``` + +## Лучшие практики + +1. **Группируйте спаунеры по регионам** для оптимальной задержки +2. **Настраивайте лимиты процессов** для каждого спаунера с учетом мощности сервера +3. **Используйте кастомные опции** для гибкой настройки спаунера +4. **Реализуйте отказоустойчивость** - если спаунер недоступен, перенаправьте задачу в другой регион +5. **Мониторьте загруженность спаунеров** для обнаружения узких мест +6. **Добавляйте тайм-ауты для задач** в очереди, чтобы избежать застревания diff --git a/Docs/Modules/WebServer.md b/Docs/Modules/WebServer.md new file mode 100644 index 0000000..03c282b --- /dev/null +++ b/Docs/Modules/WebServer.md @@ -0,0 +1,579 @@ +# Master Server Toolkit - Web Server + +## Описание +Модуль WebServer предоставляет встроенный HTTP-сервер для управления игровым сервером через веб-интерфейс, API и мониторинга системы. Позволяет создавать RESTful API для внешних сервисов и админ-панель. + +## WebServerModule + +Основной класс модуля веб-сервера. + +### Настройка: +```csharp +[Header("Http Server Settings")] +[SerializeField] protected bool autostart = true; +[SerializeField] protected string httpAddress = "127.0.0.1"; +[SerializeField] protected int httpPort = 5056; +[SerializeField] protected string[] defaultIndexPage = new string[] { "index", "home" }; + +[Header("User Credentials Settings")] +[SerializeField] protected bool useCredentials = true; +[SerializeField] protected string username = "admin"; +[SerializeField] protected string password = "admin"; + +[Header("Assets")] +[SerializeField] protected TextAsset startPage; +``` + +### Запуск и остановка: +```csharp +// Инициализация с автозапуском +public override void Initialize(IServer server) +{ + if (autostart) + { + Listen(); + } +} + +// Запуск вручную +var webServer = Mst.Server.Modules.GetModule(); +webServer.Listen(); // Используя настройки по умолчанию + +// Запуск с указанием URL +webServer.Listen("http://localhost:8080"); + +// Остановка сервера +webServer.Stop(); +``` + +### Аргументы командной строки: +```bash +--web-username "admin" # Имя пользователя для доступа к веб-интерфейсу +--web-password "secret" # Пароль для доступа к веб-интерфейсу +--web-port 8080 # Порт для HTTP сервера +--web-address "0.0.0.0" # Адрес для прослушивания (0.0.0.0 для всех интерфейсов) +``` + +## Регистрация обработчиков + +### HTTP методы (GET, POST, PUT, DELETE): +```csharp +// GET запрос +webServer.RegisterGetHandler("stats", GetStatisticsHandler); + +// POST запрос +webServer.RegisterPostHandler("users/create", CreateUserHandler, true); // true - требует авторизацию + +// PUT запрос +webServer.RegisterPutHandler("users/update", UpdateUserHandler, true); + +// DELETE запрос +webServer.RegisterDeleteHandler("users/delete", DeleteUserHandler, true); + +// Пример обработчика +private async Task GetStatisticsHandler(HttpListenerRequest request) +{ + var stats = new + { + playersOnline = 120, + roomsCount = 25, + serverUptime = "10 hours" + }; + + return new JsonResult(stats); +} +``` + +## Результаты HTTP + +### Типы результатов: +```csharp +// Текстовый ответ +var stringResult = new StringResult("Hello, World!"); +stringResult.ContentType = "text/plain"; // по умолчанию + +// JSON ответ +var jsonResult = new JsonResult(new { status = "ok", users = 5 }); + +// Код состояния +var badRequest = new BadRequest(); // 400 +var unauthorized = new Unauthorized(); // 401 +var notFound = new NotFound(); // 404 +var serverError = new InternalServerError("Database error"); // 500 +``` + +### Кастомный ответ: +```csharp +public class HtmlResult : HttpResult +{ + public string Html { get; set; } + + public HtmlResult(string html) + { + Html = html; + ContentType = "text/html"; + StatusCode = 200; + } + + public override async Task ExecuteResultAsync(HttpListenerResponse response) + { + using (var writer = new StreamWriter(response.OutputStream)) + { + await writer.WriteAsync(Html); + } + } +} + +// Использование +private async Task GetDashboardHandler(HttpListenerRequest request) +{ + string html = "

Dashboard

"; + return new HtmlResult(html); +} +``` + +## Web Controllers + +Web контроллеры позволяют создавать структурированные группы обработчиков. + +### Создание контроллера: +```csharp +public class UsersController : WebController +{ + public override void Initialize(WebServerModule server) + { + base.Initialize(server); + + // Настройка безопасности + UseCredentials = true; + + // Регистрация маршрутов + WebServer.RegisterGetHandler("users/list", GetUsersList); + WebServer.RegisterGetHandler("users/view", GetUserDetails); + WebServer.RegisterPostHandler("users/create", CreateUser, true); + WebServer.RegisterPutHandler("users/update", UpdateUser, true); + WebServer.RegisterDeleteHandler("users/delete", DeleteUser, true); + } + + private async Task GetUsersList(HttpListenerRequest request) + { + // Логика получения списка пользователей + var users = new[] + { + new { id = 1, name = "John" }, + new { id = 2, name = "Alice" } + }; + + return new JsonResult(users); + } + + private async Task GetUserDetails(HttpListenerRequest request) + { + // Получение параметров запроса + string id = request.QueryString["id"]; + + if (string.IsNullOrEmpty(id)) + return new BadRequest(); + + // Логика получения данных пользователя + var user = new { id = int.Parse(id), name = "John", email = "john@example.com" }; + + return new JsonResult(user); + } + + private async Task CreateUser(HttpListenerRequest request) + { + // Чтение тела запроса + using (var reader = new StreamReader(request.InputStream)) + { + string body = await reader.ReadToEndAsync(); + // Парсинг JSON и создание пользователя + + return new JsonResult(new { success = true, message = "User created" }); + } + } + + // Другие методы... +} +``` + +### Добавление контроллера: +```csharp +// 1. Через добавление на GameObject +gameObject.AddComponent(); + +// 2. Или программно +var controller = new UsersController(); +Controllers.TryAdd(controller.GetType(), controller); +controller.Initialize(this); +``` + +## Встроенные контроллеры + +### InfoWebController: +```csharp +// Предоставляет информацию о сервере +// GET /info - общая информация +// GET /info/modules - список модулей +// GET /info/connections - информация о подключениях +``` + +### NotificationWebController: +```csharp +// Отправка уведомлений через веб-интерфейс +// POST /notifications/all - отправить всем +// POST /notifications/room/{roomId} - отправить в комнату +// POST /notifications/user/{userId} - отправить пользователю + +// Пример использования: +// curl -X POST http://localhost:5056/notifications/all +// -u admin:admin +// -H "Content-Type: application/json" +// -d '{"message":"Server will restart in 5 minutes"}' +``` + +### AnalyticsWebController: +```csharp +// Получение аналитических данных +// GET /analytics/users - статистика пользователей +// GET /analytics/rooms - статистика комнат +``` + +## Обработка запросов + +### Чтение параметров запроса: +```csharp +private async Task SearchHandler(HttpListenerRequest request) +{ + // Query параметры (?name=value) + string query = request.QueryString["q"]; + string limit = request.QueryString["limit"] ?? "10"; + + // Заголовки + string authorization = request.Headers["Authorization"]; + string contentType = request.ContentType; + + // Cookies + Cookie sessionCookie = request.Cookies["session"]; + + // URL параметры + string[] segments = request.Url.Segments; + + // Результат + return new JsonResult(new { + query = query, + limit = int.Parse(limit), + results = new[] { "result1", "result2" } + }); +} +``` + +### Чтение тела запроса: +```csharp +private async Task CreateItemHandler(HttpListenerRequest request) +{ + // Проверка Content-Type + if (!request.ContentType.Contains("application/json")) + return new BadRequest(); + + // Чтение тела запроса + string body; + using (var reader = new StreamReader(request.InputStream, request.ContentEncoding)) + { + body = await reader.ReadToEndAsync(); + } + + try + { + // Десериализация JSON + var data = JsonUtility.FromJson(body); + + // Создание объекта + string itemId = Guid.NewGuid().ToString(); + + return new JsonResult(new { + success = true, + id = itemId + }); + } + catch (Exception ex) + { + return new BadRequest(); + } +} + +[Serializable] +public class ItemData +{ + public string name; + public int quantity; +} +``` + +## Аутентификация и авторизация + +### Настройка базовой аутентификации: +```csharp +// 1. В Inspector +[SerializeField] protected bool useCredentials = true; +[SerializeField] protected string username = "admin"; +[SerializeField] protected string password = "admin"; + +// 2. В коде при регистрации обработчика +// true - требуется авторизация, false - публичный доступ +webServer.RegisterGetHandler("admin/stats", AdminStatsHandler, true); +``` + +### Кастомная авторизация: +```csharp +public class CustomAuthController : WebController +{ + private Dictionary apiKeys = new Dictionary + { + { "client1", "key1" }, + { "client2", "key2" } + }; + + public override void Initialize(WebServerModule server) + { + base.Initialize(server); + + // Отключаем встроенную Basic Authentication + UseCredentials = false; + + WebServer.RegisterGetHandler("api/data", GetApiData); + } + + private async Task GetApiData(HttpListenerRequest request) + { + // Проверка API ключа + string apiKey = request.Headers["X-API-Key"]; + + if (string.IsNullOrEmpty(apiKey) || !apiKeys.ContainsValue(apiKey)) + return new Unauthorized(); + + // Авторизованный доступ + return new JsonResult(new { data = "Secure API data" }); + } +} +``` + +## Создание веб-интерфейса + +### Встроенная стартовая страница: +```csharp +[Header("Assets")] +[SerializeField] protected TextAsset startPage; + +// HTML страница с плейсхолдерами +// #MST-TITLE# - будет заменено на "MST v.X.X.X" +// #MST-GREETINGS# - будет заменено на "MST v.X.X.X" +``` + +### Создание HTML контроллера: +```csharp +public class DashboardController : WebController +{ + [SerializeField] private TextAsset dashboardHtmlTemplate; + [SerializeField] private TextAsset loginHtmlTemplate; + + public override void Initialize(WebServerModule server) + { + base.Initialize(server); + + // Публичные страницы + WebServer.RegisterGetHandler("login", LoginPage, false); + + // Защищенные страницы + WebServer.RegisterGetHandler("dashboard", DashboardPage, true); + WebServer.RegisterGetHandler("dashboard/users", UsersPage, true); + } + + private async Task LoginPage(HttpListenerRequest request) + { + var result = new StringResult(loginHtmlTemplate.text); + result.ContentType = "text/html"; + return result; + } + + private async Task DashboardPage(HttpListenerRequest request) + { + // Получение статистики + var playersCount = Mst.Server.ConnectionsCount; + var roomsCount = Mst.Server.Modules.GetModule()?.GetRooms().Count ?? 0; + + // Подстановка данных в шаблон + string html = dashboardHtmlTemplate.text + .Replace("{{PLAYERS_COUNT}}", playersCount.ToString()) + .Replace("{{ROOMS_COUNT}}", roomsCount.ToString()) + .Replace("{{SERVER_UPTIME}}", GetServerUptime()); + + var result = new StringResult(html); + result.ContentType = "text/html"; + return result; + } +} +``` + +## RESTful API для внешних сервисов + +### Пример игрового API: +```csharp +public class GameApiController : WebController +{ + private AuthModule authModule; + private ProfilesModule profilesModule; + + public override void Initialize(WebServerModule server) + { + base.Initialize(server); + + // Получение зависимостей + authModule = server.GetModule(); + profilesModule = server.GetModule(); + + // API маршруты + WebServer.RegisterGetHandler("api/players", GetPlayers, true); + WebServer.RegisterGetHandler("api/players/online", GetOnlinePlayers, true); + WebServer.RegisterGetHandler("api/player", GetPlayer, true); + WebServer.RegisterPostHandler("api/player/reward", AddReward, true); + } + + private async Task GetOnlinePlayers(HttpListenerRequest request) + { + var onlinePlayers = authModule.LoggedInUsers.Select(u => new { + id = u.UserId, + username = u.Username, + isGuest = u.IsGuest + }).ToList(); + + return new JsonResult(onlinePlayers); + } + + private async Task AddReward(HttpListenerRequest request) + { + // Чтение тела запроса + using (var reader = new StreamReader(request.InputStream)) + { + string body = await reader.ReadToEndAsync(); + var data = JsonUtility.FromJson(body); + + // Поиск пользователя + var user = authModule.GetLoggedInUserById(data.userId); + + if (user == null) + return new NotFound(); + + // Поиск профиля + if (!user.Peer.TryGetExtension(out ProfilePeerExtension profileExt)) + return new NotFound(); + + // Добавление валюты + if (profileExt.Profile.TryGet(ProfilePropertyOpCodes.currency, out ObservableDictionary currencies)) + { + currencies.Set(data.currencyType, currencies.Get(data.currencyType, 0) + data.amount); + return new JsonResult(new { success = true }); + } + else + { + return new InternalServerError("Currency property not found"); + } + } + } + + [Serializable] + private class RewardData + { + public string userId; + public string currencyType; + public int amount; + } +} +``` + +## Интеграция с внешними системами + +### Вебхуки (webhooks): +```csharp +// Отправка вебхука при событии в игре +public class WebhookManager : MonoBehaviour +{ + [SerializeField] private string webhookUrl = "https://example.com/webhook"; + + private AuthModule authModule; + + private void Start() + { + authModule = Mst.Server.Modules.GetModule(); + + // Подписка на события + authModule.OnUserLoggedInEvent += OnUserLoggedIn; + authModule.OnUserLoggedOutEvent += OnUserLoggedOut; + } + + private async void OnUserLoggedIn(IUserPeerExtension user) + { + var data = new WebhookData + { + eventType = "user.login", + userId = user.UserId, + username = user.Username, + timestamp = DateTime.UtcNow.ToString("o") + }; + + await SendWebhookAsync(data); + } + + private async void OnUserLoggedOut(IUserPeerExtension user) + { + var data = new WebhookData + { + eventType = "user.logout", + userId = user.UserId, + username = user.Username, + timestamp = DateTime.UtcNow.ToString("o") + }; + + await SendWebhookAsync(data); + } + + private async Task SendWebhookAsync(WebhookData data) + { + try + { + using (var client = new WebClient()) + { + client.Headers[HttpRequestHeader.ContentType] = "application/json"; + string json = JsonUtility.ToJson(data); + await client.UploadStringTaskAsync(webhookUrl, json); + } + } + catch (Exception ex) + { + Debug.LogError($"Failed to send webhook: {ex.Message}"); + } + } + + [Serializable] + private class WebhookData + { + public string eventType; + public string userId; + public string username; + public string timestamp; + } +} +``` + +## Лучшие практики + +1. **Используйте контроллеры для организации логики** - группируйте связанные обработчики в контроллеры +2. **Включайте авторизацию для админ-функций** - использование `UseCredentials = true` для защищенных маршрутов +3. **Обрабатывайте ошибки и возвращайте правильные HTTP-коды** - используйте классы результатов (BadRequest, NotFound и т.д.) +4. **Валидируйте входные данные** - всегда проверяйте параметры запроса и тело перед использованием +5. **Создавайте документацию API** - описывайте доступные маршруты, параметры и результаты +6. **Разделяйте чтение и изменение** - используйте GET для чтения, POST/PUT/DELETE для изменения +7. **Настройте CORS если необходимо** - для доступа с внешних веб-приложений +8. **Ограничивайте доступ по IP** - для критически важных API +9. **Используйте JSON для обмена данными** - предпочтительнее других форматов +10. **Имплементируйте отслеживание статистики API** - для мониторинга использования и производительности diff --git a/Docs/Modules/WorldRooms.md b/Docs/Modules/WorldRooms.md new file mode 100644 index 0000000..b57ae40 --- /dev/null +++ b/Docs/Modules/WorldRooms.md @@ -0,0 +1,309 @@ +# Master Server Toolkit - World Rooms + +## Описание +Модуль WorldRooms расширяет функциональность базового модуля Rooms для создания постоянных игровых зон (локаций) в открытом мире, с возможностью автоматического запуска и управления ими. + +## Основные компоненты + +### WorldRoomsModule +```csharp +// Настройки +[Header("Zones Settings"), SerializeField] +private string[] zoneScenes; // Сцены зон, которые будут автоматически запущены + +// Зависимости +protected SpawnersModule spawnersModule; // Для запуска серверов зон +``` + +## Автоматическое создание зон мира + +```csharp +// При регистрации спаунера автоматически запускаются все зоны +private async void Spawners_OnSpawnerRegisteredEvent(RegisteredSpawner spawner) +{ + await Task.Delay(100); + + foreach (string zoneScene in zoneScenes) + { + spawnersModule.Spawn(SpawnerProperties(zoneScene)).WhenDone(task => + { + logger.Info($"{zoneScene} zone status is: {task.Status}"); + }); + } +} +``` + +## Настройка свойств зоны + +```csharp +// Создание настроек для запуска зоны на спаунере +protected virtual MstProperties SpawnerProperties(string zoneId) +{ + var properties = new MstProperties(); + properties.Set(Mst.Args.Names.RoomName, zoneId); // Имя комнаты + properties.Set(Mst.Args.Names.RoomOnlineScene, zoneId); // Имя сцены + properties.Set(Mst.Args.Names.RoomIsPrivate, true); // Приватная комната + properties.Set(MstDictKeys.WORLD_ZONE, zoneId); // Маркер зоны мира + + return properties; +} +``` + +## Получение информации о зоне + +```csharp +// На сервере - поиск комнаты зоны по ID +RegisteredRoom zoneRoom = roomsList.Values + .Where(r => r.Options.CustomOptions.AsString(MstDictKeys.WORLD_ZONE) == zoneId) + .FirstOrDefault(); + +// Формирование информации о зоне для отправки клиенту +var game = new GameInfoPacket +{ + Id = zoneRoom.RoomId, + Address = zoneRoom.Options.RoomIp + ":" + zoneRoom.Options.RoomPort, + MaxPlayers = zoneRoom.Options.MaxConnections, + Name = zoneRoom.Options.Name, + OnlinePlayers = zoneRoom.OnlineCount, + Properties = GetPublicRoomOptions(message.Peer, zoneRoom, null), + IsPasswordProtected = !string.IsNullOrEmpty(zoneRoom.Options.Password), + Type = GameInfoType.Room, + Region = zoneRoom.Options.Region +}; +``` + +## Клиентский запрос информации о зоне + +```csharp +// Запрос информации о зоне по ее ID +Mst.Client.Connection.SendMessage(MstOpCodes.GetZoneRoomInfo, "Forest", (status, response) => +{ + if (status == ResponseStatus.Success) + { + // Получение данных о зоне + var zoneInfo = response.AsPacket(); + + // Подключение к серверу зоны + string address = zoneInfo.Address; + int roomId = zoneInfo.Id; + + ConnectToZone(address, roomId); + } + else + { + Debug.LogError("Failed to get zone room info"); + } +}); +``` + +## Переход между зонами + +```csharp +// Клиентский код для перехода между зонами +public void RequestZoneTransition(string targetZoneId, Vector3 spawnPosition) +{ + // 1. Запрос информации о целевой зоне + Mst.Client.Connection.SendMessage(MstOpCodes.GetZoneRoomInfo, targetZoneId, (status, response) => + { + if (status == ResponseStatus.Success) + { + // 2. Получение информации о зоне + var zoneInfo = response.AsPacket(); + + // 3. Сохранение позиции появления для новой зоны + PlayerPrefs.SetFloat("SpawnPosX", spawnPosition.x); + PlayerPrefs.SetFloat("SpawnPosY", spawnPosition.y); + PlayerPrefs.SetFloat("SpawnPosZ", spawnPosition.z); + + // 4. Отключение от текущей зоны + Mst.Client.Connection.Disconnect(); + + // 5. Подключение к новой зоне + ConnectToZone(zoneInfo.Address, zoneInfo.Id); + } + }); +} + +// Подключение к серверу зоны +private void ConnectToZone(string address, int roomId) +{ + // Разбор адреса в формате "ip:port" + string[] addressParts = address.Split(':'); + string ip = addressParts[0]; + int port = int.Parse(addressParts[1]); + + // Подключение к серверу + Mst.Client.Connection.Connect(ip, port, (successful, connectedPeer) => + { + if (successful) + { + Debug.Log($"Connected to zone server: {address}"); + + // Присоединение к комнате после подключения к серверу + Mst.Client.Rooms.JoinRoom(roomId, "", (successfulJoin, roomAccess) => + { + if (successfulJoin) + { + Debug.Log($"Joined zone room: {roomId}"); + } + }); + } + }); +} +``` + +## Интеграция с персистентным миром + +```csharp +// Пример кода сервера зоны для обработки подключения игрока +protected override void OnPlayerJoinedRoom(RoomPlayer player) +{ + base.OnPlayerJoinedRoom(player); + + // Получение расширения пользователя + var userExt = player.GetExtension(); + if (userExt != null) + { + // Загрузка данных игрока для текущей зоны + LoadPlayerZoneData(userExt.UserId); + + // Отправка данных о других игроках в зоне + SendZonePlayersData(player); + } +} + +// Загрузка данных игрока для конкретной зоны +private void LoadPlayerZoneData(string userId) +{ + // Получение данных из профиля или базы данных + var zoneId = gameObject.scene.name; + var zoneDataKey = $"zonedata_{zoneId}_{userId}"; + + // Запрос данных из профиля + Mst.Server.Profiles.GetProfileValues(userId, new string[] { zoneDataKey }, (success, data) => + { + if (success && data.Has(zoneDataKey)) + { + // Разбор данных зоны (позиция, инвентарь и т.д.) + var zoneData = data.AsString(zoneDataKey); + // Применение данных к игроку + ApplyZoneDataToPlayer(userId, zoneData); + } + else + { + // Использование данных по умолчанию для новых игроков в этой зоне + ApplyDefaultZoneData(userId); + } + }); +} +``` + +## Сохранение данных при выходе из зоны + +```csharp +// Сервер зоны: обработка выхода игрока из зоны +protected override void OnPlayerLeftRoom(RoomPlayer player) +{ + base.OnPlayerLeftRoom(player); + + // Получение расширения пользователя + var userExt = player.GetExtension(); + if (userExt != null) + { + // Сохранение данных игрока для текущей зоны + SavePlayerZoneData(userExt.UserId); + } +} + +// Сохранение данных игрока для конкретной зоны +private void SavePlayerZoneData(string userId) +{ + // Сбор данных игрока (позиция, инвентарь и т.д.) + string zoneData = GenerateZoneDataForPlayer(userId); + + // Формирование ключа зоны + var zoneId = gameObject.scene.name; + var zoneDataKey = $"zonedata_{zoneId}_{userId}"; + + // Сохранение в профиль + var data = new MstProperties(); + data.Set(zoneDataKey, zoneData); + + Mst.Server.Profiles.SetProfileValues(userId, data); +} +``` + +## Примеры использования + +### Настройка перехода между зонами +```csharp +// Зона "Лес" соединяется с зоной "Пещера" +public class ZoneTransition : MonoBehaviour +{ + [SerializeField] private string targetZoneId = "Cave"; + [SerializeField] private Vector3 spawnPosition = new Vector3(10, 0, 10); + + private void OnTriggerEnter(Collider other) + { + if (other.CompareTag("Player")) + { + // Отправка запроса на переход в другую зону + var zoneManager = FindObjectOfType(); + zoneManager.RequestZoneTransition(targetZoneId, spawnPosition); + } + } +} +``` + +### Глобальные события в зонах +```csharp +// Система глобальных событий для синхронизации между зонами +public class GlobalEventsManager : MonoBehaviour +{ + // Отправка глобального события всем зонам + public void BroadcastGlobalEvent(string eventType, MstProperties eventData) + { + // Отправка события на мастер сервер + var data = new MstProperties(); + data.Set("type", eventType); + data.Set("data", eventData.ToJson()); + + Mst.Client.Connection.SendMessage(MstOpCodes.GlobalZoneEvent, data); + } +} + +// На мастер сервере +private Task GlobalZoneEventHandler(IIncomingMessage message) +{ + try + { + var data = MstProperties.FromBytes(message.AsBytes()); + string eventType = data.AsString("type"); + string eventData = data.AsString("data"); + + // Рассылка всем зонам + foreach (var room in roomsList.Values) + { + if (room.Options.CustomOptions.Has(MstDictKeys.WORLD_ZONE)) + { + room.SendMessage(MstOpCodes.GlobalZoneEvent, data.ToBytes()); + } + } + + return Task.CompletedTask; + } + catch (System.Exception ex) + { + return Task.FromException(ex); + } +} +``` + +## Лучшие практики + +1. **Используйте зональное деление** для снижения нагрузки на сервер +2. **Сохраняйте данные** при переходах между зонами +3. **Оптимизируйте передачу данных** - отправляйте только необходимую информацию +4. **Реализуйте плавные переходы** между зонами с экранами загрузки +5. **Используйте общую систему событий** для синхронизации между зонами +6. **Разделяйте обязанности** между сервером зоны и мастер-сервером diff --git a/Docs/Networking.md b/Docs/Networking.md new file mode 100644 index 0000000..b5d2910 --- /dev/null +++ b/Docs/Networking.md @@ -0,0 +1,255 @@ +# Master Server Toolkit - Networking + +## Обзор архитектуры + +Сетевая система MST построена на гибкой архитектуре с поддержкой различных транспортных протоколов (WebSockets, TCP). Она обеспечивает надежную и безопасную передачу данных между клиентами и серверами. + +``` +Networking +├── Core Interfaces (IPeer, IClientSocket, IServerSocket) +├── Messages (IIncomingMessage, IOutgoingMessage) +├── Serialization +├── Transport Layer (WebSocketSharp) +└── Extensions +``` + +## Основные компоненты + +### Клиент-серверное взаимодействие + +#### IPeer +Представляет соединение между сервером и клиентом. + +```csharp +// Основные свойства +int Id { get; } // Уникальный ID пира +bool IsConnected { get; } // Статус подключения +DateTime LastActivity { get; set; } // Время последней активности + +// События +event PeerActionHandler OnConnectionOpenEvent; // Подключение +event PeerActionHandler OnConnectionCloseEvent; // Отключение + +// Расширяемость +T AddExtension(T extension); // Добавление расширения +T GetExtension(); // Получение расширения +``` + +#### IClientSocket +Клиентский сокет для подключения к серверу. + +```csharp +// Основные свойства +ConnectionStatus Status { get; } // Статус подключения +bool IsConnected { get; } // Подключен ли +string Address { get; } // IP-адрес +int Port { get; } // Порт + +// Методы подключения +IClientSocket Connect(string ip, int port, float timeoutSeconds); +void Close(bool fireEvent = true); + +// Обработка сообщений +IPacketHandler RegisterMessageHandler(ushort opCode, IncommingMessageHandler handler); +``` + +#### IServerSocket +Серверный сокет для прослушивания подключений. + +```csharp +// Безопасность +bool UseSecure { get; set; } // Использовать SSL +string CertificatePath { get; set; } // Путь к сертификату +string CertificatePassword { get; set; } // Пароль сертификата + +// События +event PeerActionHandler OnPeerConnectedEvent; // Подключение клиента +event PeerActionHandler OnPeerDisconnectedEvent; // Отключение клиента + +// Методы прослушивания +void Listen(int port); // Локальное прослушивание +void Listen(string ip, int port); // Прослушивание на IP:Port +``` + +### Система сообщений + +#### IIncomingMessage +Представляет входящее сообщение. + +```csharp +// Основные свойства +ushort OpCode { get; } // Код операции +byte[] Data { get; } // Данные сообщения +IPeer Peer { get; } // Отправитель + +// Методы чтения данных +string AsString(); // Чтение данных как строки +int AsInt(); // Чтение данных как int +float AsFloat(); // Чтение данных как float +T AsPacket(); // Десериализация в пакет +``` + +#### IOutgoingMessage +Представляет исходящее сообщение. + +```csharp +// Основные свойства +ushort OpCode { get; } // Код операции +byte[] Data { get; } // Данные сообщения + +// Методы отправки +void Respond(); // Отправка пустого ответа +void Respond(byte[] data); // Отправка бинарных данных +void Respond(ISerializablePacket packet); // Отправка пакета +void Respond(ResponseStatus status); // Отправка статуса +``` + +#### MessageFactory +Фабрика для создания сообщений. + +```csharp +// Создание сообщений +IOutgoingMessage Create(ushort opCode); // Пустое сообщение +IOutgoingMessage Create(ushort opCode, byte[] data); // С бинарными данными +IOutgoingMessage Create(ushort opCode, string data); // Со строкой +IOutgoingMessage Create(ushort opCode, int data); // С числом +``` + +### Сериализация данных + +#### ISerializablePacket +Интерфейс для сериализуемых пакетов. + +```csharp +// Обязательные методы +void ToBinaryWriter(EndianBinaryWriter writer); // Сериализация +void FromBinaryReader(EndianBinaryReader reader); // Десериализация +``` + +#### SerializablePacket +Базовый класс для сериализуемых пакетов. + +```csharp +// Реализует ISerializablePacket +// Предоставляет базовые методы для наследников +``` + +#### Serializer +Утилиты для бинарной сериализации. + +```csharp +byte[] Serialize(object data); // Сериализация объекта в байты +T Deserialize(byte[] bytes); // Десериализация байтов в объект +``` + +## Транспортный слой + +### WebSocketSharp + +```csharp +// Клиентский сокет +WsClientSocket clientSocket = new WsClientSocket(); +clientSocket.Connect("127.0.0.1", 5000); + +// Серверный сокет +WsServerSocket serverSocket = new WsServerSocket(); +serverSocket.Listen(5000); +``` + +## Шаблоны использования + +### Регистрация обработчиков + +```csharp +// Регистрация обработчика +client.RegisterMessageHandler(MstOpCodes.Ping, OnPingMessageHandler); + +// Обработчик +private void OnPingMessageHandler(IIncomingMessage message) +{ + // Отправка ответа + message.Respond("Pong"); +} +``` + +### Отправка сообщений + +```csharp +// Простая отправка +client.SendMessage(MstOpCodes.Ping); + +// Отправка с данными +client.SendMessage(MstOpCodes.Auth, "username:password"); + +// Отправка пакета +client.SendMessage(MstOpCodes.JoinLobby, new JoinLobbyPacket{ LobbyId = 123 }); + +// Отправка с ожиданием ответа +client.SendMessage(MstOpCodes.GetRooms, (status, response) => +{ + if (status == ResponseStatus.Success) + { + var rooms = response.AsPacketsList(); + ProcessRooms(rooms); + } +}); +``` + +### Создание пакетов + +```csharp +public class PlayerInfoPacket : SerializablePacket +{ + public int PlayerId { get; set; } + public string Name { get; set; } + public float Score { get; set; } + + public override void ToBinaryWriter(EndianBinaryWriter writer) + { + writer.Write(PlayerId); + writer.Write(Name); + writer.Write(Score); + } + + public override void FromBinaryReader(EndianBinaryReader reader) + { + PlayerId = reader.ReadInt32(); + Name = reader.ReadString(); + Score = reader.ReadFloat(); + } +} +``` + +## Безопасность и производительность + +### Защищенные соединения (SSL/TLS) + +```csharp +// Настройка SSL клиента +clientSocket.UseSecure = true; + +// Настройка SSL сервера +serverSocket.UseSecure = true; +serverSocket.CertificatePath = "certificate.pfx"; +serverSocket.CertificatePassword = "password"; +``` + +### Управление таймаутами + +```csharp +// Установка таймаута подключения +client.Connect("127.0.0.1", 5000, 10); // 10 секунд таймаут + +// Установка таймаута ответа +client.SendMessage(opCode, data, callback, 5); // 5 секунд таймаут +``` + +### Методы доставки + +```csharp +// Надежная доставка (гарантирует доставку) +peer.SendMessage(message, DeliveryMethod.Reliable); + +// Ненадежная доставка (быстрее, но без гарантий) +peer.SendMessage(message, DeliveryMethod.Unreliable); +``` diff --git a/Docs/ProfilesModule Documentation.md b/Docs/ProfilesModule Documentation.md deleted file mode 100644 index 43eba01..0000000 --- a/Docs/ProfilesModule Documentation.md +++ /dev/null @@ -1,578 +0,0 @@ -# Master Server Toolkit - Profile System Documentation - -## Overview - -The Master Server Toolkit (MST) Profile System provides a robust framework for managing player profiles in multiplayer game architectures. It enables real-time synchronization of player data across different contexts, with automatic change tracking and persistence. - -The system operates in three distinct contexts, each with specialized components: - -1. **Client Context** - Manages the player's local profile representation and updates -2. **Game Server Context** - Handles multiple player profiles during gameplay sessions -3. **Master Server Context** - Coordinates profiles across the entire network and provides persistence - -The Profile System excels at: -- Real-time synchronization of player data -- Automatic change tracking -- Efficient network serialization -- Seamless integration with Unity -- Database persistence -- Event-driven architecture - -## Architecture - -The Profile System architecture follows the observer pattern, creating a reactive data system where changes to profile properties automatically propagate through the network. - -### Data Flow - -1. **Property Changes**: When a property within a profile is modified, it marks itself as "dirty" and notifies its parent profile -2. **Profile Updates**: The profile collects changes and prepares them for transmission -3. **Network Synchronization**: Changes are sent to the appropriate servers/clients -4. **Database Persistence**: The master server periodically saves dirty profiles to the database - -### Component Relationships - -- **ObservableProperties** form the foundation, providing change tracking for individual data points -- **ObservableProfile** aggregates properties and manages serialization/updates -- **Context-specific Implementations** (Client/Server/Master) handle network communication -- **Database Accessor** provides persistence for profiles - -## Core Components - -### ObservableProfile - -The `ObservableProfile` class serves as the foundation of the profile system. It acts as a container for observable properties, tracking changes and providing serialization capabilities. - -Key features: - -- **Property Management**: Stores and retrieves properties by key -- **Change Tracking**: Monitors which properties have been modified -- **Serialization**: Converts profiles to bytes for network transmission -- **Update Management**: Efficiently collects and applies property updates -- **Event System**: Notifies listeners when properties change - -The profile maintains a collection of `IObservableProperty` instances, each with a unique key. When properties change, they trigger events that bubble up to the profile, which then marks those properties for update transmission. - -### ObservableServerProfile - -`ObservableServerProfile` extends the base profile with server-specific functionality: - -- **User Identification**: Associates the profile with a specific user ID -- **Peer Reference**: Maintains a link to the user's network connection -- **Server Events**: Provides events for server-side processing of changes -- **Lifecycle Management**: Handles disposal and cleanup of resources - -This server-side representation is crucial for game servers and the master server to track which profiles belong to which users and manage their lifecycle. - -### MstProfilesClient - -`MstProfilesClient` manages the client-side lifecycle of profiles, providing: - -- **Profile Loading**: Requests and retrieves profile data from the server -- **Event Notification**: Informs client code when profile data is available -- **Connection Management**: Handles network connection status and reconnection -- **Update Reception**: Processes incoming profile updates from the server - -This client component creates a seamless experience where profile data appears synchronized with the server without requiring explicit refresh calls from game code. - -### MstProfilesServer - -`MstProfilesServer` operates on game servers, providing: - -- **Profile Collection**: Manages multiple player profiles simultaneously -- **Update Batching**: Collects changes and sends them efficiently to the master server -- **Update Scheduling**: Configurable update intervals for performance tuning -- **Player Identification**: Retrieves profiles by user ID - -Game servers act as intermediaries in the profile system, collecting changes during gameplay and forwarding them to the master server while also applying updates from the master server to ensure consistency. - -### ProfilesModule - -The `ProfilesModule` is the master server component that coordinates the entire profile system: - -- **Database Integration**: Persists profiles to long-term storage -- **Authentication Integration**: Links profiles to user accounts -- **Update Management**: Processes updates from game servers -- **Client Synchronization**: Sends updates to connected clients -- **Profile Creation**: Initializes new profiles with default values -- **Profile Unloading**: Manages memory by unloading inactive profiles -- **Security**: Validates profile modification permissions - -This module serves as the central authority for profile data, ensuring consistency and persistence across the entire game network. - -## Observable Properties - -Observable properties are the fundamental building blocks of the profile system. They track changes to individual data points and emit events when modified. - -### Property Types - -The system supports various property types to accommodate different kinds of game data: - -- **ObservableString**: For text data (usernames, titles, etc.) -- **ObservableInt**: For numeric values (level, score, currency, etc.) -- **ObservableBool**: For flags and toggles (achievements, settings, etc.) -- **ObservableList**: For collections (inventory, abilities, etc.) -- **ObservableDictionary**: For key-value pairs (statistics, custom data, etc.) -- **ObservableVector3**: For positions and directions -- **ObservableDateTime**: For timestamps and durations - -Each property type handles its own serialization and change tracking, making the system extensible for custom data types. - -### Change Detection - -Observable properties implement a smart change detection system: - -1. When a property's value is modified, it calls `MarkAsDirty()` -2. This triggers the `OnDirtyEvent`, notifying listeners (typically the parent profile) -3. The parent profile adds the property to a collection of "dirty" properties -4. During the next update cycle, only the changed properties are serialized and transmitted - -This approach minimizes network traffic by sending only the data that has actually changed rather than the entire profile. - -### Property Populators - -Populators provide a convenient way to initialize properties with default values: - -- **ScriptableObject-based**: Created in the Unity editor -- **Type-specific**: Each populator creates a specific property type -- **Default Value Configuration**: Allows setting initial values -- **Key Management**: Handles property key generation - -The `ObservablePropertyPopulatorsDatabase` collects these populators, making it easy to define a complete profile structure that is consistently applied to new players. - -## Serialization and Networking - -The Profile System implements efficient serialization strategies for network transmission: - -### Full Profile Serialization - -When a client first connects or a game server requests a profile, the entire profile is serialized: - -1. The profile counts its properties -2. Each property is identified by its key -3. Each property serializes its data -4. The resulting bytes are transmitted - -### Delta Updates - -For ongoing synchronization, only changes (deltas) are transmitted: - -1. The profile collects properties that have been marked dirty -2. Only these properties are serialized -3. The resulting, much smaller, bytes are transmitted -4. The receiving end applies these targeted updates - -This delta approach significantly reduces bandwidth usage, especially for large profiles with frequent, small changes. - -### Binary Format - -The system uses a custom binary format for serialization: - -- **Endian-aware**: Works across different platforms -- **Compact**: Minimizes data size -- **Type-specific**: Each property type handles its own serialization -- **Hierarchical**: Supports nested data structures - -The binary format ensures efficient network usage while maintaining compatibility across different platforms and contexts. - -## Client-Side Implementation Details - -The client-side implementation centers around retrieving and displaying profile data while handling property updates. - -### Profile Initialization - -When a client connects to the master server, it initializes its profile system: - -1. Create a local profile instance -2. Register required property types -3. Request profile data from the server -4. Receive and apply server data -5. Set up event listeners for property changes - -This process ensures the client has an up-to-date representation of the player's profile. - -### Handling Property Changes - -The client can both receive property changes from the server and make local changes: - -- **Server Updates**: The server sends delta updates that are automatically applied to the local profile -- **Local Changes**: When the client modifies a property, the change is automatically queued for transmission to the server - -This bidirectional synchronization creates a seamless experience where changes appear consistent across all contexts. - -### UI Integration - -Profile properties can easily integrate with UI elements: - -1. Access properties through the profile -2. Display current values in UI elements -3. Register for change events -4. Update UI when properties change - -This event-driven approach eliminates the need for polling or manual refresh calls, creating a reactive UI that automatically updates when profile data changes. - -## Game Server Implementation Details - -Game servers act as intermediaries, managing multiple player profiles during gameplay sessions. - -### Profile Management - -Game servers handle multiple player profiles simultaneously: - -1. When a player connects, request their profile from the master server -2. Store the profile in a local collection -3. Associate the profile with the player's game representation -4. Make gameplay-driven updates to properties -5. Allow game systems to access profile data - -This centralized approach gives the game server a complete view of all connected players' data. - -### Gameplay Integration - -Game servers typically modify profiles based on gameplay events: - -- Award experience when players complete objectives -- Update statistics when game events occur -- Modify inventory when items are acquired or used -- Track achievements when conditions are met - -These changes are automatically tracked and synchronized with the master server. - -### Efficient Synchronization - -Game servers employ several strategies for efficient profile synchronization: - -- **Batching**: Collect multiple property changes before transmission -- **Scheduling**: Send updates at configurable intervals -- **Prioritization**: Ensure critical updates are sent promptly -- **Compression**: Minimize data size through delta updates - -These optimizations balance responsiveness with network efficiency. - -## Master Server Implementation Details - -The master server coordinates the entire profile system, managing persistence and ensuring consistency. - -### Profile Lifecycle - -The master server handles the full lifecycle of profiles: - -1. **Creation**: Initialize new profiles when players first login -2. **Loading**: Retrieve existing profiles from the database -3. **Updating**: Process changes from game servers and clients -4. **Persistence**: Save modified profiles to the database -5. **Unloading**: Remove inactive profiles from memory - -This lifecycle management ensures efficient resource usage while maintaining data integrity. - -### Database Integration - -The master server integrates with databases through the `IProfilesDatabaseAccessor` interface: - -- **Restore**: Load profile data when players connect -- **Update**: Save profile changes periodically -- **Batch Processing**: Handle multiple profiles efficiently - -This abstraction allows the profile system to work with various database technologies (SQL, NoSQL, cloud services, etc.). - -### Security and Validation - -The master server implements security measures to protect profile data: - -- **Permission Checking**: Verify that servers/clients have appropriate rights -- **Data Validation**: Ensure profile data meets expected formats and ranges -- **Timeout Handling**: Manage loading/saving timeouts gracefully -- **Error Recovery**: Implement retry mechanisms for failed operations - -These measures protect against data corruption, unauthorized access, and system failures. - -## Database Integration - -The Profile System provides a flexible database integration architecture for persisting player profiles. - -### IProfilesDatabaseAccessor Interface - -This interface defines the core functionality required for database integration: - -- **RestoreProfileAsync**: Load a profile from the database -- **UpdateProfileAsync**: Save a single profile to the database -- **UpdateProfilesAsync**: Save multiple profiles efficiently - -By implementing this interface, developers can connect the profile system to their preferred database technology. - -### Database Operation Flow - -The typical flow for database operations includes: - -1. **Player Login**: Trigger profile loading from the database -2. **Profile Modification**: Track changes in memory -3. **Periodic Saving**: Save modified profiles at configured intervals -4. **Batch Processing**: Group multiple profile saves for efficiency -5. **Player Logout**: Ensure final save of profile data - -This approach balances the need for persistence with performance considerations. - -### JSON Serialization - -While the network uses binary serialization for efficiency, database integration typically uses JSON: - -- **Human-readable**: Easier to debug and manually inspect -- **Compatibility**: Works with most database systems -- **Flexibility**: Accommodates schema evolution -- **Query Support**: Enables querying specific profile elements in supporting databases - -Each observable property implements both binary and JSON serialization methods to support both network and database operations. - -## Usage Examples - -### Basic Client Profile Setup - -```csharp -// Create a profiles client -var profilesClient = new MstProfilesClient(Mst.Client.Connection); - -// Create a local profile instance -var profile = new ObservableProfile(); - -// Add required properties -profile.Add(new ObservableString("username".ToUint16Hash(), "Player")); -profile.Add(new ObservableInt("level".ToUint16Hash(), 1)); -profile.Add(new ObservableInt("experience".ToUint16Hash(), 0)); - -// Register for profile loading event -profilesClient.OnProfileLoadedEvent += OnProfileLoaded; - -// Request profile data from server -profilesClient.FillInProfileValues(profile, (success, error) => { - if (!success) Debug.LogError($"Failed to load profile: {error}"); -}); - -// Handle profile loaded event -void OnProfileLoaded(ObservableProfile profile) -{ - // Access profile properties - var username = profile.Get("username"); - var level = profile.Get("level"); - - // Display values in UI - usernameText.text = username.Value; - levelText.text = $"Level: {level.Value}"; - - // Register for property changes - username.OnDirtyEvent += (prop) => usernameText.text = ((ObservableString)prop).Value; - level.OnDirtyEvent += (prop) => levelText.text = $"Level: {((ObservableInt)prop).Value}"; -} -``` - -### Game Server Profile Management - -```csharp -// Create a profiles server -var profilesServer = new MstProfilesServer(Mst.Server.Connection); - -// Set update interval -profilesServer.ProfileUpdatesInterval = 0.2f; // 200ms - -// When a player connects -void OnPlayerConnected(NetworkPlayer player, string userId) -{ - // Create a server profile - var profile = new ObservableServerProfile(userId); - - // Request profile data from master server - profilesServer.FillProfileValues(profile, (success, error) => { - if (success) - { - // Assign profile to player - player.Profile = profile; - } - }); -} - -// Award experience to a player -void AwardExperience(string userId, int amount) -{ - if (profilesServer.TryGetById(userId, out var profile)) - { - var experience = profile.Get("experience"); - var level = profile.Get("level"); - - // Update experience (changes automatically tracked) - experience.Value += amount; - - // Check for level up - int requiredExp = level.Value * 100; - if (experience.Value >= requiredExp) - { - level.Value += 1; - experience.Value -= requiredExp; - } - } -} -``` - -### Master Server Setup - -```csharp -void Start() -{ - // Create and add profiles module - var profilesModule = gameObject.AddComponent(); - - // Configure profiles module - profilesModule.unloadProfileAfter = 30; // Unload after 30 seconds - profilesModule.saveProfileDebounceTime = 5; // Save every 5 seconds - profilesModule.clientUpdateDebounceTime = 1; // Send updates every 1 second - profilesModule.populatorsDatabase = CreatePopulatorsDatabase(); - - // Set up database integration - profilesModule.databaseAccessorFactory = gameObject.AddComponent(); - - // Add module to server - Mst.Server.AddModule(profilesModule); -} -``` - -## Feature Highlights - -### Real-time Synchronization - -The Profile System provides real-time synchronization of player data across different contexts: - -- Changes made on the client are immediately reflected on the server -- Updates from the server are automatically applied to the client -- Game server modifications propagate to both clients and the master server - -This synchronization happens with minimal developer intervention, creating a seamless experience where data appears consistent across all parts of the system. - -### Automatic Change Tracking - -The change tracking system automatically detects and propagates modifications: - -- When a property is changed, it marks itself as "dirty" -- The parent profile collects these dirty properties -- Only the changed properties are serialized and transmitted -- The receiving end applies targeted updates - -This approach eliminates the need for explicit "save" or "update" calls, streamlining development and reducing bugs. - -### Event-Driven Architecture - -The profile system uses an event-driven architecture for responsive and decoupled code: - -- Properties emit events when their values change -- Profiles notify listeners when properties are updated -- Components can register for specific property changes -- UI elements can react directly to data modifications - -This reactive approach creates clean, maintainable code where components respond to changes without tight coupling. - -### Efficient Network Usage - -The system employs several strategies to minimize network traffic: - -- Delta updates send only changed properties -- Update batching collects multiple changes -- Configurable update intervals prevent network flooding -- Binary serialization minimizes data size - -These optimizations make the profile system suitable for games with limited bandwidth or large player populations. - -### Customizable Profile Schema - -The profile schema can be customized to fit specific game requirements: - -- Define custom properties for game-specific data -- Create property populators for initialization -- Organize properties in a logical structure -- Extend the system with custom property types - -This flexibility allows the profile system to adapt to various game genres and requirements. - -### Database Abstraction - -The database integration provides a flexible abstraction: - -- Interface-based design allows various implementations -- Async methods support different database technologies -- Batch operations enable efficient persistence -- Error handling and retry mechanisms ensure reliability - -Developers can implement the `IProfilesDatabaseAccessor` interface for their preferred database technology, whether SQL, NoSQL, or cloud-based. - -## Best Practices - -### Profile Schema Design - -Design your profile schema with consideration for: - -- **Performance**: Group related properties to minimize update overhead -- **Scalability**: Use appropriate property types for expected data sizes -- **Clarity**: Use consistent naming conventions -- **Extensibility**: Plan for future additions to the profile - -A well-designed schema lays the foundation for efficient profile management throughout your game's lifecycle. - -### Property Naming Conventions - -Establish clear naming conventions for profile properties: - -- Use descriptive, consistent names -- Consider grouping related properties with prefixes -- Document the purpose and expectations for each property -- Be mindful of hash collisions with string-based keys - -Example naming structure: -``` -user.username - Basic information -progression.level - Player progression -progression.xp - Experience points -inventory.items - Player's items -stats.kills - Game statistics -settings.audio - User preferences -``` - -### Optimizing Update Frequency - -Balance responsiveness and network efficiency: - -- Critical data (health, position) may need immediate updates -- Less important data (statistics, achievements) can use longer intervals -- Consider the game type when setting update intervals -- Monitor network usage and adjust accordingly - -Configurable settings: -- `profilesServer.ProfileUpdatesInterval`: How often game servers send updates -- `profilesModule.clientUpdateDebounceTime`: How often clients receive updates -- `profilesModule.saveProfileDebounceTime`: How often profiles are saved to the database - -### Error Handling - -Implement robust error handling: - -- Validate profile data for integrity -- Implement retry mechanisms for network operations -- Provide fallbacks for missing or corrupted data -- Log errors for debugging -- Handle timeout cases gracefully - -Proper error handling ensures a smooth player experience even when network conditions are less than ideal. - -### Performance Considerations - -Keep performance in mind: - -- Unload inactive profiles to conserve memory -- Batch database operations for efficiency -- Use appropriate property types for data size -- Consider compression for large profile data -- Profile the system under typical load conditions - -Regular performance monitoring helps identify and address bottlenecks before they impact players. - -## Conclusion - -The Master Server Toolkit's Profile System provides a robust foundation for managing player data in multiplayer games. With its real-time synchronization, automatic change tracking, and flexible architecture, it enables developers to focus on game features rather than the complexities of distributed data management. - -By following the guidelines and examples in this documentation, developers can efficiently implement profile management that scales with their game's needs while providing a seamless experience for players. - -For more information and advanced usage, refer to the official Master Server Toolkit documentation and example projects. \ No newline at end of file diff --git a/Docs/README.md b/Docs/README.md new file mode 100644 index 0000000..c2a435c --- /dev/null +++ b/Docs/README.md @@ -0,0 +1,86 @@ +# Master Server Toolkit - Документация + +## Описание +Master Server Toolkit - это фреймворк для создания многопользовательских игр с архитектурой клиент-сервер. Он предоставляет готовые модули для аутентификации, профилей, комнат, лобби, чата и многих других аспектов многопользовательской игры. + +## Основные модули + +### Ядро системы +- [Authentication](Modules/Authentication.md) - Аутентификация и управление пользователями +- [Profiles](Modules/Profiles.md) - Профили пользователей и управление данными +- [Rooms](Modules/Rooms.md) - Система комнат и игровых сессий + +### Игровые модули +- [Achievements](Modules/Achievements.md) - Система достижений +- [Censor](Modules/Censor.md) - Фильтрация нежелательного контента +- [Chat](Modules/Chat.md) - Система чата и обмена сообщениями +- [Lobbies](Modules/Lobbies.md) - Система лобби перед игрой +- [Matchmaker](Modules/Matchmaker.md) - Подбор игр и фильтрация по критериям +- [Notification](Modules/Notification.md) - Система уведомлений +- [Ping](Modules/Ping.md) - Проверка соединения и замер задержки +- [QuestsModule](Modules/QuestsModule.md) - Система квестов и заданий +- [WorldRooms](Modules/WorldRooms.md) - Система постоянных игровых зон + +### Инфраструктура +- [Spawner](Modules/Spawner.md) - Запуск игровых серверов +- [WebServer](Modules/WebServer.md) - Встроенный веб-сервер для API и админ-панели + +### Аналитика и Мониторинг +- [AnalyticsModule](Modules/AnalyticsModule.md) - Сбор и анализ игровых событий + +### Инструменты +- [Tools](Tools/README.md) - Набор вспомогательных инструментов для разработки + - [UI Framework](Tools/UI/README.md) - Система пользовательского интерфейса + - [Attributes](Tools/Attributes.md) - Расширения для инспектора Unity + - [Terminal](Tools/Terminal.md) - Отладочный терминал + - [Tweener](Tools/Tweener.md) - Инструменты анимации + - [Utilities](Tools/Utilities.md) - Вспомогательные утилиты + +## Структура модулей + +Каждый модуль обычно состоит из следующих компонентов: +1. **Серверный модуль** (`*Module.cs`) - серверная логика модуля +2. **Серверная реализация** (`*ModuleServer.cs`) - реализация API сервера +3. **Клиентская часть** (`*ModuleClient.cs`) - клиентское API для взаимодействия с сервером +4. **Пакеты** (`Packets/*.cs`) - структуры данных для обмена между клиентом и сервером +5. **Интерфейсы и модели** - определение контрактов и объектов данных + +## Начало работы + +1. **Настройка мастер-сервера**: + ```csharp + // Добавление необходимых модулей + var authModule = gameObject.AddComponent(); + var profilesModule = gameObject.AddComponent(); + var roomsModule = gameObject.AddComponent(); + + // Настройка подключения к базе данных + gameObject.AddComponent(); + ``` + +2. **Настройка клиента**: + ```csharp + // Подключение к серверу + Mst.Client.Connection.Connect("127.0.0.1", 5000, (successful, error) => { + if (successful) + Debug.Log("Подключение установлено"); + }); + + // Использование модулей + Mst.Client.Auth.SignIn(username, password, (successful, error) => { + if (successful) + Debug.Log("Аутентификация успешна"); + }); + ``` + +## Лучшие практики + +1. **Модульная архитектура** - добавляйте только те модули, которые вам нужны +2. **Использование интерфейсов** - создавайте собственные реализации для интеграции с вашей системой +3. **Безопасность** - всегда проверяйте права доступа на стороне сервера +4. **Масштабирование** - используйте систему спаунеров для балансировки нагрузки +5. **Планирование** - продумайте взаимодействие модулей заранее + +## Дополнительная информация + +Для более подробной информации обратитесь к документации соответствующих модулей. diff --git a/Docs/ThrottleDispatcher Documentation.md b/Docs/ThrottleDispatcher Documentation.md deleted file mode 100644 index 5262084..0000000 --- a/Docs/ThrottleDispatcher Documentation.md +++ /dev/null @@ -1,389 +0,0 @@ -# ThrottleDispatcher Documentation - -## Introduction - -`ThrottleDispatcher` is a class that implements the "throttle" pattern for C#, which limits the rate at which a function can be executed. Unlike debouncing (which delays execution until a period of inactivity), throttling ensures an action executes at most once within a specified time interval, regardless of how many times it's called. This is useful for rate-limiting operations like API calls, event handlers, or resource-intensive calculations. - -## Contents - -- Overview of Classes -- Installation -- Usage Examples - - Basic Throttle with Action - - Asynchronous Throttle - - Using Cancellation - - Getting Results with ThrottleDispatcher\ -- Advanced Scenarios -- Usage Recommendations - -## Overview of Classes - -The library consists of two main classes: - -1. **ThrottleDispatcher\** - The main generic class that allows you to specify the return value type. -2. **ThrottleDispatcher** - A simplified version that inherits from `ThrottleDispatcher` and provides convenient methods for actions without return values. - -## Installation - -Add the `ThrottleDispatcher.cs` and `ThrottleDispatcherGeneric.cs` files to your project and make sure you're using the correct namespace: - -```csharp -using MasterServerToolkit.DebounceThrottle; -``` - -## Usage Examples - -### Basic Throttle with Action - -```csharp -// Create a throttle dispatcher with a 1000ms interval -var throttler = new ThrottleDispatcher(1000); - -// Simulate a method called frequently, e.g., on button click -void HandleButtonClick(string buttonId) -{ - // Wrap the handler in Throttle to limit execution frequency - throttler.Throttle(() => { - Console.WriteLine($"Button {buttonId} clicked at {DateTime.Now.ToString("HH:mm:ss.fff")}"); - // Here would be code that performs an API call or other expensive operation - }); -} - -// Simulation of rapid sequential clicks -for (int i = 0; i < 10; i++) -{ - HandleButtonClick("Submit"); - Thread.Sleep(200); // Simulate clicks every 200ms -} - -// Only approximately 2-3 clicks will be processed within 2 seconds -// (First click, then one click after each 1000ms interval) -``` - -### Asynchronous Throttle - -```csharp -// Create a throttle dispatcher with a 2000ms interval -// Set delayAfterExecution to true to ensure minimum 2000ms between completions -var throttler = new ThrottleDispatcher(2000, delayAfterExecution: true); - -// Asynchronous method that makes API calls -async Task MakeApiRequestAsync(string requestData) -{ - // Use asynchronous throttle - await throttler.ThrottleAsync(async () => - { - Console.WriteLine($"Making API request with data: {requestData} at {DateTime.Now.ToString("HH:mm:ss.fff")}"); - - // Simulating an API request - await Task.Delay(500); // Simulating network operation - - Console.WriteLine($"API request completed at {DateTime.Now.ToString("HH:mm:ss.fff")}"); - }); -} - -// Usage in an asynchronous method -async Task RunApiExample() -{ - // Send multiple requests in parallel - var tasks = new List(); - for (int i = 0; i < 5; i++) - { - tasks.Add(MakeApiRequestAsync($"Request {i+1}")); - } - - // Wait for all requests to complete - await Task.WhenAll(tasks); - - // Due to throttling, these will be processed with 2 seconds between the completion of one - // and the start of the next (because we set delayAfterExecution to true) -} -``` - -### Using Cancellation - -```csharp -// Create a throttle dispatcher with a 1500ms interval -var throttler = new ThrottleDispatcher(1500); - -// Cancellation token source -using var cts = new CancellationTokenSource(); - -// Method with cancellation support -async Task PerformOperationWithCancellation() -{ - try - { - await throttler.ThrottleAsync(async () => - { - Console.WriteLine($"Starting operation at {DateTime.Now.ToString("HH:mm:ss.fff")}"); - - // Simulating a long operation - for (int i = 0; i < 5; i++) - { - // Check for cancellation token in the loop - if (cts.Token.IsCancellationRequested) - { - Console.WriteLine("Operation was cancelled during execution"); - return; - } - - await Task.Delay(300); // Simulating an operation step - Console.WriteLine($"Step {i + 1} of 5"); - } - - Console.WriteLine($"Operation completed at {DateTime.Now.ToString("HH:mm:ss.fff")}"); - }, cts.Token); - } - catch (OperationCanceledException) - { - Console.WriteLine("Operation was cancelled before execution started"); - } -} - -// Example of using with cancellation -async Task CancellationExample() -{ - // Start multiple operations - var tasks = new List(); - for (int i = 0; i < 3; i++) - { - tasks.Add(PerformOperationWithCancellation()); - } - - // After 2000ms cancel all pending operations - await Task.Delay(2000); - cts.Cancel(); - - try - { - // Wait for all tasks to complete or be cancelled - await Task.WhenAll(tasks); - } - catch (OperationCanceledException) - { - Console.WriteLine("Some operations were cancelled"); - } -} -``` - -### Getting Results with ThrottleDispatcher\ - -```csharp -// Create a generic throttle dispatcher to get numeric results -// Also enable resetIntervalOnException to reset timing when errors occur -var resultThrottler = new ThrottleDispatcher(3000, resetIntervalOnException: true); - -// Function that will return a result -async Task FetchDataWithResultAsync(string query) -{ - return await resultThrottler.ThrottleAsync(async () => - { - Console.WriteLine($"Fetching data for: {query} at {DateTime.Now.ToString("HH:mm:ss.fff")}"); - - // Simulating an API request - await Task.Delay(1000); - - // Simulate occasional errors - if (query.Contains("error")) - { - throw new Exception("Error in API request"); - } - - return query.Length * 10; // Return some calculated result - }); -} - -// Usage -async Task ResultExample() -{ - try - { - // These will all run at the throttled rate of one per 3 seconds - int result1 = await FetchDataWithResultAsync("data1"); - Console.WriteLine($"Result 1: {result1}"); - - int result2 = await FetchDataWithResultAsync("data2"); - Console.WriteLine($"Result 2: {result2}"); - - // This will throw an exception and reset the interval counter - int result3 = await FetchDataWithResultAsync("error_data"); - } - catch (Exception ex) - { - Console.WriteLine($"Caught exception: {ex.Message}"); - - // We can immediately try again because resetIntervalOnException is true - int result4 = await FetchDataWithResultAsync("recovery_data"); - Console.WriteLine($"Result after error: {result4}"); - } -} -``` - -## Advanced Scenarios - -### Implementing a Rate-Limited API Client - -```csharp -public class RateLimitedApiClient -{ - private readonly ThrottleDispatcher _apiThrottler; - private readonly HttpClient _httpClient; - - public RateLimitedApiClient(int rateLimit) - { - // Create a throttle with delayAfterExecution to respect API rate limits - _apiThrottler = new ThrottleDispatcher( - interval: 1000 * 60 / rateLimit, // Convert requests-per-minute to ms - delayAfterExecution: true, - resetIntervalOnException: false); - - _httpClient = new HttpClient(); - } - - public async Task GetAsync(string endpoint) - { - return await _apiThrottler.ThrottleAsync(async () => - { - Console.WriteLine($"Making API request to {endpoint}"); - var response = await _httpClient.GetAsync(endpoint); - response.EnsureSuccessStatusCode(); - return await response.Content.ReadAsStringAsync(); - }); - } - - public async Task PostAsync(string endpoint, string content) - { - return await _apiThrottler.ThrottleAsync(async () => - { - Console.WriteLine($"Making POST request to {endpoint}"); - var httpContent = new StringContent(content, System.Text.Encoding.UTF8, "application/json"); - var response = await _httpClient.PostAsync(endpoint, httpContent); - response.EnsureSuccessStatusCode(); - return await response.Content.ReadAsStringAsync(); - }); - } - - public void Dispose() - { - _apiThrottler.Dispose(); - _httpClient.Dispose(); - } -} -``` - -### Window Event Throttling in WPF - -```csharp -public class ResizeThrottler -{ - private readonly ThrottleDispatcher _throttler; - private readonly Window _window; - - public ResizeThrottler(Window window, int throttleRate = 300) - { - _window = window; - _throttler = new ThrottleDispatcher(throttleRate); - - // Subscribe to window size changed events - _window.SizeChanged += Window_SizeChanged; - } - - private void Window_SizeChanged(object sender, SizeChangedEventArgs e) - { - // Throttle the resize handler to prevent excessive UI updates - _throttler.Throttle(() => - { - // This will execute at most once per throttleRate ms - Console.WriteLine($"Window resized to {_window.Width}x{_window.Height}"); - - // Here you would update layout, recalculate positions, etc. - UpdateLayout(_window.Width, _window.Height); - }); - } - - private void UpdateLayout(double width, double height) - { - // Complex layout calculations that would be expensive to run on every resize event - Console.WriteLine($"Performing layout update for {width}x{height}"); - } - - public void Cleanup() - { - _window.SizeChanged -= Window_SizeChanged; - _throttler.Dispose(); - } -} -``` - -## Usage Recommendations - -1. **Choose the right interval based on your use case**: - - For UI responsiveness: 100-300 ms - - For standard API rate limits: Calculate based on limits (e.g., 60 requests/minute = 1000ms) - - For heavy computational tasks: 500+ ms - -2. **Understand delayAfterExecution parameter**: - - `false` (default): Interval starts when the function is called - - `true`: Interval starts when the function completes execution - - Use `true` for strict rate-limiting of resource-intensive operations - -3. **Understand resetIntervalOnException parameter**: - - `false` (default): Keep throttling even if operations fail - - `true`: Reset interval on exceptions, allowing immediate retry - - Use `true` when you want to retry failed operations immediately - -4. **Handle exceptions properly**: - ```csharp - try - { - await throttler.ThrottleAsync(async () => { /* code */ }); - } - catch (OperationCanceledException) - { - // Handle cancellation - } - catch (Exception ex) - { - // Handle other exceptions - } - ``` - -5. **Properly dispose throttlers to prevent memory leaks**: - ```csharp - public class MyService : IDisposable - { - private readonly ThrottleDispatcher _throttler = new ThrottleDispatcher(500); - - // Using _throttler... - - public void Dispose() - { - _throttler.Dispose(); - } - } - ``` - -6. **Understand the difference between Throttling and Debouncing**: - - **Throttling**: Limits execution to once per time interval (rate limiting) - - **Debouncing**: Delays execution until a period of inactivity (coalescing events) - - Choose the right technique for your use case: - - Use throttling for: API rate limits, scroll handlers, resource-intensive operations - - Use debouncing for: Search inputs, resize events, button clicks that should ignore double-clicks - -7. **Consider shared throttlers for system-wide rate limits**: - ```csharp - // Singleton pattern for a shared API throttler - public class ApiThrottler - { - private static readonly ThrottleDispatcher _instance = new ThrottleDispatcher(1000, true); - - public static ThrottleDispatcher Instance => _instance; - - // Private constructor to prevent instantiation - private ApiThrottler() { } - } - ``` - -This documentation covers the basic usage of `ThrottleDispatcher` and `ThrottleDispatcher` and should help you effectively apply these classes in your project. \ No newline at end of file diff --git a/Docs/Tools/Attributes.md b/Docs/Tools/Attributes.md new file mode 100644 index 0000000..64bc603 --- /dev/null +++ b/Docs/Tools/Attributes.md @@ -0,0 +1,141 @@ +# Master Server Toolkit - Attributes + +## Описание +Инструменты для работы с атрибутами и аннотациями в редакторе Unity, улучшающие пользовательский интерфейс инспектора. + +## HelpBox + +Класс для создания информационных блоков в инспекторе Unity с поддержкой различных типов сообщений. + +### Основные свойства: + +```csharp +public string Text { get; set; } // Текст сообщения +public float Height { get; set; } // Высота блока +public HelpBoxType Type { get; set; } // Тип блока (Info, Warning, Error) +``` + +### Конструкторы: + +```csharp +// Создание с указанием высоты +public HelpBox(string text, float height, HelpBoxType type = HelpBoxType.Info) + +// Создание со стандартной высотой +public HelpBox(string text, HelpBoxType type = HelpBoxType.Info) + +// Создание пустого блока +public HelpBox() +``` + +### Пример использования: + +```csharp +// В ScriptableObject или MonoBehaviour +[SerializeField] +private HelpBox infoBox = new HelpBox("Это информационный блок", HelpBoxType.Info); + +[SerializeField] +private HelpBox warningBox = new HelpBox("Внимание! Это предупреждение", 60, HelpBoxType.Warning); + +[SerializeField] +private HelpBox errorBox = new HelpBox("Ошибка! Это сообщение об ошибке", HelpBoxType.Error); +``` + +### Интеграция в редакторе: + +```csharp +// В пользовательском редакторе +[CustomPropertyDrawer(typeof(HelpBox))] +public class HelpBoxDrawer : PropertyDrawer +{ + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + var textProp = property.FindPropertyRelative("Text"); + var typeProp = property.FindPropertyRelative("Type"); + var heightProp = property.FindPropertyRelative("Height"); + + // Рисуем блок помощи в зависимости от типа + EditorGUI.HelpBox( + new Rect(position.x, position.y, position.width, heightProp.floatValue), + textProp.stringValue, + (MessageType)typeProp.enumValueIndex + ); + } + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) + { + return property.FindPropertyRelative("Height").floatValue; + } +} +``` + +### Типы блоков: + +```csharp +public enum HelpBoxType +{ + Info, // Информационное сообщение + Warning, // Предупреждение + Error // Ошибка +} +``` + +## Практическое применение + +### Документирование настроек: + +```csharp +[Serializable] +public class ConnectionSettings +{ + [SerializeField] + private HelpBox helpBox = new HelpBox( + "Укажите IP-адрес и порт сервера. Оставьте поле IP пустым для использования localhost.", + HelpBoxType.Info + ); + + [SerializeField] + private string serverIp; + + [SerializeField] + private int serverPort = 5000; +} +``` + +### Предупреждения в компонентах: + +```csharp +[RequireComponent(typeof(NetworkIdentity))] +public class PlayerController : MonoBehaviour +{ + [SerializeField] + private HelpBox helpBox = new HelpBox( + "Этот компонент требует NetworkIdentity для корректной работы с сервером", + HelpBoxType.Warning + ); + + // Реализация компонента +} +``` + +### Сообщения об ошибках: + +```csharp +[Serializable] +public class SecuritySettings +{ + [SerializeField] + private HelpBox securityNote = new HelpBox( + "ВНИМАНИЕ! Никогда не сохраняйте чувствительные данные в исходном коде!", + 80, + HelpBoxType.Error + ); + + [SerializeField] + private string certificatePath; + + [SerializeField] + private string privateKey; +} +``` diff --git a/Docs/Tools/DebounceThrottle.md b/Docs/Tools/DebounceThrottle.md new file mode 100644 index 0000000..37059a4 --- /dev/null +++ b/Docs/Tools/DebounceThrottle.md @@ -0,0 +1,195 @@ +# Master Server Toolkit - DebounceThrottle + +## Описание +Система для управления частыми вызовами функций с использованием паттернов Debounce и Throttle. Помогает оптимизировать производительность, контролируя количество выполнений операций в единицу времени. + +## Основные компоненты + +### DebounceDispatcher +Отложенный вызов - выполняет функцию только один раз, после окончания серии вызовов, с заданной задержкой. + +```csharp +// Создание с интервалом в миллисекундах +var debouncer = new DebounceDispatcher(500); // 500 мс задержка + +// Использование с асинхронной функцией +await debouncer.DebounceAsync(async () => { + await SaveDataAsync(); +}); + +// Использование с синхронной функцией +debouncer.Debounce(() => { + UpdateUi(); +}); +``` + +### ThrottleDispatcher +Ограничение вызовов - гарантирует, что функция будет вызвана не чаще, чем указанный интервал, отбрасывая промежуточные вызовы. + +```csharp +// Создание с интервалом и опциями +var throttler = new ThrottleDispatcher( + 1000, // 1000 мс интервал + delayAfterExecution: false, // Начинать отсчет интервала с начала выполнения + resetIntervalOnException: true // Сбрасывать интервал при исключении +); + +// Использование с асинхронной функцией +await throttler.ThrottleAsync(async () => { + await SendAnalyticsDataAsync(); +}); + +// Использование с синхронной функцией +throttler.Throttle(() => { + UpdateProgressBar(); +}); +``` + +## Дженерик-версии + +### DebounceDispatcher +Версия с типизированным возвращаемым значением. + +```csharp +var typedDebouncer = new DebounceDispatcher(500); + +// Получение результата из функции +int result = await typedDebouncer.DebounceAsync(async () => { + return await CalculateValueAsync(); +}); +``` + +### ThrottleDispatcher +Версия с типизированным возвращаемым значением. + +```csharp +var typedThrottler = new ThrottleDispatcher>(1000); + +// Получение результата из функции +List items = await typedThrottler.ThrottleAsync(async () => { + return await FetchItemsAsync(); +}); +``` + +## Практические примеры + +### Поиск в реальном времени + +```csharp +public class SearchHandler : MonoBehaviour +{ + private DebounceDispatcher debouncer; + + private void Start() + { + debouncer = new DebounceDispatcher(300); // 300 мс задержка + } + + // Вызывается при изменении текста в поле ввода + public void OnSearchTextChanged(string text) + { + debouncer.Debounce(() => { + // Запрос к API только когда пользователь перестал печатать + StartCoroutine(PerformSearch(text)); + }); + } +} +``` + +### Ограничение запросов к серверу + +```csharp +public class ApiClient : MonoBehaviour +{ + private ThrottleDispatcher throttler; + + private void Start() + { + // Не более 1 запроса в секунду + throttler = new ThrottleDispatcher(1000); + } + + public async Task GetPlayerDataAsync(string playerId) + { + return await throttler.ThrottleAsync(async () => { + // Запрос к серверу с ограничением частоты + return await FetchPlayerDataFromServerAsync(playerId); + }); + } +} +``` + +### Обработка ввода пользователя + +```csharp +public class InputHandler : MonoBehaviour +{ + private ThrottleDispatcher throttler; + + private void Start() + { + // Обрабатывать не чаще 10 раз в секунду + throttler = new ThrottleDispatcher(100); + } + + private void Update() + { + if (Input.GetKey(KeyCode.Space)) + { + throttler.Throttle(() => { + // Действие по нажатию пробела, ограниченное по частоте + FireWeapon(); + }); + } + } +} +``` + +### Автосохранение с отложенным запуском + +```csharp +public class DocumentEditor : MonoBehaviour +{ + private DebounceDispatcher saveDebouncer; + + private void Start() + { + // Сохранять через 2 секунды после последнего изменения + saveDebouncer = new DebounceDispatcher(2000); + } + + public void OnDocumentChanged() + { + // Визуальная индикация несохраненных изменений + ShowUnsavedChangesIndicator(); + + // Отложенное сохранение + saveDebouncer.Debounce(() => { + SaveDocument(); + HideUnsavedChangesIndicator(); + }); + } +} +``` + +## Отличия Debounce от Throttle + +### Debounce (Отложенный вызов) +- Задерживает выполнение до тех пор, пока не пройдет указанный интервал без вызовов +- Идеально для событий, которые могут быстро следовать друг за другом, но требуют обработки только после их завершения +- Примеры: поиск при вводе, изменение размера окна, прокрутка + +### Throttle (Ограничение частоты) +- Гарантирует, что функция выполняется не чаще, чем один раз в указанный интервал +- Идеально для ограничения частоты повторяющихся действий +- Примеры: отправка аналитики, обновление интерфейса, запросы к API + +## Опции и настройки + +### DebounceDispatcher +- `interval` - время в миллисекундах, которое должно пройти без вызовов, прежде чем функция будет выполнена + +### ThrottleDispatcher +- `interval` - минимальное время в миллисекундах между вызовами функции +- `delayAfterExecution` - если true, интервал отсчитывается после завершения выполнения функции +- `resetIntervalOnException` - если true, сбрасывает интервал при возникновении исключения diff --git a/Docs/Tools/README.md b/Docs/Tools/README.md new file mode 100644 index 0000000..9b1bf6a --- /dev/null +++ b/Docs/Tools/README.md @@ -0,0 +1,31 @@ +# Master Server Toolkit - Tools + +## Обзор + +Master Server Toolkit предоставляет набор инструментов для упрощения разработки многопользовательских игр. Эти инструменты решают общие задачи в разработке игр и могут использоваться независимо от других модулей фреймворка. + +## Доступные инструменты + +### [UI Framework](UI/README.md) +Система управления пользовательским интерфейсом с представлениями, компонентами и валидацией. +- [Views System](UI/Views.md) - Управление UI экранами +- [UI Components](UI/Components.md) - Готовые компоненты UI +- [Validation System](UI/Validation.md) - Валидация форм ввода + +### [Attributes](Attributes.md) +Атрибуты для упрощения инспектора Unity и отладки. + +### [DebounceThrottle](DebounceThrottle.md) +Механизмы для ограничения частоты вызова функций. + +### [Terminal](Terminal.md) +Консольный терминал для отладки и управления игрой. + +### [Tweener](Tweener.md) +Инструмент для создания плавных анимаций и переходов. + +### [Utilities](Utilities.md) +Различные вспомогательные функции и классы. + +### [WebGL](WebGL.md) +Вспомогательные инструменты для разработки под WebGL платформу. \ No newline at end of file diff --git a/Docs/Tools/Terminal.md b/Docs/Tools/Terminal.md new file mode 100644 index 0000000..067eb5c --- /dev/null +++ b/Docs/Tools/Terminal.md @@ -0,0 +1,261 @@ +# Master Server Toolkit - Terminal + +## Описание +Встроенная консоль для Unity, позволяющая выполнять команды, просматривать логи и взаимодействовать с игрой в режиме выполнения. Терминал подключается к логам Unity и предоставляет интерфейс для регистрации и выполнения пользовательских команд. + +## Основные компоненты + +### Terminal +Основной класс, управляющий отображением и взаимодействием с консолью. + +```csharp +// Получение экземпляра +Terminal terminal = Terminal.Instance; + +// Логирование сообщений +Terminal.Log("Сообщение для консоли"); +Terminal.Log(TerminalLogType.Warning, "Предупреждение: {0}", "текст предупреждения"); +Terminal.Log(TerminalLogType.Error, "Ошибка: {0}", "текст ошибки"); + +// Управление состоянием +terminal.SetState(TerminalState.OpenSmall); // Открыть маленькое окно +terminal.SetState(TerminalState.OpenFull); // Открыть полное окно +terminal.SetState(TerminalState.Close); // Закрыть консоль +terminal.ToggleState(TerminalState.OpenSmall); // Переключить состояние +``` + +### CommandShell +Обработчик команд в терминале. + +```csharp +// Доступ к командному интерпретатору +CommandShell shell = Terminal.Shell; + +// Выполнение команды +shell.RunCommand("help"); + +// Проверка наличия ошибок +if (Terminal.IssuedError) +{ + Debug.LogError(shell.IssuedErrorMessage); +} +``` + +### CommandHistory +Управляет историей ввода команд. + +```csharp +// Доступ к истории команд +CommandHistory history = Terminal.History; + +// Получение предыдущей команды (при нажатии "вверх") +string prevCommand = history.Previous(); + +// Получение следующей команды (при нажатии "вниз") +string nextCommand = history.Next(); + +// Добавление команды в историю +history.Push("help"); +``` + +### CommandAutocomplete +Обеспечивает автодополнение команд. + +```csharp +// Доступ к автодополнению +CommandAutocomplete autocomplete = Terminal.Autocomplete; + +// Регистрация новой команды для автодополнения +autocomplete.Register("my_command"); + +// Получение вариантов автодополнения +string headText = "he"; +string[] completions = autocomplete.Complete(ref headText); +// completions = ["lp", "llo"] +// headText = "he" +``` + +## Регистрация команд + +### Использование атрибутов: +```csharp +// Регистрация команды через атрибут +public class MyCommands : MonoBehaviour +{ + [RegisterCommand("hello", "Выводит приветствие")] + static void HelloCommand(CommandArg[] args) + { + Terminal.Log("Привет, мир!"); + } + + [RegisterCommand("add", "Складывает два числа")] + static void AddCommand(CommandArg[] args) + { + if (args.Length < 2) + { + throw new CommandException("Необходимо указать два числа"); + } + + int a = args[0].Int; + int b = args[1].Int; + + Terminal.Log("{0} + {1} = {2}", a, b, a + b); + } +} +``` + +### Ручная регистрация: +```csharp +// Регистрация команды вручную +Terminal.Shell.RegisterCommand("time", "Показывает текущее время", args => +{ + Terminal.Log("Текущее время: {0}", DateTime.Now.ToString("HH:mm:ss")); +}); + +// Регистрация команды с параметрами +Terminal.Shell.RegisterCommand("greet", "Приветствует пользователя", args => +{ + string name = args.Length > 0 ? args[0].String : "незнакомец"; + Terminal.Log("Привет, {0}!", name); +}); +``` + +## Настройка внешнего вида + +```csharp +[Header("Window")] +[Range(0, 1)] +[SerializeField] float maxHeight = 0.7f; // Максимальная высота окна + +[Range(100, 1000)] +[SerializeField] float toggleSpeed = 360; // Скорость открытия/закрытия + +[SerializeField] string toggleHotkey = "`"; // Горячая клавиша для открытия +[SerializeField] string toggleFullHotkey = "#`"; // Горячая клавиша для полноэкранного режима + +[Header("Input")] +[SerializeField] Font consoleFont; // Шрифт консоли +[SerializeField] string inputCaret = ">"; // Строка ввода +[SerializeField] bool showGUIButtons; // Показывать кнопки GUI +[SerializeField] bool rightAlignButtons; // Выравнивание кнопок справа + +[Header("Theme")] +[SerializeField] Color backgroundColor = Color.black; // Цвет фона +[SerializeField] Color foregroundColor = Color.white; // Основной цвет текста +[SerializeField] Color shellColor = Color.white; // Цвет сообщений оболочки +[SerializeField] Color inputColor = Color.cyan; // Цвет ввода +[SerializeField] Color warningColor = Color.yellow; // Цвет предупреждений +[SerializeField] Color errorColor = Color.red; // Цвет ошибок +``` + +## Обработка аргументов + +CommandArg предоставляет методы для конвертации строковых аргументов в различные типы: + +```csharp +Terminal.Shell.RegisterCommand("complex", "Демонстрация работы с аргументами", args => +{ + if (args.Length < 3) + { + throw new CommandException("Укажите имя, возраст и баланс"); + } + + string name = args[0].String; // Получить строку + int age = args[1].Int; // Получить целое число + float balance = args[2].Float; // Получить число с плавающей точкой + bool isPremium = args.Length > 3 ? args[3].Bool : false; // Получить булево значение + + Terminal.Log("Имя: {0}, Возраст: {1}, Баланс: {2:F2}, Премиум: {3}", + name, age, balance, isPremium ? "Да" : "Нет"); +}); +``` + +## Примеры использования + +### Системные команды: +```csharp +// help - Список всех команд +// clear - Очистить консоль +// version - Показать версию +// quit - Выйти из приложения +``` + +### Создание игровых чит-кодов: +```csharp +public class CheatCommands : MonoBehaviour +{ + [RegisterCommand("god", "Включает режим бессмертия")] + static void GodModeCommand(CommandArg[] args) + { + bool enable = args.Length == 0 || args[0].Bool; + PlayerManager.Instance.SetGodMode(enable); + Terminal.Log("Режим бессмертия: {0}", enable ? "включен" : "выключен"); + } + + [RegisterCommand("gold", "Добавляет золото")] + static void AddGoldCommand(CommandArg[] args) + { + int amount = args.Length > 0 ? args[0].Int : 100; + PlayerManager.Instance.AddGold(amount); + Terminal.Log("Добавлено {0} золота", amount); + } + + [RegisterCommand("spawn", "Создает объект")] + static void SpawnCommand(CommandArg[] args) + { + if (args.Length == 0) + { + throw new CommandException("Укажите имя объекта для создания"); + } + + string prefabName = args[0].String; + int count = args.Length > 1 ? args[1].Int : 1; + + GameManager.Instance.SpawnPrefab(prefabName, count); + Terminal.Log("Создано {0}x {1}", count, prefabName); + } +} +``` + +### Интеграция с системой логирования: +```csharp +public class CustomLogger : MonoBehaviour +{ + void Start() + { + // Перенаправляет логи Unity в терминал + Application.logMessageReceived += HandleLog; + } + + void HandleLog(string message, string stackTrace, LogType type) + { + TerminalLogType terminalLogType; + + switch (type) + { + case LogType.Warning: + terminalLogType = TerminalLogType.Warning; + break; + case LogType.Error: + case LogType.Exception: + terminalLogType = TerminalLogType.Error; + break; + default: + terminalLogType = TerminalLogType.Message; + break; + } + + Terminal.Log(terminalLogType, message); + } +} +``` + +## Лучшие практики + +1. **Используйте групповые префиксы** для связанных команд (например, `player_health`, `player_ammo`) +2. **Добавляйте информативные описания** для всех команд +3. **Обрабатывайте ошибки и краевые случаи** при работе с аргументами +4. **Категоризируйте сообщения** используя соответствующие типы логов +5. **Не выводите конфиденциальную информацию** через консоль в публичных сборках +6. **Разделяйте команды для отладки и команды для игроков** +7. **Реализуйте уровни доступа** для команд в публичных билдах diff --git a/Docs/Tools/Tweener.md b/Docs/Tools/Tweener.md new file mode 100644 index 0000000..ba19fed --- /dev/null +++ b/Docs/Tools/Tweener.md @@ -0,0 +1,299 @@ +# Master Server Toolkit - Tweener + +## Описание +Гибкая система для создания анимированных переходов, интерполяций и отложенных действий. Tweener позволяет плавно изменять значения различных типов (float, int, string) с течением времени, а также создавать последовательности действий. + +## Основные возможности + +### Базовый Tweener +Общий класс для управления и запуска всех Tween-действий. + +```csharp +// Запуск простого действия +TweenerActionInfo actionInfo = Tweener.Start(() => { + // Возвращаем true, когда действие завершено + return true; +}); + +// Отмена действия +actionInfo.Cancel(); +// или +Tweener.Cancel(actionInfo); +// или по ID +Tweener.Cancel(actionId); + +// Проверка, выполняется ли действие +bool isRunning = actionInfo.IsRunning; +// или +bool isRunning = Tweener.IsRunning(actionInfo); +// или по ID +bool isRunning = Tweener.IsRunning(actionId); + +// Обработка завершения +actionInfo.OnComplete((id) => { + Debug.Log($"Действие {id} завершено"); +}); +// или +Tweener.AddOnCompleteListener(actionInfo, (id) => { + Debug.Log($"Действие {id} завершено"); +}); +``` + +### Tween.Float +Плавная интерполяция значений с плавающей точкой. + +```csharp +// Переход от 0 до 1 за 2 секунды с линейной интерполяцией +TweenerActionInfo actionInfo = Tween.Float(0f, 1f, 2f, Easing.Linear, (float value) => { + // Обновление значения во время анимации + myCanvasGroup.alpha = value; +}); + +// Переход с отложенным стартом +TweenerActionInfo actionInfo = Tween.Float(0f, 1f, 2f, Easing.Linear, (float value) => { + myCanvasGroup.alpha = value; +}, 1f); // Задержка в 1 секунду + +// Переход с кастомной кривой анимации +AnimationCurve curve = new AnimationCurve( + new Keyframe(0, 0), + new Keyframe(0.5f, 0.8f), + new Keyframe(1, 1) +); + +TweenerActionInfo actionInfo = Tween.Float(0f, 1f, 2f, curve, (float value) => { + myCanvasGroup.alpha = value; +}); + +// Переход с обратной анимацией (пинг-понг) +TweenerActionInfo actionInfo = Tween.Float(0f, 1f, 2f, Easing.Linear, (float value) => { + myCanvasGroup.alpha = value; +}, 0f, true); +``` + +### Tween.Int +Плавная интерполяция целочисленных значений. + +```csharp +// Анимация счетчика от 0 до 100 за 3 секунды +TweenerActionInfo actionInfo = Tween.Int(0, 100, 3f, Easing.OutQuad, (int value) => { + scoreText.text = value.ToString(); +}); + +// Анимация с обратным отсчетом +TweenerActionInfo actionInfo = Tween.Int(10, 0, 5f, Easing.Linear, (int value) => { + countdownText.text = value.ToString(); +}); +``` + +### Tween.String +Анимированная замена символов в строке. + +```csharp +// Анимация печатающегося текста +TweenerActionInfo actionInfo = Tween.String("", "Привет, мир!", 2f, (string value) => { + dialogText.text = value; +}); + +// Анимация с маской ввода +TweenerActionInfo actionInfo = Tween.String("", "12345", 1f, (string value) => { + passwordText.text = new string('*', value.Length); +}); +``` + +### Вспомогательные методы + +#### Wait - ожидание заданного времени +```csharp +// Ожидание 2 секунды и выполнение действия +TweenerActionInfo actionInfo = Tween.Wait(2f, () => { + DoSomething(); +}); + +// Ожидание в последовательности действий +StartCoroutine(WaitExample()); + +IEnumerator WaitExample() +{ + Debug.Log("Действие 1"); + + // Ожидание через Tween + var waitInfo = Tween.Wait(2f, null); + yield return new WaitUntil(() => !waitInfo.IsRunning); + + Debug.Log("Действие 2"); +} +``` + +#### Sequence - последовательность действий +```csharp +// Создание последовательности действий +Tween.Sequence() + .Add(() => { + Debug.Log("Шаг 1"); + return true; + }) + .Wait(1f) + .Add(() => { + Debug.Log("Шаг 2"); + return true; + }) + .Wait(1f) + .Add(() => { + Debug.Log("Шаг 3"); + return true; + }) + .Start(); +``` + +#### RepeatForever - бесконечное повторение +```csharp +// Пульсация объекта +Tween.RepeatForever(() => { + return Tween.Sequence() + .Add(Tween.Float(1f, 1.2f, 0.5f, Easing.InOutQuad, (value) => { + transform.localScale = new Vector3(value, value, value); + })) + .Add(Tween.Float(1.2f, 1f, 0.5f, Easing.InOutQuad, (value) => { + transform.localScale = new Vector3(value, value, value); + })); +}); +``` + +## Функции анимации (Easing) + +Tweener поддерживает различные функции анимации для управления характером перехода: + +```csharp +// Линейная интерполяция +Easing.Linear + +// Квадратичные функции +Easing.InQuad +Easing.OutQuad +Easing.InOutQuad + +// Кубические функции +Easing.InCubic +Easing.OutCubic +Easing.InOutCubic + +// Пружинные функции +Easing.InBounce +Easing.OutBounce +Easing.InOutBounce + +// Эластичные функции +Easing.InElastic +Easing.OutElastic +Easing.InOutElastic + +// Кривая интерполяции из редактора +AnimationCurve customCurve = new AnimationCurve(...); +``` + +## Примеры использования + +### Анимация UI элементов +```csharp +// Плавное появление панели +void ShowPanel() +{ + panel.gameObject.SetActive(true); + panel.transform.localScale = Vector3.zero; + + Tween.Sequence() + .Add(Tween.Float(0f, 1f, 0.3f, Easing.OutBack, (value) => { + panel.transform.localScale = new Vector3(value, value, value); + })) + .Add(Tween.Float(0f, 1f, 0.2f, Easing.OutQuad, (value) => { + panel.GetComponent().alpha = value; + })) + .Start(); +} + +// Плавное исчезновение панели +void HidePanel() +{ + Tween.Sequence() + .Add(Tween.Float(1f, 0f, 0.2f, Easing.InQuad, (value) => { + panel.GetComponent().alpha = value; + })) + .Add(Tween.Float(1f, 0f, 0.3f, Easing.InBack, (value) => { + panel.transform.localScale = new Vector3(value, value, value); + })) + .Add(() => { + panel.gameObject.SetActive(false); + return true; + }) + .Start(); +} +``` + +### Анимация камеры +```csharp +// Плавное перемещение камеры +void MoveCamera(Vector3 targetPosition, float duration) +{ + Vector3 startPosition = Camera.main.transform.position; + + Tween.Float(0f, 1f, duration, Easing.InOutQuad, (value) => { + Camera.main.transform.position = Vector3.Lerp(startPosition, targetPosition, value); + }); +} + +// Плавное изменение поля зрения камеры +void ZoomCamera(float targetFOV, float duration) +{ + float startFOV = Camera.main.fieldOfView; + + Tween.Float(0f, 1f, duration, Easing.OutQuad, (value) => { + Camera.main.fieldOfView = Mathf.Lerp(startFOV, targetFOV, value); + }); +} +``` + +### Создание игровых эффектов +```csharp +// Эффект мигания при уроне +void DamageFlash(SpriteRenderer renderer) +{ + Color normalColor = renderer.color; + Color flashColor = Color.red; + + Tween.Sequence() + .Add(Tween.Float(0f, 1f, 0.1f, Easing.Linear, (value) => { + renderer.color = Color.Lerp(normalColor, flashColor, value); + })) + .Add(Tween.Float(1f, 0f, 0.1f, Easing.Linear, (value) => { + renderer.color = Color.Lerp(normalColor, flashColor, value); + })) + .Start(); +} + +// Эффект пульсации +void PulseEffect(Transform target) +{ + Vector3 originalScale = target.localScale; + Vector3 targetScale = originalScale * 1.2f; + + Tween.Sequence() + .Add(Tween.Float(0f, 1f, 0.3f, Easing.OutQuad, (value) => { + target.localScale = Vector3.Lerp(originalScale, targetScale, value); + })) + .Add(Tween.Float(1f, 0f, 0.3f, Easing.InQuad, (value) => { + target.localScale = Vector3.Lerp(originalScale, targetScale, value); + })) + .Start(); +} +``` + +## Лучшие практики + +1. **Отменяйте незавершенные действия** при уничтожении объектов или смене сцен +2. **Группируйте связанные анимации** в последовательности для лучшего контроля +3. **Используйте подходящие функции** анимации для разных типов движения +4. **Хранитие ссылки на TweenerActionInfo** для возможности отмены или проверки статуса +5. **Обрабатывайте завершение** с помощью OnComplete для последовательных операций +6. **Не злоупотребляйте бесконечными циклами** (RepeatForever), не забывайте их отменять +7. **Используйте Wait** вместо WaitForSeconds в корутинах для единообразия diff --git a/Docs/Tools/UI/Components.md b/Docs/Tools/UI/Components.md new file mode 100644 index 0000000..4b1cbd5 --- /dev/null +++ b/Docs/Tools/UI/Components.md @@ -0,0 +1,213 @@ +# Master Server Toolkit - UI Components + +## Обзор + +UI компоненты в Master Server Toolkit предоставляют готовые решения для отображения данных, настраиваемых свойств, прогресс-баров и многого другого. Они созданы для простой интеграции с системой представлений и упрощения создания сложных пользовательских интерфейсов. + +## Ключевые компоненты + +### UIProperty + +Универсальный компонент для отображения именованных свойств с возможностью задания иконки, значения и прогресс-бара: + +```csharp +public class UIProperty : MonoBehaviour +{ + // Форматы вывода значений (F0 - F5) + public enum UIPropertyValueFormat { F0, F1, F2, F3, F4, F5 } + + // Компоненты + [SerializeField] protected Image iconImage; + [SerializeField] protected TextMeshProUGUI lableText; + [SerializeField] protected TextMeshProUGUI valueText; + [SerializeField] protected Image progressBar; + [SerializeField] protected Color minColor = Color.red; + [SerializeField] protected Color maxColor = Color.green; + + // Настройки + [SerializeField] protected string id = "propertyId"; + [SerializeField] protected float minValue = 0f; + [SerializeField] protected float currentValue = 50f; + [SerializeField] protected float maxValue = float.MaxValue; + [SerializeField] protected float progressSpeed = 1f; + [SerializeField] protected bool smoothValue = true; + [SerializeField] protected string lable = ""; + [SerializeField] protected UIPropertyValueFormat formatValue = UIPropertyValueFormat.F1; + [SerializeField] protected bool invertValue = false; + + // Методы установки значений + public void SetMin(float value); + public void SetMax(float value); + public void SetValue(float value); +} +``` + +### UIProgressBar + +Упрощенная версия UIProperty, сфокусированная на отображении прогресса: + +```csharp +public class UIProgressBar : UIProperty +{ + // Специализированная версия со специфичной логикой отображения прогресса +} +``` + +### UIProgressProperty + +Компонент для связывания прогресс-бара с динамически меняющимся свойством: + +```csharp +public class UIProgressProperty : MonoBehaviour +{ + [SerializeField] protected UIProperty property; + [SerializeField] protected float updateInterval = 0.2f; + [SerializeField] protected UnityEvent onValueChangedEvent; + + // Связывание с источником значения и автоматическое обновление +} +``` + +### UILable + +Компонент для работы с текстовыми метками: + +```csharp +public class UILable : MonoBehaviour +{ + [SerializeField] protected TextMeshProUGUI textComponent; + [SerializeField] protected string text = ""; + + public string Text { get; set; } + public TextMeshProUGUI TextComponent { get; } +} +``` + +### UIMultiLable + +Компонент для синхронизации нескольких текстовых меток: + +```csharp +public class UIMultiLable : MonoBehaviour +{ + [SerializeField] protected string text = ""; + [SerializeField] protected List labels; + + // Синхронизирует текст для группы меток +} +``` + +### DataTableLayoutGroup + +Компонент для создания табличного представления данных: + +```csharp +public class DataTableLayoutGroup : MonoBehaviour +{ + [SerializeField] protected GameObject cellPrefab; + [SerializeField] protected int rowsCount = 0; + [SerializeField] protected int columnsCount = 0; + [SerializeField] protected float spacing = 2f; + [SerializeField] protected Vector2 cellSize = new Vector2(100f, 30f); + + // Методы для создания и наполнения таблицы + public void SetValue(int row, int column, string value); + public void Clear(); + public void Rebuild(int rows, int columns); +} +``` + +## Пример использования + +### Отображение статистики игрока + +```csharp +public class PlayerStatsView : UIView +{ + [SerializeField] private UIProperty healthProperty; + [SerializeField] private UIProperty manaProperty; + [SerializeField] private UIProperty experienceProperty; + + private Player player; + + public void Initialize(Player player) + { + this.player = player; + + // Настройка свойств + healthProperty.SetMin(0); + healthProperty.SetMax(player.maxHealth); + healthProperty.SetValue(player.currentHealth); + + manaProperty.SetMin(0); + manaProperty.SetMax(player.maxMana); + manaProperty.SetValue(player.currentMana); + + experienceProperty.SetMin(0); + experienceProperty.SetMax(player.experienceToNextLevel); + experienceProperty.SetValue(player.currentExperience); + + // Подписка на обновления + player.OnHealthChanged += (newValue) => healthProperty.SetValue(newValue); + player.OnManaChanged += (newValue) => manaProperty.SetValue(newValue); + player.OnExperienceChanged += (newValue) => experienceProperty.SetValue(newValue); + } +} +``` + +### Создание таблицы лидеров + +```csharp +public class LeaderboardView : UIView +{ + [SerializeField] private DataTableLayoutGroup dataTable; + + public void PopulateLeaderboard(List scores) + { + // Создание таблицы размером по количеству игроков и 3 колонки + dataTable.Rebuild(scores.Count, 3); + + // Заполнение заголовков + dataTable.SetValue(0, 0, "Ранг"); + dataTable.SetValue(0, 1, "Игрок"); + dataTable.SetValue(0, 2, "Счет"); + + // Заполнение данных игроков + for (int i = 0; i < scores.Count; i++) + { + dataTable.SetValue(i + 1, 0, (i + 1).ToString()); + dataTable.SetValue(i + 1, 1, scores[i].PlayerName); + dataTable.SetValue(i + 1, 2, scores[i].Score.ToString()); + } + } +} +``` + +### Прогресс-бар загрузки + +```csharp +public class LoadingScreen : UIView +{ + [SerializeField] private UIProgressBar progressBar; + [SerializeField] private UILable statusLabel; + + public void UpdateProgress(float progress, string status) + { + progressBar.SetValue(progress); + statusLabel.Text = status; + } + + // Использование: + // loadingScreen.UpdateProgress(0.5f, "Загрузка активов..."); +} +``` + +## Рекомендации по использованию + +1. **Именование свойств**: Присваивайте уникальные ID свойствам для удобного доступа к ним +2. **Анимация переходов**: Используйте `smoothValue = true` для плавного изменения значений +3. **Форматирование**: Подбирайте подходящий `formatValue` для читаемого вывода чисел +4. **Инверсия**: Используйте `invertValue` для инвертирования прогресс-баров (например, для урона) +5. **Реактивность**: Подписывайтесь на события изменения данных для автоматического обновления UI + +UI компоненты в Master Server Toolkit предназначены для работы как самостоятельно, так и в составе более сложных представлений. Они предоставляют базовую функциональность, которую можно расширять и настраивать под конкретные нужды проекта. \ No newline at end of file diff --git a/Docs/Tools/UI/README.md b/Docs/Tools/UI/README.md new file mode 100644 index 0000000..558daa6 --- /dev/null +++ b/Docs/Tools/UI/README.md @@ -0,0 +1,57 @@ +# Master Server Toolkit - UI Framework + +## Обзор + +UI фреймворк в Master Server Toolkit предоставляет гибкую систему для создания и управления пользовательским интерфейсом в многопользовательских играх. Фреймворк построен вокруг концепции **Views** (Представлений), которые представляют собой самостоятельные UI компоненты с возможностью анимации, валидации и модульного расширения. + +## Основные компоненты + +### [Views System](Views.md) +Система представлений, включающая базовые классы для управления UI экранами, окнами и панелями. + +### [UI Components](Components.md) +Компоненты для отображения данных, свойств, прогресс-баров и других элементов интерфейса. + +### [Validation System](Validation.md) +Система валидации пользовательского ввода с поддержкой проверки полей с помощью регулярных выражений. + +## Интеграция с MasterServerToolkit + +UI фреймворк тесно интегрирован с другими модулями MasterServerToolkit: + +- **Authentication UI**: Простое создание форм регистрации и входа +- **Lobbies UI**: Интерфейсы для лобби и комнат +- **Matchmaking UI**: Компоненты для поиска игры и подбора матчей +- **Chat UI**: Интерфейсы чата и сообщений + +## Начало работы + +Для использования UI фреймворка: + +1. Добавьте в вашу сцену `ViewsManager` компонент +2. Создайте UI представления унаследовав их от `UIView` класса +3. Зарегистрируйте представления с уникальными ID +4. Используйте `ViewsManager.Show()` и `ViewsManager.Hide()` для управления представлениями + +```csharp +// Получение представления по ID +var loginView = ViewsManager.GetView("LoginView"); + +// Показ представления +loginView.Show(); + +// Скрытие всех представлений +ViewsManager.HideAllViews(); +``` + +## Пример использования + +```csharp +// Автоматическое отображение UI при входе игрока +authModule.OnUserLoggedInEvent += (user) => { + // Скрываем экран логина + ViewsManager.Hide("LoginView"); + // Показываем главное меню + ViewsManager.Show("MainMenuView"); +}; +``` \ No newline at end of file diff --git a/Docs/Tools/UI/Validation.md b/Docs/Tools/UI/Validation.md new file mode 100644 index 0000000..694522c --- /dev/null +++ b/Docs/Tools/UI/Validation.md @@ -0,0 +1,315 @@ +# Master Server Toolkit - Validation System + +## Обзор + +Система валидации в Master Server Toolkit предоставляет набор инструментов для проверки пользовательского ввода в формах. Она поддерживает проверку обязательных полей, валидацию по регулярным выражениям, сравнение значений полей и многое другое. + +## Ключевые компоненты + +### IValidatableComponent + +Базовый интерфейс для всех компонентов валидации: + +```csharp +public interface IValidatableComponent +{ + bool IsValid(); +} +``` + +### ValidatableBaseComponent + +Абстрактный базовый класс для компонентов валидации: + +```csharp +public abstract class ValidatableBaseComponent : MonoBehaviour, IValidatableComponent +{ + [Header("Base Settings")] + [SerializeField] protected bool isRequired = false; + [SerializeField, TextArea(2, 10)] protected string requiredErrorMessage; + [SerializeField] protected Color invalidColor = Color.red; + [SerializeField] protected Color normalColor = Color.white; + + public abstract bool IsValid(); + + protected void SetInvalidColor(); + protected void SetNormalColor(); +} +``` + +### ValidatableInputFieldComponent + +Компонент валидации для текстовых полей ввода: + +```csharp +public class ValidatableInputFieldComponent : ValidatableBaseComponent +{ + [Header("Text Field Components")] + [SerializeField] private TMP_InputField currentInputField; + [SerializeField] private TMP_InputField compareToInputField; + [SerializeField, TextArea(2, 10)] protected string compareErrorMessage; + + [Header("Text Field RegExp Validation")] + [SerializeField, TextArea(2, 10)] protected string regExpPattern; + [SerializeField, TextArea(2, 10)] protected string regExpErrorMessage; + + // Проверяет обязательность поля, соответствие регулярному выражению и + // равенство значения с compareToInputField (если указан) + public override bool IsValid(); +} +``` + +### ValidatableDropdownComponent + +Компонент валидации для выпадающих списков: + +```csharp +public class ValidatableDropdownComponent : ValidatableBaseComponent +{ + [Header("Dropdown Components")] + [SerializeField] private TMP_Dropdown currentDropdown; + + [Header("Dropdown Settings")] + [SerializeField] protected List invalidValues = new List(); + [SerializeField, TextArea(2, 10)] protected string invalidValueErrorMessage; + + // Проверяет, что выбранное значение не находится в списке invalidValues + public override bool IsValid(); +} +``` + +### ValidationFormComponent + +Компонент для валидации всей формы: + +```csharp +public class ValidationFormComponent : MonoBehaviour, IUIViewComponent +{ + [Header("Settings")] + [SerializeField] protected bool validateOnStart = false; + [SerializeField] protected bool validateOnEnable = false; + [SerializeField] protected bool validateBeforeSubmit = true; + + [Header("Components")] + [SerializeField] protected Button submitButton; + + // События + public UnityEvent OnFormValidEvent; + public UnityEvent OnFormInvalidEvent; + public UnityEvent OnSubmitEvent; + + // Публичные методы + public bool Validate(); + public void Submit(); +} +``` + +## Регулярные выражения для валидации + +### Электронная почта + +``` +^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ +``` + +### Пароль (минимум 8 символов, хотя бы одна буква и одна цифра) + +``` +^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$ +``` + +### Имя пользователя (только буквы и цифры, 3-16 символов) + +``` +^[a-zA-Z0-9]{3,16}$ +``` + +### URL + +``` +^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$ +``` + +### Телефонный номер (международный формат) + +``` +^\+?[1-9]\d{1,14}$ +``` + +## Примеры использования + +### Форма регистрации + +```csharp +public class RegistrationForm : UIView +{ + [SerializeField] private ValidationFormComponent validationForm; + + // Поля ввода с компонентами валидации + [SerializeField] private TMP_InputField usernameField; + [SerializeField] private ValidatableInputFieldComponent usernameValidator; + + [SerializeField] private TMP_InputField emailField; + [SerializeField] private ValidatableInputFieldComponent emailValidator; + + [SerializeField] private TMP_InputField passwordField; + [SerializeField] private ValidatableInputFieldComponent passwordValidator; + + [SerializeField] private TMP_InputField confirmPasswordField; + [SerializeField] private ValidatableInputFieldComponent confirmPasswordValidator; + + protected override void Awake() + { + base.Awake(); + + // Настройка валидаторов + usernameValidator.isRequired = true; + usernameValidator.requiredErrorMessage = "Имя пользователя обязательно"; + usernameValidator.regExpPattern = "^[a-zA-Z0-9]{3,16}$"; + usernameValidator.regExpErrorMessage = "Имя должно содержать от 3 до 16 символов (только буквы и цифры)"; + + emailValidator.isRequired = true; + emailValidator.requiredErrorMessage = "Email обязателен"; + emailValidator.regExpPattern = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"; + emailValidator.regExpErrorMessage = "Неверный формат email"; + + passwordValidator.isRequired = true; + passwordValidator.requiredErrorMessage = "Пароль обязателен"; + passwordValidator.regExpPattern = "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{8,}$"; + passwordValidator.regExpErrorMessage = "Пароль должен содержать минимум 8 символов, одну букву и одну цифру"; + + confirmPasswordValidator.isRequired = true; + confirmPasswordValidator.requiredErrorMessage = "Подтверждение пароля обязательно"; + confirmPasswordValidator.compareToInputField = passwordField; + confirmPasswordValidator.compareErrorMessage = "Пароли не совпадают"; + + // Подписка на события формы + validationForm.OnFormValidEvent.AddListener(OnFormValid); + validationForm.OnFormInvalidEvent.AddListener(OnFormInvalid); + validationForm.OnSubmitEvent.AddListener(OnSubmit); + } + + private void OnFormValid() + { + // Форма прошла валидацию + Debug.Log("Форма валидна"); + } + + private void OnFormInvalid() + { + // Форма не прошла валидацию + Debug.Log("Форма невалидна"); + } + + private void OnSubmit() + { + // Отправка данных формы + string username = usernameField.text; + string email = emailField.text; + string password = passwordField.text; + + // Регистрация пользователя + RegisterUser(username, email, password); + } + + private void RegisterUser(string username, string email, string password) + { + // Логика регистрации пользователя + // ... + } +} +``` + +### Валидация в форме входа + +```csharp +public class LoginForm : UIView +{ + [SerializeField] private ValidationFormComponent validationForm; + [SerializeField] private TMP_InputField usernameField; + [SerializeField] private ValidatableInputFieldComponent usernameValidator; + [SerializeField] private TMP_InputField passwordField; + [SerializeField] private ValidatableInputFieldComponent passwordValidator; + [SerializeField] private Button loginButton; + + private void Start() + { + // Настройка валидации + usernameValidator.isRequired = true; + passwordValidator.isRequired = true; + + // Активация валидации при нажатии кнопки входа + loginButton.onClick.AddListener(() => { + if (validationForm.Validate()) + { + // Если валидация успешна, выполняем вход + Mst.Auth.SignInWithCredentials(usernameField.text, passwordField.text, (isSuccess, error) => { + if (isSuccess) + { + Hide(); + ViewsManager.Show("MainMenuView"); + } + else + { + Debug.LogError($"Ошибка входа: {error}"); + } + }); + } + }); + } +} +``` + +### Кастомный валидатор + +```csharp +// Пример создания кастомного валидатора для проверки возраста +public class AgeValidatorComponent : ValidatableBaseComponent +{ + [SerializeField] private TMP_InputField ageField; + [SerializeField] private int minAge = 18; + [SerializeField] private int maxAge = 100; + [SerializeField] private string invalidAgeMessage = "Возраст должен быть от {0} до {1} лет"; + + public override bool IsValid() + { + if (!ageField.interactable) + return true; + + if (isRequired && string.IsNullOrEmpty(ageField.text)) + { + Debug.LogError(requiredErrorMessage); + SetInvalidColor(); + return false; + } + + if (!int.TryParse(ageField.text, out int age)) + { + Debug.LogError("Возраст должен быть числом"); + SetInvalidColor(); + return false; + } + + if (age < minAge || age > maxAge) + { + Debug.LogError(string.Format(invalidAgeMessage, minAge, maxAge)); + SetInvalidColor(); + return false; + } + + SetNormalColor(); + return true; + } +} +``` + +## Лучшие практики + +1. **Сообщения об ошибках**: Используйте ясные и понятные сообщения об ошибках, указывающие, как исправить проблему +2. **Визуальная обратная связь**: Сочетайте текстовые сообщения с визуальными индикаторами (цвет, иконки) +3. **Валидация в реальном времени**: Рассмотрите возможность валидации полей при изменении значения +4. **Предотвращение отправки**: Блокируйте кнопку отправки, если форма невалидна +5. **Кастомные валидаторы**: Создавайте специализированные валидаторы для особых случаев +6. **Группирование ошибок**: Собирайте и показывайте все ошибки сразу, а не по одной + +Система валидации в Master Server Toolkit позволяет гибко настраивать проверку форм, обеспечивая хороший пользовательский опыт и предотвращая отправку некорректных данных на сервер. \ No newline at end of file diff --git a/Docs/Tools/UI/Views.md b/Docs/Tools/UI/Views.md new file mode 100644 index 0000000..77f121b --- /dev/null +++ b/Docs/Tools/UI/Views.md @@ -0,0 +1,291 @@ +# Master Server Toolkit - System Views + +## Обзор + +Система Views в Master Server Toolkit предоставляет мощный инструмент для организации пользовательского интерфейса. Она основана на концепции отдельных представлений, которые могут независимо отображаться, скрываться и анимироваться. + +## Ключевые классы + +### IUIView + +Интерфейс, определяющий базовые операции представления: + +```csharp +public interface IUIView +{ + string Id { get; } + bool IsVisible { get; } + RectTransform Rect { get; } + bool IgnoreHideAll { get; set; } + bool BlockInput { get; set; } + bool UnlockCursor { get; set; } + + void Show(bool instantly = false); + void Hide(bool instantly = false); + void Toggle(bool instantly = false); +} +``` + +### UIView + +Базовый класс представления, реализующий `IUIView`: + +```csharp +[RequireComponent(typeof(Canvas), typeof(GraphicRaycaster), typeof(CanvasGroup))] +public class UIView : MonoBehaviour, IUIView +{ + [Header("Identity Settings")] + [SerializeField] protected string id = "New View Id"; + [SerializeField] protected string title = ""; + + [Header("Shared Settings")] + [SerializeField] protected bool hideOnStart = true; + [SerializeField] protected bool allwaysOnTop = false; + [SerializeField] protected bool ignoreHideAll = false; + [SerializeField] protected bool useRaycastBlock = true; + [SerializeField] protected bool blockInput = false; + [SerializeField] protected bool unlockCursor = false; + + // События + public UnityEvent OnShowEvent; + public UnityEvent OnHideEvent; + public UnityEvent OnShowFinishedEvent; + public UnityEvent OnHideFinishedEvent; + + // Методы Show, Hide, Toggle +} +``` + +### UIViewPanel + +Расширение `UIView` для создания панелей: + +```csharp +public class UIViewPanel : UIView +{ + // Дополнительная функциональность для панелей +} +``` + +### UIViewSync + +Компонент для синхронизации нескольких представлений: + +```csharp +public class UIViewSync : MonoBehaviour +{ + [SerializeField] protected string mainViewId; + [SerializeField] protected List syncViewIds = new List(); + [SerializeField] protected bool hideOnStart = true; + + // Синхронизирует состояние всех присоединенных представлений +} +``` + +### PopupView + +Специализированное представление для диалоговых окон: + +```csharp +public class PopupView : MonoBehaviour, IUIViewComponent +{ + public Button confirmButton; + public Button declineButton; + public TextMeshProUGUI titleText; + public TextMeshProUGUI messageText; + + // События для подтверждения/отклонения + public UnityEvent OnConfirmEvent; + public UnityEvent OnDeclineEvent; +} +``` + +## ViewsManager + +Статический класс для управления всеми представлениями: + +```csharp +public static class ViewsManager +{ + // Проверка блокировки ввода и курсора + public static bool AnyInputBlockViewVisible { get; } + public static bool AnyCursorUnlockViewVisible { get; } + + // Регистрация и получение представлений + public static void Register(string viewId, IUIView view); + public static void Unregister(string viewId); + public static T GetView(string viewId) where T : class, IUIView; + + // Управление представлениями + public static void Show(string viewId); + public static void Hide(string viewId); + public static void HideAllViews(bool instantly = false); + public static void HideViewsByName(bool instantly = false, params string[] names); +} +``` + +## Компоненты взаимодействия с представлениями + +### IUIViewComponent + +Интерфейс для компонентов, работающих с представлениями: + +```csharp +public interface IUIViewComponent +{ + void OnOwnerShow(IUIView owner); + void OnOwnerHide(IUIView owner); +} +``` + +### IUIViewInputHandler + +Интерфейс для обработчиков ввода: + +```csharp +public interface IUIViewInputHandler : IUIViewComponent +{ + // Специфичная логика обработки ввода +} +``` + +### UIViewKeyInputHandler + +Обработчик ввода с клавиатуры: + +```csharp +public class UIViewKeyInputHandler : MonoBehaviour, IUIViewInputHandler +{ + [SerializeField] protected KeyCode toggleKey = KeyCode.Escape; + [SerializeField] protected string toggleViewId = ""; + + // Переключает вид при нажатии указанной клавиши +} +``` + +### IUIViewTweener + +Интерфейс для аниматоров представлений: + +```csharp +public interface IUIViewTweener : MonoBehaviour +{ + IUIView UIView { get; set; } + void PlayShow(); + void PlayHide(); + void OnFinished(UnityAction callback); +} +``` + +## UI Sound Components + +Компоненты для воспроизведения звуков UI: + +```csharp +public class UIViewSound : MonoBehaviour, IUIViewComponent +{ + [SerializeField] protected AudioClip showClip; + [SerializeField] protected AudioClip hideClip; + + // Воспроизводит звуки при появлении/скрытии представления +} + +public class UIButtonSound : MonoBehaviour +{ + [SerializeField] protected AudioClip clickClip; + [SerializeField] protected AudioClip hoverClip; + + // Добавляет звуки к кнопкам +} + +public class UIToggleSound : MonoBehaviour +{ + [SerializeField] protected AudioClip onClip; + [SerializeField] protected AudioClip offClip; + + // Добавляет звуки к переключателям +} +``` + +## Примеры использования + +### Базовое создание представления + +```csharp +public class GameMenuView : UIView +{ + [SerializeField] private Button playButton; + [SerializeField] private Button optionsButton; + [SerializeField] private Button quitButton; + + protected override void Awake() + { + base.Awake(); + + // Подписка на события кнопок + playButton.onClick.AddListener(OnPlayClick); + optionsButton.onClick.AddListener(OnOptionsClick); + quitButton.onClick.AddListener(OnQuitClick); + } + + private void OnPlayClick() + { + // Скрываем текущее представление + Hide(); + // Показываем представление выбора игры + ViewsManager.Show("GameSelectionView"); + } + + private void OnOptionsClick() + { + ViewsManager.Show("OptionsView"); + } + + private void OnQuitClick() + { + Application.Quit(); + } +} +``` + +### Создание диалога подтверждения + +```csharp +// Получение popup представления +var popup = ViewsManager.GetView("ConfirmPopup"); + +// Настройка текста +popup.SetTitle("Подтверждение"); +popup.SetMessage("Вы уверены, что хотите выйти?"); + +// Подписка на события +popup.OnConfirmEvent.AddListener(() => { + // Действие при подтверждении + Application.Quit(); +}); + +popup.OnDeclineEvent.AddListener(() => { + // Действие при отмене + popup.Hide(); +}); + +// Показ popup +popup.Show(); +``` + +### Работа с анимированными представлениями + +```csharp +// Создание анимированного представления +var view = gameObject.AddComponent(); +var tweener = gameObject.AddComponent(); // Наследник IUIViewTweener + +// Показ с анимацией +view.Show(); // Анимированный +view.Show(true); // Мгновенный + +// Подписка на завершение анимации +view.OnShowFinishedEvent.AddListener(() => { + Debug.Log("Анимация появления завершена"); +}); +``` \ No newline at end of file diff --git a/Docs/Tools/Utilities.md b/Docs/Tools/Utilities.md new file mode 100644 index 0000000..52c9ef1 --- /dev/null +++ b/Docs/Tools/Utilities.md @@ -0,0 +1,469 @@ +# Master Server Toolkit - Utilities + +## Описание +Набор универсальных утилит и вспомогательных классов для упрощения разработки. Включает паттерны проектирования, расширения стандартных типов, вспомогательные классы для Unity и многое другое. + +## Основные компоненты + +### Шаблоны проектирования + +#### Singleton +```csharp +// Базовый синглтон для MonoBehaviour +public class PlayerManager : SingletonBehaviour +{ + // Доступ из любого места проекта + public static PlayerManager Instance => GetInstance(); + + public void DoSomething() + { + // Реализация + } +} + +// Использование +PlayerManager.Instance.DoSomething(); +``` + +#### Динамический Singleton +```csharp +// Автоматически создаваемый синглтон +public class AudioManager : DynamicSingletonBehaviour +{ + // Будет создан на сцене, если отсутствует + public static AudioManager Instance => GetInstance(); +} + +// Использование +AudioManager.Instance.PlaySound("explosion"); +``` + +#### Глобальный Singleton +```csharp +// Синглтон, который сохраняется при смене сцен +public class GameManager : GlobalDynamicSingletonBehaviour +{ + // Переживает смену сцен + public static GameManager Instance => GetInstance(); +} + +// Использование +GameManager.Instance.StartNewGame(); +``` + +#### Пул объектов +```csharp +// Создание пула для эффективного управления объектами +public class BulletPool : MonoBehaviour +{ + [SerializeField] private Bullet bulletPrefab; + [SerializeField] private int initialSize = 50; + + private GenericPool bulletPool; + + private void Awake() + { + bulletPool = new GenericPool(CreateBullet, initialSize); + } + + private Bullet CreateBullet() + { + return Instantiate(bulletPrefab); + } + + public Bullet GetBullet() + { + return bulletPool.Get(); + } + + public void ReturnBullet(Bullet bullet) + { + bulletPool.Return(bullet); + } +} + +// Использование +var bullet = bulletPool.GetBullet(); +// После использования +bulletPool.ReturnBullet(bullet); +``` + +#### Реестр объектов +```csharp +// Создание реестра для управления объектами по ключу +public class ItemRegistry : BaseRegistry +{ + // Методы регистрации уже реализованы в базовом классе +} + +// Использование +var registry = new ItemRegistry(); +registry.Register("sword", new ItemData { /* данные */ }); +var item = registry.Get("sword"); +``` + +### Расширения + +#### Расширения для строк +```csharp +// Проверка строки на пустоту +if (username.IsNullOrEmpty()) +{ + // Обработка +} + +// Хеширование строки +string passwordHash = password.GetMD5(); + +// Преобразование в Base64 +string encoded = text.ToBase64(); +string decoded = encoded.FromBase64(); + +// Извлечение последнего сегмента пути +string filename = filePath.GetLastSegment('\\'); + +// Безопасное разделение строки +string[] parts = path.SplitSafe('/'); +``` + +#### Расширения для массивов байтов +```csharp +// Преобразование в строку +byte[] data = GetData(); +string text = data.ToString(StringFormat.Utf8); + +// Преобразование в Base64 +string base64 = data.ToBase64(); + +// Создание подмассива +byte[] header = data.SubArray(0, 10); + +// Слияние массивов +byte[] fullPacket = headerBytes.CombineWith(bodyBytes); + +// Проверка на равенство +if (hash1.BytesEqual(hash2)) +{ + // Обработка +} +``` + +#### Расширения для Transform +```csharp +// Сброс локальных трансформаций +transform.ResetLocal(); + +// Масштабирование всех дочерних объектов +transform.ScaleAllChildren(0.5f); + +// Уничтожение всех дочерних объектов +transform.DestroyChildren(); + +// Поиск в глубину +var target = transform.FindRecursively("Player/Weapon/Barrel"); + +// Установка локальной позиции по отдельным осям +transform.SetLocalX(10f); +transform.SetLocalY(5f); +transform.SetLocalZ(0f); +``` + +### Вспомогательные классы + +#### ScenesLoader +```csharp +// Асинхронная загрузка сцены с прогрессом +ScenesLoader.LoadSceneAsync("Level1", (progress) => { + // Обновление индикатора загрузки + loadingBar.value = progress; +}, () => { + // Вызывается по завершении загрузки + Debug.Log("Load complete"); +}); + +// Загрузка сцены с затемнением +ScenesLoader.LoadSceneWithFade("MainMenu", Color.black, 1.5f); + +// Перезагрузка текущей сцены +ScenesLoader.ReloadCurrentScene(); +``` + +#### ScreenshotMaker +```csharp +// Захват скриншота экрана +ScreenshotMaker.TakeScreenshot((texture) => { + // Использование полученной текстуры + screenshotImage.texture = texture; +}); + +// Сохранение скриншота в файл +ScreenshotMaker.SaveScreenshot("Screenshots/screenshot.png"); + +// Захват скриншота определенной камеры +ScreenshotMaker.TakeScreenshot(myCamera, 1920, 1080, (texture) => { + // Обработка +}); +``` + +#### SimpleNameGenerator +```csharp +// Генерация случайных имен +string randomName = SimpleNameGenerator.Generate(length: 6); + +// Генерация имени с заданным префиксом +string playerName = SimpleNameGenerator.Generate("Player_", 4); + +// Генерация имени из слогов +string nameFromSyllables = SimpleNameGenerator.GenerateFromSyllables(3); + +// Создание уникального идентификатора +string uniqueId = SimpleNameGenerator.GenerateUniqueName(); +``` + +#### MstWebBrowser +```csharp +// Открытие URL во внешнем браузере +MstWebBrowser.OpenURL("https://example.com"); + +// Открытие URL с проверкой поддержки +if (MstWebBrowser.CanOpenURL) +{ + MstWebBrowser.OpenURL("https://example.com"); +} + +// Открытие локального HTML-файла +MstWebBrowser.OpenLocalFile("Documentation.html"); +``` + +#### NetWebRequests +```csharp +// Отправка GET-запроса +NetWebRequests.Get("https://api.example.com/data", (success, response) => { + if (success) + { + // Обработка ответа + Debug.Log(response); + } +}); + +// Отправка POST-запроса +var data = new Dictionary +{ + { "username", "player1" }, + { "score", "100" } +}; + +NetWebRequests.Post("https://api.example.com/scores", data, (success, response) => { + if (success) + { + Debug.Log("Score submitted"); + } +}); + +// Загрузка текстуры +NetWebRequests.GetTexture("https://example.com/image.jpg", (success, texture) => { + if (success) + { + // Использование текстуры + profileImage.texture = texture; + } +}); +``` + +### Сериализуемые структуры + +#### SerializedKeyValuePair +```csharp +// Сериализуемая пара ключ-значение для использования в инспекторе Unity +[Serializable] +public class StringIntPair : SerializedKeyValuePair { } + +public class InventoryManager : MonoBehaviour +{ + [SerializeField] + private List startingItems = new List(); + + private void Start() + { + foreach (var item in startingItems) + { + AddItem(item.Key, item.Value); + } + } + + private void AddItem(string itemId, int count) + { + // Реализация + } +} +``` + +## Примеры использования + +### Создание игрового менеджера +```csharp +public class GameManager : GlobalDynamicSingletonBehaviour +{ + // Состояние игры + public GameState CurrentState { get; private set; } + + // События + public event Action OnGameStateChanged; + + // Изменение состояния игры + public void ChangeState(GameState newState) + { + CurrentState = newState; + OnGameStateChanged?.Invoke(newState); + } + + // Загрузка нового уровня + public void LoadLevel(int levelIndex) + { + ChangeState(GameState.Loading); + + ScenesLoader.LoadSceneAsync($"Level_{levelIndex}", (progress) => { + // Обновление прогресса + }, () => { + ChangeState(GameState.Playing); + }); + } +} + +// Использование +GameManager.Instance.LoadLevel(1); +``` + +### Реализация системы пулинга +```csharp +public class EffectsPool : SingletonBehaviour +{ + [Serializable] + public class EffectPoolData + { + public string effectId; + public GameObject prefab; + public int initialSize; + } + + [SerializeField] private List effectsData; + + private Dictionary> effectPools = new Dictionary>(); + + protected override void Awake() + { + base.Awake(); + + // Инициализация пулов + foreach (var data in effectsData) + { + var pool = new GenericPool(() => Instantiate(data.prefab), data.initialSize); + effectPools.Add(data.effectId, pool); + } + } + + public GameObject SpawnEffect(string effectId, Vector3 position, Quaternion rotation) + { + if (effectPools.TryGetValue(effectId, out var pool)) + { + var effect = pool.Get(); + effect.transform.position = position; + effect.transform.rotation = rotation; + effect.SetActive(true); + + return effect; + } + + Debug.LogWarning($"Effect {effectId} not found in pools"); + return null; + } + + public void ReturnEffect(string effectId, GameObject effect) + { + if (effectPools.TryGetValue(effectId, out var pool)) + { + effect.SetActive(false); + pool.Return(effect); + } + } +} + +// Использование +void PlayExplosion(Vector3 position) +{ + var effect = EffectsPool.Instance.SpawnEffect("explosion", position, Quaternion.identity); + + // Автоматический возврат в пул через 2 секунды + StartCoroutine(ReturnAfterDelay("explosion", effect, 2f)); +} + +IEnumerator ReturnAfterDelay(string effectId, GameObject effect, float delay) +{ + yield return new WaitForSeconds(delay); + EffectsPool.Instance.ReturnEffect(effectId, effect); +} +``` + +### Создание менеджера настроек +```csharp +public class SettingsManager : SingletonBehaviour +{ + // Настройки + public float MasterVolume { get; private set; } = 1f; + public float MusicVolume { get; private set; } = 0.8f; + public float SfxVolume { get; private set; } = 1f; + public int QualityLevel { get; private set; } = 2; + public bool Fullscreen { get; private set; } = true; + + // События + public event Action OnSettingsChanged; + + private void Start() + { + LoadSettings(); + } + + public void SetMasterVolume(float volume) + { + MasterVolume = Mathf.Clamp01(volume); + OnSettingsChanged?.Invoke(); + SaveSettings(); + } + + // Аналогичные методы для других настроек + + private void SaveSettings() + { + PlayerPrefs.SetFloat("MasterVolume", MasterVolume); + PlayerPrefs.SetFloat("MusicVolume", MusicVolume); + PlayerPrefs.SetFloat("SfxVolume", SfxVolume); + PlayerPrefs.SetInt("QualityLevel", QualityLevel); + PlayerPrefs.SetInt("Fullscreen", Fullscreen ? 1 : 0); + PlayerPrefs.Save(); + } + + private void LoadSettings() + { + MasterVolume = PlayerPrefs.GetFloat("MasterVolume", 1f); + MusicVolume = PlayerPrefs.GetFloat("MusicVolume", 0.8f); + SfxVolume = PlayerPrefs.GetFloat("SfxVolume", 1f); + QualityLevel = PlayerPrefs.GetInt("QualityLevel", 2); + Fullscreen = PlayerPrefs.GetInt("Fullscreen", 1) == 1; + + OnSettingsChanged?.Invoke(); + } +} + +// Использование +SettingsManager.Instance.SetMasterVolume(0.5f); +``` + +## Лучшие практики + +1. **Используйте синглтоны с осторожностью** — они упрощают код, но могут создать проблемы с зависимостями +2. **Предпочитайте пулинг для часто создаваемых/уничтожаемых объектов** — это значительно улучшит производительность +3. **Используйте расширения методов для повышения читаемости кода** +4. **Помните о статусе экспериментальных функций** — некоторые утилиты могут работать не на всех платформах +5. **Избегайте излишнего усложнения** — использование утилит должно упрощать код, а не усложнять его +6. **Максимально применяйте типизацию** для предотвращения ошибок в рантайме +7. **Документируйте собственные расширения** этих утилит для поддержки кода в будущем diff --git a/Docs/Tools/WebGL.md b/Docs/Tools/WebGL.md new file mode 100644 index 0000000..28cedd5 --- /dev/null +++ b/Docs/Tools/WebGL.md @@ -0,0 +1,334 @@ +# Master Server Toolkit - WebGL + +## Описание +Модуль для улучшения поддержки WebGL-платформы, содержащий вспомогательные компоненты и утилиты для работы с веб-специфичными особенностями и ограничениями Unity WebGL. + +## WebGlTextMeshProInput + +Компонент для улучшения работы с текстовыми полями ввода TextMeshPro в WebGL-сборках. Решает проблемы с виртуальными клавиатурами на мобильных устройствах и особенностями ввода в веб-среде. + +### Основные возможности + +```csharp +[RequireComponent(typeof(TMP_InputField))] +public class WebGlTextMeshProInput : MonoBehaviour, IPointerClickHandler +{ + [Header("Settings"), SerializeField] + private string title = "Input Field"; // Заголовок модального окна ввода + + // Обработка нажатия на поле ввода + public void OnPointerClick(PointerEventData eventData) + { + // Вызов нативного JavaScript-окна ввода + } + + // Обработка подтверждения ввода + public void OnPromptOk(string message) + { + GetComponent().text = message; + } + + // Обработка отмены ввода + public void OnPromptCancel() + { + GetComponent().text = ""; + } +} +``` + +### Использование + +```csharp +// Добавление к существующему полю ввода +TMP_InputField inputField = GetComponent(); +inputField.gameObject.AddComponent(); + +// Настройка через редактор Unity +// 1. Добавьте компонент WebGlTextMeshProInput к объекту с TMP_InputField +// 2. Настройте заголовок для модального окна ввода +``` + +### JavaScript интеграция + +WebGlTextMeshProInput использует jslib-плагин для вызова нативного JavaScript-кода: + +```javascript +// MstWebGL.jslib +mergeInto(LibraryManager.library, { + MstPrompt: function(name, title, defaultValue) { + var result = window.prompt(UTF8ToString(title), UTF8ToString(defaultValue)); + + if (result !== null) { + var gameObject = UTF8ToString(name); + SendMessage(gameObject, "OnPromptOk", result); + } else { + var gameObject = UTF8ToString(name); + SendMessage(gameObject, "OnPromptCancel"); + } + } +}); +``` + +## Мобильная поддержка + +Компонент особенно полезен при использовании WebGL на мобильных устройствах: + +1. **Решает проблему с виртуальными клавиатурами** на iOS и Android +2. **Обеспечивает корректный ввод** на устройствах с различными форматами экранов +3. **Поддерживает многострочные поля ввода** через нативный интерфейс + +### Пример для многострочного ввода + +```csharp +public class WebGlMultilineInput : MonoBehaviour +{ + [SerializeField] private TMP_InputField inputField; + [SerializeField] private Button editButton; + [SerializeField] private string dialogTitle = "Введите текст"; + + private void Start() + { + // Добавляем обработчик кнопки редактирования + editButton.onClick.AddListener(OnEditButtonClick); + } + + private void OnEditButtonClick() + { + // Вызываем модальное окно ввода + WebGLInput.ShowPrompt(dialogTitle, inputField.text, OnPromptComplete); + } + + private void OnPromptComplete(string result, bool isCancelled) + { + if (!isCancelled) + { + inputField.text = result; + // Дополнительная обработка введенного текста + } + } +} +``` + +## Интеграция с другими веб-функциями + +### Взаимодействие с буфером обмена + +```csharp +public class WebGlClipboard : MonoBehaviour +{ + // Копирование текста в буфер обмена + public void CopyToClipboard(string text) + { +#if UNITY_WEBGL && !UNITY_EDITOR + WebGLCopyToClipboard(text); +#else + GUIUtility.systemCopyBuffer = text; +#endif + } + + // Вставка текста из буфера обмена + public void PasteFromClipboard(TMP_InputField inputField) + { +#if UNITY_WEBGL && !UNITY_EDITOR + WebGLRequestClipboardContent(gameObject.name); +#else + inputField.text = GUIUtility.systemCopyBuffer; +#endif + } + + // Обработчик для получения содержимого буфера обмена + public void OnClipboardContent(string content) + { + // Использование полученного содержимого + FindObjectOfType().text = content; + } + +#if UNITY_WEBGL && !UNITY_EDITOR + [DllImport("__Internal")] + private static extern void WebGLCopyToClipboard(string text); + + [DllImport("__Internal")] + private static extern void WebGLRequestClipboardContent(string gameObjectName); +#endif +} +``` + +### Адаптация к ориентации экрана + +```csharp +public class WebGlOrientationHandler : MonoBehaviour +{ + [SerializeField] private CanvasScaler canvasScaler; + [SerializeField] private float portraitMatchWidthOrHeight = 0.5f; + [SerializeField] private float landscapeMatchWidthOrHeight = 0; + + private void Start() + { +#if UNITY_WEBGL && !UNITY_EDITOR + WebGLAddOrientationChangeListener(gameObject.name); + UpdateCanvasScaling(); +#endif + } + + // Вызывается из JavaScript при изменении ориентации + public void OnOrientationChanged() + { + UpdateCanvasScaling(); + } + + private void UpdateCanvasScaling() + { + bool isPortrait = Screen.height > Screen.width; + canvasScaler.matchWidthOrHeight = isPortrait ? portraitMatchWidthOrHeight : landscapeMatchWidthOrHeight; + } + +#if UNITY_WEBGL && !UNITY_EDITOR + [DllImport("__Internal")] + private static extern void WebGLAddOrientationChangeListener(string gameObjectName); +#endif +} +``` + +## Обработка локализации + +Компонент WebGlTextMeshProInput также интегрируется с системой локализации Master Server Toolkit для корректного отображения заголовков диалогов: + +```csharp +// Использование локализации для заголовка +private string title = "Input Field"; + +public void OnPointerClick(PointerEventData eventData) +{ +#if UNITY_WEBGL && !UNITY_EDITOR && !UNITY_STANDALONE + var input = GetComponent(); + MstPrompt(name, Mst.Localization[title], input.text); +#endif +} +``` + +## Практические примеры + +### Форма регистрации в WebGL + +```csharp +public class WebGlRegistrationForm : MonoBehaviour +{ + [SerializeField] private TMP_InputField usernameField; + [SerializeField] private TMP_InputField emailField; + [SerializeField] private TMP_InputField passwordField; + [SerializeField] private Button submitButton; + + private void Start() + { + // Добавляем компоненты для улучшения ввода в WebGL + usernameField.gameObject.AddComponent().title = "Enter Username"; + emailField.gameObject.AddComponent().title = "Enter Email"; + passwordField.gameObject.AddComponent().title = "Enter Password"; + + // Настраиваем кнопку отправки + submitButton.onClick.AddListener(OnSubmitButtonClick); + } + + private void OnSubmitButtonClick() + { + // Проверка ввода и отправка формы + if (string.IsNullOrEmpty(usernameField.text) || + string.IsNullOrEmpty(emailField.text) || + string.IsNullOrEmpty(passwordField.text)) + { + ShowError("All fields are required"); + return; + } + + // Отправка данных на сервер + SendRegistrationData(usernameField.text, emailField.text, passwordField.text); + } + + private void SendRegistrationData(string username, string email, string password) + { + // Логика отправки данных + } + + private void ShowError(string message) + { + // Отображение ошибки + } +} +``` + +### Сохранение и загрузка данных + +```csharp +public class WebGlStorageHandler : MonoBehaviour +{ + // Сохранение данных в LocalStorage + public void SaveData(string key, string value) + { +#if UNITY_WEBGL && !UNITY_EDITOR + WebGLSaveToLocalStorage(key, value); +#else + PlayerPrefs.SetString(key, value); + PlayerPrefs.Save(); +#endif + } + + // Загрузка данных из LocalStorage + public string LoadData(string key, string defaultValue = "") + { +#if UNITY_WEBGL && !UNITY_EDITOR + return WebGLLoadFromLocalStorage(key, defaultValue); +#else + return PlayerPrefs.GetString(key, defaultValue); +#endif + } + + // Удаление данных + public void DeleteData(string key) + { +#if UNITY_WEBGL && !UNITY_EDITOR + WebGLRemoveFromLocalStorage(key); +#else + PlayerPrefs.DeleteKey(key); + PlayerPrefs.Save(); +#endif + } + +#if UNITY_WEBGL && !UNITY_EDITOR + [DllImport("__Internal")] + private static extern void WebGLSaveToLocalStorage(string key, string value); + + [DllImport("__Internal")] + private static extern string WebGLLoadFromLocalStorage(string key, string defaultValue); + + [DllImport("__Internal")] + private static extern void WebGLRemoveFromLocalStorage(string key); +#endif +} +``` + +## Лучшие практики + +1. **Используйте условную компиляцию** для платформо-зависимого кода +```csharp +#if UNITY_WEBGL && !UNITY_EDITOR + // WebGL-специфичный код +#else + // Код для других платформ +#endif +``` + +2. **Тестируйте на реальных мобильных устройствах** для проверки работы виртуальных клавиатур + +3. **Учитывайте ограничения WebGL**: + - Отсутствие многопоточности + - Ограничения безопасности браузеров + - Проблемы с вводом на мобильных устройствах + +4. **Предоставляйте альтернативные методы ввода** для сложных форм + +5. **Интегрируйте с JavaScript API браузеров** для расширения функциональности: + - LocalStorage для хранения данных + - Clipboard API для работы с буфером обмена + - Screen API для работы с ориентацией экрана + +6. **Обрабатывайте потерю фокуса окна браузера** для корректного функционирования приложения From 831e1338484db8c7a38ef266ebf9ea2b3e51a52b Mon Sep 17 00:00:00 2001 From: Petr Tsap Date: Tue, 13 May 2025 12:14:32 +0200 Subject: [PATCH 3/4] =?UTF-8?q?=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=B8=D0=B5=D1=80=D0=B0=D1=80=D1=85=D0=B8=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Docs/README.md | 52 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/Docs/README.md b/Docs/README.md index c2a435c..bfe61f4 100644 --- a/Docs/README.md +++ b/Docs/README.md @@ -3,14 +3,31 @@ ## Описание Master Server Toolkit - это фреймворк для создания многопользовательских игр с архитектурой клиент-сервер. Он предоставляет готовые модули для аутентификации, профилей, комнат, лобби, чата и многих других аспектов многопользовательской игры. -## Основные модули - -### Ядро системы +## Структура системы + +### [Ядро системы](Core/README.md) +Базовые компоненты и инфраструктура фреймворка: +- [MasterServer](Core/MasterServer.md) - Центральный компонент системы +- [Client](Core/Client.md) - Клиентская часть системы +- [Server](Core/Server.md) - Серверная часть системы +- [Database](Core/Database.md) - Абстракция базы данных +- [Events](Core/Events.md) - Система событий +- [Keys](Core/Keys.md) - Система констант и ключей +- [Localization](Core/Localization.md) - Система локализации +- [Logger](Core/Logger.md) - Система логирования +- [Mail](Core/Mail.md) - Система отправки email + +### [Networking](Networking.md) +Документация по сетевому взаимодействию в системе. + +### Модули + +#### Основные модули - [Authentication](Modules/Authentication.md) - Аутентификация и управление пользователями - [Profiles](Modules/Profiles.md) - Профили пользователей и управление данными - [Rooms](Modules/Rooms.md) - Система комнат и игровых сессий -### Игровые модули +#### Игровые модули - [Achievements](Modules/Achievements.md) - Система достижений - [Censor](Modules/Censor.md) - Фильтрация нежелательного контента - [Chat](Modules/Chat.md) - Система чата и обмена сообщениями @@ -21,22 +38,27 @@ Master Server Toolkit - это фреймворк для создания мно - [QuestsModule](Modules/QuestsModule.md) - Система квестов и заданий - [WorldRooms](Modules/WorldRooms.md) - Система постоянных игровых зон -### Инфраструктура +#### Инфраструктурные модули - [Spawner](Modules/Spawner.md) - Запуск игровых серверов - [WebServer](Modules/WebServer.md) - Встроенный веб-сервер для API и админ-панели -### Аналитика и Мониторинг +#### Аналитика и мониторинг - [AnalyticsModule](Modules/AnalyticsModule.md) - Сбор и анализ игровых событий -### Инструменты -- [Tools](Tools/README.md) - Набор вспомогательных инструментов для разработки - - [UI Framework](Tools/UI/README.md) - Система пользовательского интерфейса - - [Attributes](Tools/Attributes.md) - Расширения для инспектора Unity - - [Terminal](Tools/Terminal.md) - Отладочный терминал - - [Tweener](Tools/Tweener.md) - Инструменты анимации - - [Utilities](Tools/Utilities.md) - Вспомогательные утилиты - -## Структура модулей +### [Инструменты](Tools/README.md) +Набор вспомогательных инструментов для разработки: +- [UI Framework](Tools/UI/README.md) - Система пользовательского интерфейса + - [Views System](Tools/UI/Views.md) - Управление UI экранами + - [UI Components](Tools/UI/Components.md) - Готовые компоненты UI + - [Validation System](Tools/UI/Validation.md) - Валидация форм ввода +- [Attributes](Tools/Attributes.md) - Расширения для инспектора Unity +- [Terminal](Tools/Terminal.md) - Отладочный терминал +- [Tweener](Tools/Tweener.md) - Инструменты анимации +- [Utilities](Tools/Utilities.md) - Вспомогательные утилиты +- [DebounceThrottle](Tools/DebounceThrottle.md) - Ограничение частоты вызовов +- [WebGL](Tools/WebGL.md) - Инструменты для WebGL платформы + +## Структура модуля Каждый модуль обычно состоит из следующих компонентов: 1. **Серверный модуль** (`*Module.cs`) - серверная логика модуля From a74aee448d060894b8975f9ef8de984bafc3a0ae Mon Sep 17 00:00:00 2001 From: Petr Tsap Date: Tue, 13 May 2025 12:26:34 +0200 Subject: [PATCH 4/4] =?UTF-8?q?=D0=98=20=D0=B5=D1=89=D0=B5=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BA=D0=B0=20=D0=B8=D0=BD=D0=B4=D0=B5=D0=BA=D1=81?= =?UTF-8?q?=D0=B0=D1=82=D0=BE=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Docs/Modules/README.md | 133 +++++++++++++++++++++++++++++++++++++++++ Docs/README.md | 43 ++++++++----- 2 files changed, 161 insertions(+), 15 deletions(-) create mode 100644 Docs/Modules/README.md diff --git a/Docs/Modules/README.md b/Docs/Modules/README.md new file mode 100644 index 0000000..77340eb --- /dev/null +++ b/Docs/Modules/README.md @@ -0,0 +1,133 @@ +# Master Server Toolkit - Модули + +## Обзор + +Модули в Master Server Toolkit - это функциональные компоненты, которые предоставляют готовую логику для конкретных аспектов многопользовательной игры. Каждый модуль реализует часть функциональности, необходимой для создания многопользовательной игры, и может использоваться независимо или в сочетании с другими модулями. + +## Категории модулей + +### Основные модули + +Фундаментальные компоненты для большинства многопользовательских игр: + +- [Authentication](Authentication.md) - Регистрация, вход, управление учетными записями +- [Profiles](Profiles.md) - Хранение и управление профилями игроков +- [Rooms](Rooms.md) - Создание и управление игровыми комнатами + +### Игровые модули + +Компоненты для расширения геймплейных возможностей: + +- [Achievements](Achievements.md) - Система достижений и наград +- [Censor](Censor.md) - Фильтрация нежелательного контента +- [Chat](Chat.md) - Система чата и обмена сообщениями +- [Lobbies](Lobbies.md) - Лобби для подготовки к игре +- [Matchmaker](Matchmaker.md) - Подбор игроков и матчей +- [Notification](Notification.md) - Пуш-уведомления и оповещения +- [Ping](Ping.md) - Проверка соединения и замер задержки +- [QuestsModule](QuestsModule.md) - Система квестов и заданий +- [WorldRooms](WorldRooms.md) - Постоянные игровые миры + +### Инфраструктурные модули + +Модули для развертывания и поддержки игровой инфраструктуры: + +- [Spawner](Spawner.md) - Динамический запуск игровых серверов +- [WebServer](WebServer.md) - Встроенный HTTP сервер для API и админ-панели + +### Аналитика и мониторинг + +Модули для сбора и анализа данных: + +- [AnalyticsModule](AnalyticsModule.md) - Сбор и анализ игровых событий + +## Архитектура модуля + +Каждый модуль обычно включает: + +1. **Серверная часть** + - Модуль (`*Module.cs`) - Основная логика модуля + - Реализация API (`*ModuleServer.cs`) - Серверная реализация + +2. **Клиентская часть** + - Клиентское API (`*ModuleClient.cs`) - Интерфейс для клиента + - Обработчики событий - Компоненты для реакции на события модуля + +3. **Общие компоненты** + - Пакеты данных (`Packets/*.cs`) - Структуры для обмена между клиентом и сервером + - Модели данных - Классы, описывающие данные модуля + - Интерфейсы - Контракты для расширения функциональности + +4. **Расширения** + - Интеграции с другими модулями + - Интеграции с внешними сервисами + +## Использование модулей + +Для использования модуля требуется: + +1. **Подключение на сервере**: + ```csharp + // Добавление модуля на сервер + var module = server.AddModule(); + + // Настройка модуля + module.someProperty = value; + + // Подписка на события + module.someEvent += HandleEvent; + ``` + +2. **Использование на клиенте**: + ```csharp + // Доступ к клиентскому API + var moduleClient = Mst.Client.GetModule(); + + // Вызов методов + moduleClient.SomeMethod(args, (successful, response) => { + // Обработка ответа + }); + ``` + +## Взаимодействие модулей + +Модули могут взаимодействовать между собой через: + +1. **Прямые зависимости** - Явные ссылки на другие модули +2. **Систему событий** - Слабосвязанное взаимодействие через события +3. **Общий контекст** - Доступ к общим данным через MasterServer + +## Расширение функциональности + +Для расширения модуля можно: + +1. **Наследоваться от базовых классов модуля**: + ```csharp + public class CustomAuth : AuthModule + { + // Расширенная функциональность + } + ``` + +2. **Реализовать требуемые интерфейсы**: + ```csharp + public class CustomDatabase : IAccountsDatabaseAccessor + { + // Пользовательская реализация доступа к данным + } + ``` + +3. **Подписаться на события модуля**: + ```csharp + module.OnSomeEvent += (data) => { + // Дополнительная логика при событии + }; + ``` + +## Лучшие практики + +1. **Минимализм** - Используйте только необходимые модули +2. **Абстракция** - Работайте с модулями через интерфейсы +3. **Расширяемость** - Создавайте наследников для кастомизации +4. **Слабая связь** - Используйте события вместо прямых зависимостей +5. **Безопасность** - Проверяйте все данные на стороне сервера \ No newline at end of file diff --git a/Docs/README.md b/Docs/README.md index bfe61f4..8006404 100644 --- a/Docs/README.md +++ b/Docs/README.md @@ -20,7 +20,8 @@ Master Server Toolkit - это фреймворк для создания мно ### [Networking](Networking.md) Документация по сетевому взаимодействию в системе. -### Модули +### [Модули](Modules/README.md) +Функциональные компоненты для создания многопользовательских игр: #### Основные модули - [Authentication](Modules/Authentication.md) - Аутентификация и управление пользователями @@ -58,14 +59,26 @@ Master Server Toolkit - это фреймворк для создания мно - [DebounceThrottle](Tools/DebounceThrottle.md) - Ограничение частоты вызовов - [WebGL](Tools/WebGL.md) - Инструменты для WebGL платформы -## Структура модуля - -Каждый модуль обычно состоит из следующих компонентов: -1. **Серверный модуль** (`*Module.cs`) - серверная логика модуля -2. **Серверная реализация** (`*ModuleServer.cs`) - реализация API сервера -3. **Клиентская часть** (`*ModuleClient.cs`) - клиентское API для взаимодействия с сервером -4. **Пакеты** (`Packets/*.cs`) - структуры данных для обмена между клиентом и сервером -5. **Интерфейсы и модели** - определение контрактов и объектов данных +## Структура проекта + +Базовая структура проекта Master Server Toolkit: + +``` +Assets/ +└── MasterServerToolkit/ + ├── Core/ - Ядро системы + ├── Modules/ - Модули + │ ├── Authentication/ - Модуль аутентификации + │ ├── Profiles/ - Модуль профилей + │ ├── Rooms/ - Модуль комнат + │ └── ... - Другие модули + ├── Tools/ - Инструменты + │ ├── UI/ - UI фреймворк + │ ├── Terminal/ - Терминал + │ └── ... - Другие инструменты + ├── Examples/ - Примеры использования + └── ThirdParty/ - Сторонние библиотеки +``` ## Начало работы @@ -97,12 +110,12 @@ Master Server Toolkit - это фреймворк для создания мно ## Лучшие практики -1. **Модульная архитектура** - добавляйте только те модули, которые вам нужны -2. **Использование интерфейсов** - создавайте собственные реализации для интеграции с вашей системой -3. **Безопасность** - всегда проверяйте права доступа на стороне сервера -4. **Масштабирование** - используйте систему спаунеров для балансировки нагрузки -5. **Планирование** - продумайте взаимодействие модулей заранее +1. **Модульная архитектура** - Добавляйте только те модули, которые вам нужны +2. **Использование интерфейсов** - Создавайте собственные реализации для интеграции с вашей системой +3. **Безопасность** - Всегда проверяйте права доступа на стороне сервера +4. **Масштабирование** - Используйте систему спаунеров для балансировки нагрузки +5. **Планирование** - Продумайте взаимодействие модулей заранее ## Дополнительная информация -Для более подробной информации обратитесь к документации соответствующих модулей. +Для более подробной информации обратитесь к документации соответствующих разделов.