← Назад к вопросам

Приведи пример сетевых проблем в своих проектах

1.6 Junior🔥 141 комментариев
#Опыт и софт-скиллы

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Примеры сетевых проблем в проектах Unity

В моей практике на проектах от мобильных экшенов до MMO я сталкивался с широким спектром сетевых проблем. Основной фреймворк — Unity Netcode, ранее UNET, а для кастомных решений — LiteNetLib или прямой Socket API.

1. Десинхронизация игрового состояния

Проблема: В многопользовательском шутере игроки видели противников в разных позициях. Пули проходили «сквозь» цели, или игрок погибал, будучи уже за укрытием.

Причина: Использование простой экстраполяции (dead reckoning) без коррекции и серверного авторитета. Клиенты предсказывали движение на основе старых пакетов, а расхождение не исправлялось.

Решение: Внедрили серверный авторитет с периодической сверкой состояния. Ключевой код на стороне сервера:

// Пример проверки позиции игрока на сервере
void FixedUpdate() {
    foreach (var player in connectedPlayers) {
        Vector3 reportedPos = player.LastReceivedPosition;
        Vector3 serverPos = player.ServerPosition;
        
        // Если расхождение критично, корректируем
        if (Vector3.Distance(reportedPos, serverPos) > tolerance) {
            SendCorrection(player.Id, serverPos);
            player.IsValidPosition = false;
        }
    }
}

2. Проблемы с пропуском пакетов и лагом

Проблема: В кооперативной игре предметы внезапно «терялись», или открытие двери задерживалось на 2-3 секунды.

Причина: Использование ненадёжных UDP-пакетов (Unreliable) для важных событий, отсутствие подтверждения получения и повторной отправки.

Решение: Разделили трафик на типы:

  • Unreliable для частых потоковых данных (позиция, вращение).
  • Reliable Ordered для ключевых событий (подбор предмета, урон).
// Пример отправки надёжного события через кастомный менеджер
public void SendReliableEvent(NetworkEventType eventType, object data, int targetId) {
    byte[] serializedData = SerializeEvent(eventType, data);
    // Отправка с подтверждением
    networkManager.SendReliable(serializedData, targetId, OnDeliveryConfirmed);
}

void OnDeliveryConfirmed(int ackId) {
    // Логика при подтверждении, очистка из буфера
    pendingEvents.Remove(ackId);
}

3. «Баттл-роял»: проблемы с масштабированием и лагами при схватках

Проблема: В начале матча при массовом сбросе игроков или в финальной зоне с 20+ игроками возникали сильные лаги, пакеты терялись.

Причина: Наивная рассылка всем данных обо всех (O(n²)). Каждый клиент получал обновления о каждом игроке 30 раз в секунду, даже если тот был за километр.

Решение:

  • Приоритизация и дистанционный куллинг: Сервер рассылал полные данные только об игроках в зоне видимости.
  • Зонирование обновлений: Для дальних игроков отправлялись редкие (5 Гц) обновления только позиции.
  • Компрессия данных: Квантование значений (например, позиция с точностью до 0.1м).
// Пример приоритизации на сервере
List<PlayerUpdate> GetUpdatesForPlayer(Player observer) {
    var updates = new List<PlayerUpdate>();
    foreach (var player in allPlayers) {
        if (player == observer) continue;
        
        float distance = Vector3.Distance(observer.Position, player.Position);
        PlayerUpdate update = new PlayerUpdate(player.Id);
        
        if (distance < highPriorityRange) {
            update.Position = Quantize(player.Position, 0.1f);
            update.Rotation = Quantize(player.Rotation, 5f);
            update.Health = player.Health;
            update.Priority = Priority.High;
        } else if (distance < mediumPriorityRange) {
            update.Position = Quantize(player.Position, 0.5f);
            update.Priority = Priority.Medium;
        }
        // Игроки слишком далеко — не отправляем
        if (update.Priority != Priority.None) updates.Add(update);
    }
    return updates.OrderByDescending(u => u.Priority);
}

4. Античит и безопасность

Проблема: Игроки использовали читы на ускорение (speedhack), телепортацию, бесконечное здоровье.

Причина: Частичный клиентский авторитет и отсутствие валидации на сервере. Клиент сообщал: «Я нанёс 100 урона», а сервер принимал это.

Решение:

  • Серверная валидация всех действий: Сервер проверяет, может ли игрок находиться в этой точке, нанести такой урон и т.д.
  • Временные метки и симуляция: Сервер хранит историю состояний и пересчитывает, могло ли событие произойти.
// Упрощённая проверка перемещения на сервере
bool ValidateMovement(Player player, Vector3 newPosition, float timestamp) {
    Vector3 oldPosition = player.Position;
    float maxSpeed = player.MaxSpeed;
    float deltaTime = timestamp - player.LastMoveTime;
    
    // Расчёт максимально возможной дистанции
    float maxPossibleDistance = maxSpeed * deltaTime;
    float actualDistance = Vector3.Distance(oldPosition, newPosition);
    
    // Учитываем погрешность сети (лаг компенсация)
    float allowedDistance = maxPossibleDistance + networkTolerance;
    
    if (actualDistance > allowedDistance) {
        // Возможный читер. Откатываем, логируем, наказываем
        player.Position = oldPosition;
        LogCheatAttempt(player.Id, "Speedhack", actualDistance, allowedDistance);
        return false;
    }
    return true;
}

Общие выводы и практики

  • Проектирование с нуля: Сетевая архитектура должна быть продумана в начале, а не «прикручена» позже.
  • Инструменты — ключ: Собственные дашборды для мониторинга пинга, потерь пакетов, десинхронизации незаменимы.
  • Тестирование в реальных условиях: Эмуляция плохого соединения (jitter, packet loss) обязательна на этапе QA.
  • Компенсация лага (Lag Compensation): Использование rewind-техник на сервере для расчёта попаданий с учётом прошлых позиций игроков.
  • Оптимизация bandwidth: Снижение частоты обновлений, дельта-компрессия (отправка только изменений), битовая упаковка.

Большинство проблем сводится к фундаментальным компромиссам между отзывчивостью (responsiveness), консистентностью (consistency) и пропускной способностью (bandwidth). Универсального решения нет — подход зависит от жанра: для казуальной мобильной игры важна плавность, для соревновательного шутера — честность и минимальный лаг.