Как устроены онлайн игры? Создаем примитивную 2д онлайн игру

Добавлен , опубликован

Вступление

  • Как работают онлайн игры?
Этим вопросом задавался практически каждый, кто провел в них большое количество времени.
Разбираясь в этом вопросе, мне захотелось сделать её собственными руками.
Задачей было сделать максимально примитивное приложение, которое работало бы через интернет, позволяя игрокам с разных точек планеты подключаться и делать примитивные игровые действия.
А именно выбирать игровое лобби, писать в чат, и менять позицию своего "персонажа" в игровой локации.
  • Это не так сложно как ожидалось.
Механизм отправки сообщения на сервер и его трансляции всем клиентам на практике оказался прост, а задача сделать проект "любыми методами" не подразумевала идеальной технической реализации и оптимизации как в популярных онлайн играх.
Далее я поделюсь тем, какие решения в разработке я выбрал и как достиг того что получилось.


Как устроена эта игра?

  • Установка подключения и клиент-серверный обмен сообщениями.
Клиент подсоединяется к серверу по его url адресу. Далее они обмениваются сообщениями.
При подключении сервер "регистрирует" клиента в своей памяти, назначая ему случайный никнейм на возникший конект.
Далее назначает ему лобби, если лобби на сервере нет, создает его. После этого отправляет клиенту стартовую информацию о лобби в которое он зашел, оповещая в чате всех других игроков, находящихся в этом лобби.
  • Реакция на действия игрока
Как только игрок делает какое-либо действие, например пишет сообщение в чат, или кликает на 2д локацию, его клиент отправляет сообщение формата JSON, содержащее информацию о проделанной клиентом операции. Сервер принимает это сообщение, и на основе своих вычислений решает как ему на это отреагировать..
Например, если игрок что-то написал в чат, сервер определит что это за игрок, определит его лобби и добавит его сообщение в хранилище сообщений чата этого лобби, далее транслируя его всем другим клиентам и все они увидят на своем клиенте новое сообщение в чате.
Такая же логика и при клике игрока на канвас, игрок кликает, отправляется сообщение. Сервер его принимает, меняет внутри себя состояние локации 2д локации этого лобби, и транслирует информацию о новом состоянии локации всем игрокам этого лобби.
При отключении соединения, сервер оповещает всех игроков в лобби этого игрока о том что он вышел, и удаляет все данные о нем, обрывая связь.


Как я реализовал обмен сообщениями по сети?

  • Выбираем обмен сообщениями через websocket
Простота разработки стояла превыше оптимизации, поэтому я выбрал не самое удачное сетевое решение для онлайн игры, но достаточно легкое в реализации. Используя протоколы верхнего уровня, мой выбор пал на технологию Websocket.
Она позволяет установить клиенту и серверу постоянное подключение, по которому можно обмениваться сообщениями в реальном времени в двустороннем направлении. От сервера к клиенту, и от клиента к серверу.
  • Сообщения формата JSON
Тип сообщения достаточно тяжелый и имеет в себе лишнюю нагрузку, заголовки и прочее, одним словом JSON. Но с ним достаточно просто работать в отличии от закодированных бинарных, сжатых сообщений. В JSON ты отправляешь простой текст, который удобно без лишних действий читать и отлаживать.


Клиентская часть

  • Десктопное приложение или html-страница в браузере, сетевой обмен данными один и тот же
Первоначально идеей было создать десктопное приложение, но потом я подумал, что отличий десктопного приложения от html странички с javascript кодом на принципиальном уровне особо не будет. Вдобавок, случайному человеку легче набрать url в браузере, чем скачивать непонятные приложения от неизвестного автора и запускать их. Но принцип работы обмена данными по сети в обоих случаях выйдет один и тот же. Клиент устанавливает по заданному адресу websocket соединение и начинается обмен сообщениями. Запрос - ответ.
  • Из чего состоит клиентская часть?
Несколько html блоков с кнопками и формами текстового ввода, canvas на котором мы будем рисовать кубы наших игроков с никами.
И javascript обработчики этих кнопок и форм ввода.
  • "Сердце" кода клиентской части
Особого внимания заслуживают здесь только несколько строк кода на которых все держится. Благодаря высоким уровням абстракции, предоставленными умными людьми, нам не нужно самостоятельно копаться в реализации нижних уровней протоколов и сетевых драйверов.
Javascript дает нам API для работы с вебсокетами, и всего одной строчкой мы создаем websocket подключение, а закулисами под этой строкой происходит множество непонятных нам действий, благодаря которым чипы вашего компьютера могут обмениваться сигналами с чипами серверного компьютера.
вот так мы устанавливаем websocket соединение по адресу
	const socket = new WebSocket('ws://localhost:8080/ws'); // - замените url на адрес и порт своего сервера

А этой функцией мы читаем пришедшие по этому соединению сообдения
        //слушаем сервер
        socket.onmessage = function(event) {
            const message = JSON.parse(event.data);  
			//далее пишем реакцию вашего клиента на полученное сообщение	
		}
А вот такой строкой мы отправляем сообщение серверу
    socket.send(JSON.stringify(jsonChatMessage));
Я буду приводить вам только самые важные строки кода, на которых держится все мое приложение. Весь остальной код это уже попытка их организовать во что-то большее и сложное.


Серверная часть

  • Выбор технологий для серверной части
В качестве языка для сервера я выбрал Golang, на котором практикуюсь в нынешнее время. Но websocket должны поддерживать и другие популярные языки.
  • Главные моменты в серверном коде, благодаря которому обмен сообщениями возможен
Когда клиент набирает url в браузере его встречают серверные обработчики этих адресов

Если клиент обратится по url сервера, сервер даст ему html страничку.
	fs := http.FileServer(http.Dir(basePath)) 
	mux.Handle("/", http.StripPrefix("/", fs))

Как только html страница будет получена клиентом, она обратится по другому url к серверу, чтобы установить websocket соединение
	mux.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
		transport.ServeWs(MyHub, MyConnectionsManages, w, r)
	})

Клиент в браузер вводит url, функция-обработчик на стороне сервера на это реагирует и отправляет что-то в ответ клиенту.
Теперь рассмотрим как на стороне сервера формируется подключение по websocket в обработчике websocket url
		conn, err := upgrader.Upgrade(w, r, nil)
		if err != nil {
			log.Println("Ошибка установки соединения WebSocket:", err)
			return
		}  
		defer conn.Close()
  • Подключение также создается одной строкой, или выдаст ошибку если что-то пошло не так внутри.
Конструкция defer в golang автоматически закроет его как функция-обработчик завершится
Этой строкой кода, сервер будет читать входящие сообщения по этому соединению
	err := wsCon.conn.ReadJSON(&msgData)
	if err != nil {
		log.Printf("Error reading JSON: %v", err)
		break
	} else {
		cm.DistributeMessage(wsCon.Name, msgData)
	}
Получили, проверили на ошибки? Все хорошо? Делаем какие-то вычисления с этим сообщением
Вот так отправляем сообщение клиенту
	err := conn.conn.WriteMessage(websocket.TextMessage, messageJSON)
  • Все так просто?
Да, в эти небольшие строчки кода умещается основная работа всего моего клиент-серверного проекта. Все остальные конструкции кода - это попытка организовать эти элементарные сетевые механизмы в более сложную и надежную систему.
Как вы можете наблюдать, все устроено довольно просто, а умные люди, разрабатывающие языки программирования и сетевые библиотеки для них, уже написали для нас свои API, благодаря которым все сложные сетевые взаимодействия превращаются для нас в 2-3 строки кода.


Как организована серверная логика?

  • Механизмы, координирующие множество подключений и сообщений
Подключенный клиент добавляется в пул подключений в сущности hub
Все пришедшие от него JSON сообщения конвертируются в структуры golang написанные мной чтобы сервер мог ими манипулировать.
Специальные утилиты такие как connections manager и exchanger работают как посредники между пулом всех активных соединений hub и конкретным websocket соединением, определяя кого в какое лобби назначить, или кому какое сообщение в данный момент отправить и так далее.
  • Конкурентный доступ к данным
Также в golang есть хорошие инструменты реализующие многопоточность и конкурентный доступ к общим данным. Чтобы когда несколько клиентов одновременно обращались за какой-то одной ячейкой памяти нужной им, не произошло непонятных ситуаций и ошибок. Именно этим меня привлекает golang и той легкостью с которой в нем все это реализуется.


Как получилось сделать игру доступную в интернете?

  • Докер вездесущий
Все очень просто, я собрал docker образ в котором была как клиентская часть index.html, так и собранное в бинарный файл приложение сервера.
Загрузил его на docker hub.
  • Дешевые хостинги
Далее нашел дешевый хостинг серверов, арендовал серверную машину с операционной системой linux.
После этого установил на нее докер, скачал с docker hub свой образ и собственно, запустил контейнер на основе этого образа, поигравшись с портами и другими конфигами. Немного усилий и приложение доступно в интернете.


Полезные ссылки

В конечном итоге у меня все получилось и в эту игру вы можете сыграть прямо сейчас, попрыгать кубом по канвасу, и початиться с друзьями в разных лобби:
  • Сыграть в поделку
Торопитесь, через месяц хостинг игры закончится
  • Исходный код проекта:
`
ОЖИДАНИЕ РЕКЛАМЫ...

Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
0
37
4 месяца назад
0
Вот это стена текста, а можно хотябы параграфы и заголовки проставить?
0
4
4 месяца назад
0
Вот это стена текста, а можно хотябы параграфы и заголовки проставить?
да разве-ж это стена
сочная выжимка самых важных строк кода
и репецт как повторить
только сильные преодолеют стену
1
37
4 месяца назад
Отредактирован ScorpioT1000
1
Раздели хотябы на параграфы и опубликуем

P.s. nginx умеет рулить вебсокеты без отдельного роута /ws, прямо туда же где и http сервер слушается и по тому же порту
0
4
4 месяца назад
0
Раздели хотябы на параграфы и опубликуем

P.s. nginx умеет рулить вебсокеты без отдельного роута /ws, прямо туда же где и http сервер слушается и по тому же порту
не нашел параграфы, сделал отступы больше
текста в статье буквально кот наплакал
все самое важное, 6 строчек кода на которых все стоит
1
37
4 месяца назад
1
Ну, я имел ввиду двойной перенос строки по сути.
Опубликовал
2
29
4 месяца назад
2
Я так понимаю, сервер доверяет всем данным и можно слать всякое?
3
37
4 месяца назад
3
nazarpunk, забыли дописать в статью пару простых томов про sso, oauth2, rbac, antifraud, antiddos, ipsec и incident response для чайников?)
Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.