На этом уроке мы будем использовать Phaser 3 и Socket.io для создания простой многопользовательской игры на основе обычной клиент-серверной архитектуры: клиент отвечает за отображение игры для каждого игрока, обработку ввода игрока и связь с сервером, а сервер отвечает за передачу этих данных каждому клиенту.
Цель этого урока - научить вас основам создания многопользовательской игры.
На этом уроке вы узнаете как:
Вы можете скачать все файлы первой части здесь.
Для этого урока мы будем использовать Node.js и Express для создания нашего сервера. Мы также будем использовать менеджер пакетов NPM для установки необходимых для работы сервера пакетов. Чтобы следовать этому руководству, вам необходимо иметь локально установленные Node.js и NPM, или же вам потребуется доступ к среде, в которой они уже установлены. Мы также будем использовать командную строку (Windows) или терминал (Mac, Linux) для установки необходимых пакетов и запуска/остановки нашего Node-сервера.
Наличие опыта работы с этими инструментами является плюсом, но для этого урока это необязательно. Мы не будем рассказывать о том, как установить эти инструменты, так как основное внимание в этом руководстве уделяется созданию игры с использованием Phaser. Последнее, что вам понадобится, это IDE или текстовый редактор для редактирования вашего кода.
Чтобы установить Node.js, нажмите на ссылку здесь: и выберите версию LTS. Вы можете скачать и использовать текущую версию, однако, версия LTS рекомендуется для большинства пользователей. При установке Node.js, вы одновременно установите и NPM на вашем компьютере. После того, как вы установите эти инструменты, вы можете перейти к следующей части.
Первое, что мы собираемся сделать - это создать базовый сервер Node.js, который будет обслуживать нашу игру. Для начала создайте новую папку на своем компьютере, она может называться как угодно. Затем перейдите в эту папку и создайте новый файл с именем server.js
. Откройте этот файл и добавьте в него следующий код:
var express = require('express');
var app = express();
var server = require('http').Server(app);
app.use(express.static(__dirname + '/public'));
app.get('/', function (req, res) {
res.sendFile(__dirname + '/index.html');
});
server.listen(8081, function () {
console.log(`Прослушиваем ${server.address().port}`);
});
В приведенном выше коде мы:
app
.http
наш экземпляр app
, что позволит Express обрабатывать HTTP-запросы.express.static
.index.html
в качестве главной страницы.Прежде чем мы сможем запустить сервер, нам необходимо установить необходимые модули для сервера. Откройте свой терминал/командную строку и перейдите в папку вашего проекта. Оказавшись там, вам нужно будет выполнить следующую команду: npm init -f
. Эта команда создаст файл package.json
в папке вашего проекта. Мы будем использовать этот файл для отслеживания всех пакетов, от которых зависит наш проект.
Теперь мы установим Express. В вашем терминале выполните следующую команду: npm install --save express
. Эта команда создаст папку с именем node_modules
в папке вашего проекта, а, ключ --save
укажет npm сохранить этот пакет в нашем файле package.json
.
Закончив писать базовый код сервера, мы приступим к настройке кода на стороне клиента. В папке вашего проекта создайте новую папку с именем public
. Любой файл, который мы помещаем в эту папку, будет отображаться на сервере, который мы настроили. Поэтому мы хотим поместить все наши статические файлы на стороне клиента в эту папку. Теперь внутри папки public
создайте новый файл с именем index.html
. Откройте этот файл и добавить следующий код к нему:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/phaser@3.20.1/dist/phaser.min.js"></script>
<script src="js/game.js"></script>
</body>
</html>
В приведенном выше коде мы создали простую HTML-страницу и сослались на два файла JavaScript: phaser.min.js
(игровой фреймворк Phaser) и game.js
(наш код игры Phaser). Вернитесь в папку public
, создайте в ней подпапку с именем js
и в этой папке создайте новый файл с именем game.js
. Откройте этот файл и добавьте в него следующий код:
var config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
physics: {
default: 'arcade',
arcade: {
debug: false,
gravity: { y: 0 }
}
},
scene: {
preload: preload,
create: create,
update: update
}
};
var game = new Phaser.Game(config);
function preload() {}
function create() {}
function update() {}
Давайте рассмотрим код, который мы только что добавили:
config
в поле type
мы устанавливаем тип рендерера для нашей игры. Два основных типа: Canvas
и WebGL
. WebGL
является более быстрым средством рендеринга и имеет лучшую производительность, но не все браузеры поддерживают его. Значение типа AUTO
указывает Phaser будет использовать WebGL, если он доступен, в противном случае использовать Canvas.config
поле parent
используется, чтобы сообщить Phaser, что нужно рендерить нашу игру в имеющийся элемент <canvas>
с этим идентификатором, если он существует. Если он не существует, то Phaser создаст для нас элемент <canvas>
.config
мы указываем ширину и высоту видимой области нашей игры.config
мы включили аркадную физику, доступную в Phaser, и установили гравитацию на 0.config
мы встроили объект сцены, который будет использовать функции preload
, create
, update
.Теперь мы протестируем наш сервер и убедимся, что все работает правильно. Вернувшись в терминал/командную строку, выполните следующую команду: node server.js
, и вы должны увидеть следующую строку "Прослушиваем 8081". Затем, если вы откроете свой веб-браузер и перейдете по адресу http://localhost:8081/, вы увидите черный прямоугольник на веб-странице , а если вы откроете консоль в инструментах разработчика, вы должны увидеть журнал с версией Phaser, это значит, что ваша игра работает.
Теперь, когда наш сервер рендерит нашу игру, мы будем работать над добавлением Socket.IO
в нашу игру. Socket.IO - это библиотека JavaScript, которая обеспечивает двустороннюю связь между веб-клиентами и серверами в режиме реального времени. Чтобы использовать Socket.IO, нам нужно обновить наш клиентский и серверный код, чтобы обеспечить связь между ними.
Вернувшись в свой терминал, выполните следующую команду: npm install --save socket.io
. Если ваш сервер все еще работает, вы можете либо: открыть новое окно терминала и запустить данную команду в папке вашего проекта, либо остановить сервер (CTRL + C), а затем выполнить ее. Эта команда установит пакет Socket.IO
для node и сохранит его в нашем файле package.json
.
Теперь в файле server.js
добавьте под строчкой var server = require('http').Server(app);
следующий код:
var io = require('socket.io').listen(server);
Затем добавьте над строкой server.listen
следующий код:
io.on('connection', function (socket) {
console.log('подключился пользователь');
socket.on('disconnect', function () {
console.log('пользователь отключился');
});
});
В приведенном выше коде мы:
socket.io
и прослушиваем наш объект сервера.Socket.IO
. Откройте файл index.html
и добавьте следующую строку вначале элемента <body>
:
<script src="/socket.io/socket.io.js"></script>
А теперь откройте файл game.js
и добавьте следующий код в функцию create
:
this.socket = io();
Теперь, если вы снова запустите сервер и обновите несколько раз игру в своем браузере, вы должны увидеть сообщения о подключении/отключении пользователя в вашем терминале.
Теперь, когда у нас настроены сокетные соединения, мы можем перейти к добавлению игроков в нашу игру. Чтобы синхронизировать всех игроков в игре, нам нужно будет уведомить их всех, когда пользователь подключается или отключается от игры. Кроме того, когда подключается новый игрок, нам нужен способ сообщить игроку обо всех других игроках в игре. Чтобы сделать все это, нам нужно будет хранить некоторые данные игроков, и мы будем использовать сокетные соединения для отправки сообщений нашим игрокам.
Для этого урока мы будем хранить данные по игрокам в оперативной памяти на сервере. В реальных проектах данные лучше хранить в какой-то базе данных, чтобы они были постоянными, и в случае сбоя сервера мы могли бы легко восстановить состояние игры.
В файле server.js
добавьте следующую строку ниже объявления переменной io
:
var players = {};
Мы будем использовать этот объект для отслеживания всех игроков, которые в данный момент находятся в игре. Затем в функцию обратного вызова объекта socket.io
для события connection
добавьте следующий код после строки console.log('подключился пользователь');
:
// создание нового игрока и добавление го в объект players
players[socket.id] = {
rotation: 0,
x: Math.floor(Math.random() * 700) + 50,
y: Math.floor(Math.random() * 500) + 50,
playerId: socket.id,
team: (Math.floor(Math.random() * 2) == 0) ? 'red' : 'blue'
};
// отправляем объект players новому игроку
socket.emit('currentPlayers', players);
// обновляем всем другим игрокам информацию о новом игроке
socket.broadcast.emit('newPlayer', players[socket.id]);
Давайте рассмотрим код, который мы только что добавили:
players
, при этом мы используем в качестве ключа значение socket.id
.rotation
, x
и y
игрока, в дальнейшем мы будем использовать эти значения для управления спрайтами на стороне клиента, и используем эти данные для обновления всех игроков. Мы также сохраняем playerId
, чтобы мы могли ссылаться на него в игре, и мы добавили атрибут team
, который будет использоваться позже.socket.emit
и socket.broadcast.emit
для отправки события клиентскую часть сокета. socket.emit
будет выдавать событие только на этот конкретный сокет (новый подключенный проигрыватель). socket.broadcast.emit
(трансляция) отправит событие всем другим сокетам (существующим игрокам).currentPlayers
мы передаем объект players
новому игроку. Эти данные будут использоваться для заполнения всех спрайтов игроков в игре нового игрока.newPlayer
мы передаем данные нового игрока всем остальным игрокам, чтобы можно было добавить в их игру новый спрайт.Когда игрок отключается, нам нужно удалить данные этого игрока из объекта players
, и нам нужно отправить всем остальным игрокам сообщение об уходе этого пользователя, чтобы мы могли удалить спрайт этого игрока из игры.
В функции обратного вызова объекта socket.io
для события connection
добавьте следующий код после строки console.log('пользователь отключился');
:
// удаляем игрока из нашего объекта players
delete players[socket.id];
// отправляем сообщение всем игрокам, чтобы удалить этого игрока
io.emit('disconnect', socket.id);
Ваш файл server.js
должен выглядеть следующим образом:
var express = require('express');
var app = express();
var server = require('http').Server(app);
var io = require('socket.io').listen(server);
var players = {};
app.use(express.static(__dirname + '/public'));
app.get('/', function (req, res) {
res.sendFile(__dirname + '/index.html');
});
io.on('connection', function (socket) {
console.log('подключился пользователь');
// создание нового игрока и добавление го в объект players
players[socket.id] = {
rotation: 0,
x: Math.floor(Math.random() * 700) + 50,
y: Math.floor(Math.random() * 500) + 50,
playerId: socket.id,
team: (Math.floor(Math.random() * 2) == 0) ? 'red' : 'blue'
};
// отправляем объект players новому игроку
socket.emit('currentPlayers', players);
// обновляем всем другим игрокам информацию о новом игроке
socket.broadcast.emit('newPlayer', players[socket.id]);
socket.on('disconnect', function () {
console.log('пользователь отключился');
// удаляем игрока из нашего объекта players
delete players[socket.id];
// отправляем сообщение всем игрокам, чтобы удалить этого игрока
io.emit('disconnect', socket.id);
});
});
server.listen(8081, function () {
console.log(`Прослушиваем ${server.address().port}`);
});
На этом мы закончим первую часть этого урока. Во второй части мы завершим нашу многопользовательскую игру: