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

Добавил пользователь Cypher
Обновлено: 19.09.2024

  • Опубліковано 30 кві 2020
  • Привет друзья! Сегодня мы с вами сделаем игру Agar.io в Scratch! Довольно интересная игра, особенно с движущимся фоном. На клеточном поле есть еда, которую поедает бактерия, которой вы управляете мышкой. И она постепенно растет. 😊

✔ Научимся делать простой движущийся фон.
✔ Научимся управлять движением в зависимости от положения мышки.
✔ Сделаем игру Агарио (Agar.io).

❗️❗️❗️ Это первая часть игры, вторая часть с врагами будет выложена, когда видео наберет 500 позитивов.

Ставь лайк, если тебе понравилось видео 👍
►► Подписывайся на канал!

✅ Начинаете программировать? - Повторяйте все то, что показываем в обучающем видео. Тогда вы будете закреплять материал быстрее и сможете начать делать классные игры на Scratch.


Проект Agar.io появился пару месяцев назад и стал безумно популярен благодаря вирусному распространению в социальных сетях, на имидж-бордах и форумах. Управление Agar.io до безобразия простое, но во многом именно секрет в простоте сделал Агарио столь популярной игрой во всём мире.

Управление и тактика в Agario

Agar.io представляет собой симулятор жизни простейшего организма — бактерии. Главная цель — выжить любой ценой, став как можно массивнее. Не секрет что для того чтобы стать больше необходимо питаться. На начальных этапах придется поглощать питательную среду, которая находится повсюду на карте. Однако достигнув определенного размера, маленькие кусочки еды перестают существенно влиять на рост бактерии, что провоцирует охоту за другими бактериями. Для того чтобы отыскать как можно больше жертв, и при этом самому не быть съеденным, в первую очередь нужно как следует освоить руководство по управлению в Agar.io.

Как управлять бактерией Agar.io?

Пояснения по игровому полю

Секреты игры Агарио

Инструкция по игре Agar.io с другом -> /materials/425.

В игре можно расширять возможности с помощью Server Browser на Agar.io и читов.

Набрав 100 очков, можно начинать охоту умело управляя своим шаром в Agar.io. Для этого часто придется использовать деление. Однако делиться рекомендуется только в тех случаях, когда проглоченная масса сможет почти полностью компенсировать отделившийся кусок. После того как бактерия поделилась лучше переждать вдали от остальных до той поры, пока организм вновь не станет целостным.

Тактика опытных игроков. Имея от 1000 до 5000 очков в Agar.io придется передвигаться особо осторожно. Теперь вышей бактерии страшны не только другие игроки, но и вирусы, которые можно повстречать на карте. Их следует обходить стороной. Для того чтобы набрать дополнительный вес придется держаться около гигантов, которые то и дело отделяют от себя небольшие кусочки. Их и придется стараться ухватить.

Совет и напутствие. Достигнув 5000+ вы сами станете гигантом. Главная цель Agar.io теперь — это сохранить свой вес.

Внешний вид бактерии

В Agar.io можно установить скин или картинку на шар. Использовать можно те изображения, которые уже имеются в системе и создать свою картинку Агарио.

Все стандартные изображения:

Ники для создания скинов в Agario

Ники для создания скинов - 2 часть

Привет друзья! Сегодня мы с вами сделаем игру Agar.io в Scratch! Довольно интересная игра, особенно с движущимся фоном. На клеточном поле есть еда, которую поедает бактерия, которой вы управляете мышкой. И она постепенно растет. 😊

✔ Научимся делать простой движущийся фон.
✔ Научимся управлять движением в зависимости от положения мышки.
✔ Сделаем игру Агарио (Agar.io).

❗️❗️❗️ Это первая часть игры, вторая часть с врагами будет выложена, когда видео наберет 500 позитивов.

Ставь лайк, если тебе понравилось видео 👍
►► Подписывайся на канал!

✅ Начинаете программировать? - Повторяйте все то, что показываем в обучающем видео. Тогда вы будете закреплять материал быстрее и сможете начать делать классные игры на Scratch.

Код Дурова

1. Обзор проекта и его структуры

Для наглядного примера лучше скачать исходный код игры.

Наш проект будет использовать:

  • Express.js — самый популярный веб фреймворк для Node.js
  • Socket.io — WebSocket-библиотека для настройки коммуникации между сервером и клиентом
  • Webpack модуль

/public

Всё, что находится в этой папке, будет статически обрабатываться нашим сервером. А в папку /public/assets необходимо размещать картинки нашего проекта.

Весь исходный код хранится в папке src/

  • client/ — для хранения файлов клиента,
  • server/ — для хранения файлов сервера,
  • shared/ — содержит файлы, которые импортированы из клиента и сервера.

2. Настройка проекта, настройка инструментов для разработки

Как говорилось ранее, мы будем использовать Webpack модуль для создания нашего проекта. Давайте рассмотрим его конфигурацию.

Нас прежде всего интересуют строки:

src/client/index.js — это входная точка у Javascript. Webpack начнёт свою работу оттуда и далее рекурсивно будет просматривать остальные файлы, что были указаны в файле.

JS точка выхода нашего Webpack билда будет расположена в папке /dist. Я буду называть этот файл JS bundle.

Мы используем Babel, а именно @babel/preset-env конфиг, для конфигурации нашего JS кода под старые браузеры.

Мы используем плагин для извлечения CSS на который ссылается наш JS код и объединяем это все вместе в bundle. Я буду называть это CSS bundle.

Вы могли заметить странные '[name].[contenthash].ext' названия bundle файлов. Они включают в себя Webpack названия, в поле [name] будет подставлено имя точки входа — game, а contenthash заменяется хешем файла. Мы делаем это для оптимизации кеширования, чтобы позволить браузерам кешировать наши файлы вечно, потому что, если bundle меняется, то меняется имя файла тоже (contenthash меняется). В финальном результате файл будет выглядеть вот так: game.dbee4534d3r4345n.js.

webpack.dev.js

Мы используем webpack.dev.js для более эффективной разработки и переключаемся на webpack.prod.js для оптимизации размера бандлов, когда запускаем в production.

Локальная установка

Рекомендуется устанавливать проект на локальном компьютере, это довольно просто. Для этого нужно установить Node и NPM, а после выполнить эти команды:

И все, теперь можно начинать. Чтобы запустить development server, надо выполнить:

$ npm run develop

Затем следует перейти в браузере по адресу localhost:3000, после чего development server автоматически пересоздаст JS и CSS бандлы во время редактирования кода: чтобы увидеть изменения, необходимо будет просто обновить страницу.

3. Точки входа к клиенту. index.html и index.js

Теперь давайте перейдём к коду. Чтобы начать, нужно создать index.html, так как это первый файл, к которому обращается браузер.

Index.html

  • HTML5 Canvas (<canvas>), который используется для рендеринга игры,
  • <link> для подключения CSS bundle,
  • <script> для подключения Javascript bundle,
  • Главное меню с Username <input> и кнопкой “PLAY” <button>.

После того, как главная страница загрузится в браузере, начнёт выполняться JavaScript код, начиная с точки входа src/client/index.js

Index.js

Этот код может показаться немного сложным, но на самом деле здесь не много чего происходит:

Основная логика клиента будет распределена в файлах, которые импортируются с помощью index.js.

4. Коммуникация клиента с сервером

Для того чтобы обеспечить полностью такую коммуникацию, мы создадим файл src/client/networking.js

Networking.js

В этом файле происходит 3 основных момента:

5. Рендеринг на клиенте

Теперь необходимо настроить проект на вывод на экран. Для этого нужно загрузить все assets, поэтому надо написать сначала Assets manager.

Assets.js

Основная задача этого файла состоит в хранении объекта assets, который сопоставляет ключ имени файла со значением объекта image. Когда загрузка изображений завершена, они загружаются в объект assets для того, чтобы в дальнейшем обращаться в этому объекту. Как только загрузка каждого отдельного ассета завершена, выполняется downloadPromise.

Для рендеринга используется HTML 5 Canvas (<canvas>), который отрисует страницу. Так как игра довольно простая, то достаточно будет выполнить следующие действия:

  • Фон,
  • Корабль игрока,
  • Другие игроки в игре,
  • Пули.

Самые важные части файла src/client/render.js, который отрисовывает перечисленные выше пункты.

Render.js

render() - это основная функция этого файла.

startRendering() и stopRendering() управляют активацией рендеринга 60 FPS.

Реализация отдельных вспомогательных функций рендеринга (например, renderBullet()) не так важна, но вот один пример:

Render.js

Обратите внимание, как используется метод getAsset(), который мы видели ранее в asset.js. Так же можете прочесть остальную часть src/client/render.js, если вам интересно увидеть другие вспомогательные функции для рендеринга.

6. Взаимодействие клиента с пользователями

Теперь необходимо настроить управление игрой. Они очень просты: мышка для ПК, либо тачскрин для мобильного для выбора направления движения. Чтобы сделать это, нужно объявить Event Listeners для мышки и тачскрина.

нужные функции будет содержать файл src/client/input.js

Input.js

onMouseInput() и onTouchInput() — это обработчики событий, которые вызывают функцию updateDirection() (из network.js), когда происходит определённое событие, например, движется мышь.

updateDirection() — обеспечит общение с сервером, который обрабатывает событие ввода и соответствующим образом обновляет игру.

7. Статус клиента: обработка информации с сервера

Последняя часть, которую надо выполнить на клиенте — это состояние. Помните этот код из раздела рендеринга?

Render.js

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

Вот пример обновлений, которые отправляет сервер:

Каждое обновление содержит такие данные:

  • T — время на сервере, когда была совершена отправка,
  • Me — информация об игроке, который получает данные,
  • Others — массив информации об игроках для других игроков в той же игре,
  • Bullets — массив данных о пулях в игре,
  • Leaderboard — таблица лидеров.

7.1 Наивный статус клиента

В наивной имплементации getCurrentState() может напрямую возвращать данные из самого последнего полученного апдейта.

Naive-state.js

Этот код выглядит просто и лаконично, но не всё так просто. Одна из причин, по которой эта реализация проблематична, заключается в том, что она ограничивает частоту кадров рендеринга до тактовой частоты сервера.

Частота кадров — количество кадров (например, вызовов render()) в секунду или FPS. Игры обычно нацелены не менее чем на 60 кадров в секунду.

Тактовая частота сервера — скорость, с которой сервер отправляет обновления игры клиентам. Часто это ниже частоты кадров. В нашей игре сервер работает со скоростью 30 обновлений в секунду.

Если мы просто будем рендерить последние обновления сервера, наш FPS не сможет превысить 30 кадров в секунду, потому что мы никогда не будем получать с сервера более 30 обновлений в секунду.

Даже если мы вызовем render() 60 раз в секунду, половина этих вызовов просто перерисует то же самое, фактически ничего не делая.

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

Вот идеальная схема для обновлений.

Но, к сожалению, в реальности обновления выглядят вот так.

Наивная реализация — это фактически наихудший сценарий, когда дело доходит до лагов. Если обновление игры приходит с опозданием на 50 мс, клиент зависает еще на 50 мс, поскольку он всё ещё отображает состояние игры из предыдущего обновления. Вы можете себе представить, как это будет плохо для игрока: игра будет дерганной и нестабильной из-за случайного зависания.

7.2 Улучшенное состояние клиенте

Это дает нам буфер 100 мс, чтобы выдерживать непредсказуемые события.

Стоимость такой реализации — постоянная задержка в 100 мс. Это небольшая цена за стабильный и плавный игровой процесс: большинство игроков (особенно случайных) даже не заметят задержки. Людям гораздо проще приспособиться к постоянной задержке в 100 мс, чем пытаться играть с непредсказуемой задержкой.

Ещё одно улучшение, которое можно сделать, — это использование линейной интерполяции. Из-за задержки рендеринга у нас обычно уже есть по крайней мере одно обновление клиента. Каждый раз, когда вызывается getCurrentState(), мы можем выполнять линейную интерполяцию между обновлениями игры непосредственно до и после текущего времени клиента.

Это решает проблему с частотой кадров: теперь можно отображать уникальные кадры настолько часто, насколько захотим.

7.3 Имплементация улучшенного состояния клиента

На примере имплементации в файле src/client/state.js используется задержка рендеринга и линейная интерполяция. Давайте разобьем этот код на 2 части для лучшего понимания. Вот первая часть:

State.js

Первое, что нужно понять — это то, что делает currentServerTime(). Как мы видели ранее, каждое обновление игры включает отметку времени сервера. Мы хотим использовать задержку рендеринга для рендеринга в 100 мс с отставанием от сервера, но мы никогда не узнаем, какое текущее время на сервере, потому что мы не можем знать, сколько потребовалось для получения любого данного обновления, так как скорость интернета бывает довольно непредсказуемой.

Чтобы обойти эту проблему, нужно использовать разумное приближение: мы делаем вид, что первое обновление пришло мгновенно. Если бы это было так, то мы бы знали время сервера именно в этот момент. Мы храним серверное время в firstServerTimestamp, и сохраняем наше локальное (клиентское) время в тот же момент в gameStart.

Поэтому время на сервере и на клиенте будет разным. Date.now() будет возвращать разное время на клиенте и сервере в зависимости от разных факторов, локальных для этих машин. Не стоит рассчитывать, что время будет одинаковыми на разных машинах.

Теперь ясно, что делает currentServerTime(): он возвращает время на сервера в момент рендеринга. Другими словами, это текущее время сервера (firstServerTimestamp + (Date.now () - gameStart)) за вычетом задержки рендеринга (RENDER_DELAY).

Теперь давайте разберемся, как обрабатываются обновления игры. processGameUpdate() вызывается каждый раз, когда с сервера поступает обновление, оно сохраняет новое обновление в массиве gameUpdates. Затем, чтобы контролировать использование памяти, удаляются все старые обновления, сделанные до базового обновления, поскольку они больше не нужны.

Что именно представляет собой базовое обновление? Это первое обновление, которое находится перед текущим серверным временем.

Для чего используется базовое обновление? Почему мы можем выбросить обновления до базового обновления? Чтобы это выяснить посмотрим на реализацию getCurrentState():

State.js

Здесь обрабатываются 3 события:

  • base <0 означает отсутствие обновлений до текущего времени рендеринга (см. реализацию getBaseUpdate() выше). Это может произойти в самом начале игры из-за задержки рендеринга. В этом случае используем самое последнее обновление, которое у нас есть.
  • base — это самое последнее обновление, которое у нас есть. Это может произойти из-за задержки или плохого подключения к Интернету. В этом случае также используем самое последнее обновление, которое у нас есть.
  • У нас есть обновление как до, так и после текущего времени рендеринга, поэтому мы можем интерполировать.

Все, что осталось в state.js, — это реализация линейной интерполяции, которая представляет собой чистую математику. Если хотите убедиться в этом сами, посмотрите state.js на Github.

Читайте также: