Интеграция с Socket.IO

Интеграция сетевого взаимодействия в игру на базе Phaser строится вокруг событийной модели. Библиотека Phaser отвечает за визуализацию, физику, управление сценами и игровую логику на клиенте, а Socket.IO обеспечивает двустороннюю передачу данных между клиентом и сервером в реальном времени.

Основная идея заключается в разделении ответственности:

  • Phaser — рендеринг, анимации, обработка ввода.
  • Socket.IO — передача событий, синхронизация состояния.
  • Сервер (Node.js) — авторитетная логика и координация игроков.

Такой подход позволяет реализовывать мультиплеерные режимы: кооперативные игры, PvP-сражения, совместное прохождение уровней и MMO-структуры.


Клиент-серверная модель

Socket.IO реализует событийную архитектуру:

  • Клиент отправляет события (emit)
  • Сервер принимает их (on)
  • Сервер транслирует изменения другим клиентам

Схема взаимодействия:

Игрок A → Сервер → Игрок B
Игрок B → Сервер → Игрок A

Сервер выступает центральной точкой истины (authoritative server), предотвращая читерство и рассинхронизацию.


Подключение Socket.IO к проекту Phaser

1. Серверная часть (Node.js)

Установка зависимостей:

npm install express socket.io

Базовый сервер:

const express = require('express');
const http = require('http');
const { Server } = require('socket.io');

const app = express();
const server = http.createServer(app);
const io = new Server(server);

io.on('connection', (socket) => {
    console.log('Игрок подключился:', socket.id);

    socket.on('playerMove', (data) => {
        socket.broadcast.emit('playerMoved', {
            id: socket.id,
            x: data.x,
            y: data.y
        });
    });

    socket.on('disconnect', () => {
        io.emit('playerDisconnected', socket.id);
    });
});

server.listen(3000, () => {
    console.log('Сервер запущен на порту 3000');
});

2. Клиентская интеграция в Phaser

Подключение клиента Socket.IO:

<script src="/socket.io/socket.io.js"></script>

Инициализация в сцене Phaser:

class GameScene extends Phaser.Scene {
    constructor() {
        super('GameScene');
    }

    create() {
        this.socket = io('http://localhost:3000');

        this.player = this.add.rectangle(100, 100, 32, 32, 0x00ff00);

        this.otherPlayers = {};

        this.socket.on('playerMoved', (data) => {
            if (this.otherPlayers[data.id]) {
                this.otherPlayers[data.id].x = data.x;
                this.otherPlayers[data.id].y = data.y;
            }
        });

        this.socket.on('playerDisconnected', (id) => {
            if (this.otherPlayers[id]) {
                this.otherPlayers[id].destroy();
                delete this.otherPlayers[id];
            }
        });
    }

    update() {
        if (this.input.keyboard.isDown(Phaser.Input.Keyboard.KeyCodes.LEFT)) {
            this.player.x -= 2;
        }

        this.socket.emit('playerMove', {
            x: this.player.x,
            y: this.player.y
        });
    }
}

Синхронизация игроков

Инициализация новых игроков

При подключении нового клиента сервер должен:

  1. Отправить ему список существующих игроков.
  2. Сообщить остальным о появлении нового игрока.

Пример серверной логики:

let players = {};

io.on('connection', (socket) => {

    players[socket.id] = { x: 100, y: 100 };

    socket.emit('currentPlayers', players);

    socket.broadcast.emit('newPlayer', {
        id: socket.id,
        x: 100,
        y: 100
    });

    socket.on('disconnect', () => {
        delete players[socket.id];
        io.emit('playerDisconnected', socket.id);
    });
});

На клиенте:

this.socket.on('currentPlayers', (players) => {
    Object.keys(players).forEach((id) => {
        if (id !== this.socket.id) {
            this.addOtherPlayer(id, players[id]);
        }
    });
});

this.socket.on('newPlayer', (playerInfo) => {
    this.addOtherPlayer(playerInfo.id, playerInfo);
});

Метод добавления:

addOtherPlayer(id, info) {
    const player = this.add.rectangle(info.x, info.y, 32, 32, 0xff0000);
    this.otherPlayers[id] = player;
}

Сглаживание движения (Interpolation)

При прямой передаче координат возможны рывки из-за сетевых задержек. Используются методы:

  • Линейная интерполяция
  • Буферизация состояний
  • Клиентская предикция

Пример интерполяции:

this.socket.on('playerMoved', (data) => {
    const player = this.otherPlayers[data.id];

    if (player) {
        this.tweens.add({
            targets: player,
            x: data.x,
            y: data.y,
            duration: 50,
            ease: 'Linear'
        });
    }
});

Авторитет сервера

В соревновательных играх нельзя доверять клиенту. Правильная схема:

  1. Клиент отправляет намерение (например, направление движения).
  2. Сервер рассчитывает позицию.
  3. Сервер отправляет обновлённое состояние всем клиентам.

Пример:

socket.on('moveRequest', (direction) => {
    const player = players[socket.id];

    if (direction === 'left') player.x -= 5;
    if (direction === 'right') player.x += 5;

    io.emit('stateUpdate', players);
});

Клиент лишь отображает полученное состояние.


Комнаты и лобби

Socket.IO поддерживает комнаты:

socket.join('room1');
io.to('room1').emit('startGame');

Применение в Phaser:

  • Разделение матчей
  • Игровые лобби
  • Турнирные сетки
  • PvP-комнаты

Пример создания комнаты:

socket.on('createRoom', (roomName) => {
    socket.join(roomName);
});

Передача игровых событий

Передаются не только координаты, но и:

  • Выстрелы
  • Получение урона
  • Подбор предметов
  • Смерть игрока
  • Смена состояния анимации

Пример события выстрела:

this.socket.emit('shoot', {
    x: this.player.x,
    y: this.player.y,
    angle: this.player.rotation
});

На сервере:

socket.on('shoot', (data) => {
    socket.broadcast.emit('playerShot', {
        id: socket.id,
        ...data
    });
});

Оптимизация сетевого трафика

1. Ограничение частоты обновлений

Отправка координат 60 раз в секунду перегружает сеть.

Используется throttling:

if (this.time.now > this.lastSent + 50) {
    this.socket.emit('playerMove', {...});
    this.lastSent = this.time.now;
}

2. Отправка только изменений

Передаются только изменённые параметры.

3. Сжатие данных

Использование коротких ключей:

{ x: 120, y: 50 }

вместо

{ positionX: 120, positionY: 50 }

Обработка задержек и потери соединения

Socket.IO предоставляет события:

this.socket.on('connect');
this.socket.on('disconnect');
this.socket.on('reconnect');

Пример обработки:

this.socket.on('disconnect', () => {
    this.add.text(200, 200, 'Соединение потеряно');
});

При переподключении сервер должен заново синхронизировать состояние.


Синхронизация физики

Если используется Arcade Physics или Matter.js:

  • Клиентская физика может отличаться от серверной.
  • Для точности сервер должен рассчитывать коллизии.
  • Клиент выполняет визуальную симуляцию.

Подходы:

  1. Полная серверная физика.
  2. Гибридная модель.
  3. Lockstep-синхронизация (RTS).

Масштабирование

При увеличении числа игроков:

  • Используется Redis-адаптер Socket.IO.
  • Разделение по инстансам.
  • Горизонтальное масштабирование.

Стратегия:

  • Один сервер — до 50–100 игроков.
  • Микросервисная архитектура для MMO.

Структура проекта

/server
    server.js
/client
    index.html
    main.js
    scenes/
        GameScene.js

Разделение логики:

  • Игровая логика клиента — внутри сцен Phaser.
  • Сетевая логика — в отдельном модуле (NetworkManager).

Пример:

class NetworkManager {
    constructor(scene) {
        this.scene = scene;
        this.socket = io();
    }
}

Безопасность

Основные угрозы:

  • Подмена координат
  • Спам событиями
  • Подмена ID
  • Массовые подключения

Методы защиты:

  • Валидация данных на сервере
  • Ограничение частоты запросов
  • JWT-аутентификация
  • HTTPS и WSS

Практические сценарии использования

Кооперативный платформер

  • Совместное перемещение
  • Общие объекты
  • Синхронизация врагов

PvP-арена

  • Серверная обработка урона
  • Лаг-компенсация
  • Таблица лидеров

MMO-локация

  • Загрузка чанков мира
  • Видимость по радиусу
  • Потоковая передача состояния

Интеграция Socket.IO с Phaser превращает однопользовательскую игру в распределённую систему реального времени. Ключевыми элементами являются авторитет сервера, оптимизация передачи данных, корректная синхронизация состояния и масштабируемая архитектура.