Как сделать игру на arduino
Разработка игр для консоли на базе arduino с помощью Unity
Привет, Гик Таймс!
Сегодня я поведаю вам одну не очень интересную историю о том, как создал простую игровую консоль на базе arduino и сделал несложную игру для нее в моем любимом игровом движке — Unity.
Вот уже почти четыре года я занимаюсь разработкой игр на популярном игровом движке Unity (ранее Unity3D). За это время я успел создать несколько небольших игр для мобильных устройств, а также объемный многопользовательский проект.
Это область для меня очень интересна и доставляет огромное удовольствие работать в ней.
Девайсы
Но иногда появляется желание попробовать что-то новое, и в один из таких дней я решил попробовать arduino.
Мне стало очень интересно, как создавать свои собственные устройства и как их программировать. Почему arduino? В интернете и даже на том-же хабре и гик таймс море постов о том, почему стоит брать arduino. Но отмечу, что для меня решающим фактором в выборе arduino является простота в использовании.
А как это объединить?
Сама консоль
Первым делом я спроектировал и сделал ЛУТ-ом печатную плату для портативной консоли. Это было ошибкой — сначала надо было протестировать программу на отладочной плате, например arduino uno, так как я не предусмотрел возможность быстро и удобно заливать программы в контроллер. А еще я ошибся в самой схеме, это можно было исправить проводами, но все же обидно.
После того, как я понял, что ошибся, я подключил кнопки через плату к arduino uno, а экран я подключил напрямую.
Вот что вышло:
А теперь к играм
Для создания игр я решил использовать игровой движок Unity. Писать компилятор из Mono C# в программу для arduino я не стал, но решил написать набор скриптов с помощью которых можно легко собирать игры.
Все скрипты я разделил на 3 группы — действия, условия и комбайнеры.
Я думаю что назначение действий и условий объяснять не надо, а вот для чего нужен комбайнер я объясню. Комбайнер проверяет выполняется условие, а затем выполняет действие.
Из скриптов, а точнее их наличия и комбинаций алгоритм создает программу для arduino.
А что за игра?
Начать я решил с чего нибудь простого. А какая сама простая игра которую вы знаете? Правильно — понг. Но я решил сделать не совсем понг, а понг на одного — есть одна ракетка, мяч и стена, вместо второй ракетки.
Я собрал ее из написанного конструктора, скомпилировал и залил в контроллер. Работает!
А теперь приведем все в порядок
Когда игра запустилась, и я в нее наигрался, я решил, что стоит переделать плату, предать ей красивый вид, использовать smd компоненты и убрать все лишнее. Я переделал схему и сделал плату.
Вот что вышло
Простейшая игра на Arduino с дисплеем 1602 — Часть #1
Вот что у нас должно получиться, ну он еще умеет прыгать, ходить и бить злые кактусы, которые на него нападают, но к этому придем поэтапно 🙂
ЧАСТЬ #1 основы
Я заказал себе arduino, «так себе игрушка» подумал я, комплект маленький (для пробы) о чем в последствии пожалел. Хотелось раскрыть потенциал, но из-за отсутствия дополнительных модулей этого не выходило, пришлось экспериментировать, подрубал arduino к системе безопасности и смотрел как датчики делают свою работу, потом решил сделать звуковую сигнализацию (используя пративную пищалку из комплекта), так сказать отучивал собак громко или неожиданно лаять 🙂 и тут мои руки дошли до дисплея 1602. «Хм… это же настоящий дисплей» подумал я, но тут же разочаровался узнав что он сжирает почти половину всех контактов на самой arduino. Покопавшись я нашел странную плату в комплекте «i2C» и очень подозрительно было ТО! Что количество дырдочек совпало с количеством пимпочек на дисплее. «Хм, не с проста. » подумал я, и решил их спаять. Чуть позже я понял что сделал верную штуку и теперь мой дисплей съедает всего два канала. Начал изучать, что же это за дисплей и что он умеет. Изучив достаточное количество материала я узнал, что дисплей чисто текстовый, но о чудо! Он может обработать 8 новых символов, габаритами 5х8 пикселей. Ну что же, давайте начнем писать игру! Первое, это надо придумать что за игра будет, решил сделать подобие динозаврика гуугл хром, но с парочкой фишек так сказать, для начала я думаю сойдет 🙂 но ведь надо еще чем-то управлять, причем многокнопочным, долго думать не пришлось. Взял ИК пульт из комплекта.
«Вот и джойстик» подозрительно пробормотал я себе под нос, думая о задержке от пульта, четкости работы ИК датчика да и вообще об адекватности данной идеи, но делать было нечего, я мог бы обучить arduino работать с клавиатурой для компа, но было действительно лень это делать. Так что приступил я записывать коды пульта, что бы в дальнейшем с ними работать. Код для микроконтролера тут простейший:
Выглядит код сигнала пульта так: «FF18E7» у вас коды будут конечно другие, но суть вы должны понять, а когда будете делать к этому коду обращение, в начале дописываем «0х» и у нас получится (0хFF18E7).
После заливки сего в arduino и подключив его как надо, мы можем начать записывать с лога порта цифорки, после нажатия на кнопки ИК устройства. Но тут как раз я хочу вам уточнить про то, как надо подключать датчик ИК.
Если мы смотрим на датчик, мы видим три ножки, левая (аналоговый сигнал), средняя (масса), правая (плюс 5V).
Так как я еще мало представлял как это будет вообще работать, я начал эксперименты. Сначала делал код скетча шаговый, через (delay(time)) сначала я не подозревал что это плохая идея 🙂
В чем главный косяк. Данный микроконтроллер не умеет делать мультизадачность. Он считает код сверху вниз, проходя по всем веткам и функциям и после завершения, он начинает заново. И вот, когда у нас этих «delay» в коде становиться очень много, мы начинаем замечать явные задержки. Кстати да, зачем нам много «delay» вообще нужно. Когда мы делаем игру, у нас начинает расти количество проверок и взаимодействий. Например к нам движется враг а я хочу его перепрыгнуть, я нажимаю «прыжок» а по плану, я должен зависнуть в воздухе к примеру на 0.8f секунд в воздухе, вот и вся игра и зависает на эти 0.8f секунды. «Косяк» подумал я и начал думать над решением. Решение было найдено быстро. Сам микроконтроллер умеет достаточно быстро читать код от начала до конца, (если ему не мешать) и еще он умеет считать время с начала его включения. Вот это то нам и надо. Теперь мы всего лишь создаем переменные которые будут хранить время на то или иное действие и переменную которая сверяет разницу от того сколько сейчас время и во сколько надо активировать код. Arduino за секунду, берет 1000 миллисекунд, достаточно удобно. Вот фрагмент когда что бы стало понятнее:
Главный герой будет у нас выглядеть так:
Процесс вписывания нового символа, я произвожу двоичным кодом (мне так удобно)
выглядеть он будет так:
01110
01110
00100
01110
10101
00100
01110
01010
Если всмотреться, то из единичек, мы увидим нашего персонажа, но что бы он без дела не стоял, давайте сделаем ему анимацию.
Теперь к нашему коду, добавиться еще один набор двоичных цифорок, а именно такой:
00000
01110
01110
00100
11111
00100
01110
01010
Как сделать анимацию на этом дисплее, логику я указал выше, а теперь перейдем к практике, в данный момент, расположим его на центр экрана, и заставим просто стоять на месте, и помните, наша задача использовать только одну ячейку памяти на два спрайта анимации. Это легче чем кажется:
После запуска, мы видим чУловечка, который находиться в центре экрана, на 2й строке и качается, так сказать.
Вывод: сегодня я рассказал как узнать данные через ИК порт, как обойти задержку работы кода микроконтроллера и как сделать начальную анимацию.
Остальное скоро 🙂 писать еще очень много чего, так что гляну как это вообще будет вам интересно и если да, то завтра же приступлю к написанию продолжения.
Купить платы можно на Алиэкспресс :
Функционал кнопок:
Для динамики игры, помимо ручного ускорения, каждые 5 секунд происходит программное увеличение скорости звездолета. На 25 секунде включается максимальная сложность «UNREAL» и скорость больше не растет.
После каждой игры на дисплее высвечивается время текущей игры, а также время лучшей попытки.
Подписывайся на Geek каналы :
★ Моя партнёрка с Aliexpress ★
★ Получай 10.5% скидку с любой покупки на Aliexpress! ★
★ Полезное браузерное приложение для кэшбэка ★
Похожие статьи
Сенсорная кнопка и Arduino
Подобные кнопки/датчики используют ваше тело как часть электрической цепи. Когда вы касаетесь чувствительной поверхности сенсорной кнопки, емкость цепи изменяется и фиксируется. Изменение емкости приводит к изменению выходного сигнала.
Вероятно, многим может показаться, что использовать подобный модуль непросто и периодически придется иметь дело с неожиданным результатом. Что ж, оказывается, что все не так. Сенсорная кнопка отлично справляется с поставленными задачами и не преподносит никаких сюрпризов, хотя стоимость и настораживает.
Подключение OLED I2C дисплея с размером экрана 0,96″ и разрешением 128*64 точек к плате Arduino UNO.
Сегодня мы рассмотрим подключение OLED I2C дисплея с размером экрана 0,96″ и разрешением 128*64 точек к плате Arduino UNO.
В данной статье я продемонстрирую простой способ подключения беспроводного геймпада 2.4 от плейстейшен к плате Arduino UNO
Выставка электроники Hong Kong Electronics Fair 2019 которую стоит посетить
Создаём собственный игровой контроллер
Источник вдохновения
На игровых выставках разработчики Objects in Space показывали демо своей игры с контроллером на кокпите огромного космического корабля. Он был дополнен загорающимися кнопками, аналоговыми приборами, световыми индикаторами состояния, переключателями и т.д… Это сильно влияет на погружение в игру:
На сайте игры выложен туториал по Arduino с описанием коммуникационного протокола для подобных контроллеров.
Я хочу создать то же самое для своей игры
В этом примере я потрачу примерно 40 долларов, чтобы добавить красивые, большие и тяжёлые переключатели на кокпит симулятора гонок. Основные затраты связаны с этими самыми переключателями — если бы я использовал простые переключатели/кнопки, то цена была в два раза ниже! Это настоящее оборудование, способное выдерживать 240 Вт мощности, а я буду пускать по ним только примерно 0,03 Вт.
Предупреждение: я решил сэкономить, поэтому оставляю ссылку на дешёвый китайский веб-сайт, где закупаю кучу разных компонентов/инструментов. Один из недостатков покупки компонентов по дешёвке заключается в том, что часто у них нет никакой документации, поэтому в статье я решу и эту проблему.
Основные компоненты
Рекомендуемые инструменты
Программное обеспечение
Предупреждение
Я изучал электронику в старшей школе, научился пользоваться паяльником, узнал, что красные провода нужно соединять с красными, а чёрные с чёрными… Вольты, амперы, сопротивление и связывающие их уравнения — вот и всё, чем исчерпывалось моё формальное обучение электронике.
Для меня это был обучающий проект, поэтому в нём могут быть плохие советы или ошибки!
Часть 1. Собираем контроллер!
Работаем с переключателями без документации.
Как сказано выше, я покупаю дешёвые детали у розничного продавца с низкой маржей, поэтому первым делом нужно разобраться, как работают эти переключатели/кнопки.
Простая двухконтактная кнопка/переключатель
С кнопкой всё просто — в ней нет светодиодов и всего два контакта. Переключаем мультиметр в режим непрерывности/прозвонки () и касаемся щупами разных контактов — на экране будет отображаться OL (open loop, разомкнутая цепь): это означает, что между двумя щупами нет соединения. Затем нажимаем на кнопку, по-прежнему касаясь щупами контактов — на экране теперь должно отобразиться что-то типа 0.1Ω и мультиметр начнёт пищать (сообщая о том, что между щупами присутствует очень низкое сопротивление — замкнутая цепь).
Теперь мы знаем, что при нажатии кнопки цепь замыкается, а при отжатии — размыкается. На схеме это можно обозначить как простой выключатель:
Подключаем переключатель к Arduino
Найдите на плате Arduino два контакта: помеченный GND и помеченный «2» (или любым другим произвольным числом — это контакты ввода-вывода общего назначения, которыми мы можем управлять через ПО).
Если мы подключим переключатель таким образом, а потом прикажем Arduino сконфигурировать контакт «2» как контакт INPUT, то получим цепь, показанную слева (на рисунке ниже). При нажатии кнопки контакт 2 будет напрямую соединяться с землёй / 0V, а при отжатии контакт 2 не будет соединён ни с чем. Это состояние (ни с чем не соединён) называется «floating» (состояние с высоким импедансом) и, к сожалению, это не очень хорошее состояние для наших целей. Когда мы считываем данные с контакта в ПО (с помощью digitalRead(2)), получаем LOW, если контакт заземлён, и непредсказуемый результат (LOW или HIGH), если контакт находится в состоянии floating!
Чтобы исправить это, мы можем сконфигурировать контакт так, чтобы он находился в режиме INPUT_PULLUP, который соединяется с резистором внутри процессора и создаёт схему, показанную справа. В этой цепи при разомкнутом переключателе контакт 2 имеет путь к +5V, поэтому при его считывании результатом всегда будет HIGH. При замыкании переключателя у контакта по-прежнему будет путь с высоким сопротивлением к +5V, а также путь без сопротивления к земле / 0V, который «побеждает», благодаря чему при считывании контакта мы получаем LOW.
Разработчикам ПО порядок может показаться обратным — при нажатии кнопки мы считываем false / LOW, а при отжатии — true / HIGH.
Можно сделать и наоборот, но у процессора есть только встроенные подтягивающие резисторы и нет утягивающих вниз резисторов, поэтому мы будем придерживаться этой модели.
Простейшая программа для Arduino, которая считывает состояние переключателя и сообщает PC о его состоянии, выглядит примерно так, как показано ниже. Вы можете нажать кнопку загрузки в Arduino IDE, а затем открыть Serial Monitor (в меню Tools), чтобы увидеть результаты.
Другие переключатели почти без документации.
Светодиодный переключатель с тремя контактами
К счастью, на основных переключателях моей панели есть пометки трёх контактов:
Я не полностью уверен, как он работает, поэтому мы снова переключим мультиметр в режим непрерывности и коснёмся всех пар контактов при включенном и отключенном переключателе… однако на этот раз мультиметр вообще не пищит, когда мы касаемся щупами [GND] и [+] при «включенном» переключателе! Единственная конфигурация, при которой мультиметр пищит (обнаруживает соединение) — когда переключатель «включен», а щупы находятся на [+] и [lamp].
Светодиод внутри переключателя блокирует измерения непрерывности, поэтому из проведённых выше проверок мы можем предположить, что LED подключен непосредственно к контакту [GND], а не к контактам [+] и [lamp]. Далее мы переключим мультиметр в режим проверки диодов (символ ) и снова проверим пары контактов, но на этот раз важна полярность (красный и чёрный щуп). Теперь если мы соединим красный щуп с [lamp], а чёрный — с [GND], то светодиод загорится, а на мультиметре отобразится 2.25V. Это прямое напряжение диода, или минимальное напряжение, необходимое для его включения. Вне зависимости от положения переключателя, 2.25V от [lamp] к [GND] заставляет LED загореться. Если мы соединим красный щуп с [+], а чёрный — с [GND], то светодиод загорится только при включённом переключателе.
Из этих показаний мы можем предположить, что внутренности этого переключателя выглядят примерно как на схеме ниже:
Честно говоря, о присутствии резистора здесь можно только догадываться. Светодиод должен быть соединён с соответствующим резистором, чтобы ограничивать подаваемый на него ток, или он сгорит. Мой не сгорел и похоже, что работает правильно. На форуме веб-сайта продавца я нашёл пост, в котором говорится об установленном резисторе, поддерживающем работу до 12 В, и это сэкономило мне время на проверку/вычисления подходящего резистора.
Подключаем переключатель к Arduino
Проще всего использовать переключатель с Arduino, проигнорировав контакт [lamp]: подключить [GND] к GND в Arduino и соединить [+] с одним из пронумерованных контактов Arduino, например 3.
Если мы сконфигурируем контакт 3 как INPUT_PULLUP (так же, как и для предыдущей кнопки), то придём к показанному ниже результату. Слева вверху показано значение, которое мы будем получать, выполнив «digitalRead(3)» в коде Arduino.
Когда переключатель включен/замкнут, мы считываем LOW и светодиод загорается! Для использования такого переключателя в данной конфигурации мы можем использовать тот же код Arduino, что и в примере с кнопкой.
Проблемы этого решения
После подключения к Arduino полная цепь выглядит так:
Однако здесь мы можем увидеть, что при замыкании переключателя кроме небольшого ограничивающего ток резистора перед LED (я предполагаю, что его сопротивление 100 Ом) есть и ещё и подтягивающий резистор на 20 кОм, который ещё больше снижает величину тока, текущего через светодиод. Это означает, что хотя цепь и работает, светодиод будет не очень ярким.
Ещё один недостаток этой схемы в том, что у нас нет программного контроля над LED — он включён, когда включён переключатель, и отключен в противоположном случае.
Можно посмотреть, что случится, если мы подключим контакт [lamp] или к 0V, или к +5V.
Если [lamp] подключен к 0V, то светодиод постоянно отключен (вне зависимости от позиции переключателя), а распознавание позиции Arduino всё равно выполняется. Это позволяет нам при желании программно отключать LED!
Если [lamp] подключен к +5V, то светодиод постоянно включен (вне зависимости от позиции переключателя), однако распознавание позиции Arduino поломано — с контакта всегда будет считываться HIGH.
Подключаем этот переключатель к Arduino правильно
Мы можем преодолеть описанные выше ограничения (низкий ток/яркость светодиода и отсутствие программного контроля над светодиодом), написав больше кода! Чтобы разрешить конфликт между возможностью управления светодиодом и сломанным из-за него распознаванием позиции, мы можем разделить две задачи по времени, то есть временно отключать LED при считывании контакта датчика (3).
Сначала подключим контакт [lamp] к ещё одному контакту Arduino общего назначения, например, к 4, чтобы можно было управлять lamp.
Чтобы создать программу, которая будет правильно считывать позицию переключателя и управлять светодиодом (мы заставим его мигать), нам достаточно просто отключать светодиод перед считыванием состояния переключателя. Светодиод будет отключаться всего на доли миллисекунд, поэтому мерцание не должно быть заметно:
В Arduino Mega контакты 2-13 и 44-46 могут использовать функцию analogWrite, которая на самом деле не создаёт напряжения от 0V до +5V, а аппроксимирует его при помощи прямоугольной волны. При желании можно использовать её для управления яркостью светодиода! Этот код заставит свет пульсировать, а не просто мерцать:
Подсказки по сборке
Пост и так уже довольно большой, так что я не буду добавлять ещё и туториал по пайке, можете его загуглить!
Однако приведу самые базовые советы:
Часть 2. Превращаем устройство в игровой контроллер!
Чтобы ОС распознала устройство как игровой USB-контроллер, нужен достаточно простой код, но, к сожалению, также необходимо заменить firmware USB-чипа Arduino другим, которое можно взять здесь: https://github.com/harlequin-tech/arduino-usb.
Но после заливки этого firmware в Arduino устройство становится USB-джойстиком и перестаёт быть Arduino. Поэтому чтобы перепрограммировать его, нужно заново перепрошить исходную firmware Arduino. Эти итерации довольно мучительны — загружаем код Arduino, прошиваем firmware джойстика, тестируем, прошиваем firmware arduino, повторяем…
Пример программы для Arduino, которую можно использовать с этим firmware, показан ниже — он конфигурирует три кнопки в качестве вводов, считывает их значения, копирует значения в структуру данных, ожидаемую этим firmware, а затем отправляет данные. Смыть, намылить, повторить.
Часть 3. Интегрируем устройство с собственной игрой!
Если у вас есть контроль над игрой, с которой должно взаимодействовать устройство, то в качестве альтернативы можно общаться с контроллером напрямую — нет необходимости делать его видимым для ОС как джойстик! В начале поста я упомянул Objects In Space; именно такой подход использовали её разработчики. Они создали простой коммуникационный ASCII-протокол, позволяющий контроллеру и игре общаться друг с другом. Достаточно просто перечислить последовательные порты системы (они же COM-порты в Windows; кстати, посмотрите, как ужасно это выглядит на C), найти порт, к которому подключено устройство с названием «Arduino», и начать считывать/записывать ASCII по этой ссылке.
На стороне Arduino мы просто используем функции Serial.print, которые применялись в показанных выше примерах.
В начале этого поста я также упоминал мою библиотеку для решения этой задачи: https://github.com/hodgman/ois_protocol.
Она содержит код на C++, который можно интегрировать в игру и использовать её в качестве «сервера», и код Arduino, который можно выполнять в контроллере, чтобы использовать его в качестве «клиента».
Настраиваем Arduino
В example_hardware.h я создал классы, чтобы абстрагировать отдельные кнопки/переключатели; например, «Switch» — это простая кнопка из первого примера., а «LedSwitch2Pin» — переключатель с управляемым светодиодом из второго примера.
Код примера для моей панели кнопок находится в example.ino.
В качестве небольшого примера давайте допустим, что у нас есть единственная кнопка, которую нужно отправлять в игру, и один управляемый игрой светодиод. Необходимый код Arduino выглядит так:
Настраиваем игру
Код игры написан в стиле «single header». Для импорта библиотеки включим в игру oisdevice.h.
В едином файле CPP, прежде чем выполнять #include заголовка, напишем #define OIS_DEVICE_IMPL и #define OIS_SERIALPORT_IMPL — это добавит в файл CPP исходный код классов. Если у вас есть собственные утверждения, логгинг, строки или векторы, то существует несколько других макросов OIS_*, которые можно определить перед импортом заголовка, чтобы воспользоваться возможностями движка.
Для перечисления COM-портов и создания соединения с конкретным устройством можно использовать такой код:
Получив экземпляр OisDevice, нужно регулярно вызывать его функцию-член Poll (например, в каждом кадре), можно получать текущее состояние вывода контроллера с помощью DeviceOutputs(), использовать события устройства с помощью PopEvents() и отправлять устройству значения с помощью SetInput().
Пример приложения, делающего всё это, можно найти здесь: example_ois2vjoy/main.cpp.
Часть 4. Что если я хочу части 2 и 3 одновременно?
Чтобы контроллер мог работать в других играх (часть 2), нужно установить собственное firmware и одну программу Arduino, но чтобы контроллер полностью программировался игрой, мы использовали стандартное firmware Arduino и другую программу Arduino. Но что если мы хотим иметь обе возможности одновременно?
Пример приложения, на который я давал ссылку выше (ois2vjoy), решает эту проблему.
Это приложение общается с OIS-устройством (программа из части 3), а затем на PC преобразует эти данные в обычные данные контроллера/джойстика, которые потом передаются в виртуальное устройство контроллера/джойстика. Это означает, что можно позволить своему контроллеру постоянно использовать библиотеку OIS (другое firmware не требуется), а если мы захотим использовать его как обычный контроллер/джойстик, то просто запустим на PC приложение ois2vjoy, выполняющее преобразование.
Часть 5. Завершение
Надеюсь, кому-то эта статья показалась полезной или интересной. Спасибо, что дочитали до конца!
Если вам стало любопытно, то я приглашаю вас поучаствовать в развитии библиотеки ois_protocol! Думаю, будет здорово разработать единый протокол для поддержки всевозможных самодельных контроллеров в играх и стимулировать игры к прямой поддержке самодельных контроллеров!