Интеграция сетевого взаимодействия в игру на базе Phaser строится вокруг событийной модели. Библиотека Phaser отвечает за визуализацию, физику, управление сценами и игровую логику на клиенте, а Socket.IO обеспечивает двустороннюю передачу данных между клиентом и сервером в реальном времени.
Основная идея заключается в разделении ответственности:
Такой подход позволяет реализовывать мультиплеерные режимы: кооперативные игры, PvP-сражения, совместное прохождение уровней и MMO-структуры.
Socket.IO реализует событийную архитектуру:
emit)on)Схема взаимодействия:
Игрок A → Сервер → Игрок B
Игрок B → Сервер → Игрок A
Сервер выступает центральной точкой истины (authoritative server), предотвращая читерство и рассинхронизацию.
Установка зависимостей:
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');
});
Подключение клиента 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
});
}
}
При подключении нового клиента сервер должен:
Пример серверной логики:
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;
}
При прямой передаче координат возможны рывки из-за сетевых задержек. Используются методы:
Пример интерполяции:
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'
});
}
});
В соревновательных играх нельзя доверять клиенту. Правильная схема:
Пример:
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:
Пример создания комнаты:
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
});
});
Отправка координат 60 раз в секунду перегружает сеть.
Используется throttling:
if (this.time.now > this.lastSent + 50) {
this.socket.emit('playerMove', {...});
this.lastSent = this.time.now;
}
Передаются только изменённые параметры.
Использование коротких ключей:
{ 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:
Подходы:
При увеличении числа игроков:
Стратегия:
/server
server.js
/client
index.html
main.js
scenes/
GameScene.js
Разделение логики:
Пример:
class NetworkManager {
constructor(scene) {
this.scene = scene;
this.socket = io();
}
}
Методы защиты:
Интеграция Socket.IO с Phaser превращает однопользовательскую игру в распределённую систему реального времени. Ключевыми элементами являются авторитет сервера, оптимизация передачи данных, корректная синхронизация состояния и масштабируемая архитектура.