Как без боли сделать мультиплеер на Godot, который будет работать в браузере

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Представьте, что вы пишете свою десктопную мультиплеерную игру мечты, а потом вам захотелось, чтобы ваш проект также работал и в вебе. К сожалению, в интернете можно найти кучу гайдов по созданию только десктопного мультиплеера на Godot, но не браузерного мультиплеера, даже официальная документация самого движка никак не поможет и не предоставит примеров. У вас быстро опустятся руки и вы забьёте на эту затею, потому что ваш проект так и не заработает в браузере.

К счастью, эта проблема легко решается!

Вам может сказочно повезти, если поисковик выдаст этот пост на Reddit. Мне вот повезло, поэтому спешу рассказать и другим. Моя статья будет не столько переводом этого поста (и материалов, на которые она ссылается) на русский язык, сколько пересказом от моего лица с имеющимся опытом разработки + дополнительно поведаю о некоторых вещах, что не были упомянуты в посте.

Разделяем свой проект на серверную и клиентскую части

Игровой движок Godot предоставляет разработчикам на выбор два варианта многоранговой сети:

  • Listen-сервер (или хост-клиент) - один из игроков является также сервером, а остальные игроки подключаются к нему. Если игрок-сервер закроет игру, также будут выкинуты из сессии все остальные игроки. В таком случае у нас на руках будет один проект.

  • Клиент-сервер - сервер не является игроком и нужен только для хранения и передачи информации для всех подключенных и подключающихся игроков. В таком случае у нас на руках находится уже два проекта - это, собственно, серверная часть и клиентская часть.

Нам нужна клиент-серверная архитектура. Как она выглядит в Godot вы можете посмотреть в этом видео.

Немного пояснений

По-умолчанию мультиплеер на Godot работает с протоколом UDP, однако браузеры не принимают этот протокол из соображений безопасности. Чтобы мультиплеер заработал после экспорта в HTML5, нам нужны веб-сокеты, которые работают поверх безопасного протокола TCP.

Если вы загляните в документацию Godot, то увидите, что веб-сокеты реализуются крайне сложно, всё непонятно, какие-то ещё сторонние средства нужно использовать. Но это всё не нужно, потому что в Godot есть одна хитрость, благодаря которой можно и веб-сокеты использовать, и высокоуровневый API оставить. Как говорится, и рыбку съесть, и костью не подавиться.

В Godot есть два класса WebSocketServer и WebSocketClient. В документации говорится, что после экспорта проекта в HTML5 WebSocketServer перестанет работать из-за ограничений браузеров. Однако это не говорится в случае с WebSocketClient. Это означает, что клиентскую часть мы можем экспортировать в веб, а серверную часть придётся экспортировать как десктопное приложение, которое, например, мы можем запустить на сервере. Именно поэтому мы и выбрали архитектуру "клиент-сервер", потому что только она и будет работать.

Пишем код

Как правило в серверной части пишут следующий код:

var port = 8080
var max_clients = 20

func _ready():
	get_tree().connect("network_peer_connected", self, "client_connected")
	get_tree().connect("network_peer_disconnected", self, "client_disconnected")
  
	var server = NetworkedMultiplayerENet.new()
	server.create_server(port, max_clients)
	get_tree().set_network_peer(server)

а в клиентской части этот код:

# Локальный IP для запуска и проверки работоспособности мультиплеера на своей машине.
var ip = '127.0.0.1'
var port = 8080

func _ready():
	get_tree().connect("connected_to_server", self, "connected_to_server")
	get_tree().connect("server_disconnected", self, "server_disconnected")
  
  var client = NetworkedMultiplayerENet.new()
  client.create_client(ip, port)
  get_tree().set_network_peer(client)

В случае с веб-сокетом достаточно заменить несколько строк кода в серверной части:

var server
var port = 8080

func _ready():
	get_tree().connect("network_peer_connected", self, "client_connected")
	get_tree().connect("network_peer_disconnected", self, "client_disconnected")
	
	server = WebSocketServer.new()
	server.listen(port, PoolStringArray(), true)
	get_tree().set_network_peer(server)

func _process(delta):
	if server.is_listening():
		server.poll()

и клиентской части:

var client
var ip = 'ws://127.0.0.1:'
var port = '8080'

func _ready():
	get_tree().connect("connected_to_server", self, "connected_to_server")
	get_tree().connect("server_disconnected", self, "server_disconnected")
	
	client = WebSocketClient.new()
	var url = ip + port
	var error = client.connect_to_url(url, PoolStringArray(), true)
	get_tree().set_network_peer(client)

func _process(delta):
	if (client.get_connection_status() == NetworkedMultiplayerPeer.CONNECTION_CONNECTED || client.get_connection_status() == NetworkedMultiplayerPeer.CONNECTION_CONNECTING):
		client.poll()

Скорее всего, у вас не будет проблем с передачей данных между сервером и клиентом. У меня же проект был специфический и при запуске клиент принимал от сервера большой массив данных. Если вам тоже по какой-то причине понадобилось гонять огромные данные, то вам нужно зайти в настройки проекта -> Основное -> Network -> Limits -> Websocket Server (и Websocket Client) и увеличить значения полей Max Out Buffer Kb и Max In Buffer Kb.

Сертификат и HTTPS

Чтобы мультиплеер заработал на сайте с соединением через HTTPS (ещё один безопасный протокол), нужно обязательно получить криптографический сертификат и ключ. Но сертификат не должен быть самоподписанным, например, полученным при помощи утилиты OpenSSL. Сертификат должен быть получен из официальных центров, например, центра "Let's Encrypt".

Для этого нужно получить или купить доменное имя. Представим, что купили домен domainnameserver.ru

Там, где получали или покупали домен, должна быть возможность настроить DNS. В этих настройках нужно добавить поддомен _acme-challenge, в результате в наличие будет домен domainnameserver.ru и поддомен _acme-challenge.domainnameserver.ru

У меня, например, это выглядит так.
У меня, например, это выглядит так.

Теперь нужно арендовать VPS (Virtual Private Server) желательно на дистрибутиве Ubuntu или Debian. Заходим в терминал сервера, логин будет root, а пароль тот, что указывали при оформлении аренды.

Обновляемся:

apt update
apt upgrade

Устанавливаем утилиту Certbot:

sudo apt-get install certbot

Отправляем запрос в центр сертификации. После команды -d вписываем свой домен.

certbot certonly –manual -d domainnameserver.ru –agree-tos –manual-public-ip-logging-ok –preferred-challenges dns-01 –server https://acme-v02.api.letsencrypt.org/directory –register-unsafely-without-email –rsa-key-size 4096

Certbot вернёт запись типа TXT, после чего встанет на паузу:

Это один из примеров работы Certbot, запись TXT помечена красной стрелкой.
Это один из примеров работы Certbot, запись TXT помечена красной стрелкой.

Эту запись нужно скопировать, после чего вернуться к настройкам DNS и выбрать поддомен _acme-challenge.domainnameserver.ru.

У каждого домена и поддомена имеются записи различных типов (A, MX, TXT и т.д). Нам нужен тип TXT, поэтому заменяем у поддомена запись TXT на скопированную запись от Certbot.

И пока мы находимся в настройках DNS, можно ещё в запись типа A у домена domainnameserver.ru вписать внешний IP виртуального сервера.

Возвращаемся в терминал виртуального сервера и нажимаем Enter, чтобы Certbot продолжил работу. Сертификат и ключ появятся в директории /etc/letsencrypt/live/domainnameserver.ru

Переходим в эту директорию. Не обращаем внимания на файлы cert.pem и chain.pem, важны только файлы fullchain.pem и key.pem. Эти два файла при помощи утилиты OpenSSL конвертируем в понятные для Godot форматы CRT и KEY.

cd /etc/letsencrypt/live/pixelsplace.space
openssl x509 -outform der -in fullchain.pem -out fullchain.crt
openssl rsa -outform PEM -in privkey.pem -out privkey.key

Полученные файлы fullchain.crt и privkey.key скачиваем на свой компьютер через FTP-клиент и помещаем эти файлы в папку с серверной частью нашего мультиплеера.

В методе _ready() загрузим сертификат и ключ, т.е. добавим всего пару строк кода. Теперь код должен выглядеть так:

var server
var port = 8080

func _ready():
	get_tree().connect("network_peer_connected", self, "client_connected")
	get_tree().connect("network_peer_disconnected", self, "client_disconnected")
	
	server = WebSocketServer.new()
  server.private_key = load("res://HTTPSKeys/privkey.key")
	server.ssl_certificate = load("res://HTTPSKeys/fullchain.crt")
	server.listen(port, PoolStringArray(), true)
	get_tree().set_network_peer(server)

func _process(delta):
	if server.is_listening():
		server.poll()

Помимо этого заходим в настройки проекта -> Основное -> Network -> Ssl и в поле Certificates указываем файл fullchain.crt

В клиентской части заменяем var ip = 'ws://127.0.0.1:' на var ip = 'wss://domainnameserver.ru:'. Убедитесь, что у домена в записи A указан IP-адрес сервера.

Запуск серверной части

Экспортируем нашу серверную часть под Linux в формате PCK. Давайте обзовём его server.pck

Желательно создать папку на сервере, например, "game" и перекинуть уже туда файл server.pck (опять же через FTP-клиент, или можно скинуть на облако и скачать оттуда при помощи команды wget).

cd
mkdir /game
cd /game

Скачиваем в папку "game" Godot-сервер с официального сайта Godot, распаковываем и через него запускаем server.pck

wget https://downloads.tuxfamily.org/godotengine/3.4.4/Godot_v3.4.4-stable_linux_server.64.zip
unzip Godot_v3.4.4-stable_linux_server.64.zip
./Godot_v3.4.4-stable_linux_server.64 --main-pack server.pck

Можете для разнообразия глянуть это видео, но не обязательно.

Запуск клиентской части

Всё готово! Осталось только экспортировать клиентскую часть в HTML5. Не забудьте проверить, что файл для запуска называется index.html

Теперь вы можете выкладывать клиентскую часть на свой хост или на сайты, которые предназначены для игр, например, itch.io или gotm.io. Кстати, gotm.io как раз предназначен для запуска игр, разработанных на Godot.

Если вы следовали инструкции, то у вас всё заработает. Вы превосходны!

Источник: https://habr.com/ru/post/670238/


Интересные статьи

Интересные статьи

Сегодня рассказываем о том, как все устроено в Luxoft — глобальной компании, которая занимается разработкой ПО для клиентов по всему миру. Мы традиционно расспросили ребя...
В последний раз мы видели как изменение кода в скопированном коммите создаёт опасность конфликта, который сидит себе тихо пока обе копии где-нибудь не сольются, что может произойти и в оч...
В этот четверг на онлайн-митапе TechFest от компании Luxoft можно будет послушать и обсудить четыре доклада о разных вещах:– Высокопроизводительном транспорте данных Aero...
Многие знают, что ABBYY занимается обработкой и извлечением данных из разных документов. Но у наших продуктов есть и другие интересные возможности. В частности, с помощью решения ABBY...
В Python есть 3 способа форматировать строки, и один из них лучше других. Но не будем забегать наперед — о каком именно форматировании вообще речь? Каждый раз когда мы хотим поприветствовать ...