Разработка игры в 115 кб — хаки, баги и досада


В начале ноября я участвовал в 115-ом по счете конкурсе сообщества Independent Games Developers Contests (IGDC), темой которого была разработка аркадного шутера с лимитом в 115 килобайт за неделю. Под катом история разработки игры на OpenGL + Free Pascal, эксперименты с LZO, обход багов компилятора FPC для uFMOD, простейшая генерация текстур и досадный баг на видеокартах NVidia, который все испортил.

Видео, бинарник для Windows и исходный код также прилагаются — ищите в конце статьи.


Лирическое вступление


Разработка игр — мое основное, любимое и очень давнее хобби. Я кручусь в любительском gamedev без особых успехов, громких релизов и титанических долгостроев около 10 лет. Замороженных проектов много, доведенных до ума — единицы. В какой-то момент я отчаялся, что у меня ничего не выходит. А потом осознал, что мне нравится не только конечный результат, но и сам процесс разработки игр. С этого момента жить стало спокойнее, но я все еще не отпускаю мысль, что когда-нибудь пересилю себя и выведу какой-нибудь проект на коммерческий уровень.

В какой-то момент я набрел на сообщество IGDC, на котором проводятся короткие (от пары дней до 3 недель) конкурсы по разработке игр на заданную тему. Очень, знаете ли, теплые и ламповые конкурсы, в которых главное — участие, а не приз. Полученный опыт и удовольствие от проделанной работы, а не маркетинг и монетизация.

В наше время, когда порог вхождения в gamedev снизился в разы, и поток откровенно неудачных игр заполоняет мобильные платформы, авторы которых мечтают заработать на безбедную жизнь очередным Flappy Bird… В такое время трудно найти людей, занимающихся разработкой игр ради удовольствия, а не выгоды.

Конечно, есть могучий Ludum Dare, но его хардкорные сроки пока что идут вразрез с моей семейной жизнью.


Начало


Итак, 7 ноября 2014 г. на сайте сообщества объявляется очередной конкурс. Условия:

  • «Пыщ-пыщ» и враги — именно такими словами Ведущий конкурса охарактеризовал аркадный шутер
  • Размер — строго до 115 килобайт, ведь конкурс — 115-й по счету
  • Срок — неделя


К этим условиям добавляются постоянные обязательные условия: должно работать offline и без установки сторонних пакетов и redistributable. Все, что требуется для запуска игры и не идет в комплекте с системой, должно поставляться вместе с релизом и укладываться в те самые 115 килобайт.

Ограничение неприлично большое для true demoscene (4к, 32к…), но достаточное, чтобы как следует «извратиться». Беглый анализ средств разработки дает довольно внушительный список того, что укладывается в эти требования:

  • Flash
  • html5 + js
  • С, C++, C#
  • Delphi, FreePascal


За бортом остаются Unity, Cry Engine, Unreal Engine, JVM-based языки (требуется предустановленный jre), а также большая часть игровых конструкторов.

О Flash
Несмотря на то, что для Flash необходим установленный Adobe Flash Player, он все же разрешен для использования, в качестве исключения (считается, что Adobe Flash Player все же стоит у большинства). Так сложилось исторически.


За свою жизнь мне удалось попробовать много языков и технологий, и большая часть из списка выше мне знакома не понаслышке. Но ваш покорный слуга выбрал не самый простой вариант — Free Pascal. Почему не самый простой?

Во-первых, в 2014 году Free Pascal (как и Delphi) считается немодным — как следствие, компилятор FPC имеет мало пользователей и немало багов, несмотря на Open Source и кроссплатформенность. Во-вторых, размер скомпилированного ехе у связки Lazarus IDE + FPC — повод для отдельной страницы в wiki. В-третьих, очень мало синтаксического сахара, что остро ощущается, когда постоянно используешь много других языков и технологий.

Конечно же, есть и плюсы:

  • Правильно приготовленный ехе самодостаточен, при этом сравним с ехе от С/С++ со статической линковкой CRT
  • С настройками по умолчанию, не позволяет выстрелить себе в ногу, как в С/С++
  • С нужными настройками отстреливает напрочь обе ноги (что иногда хочется)
  • Совершенно случайно, у меня уже есть мини-фреймворк на Free Pascal + OpenGL


И я уже делал на нем вольный ремейк Lunar Lander




Поехали!



Первым делом я определился с концептом будущей игры — аркадный 2D-шутер с видом сверху, в котором игрок управляет «танком» и всячески отстреливает толпы врагов, с которых валятся бонусы для еще более веселого отстрела врагов. Вакхналия продолжается, пока смерть не разлучит вас с вашим альтер-эго. Ближайший аналог — Crimsonland.

Привычным движением откомпилировав шаблон, я создал себе первую проблему — скомпилированный ехе с моим фреймворком с учетом всех хитрых опций компилятора занимал 120 килобайт. Конечно, с учетом того, что фреймворк умеет (и держа в уме что это все-таки FPC) — это даже достижение. Но нас это абсолютно не устраивает, поэтому безжалостно режем ехе с помощью UPX — 48 килобайт. С этим уже вполне можно работать.

Конечно, тут еще можно было выиграть несколько килобайт, если урезать функционал фреймворка до самого необходимого. Я от этого отказался по причине нехватки времени, как оказалось впоследствии — не зря. В итоге лимита в 115 килобайт мне хватило с избытком.


Внедряем LZO


Редкая игра может обойтись без вывода текстовой или числовой информации. Хотя, изначально в моей голове витала мысль создать именно такую игру, но придумать интересную реализацию не получилось.

Поэтому задача — выводить текст на экран силами OpenGL. Не прибегая к архаичному способу вывода векторного текста, следует использовать растровые шрифты (Bitmap Fonts). Мой фреймворк уже имел поддержку вывода текста с использованием заранее сгенерированных и заботливо «запеченых» растровых шрифтов. Задача решена?

Коротко о реализации
Есть собственная велосипедная утилита, которая пакует нужные символы относительно компактно, а затем «запекает» в bmp-файл, в конец которому безжалостно дописывается служебная информация о метрике символов (координаты, размер, оригинальный размер, etc). Любой графический редактор не видит подвоха и вполне корректно открывает файл. Еще бы научить эти самые редакторы не перезаписывать весь файл целиком при сохранении, и можно было бы накладывать на такой шрифт пост-эффекты…



Нет, задача усложняется. Полученный таким образом файл с русскими и латинскими буквами (плюс спецсимволы и цифры) занимал 135 килобайт. Убираем русские символы, уменьшаем физический размер самого шрифта, изображение уменьшается вдвое по одному из измерений и, соответственно, вдвое в размере — 67 килобайт. Но это все равно никуда не годится, так как в сумме с «пустым» проектом это дает ровно 115 килобайт.

Сейчас я осознаю, что наиболее правильным и простым шагом было бы просто взять и генерировать шрифт прямо при запуске, из системного шрифта, благо «копипаст» кода несложен. Более того, в моем предыдущем фреймворке именно так шрифты и генерировались — в «рантайме» из системных шрифтов или otf/ttf файлов.

Но душа хотела романтики, а пятая точка — мучений. И я вспомнил, о том, что товарищ XProger в далеком 2010 году совершил акт насилия над библиотекой MiniLZO, дернув ее дамп и обернув его в несложные asm-инструкции. Выглядит это примерно так, в случае с извлечением:

function lzo_decompress(const CData; CSize: LongInt; var Data; var Size: LongInt): LongInt; cdecl; asm   DB $51   DD $458B5653,$C558B08,$F08BD003,$33FC5589,$144D8BD2,$68A1189,$3C10558B,$331C7611,$83C88AC9   DD $8346EFC1,$820F04F9,$1C9,$8846068A,$75494202,$3366EBF7,$460E8AC9,$F10F983,$8D83,$75C98500,$8107EB18   DD $FFC1,$3E804600,$33F47400,$83068AC0,$C8030FC0,$83068B46,$28904C6,$4904C283,$F9832F74,$8B217204,$83028906   DD $C68304C2,$4E98304,$7304F983,$76C985EE,$46068A14,$49420288,$9EBF775,$8846068A,$75494202,$8AC933F7   DD $F983460E,$C12B7310,$828D02E9,$FFFFF7FF,$C933C12B,$C1460E8A,$C12B02E1,$8840088A,$88A420A,$420A8840   DD $288008A,$113E942,$F9830000,$8B207240,$FF428DD9,$8302EBC1,$C32B07E3,$1E8ADB33,$3E3C146,$2B05E9C1   DD $D9E949C3,$83000000,$2F7220F9,$851FE183,$EB1875C9,$FFC18107,$46000000,$74003E80,$8AC033F4,$1FC08306   DD $F46C803,$FBC11EB7,$FF428D02,$C683C32B,$8369EB02,$457210F9,$D98BC28B,$C108E383,$C32B0BE3,$8507E183   DD $EB1875C9,$FFC18107,$46000000,$74003E80,$8ADB33F4,$7C3831E,$F46CB03,$FBC11EB7,$83C32B02,$D03B02C6   DD $9A840F,$2D0000,$EB000040,$2E9C11F,$2BFF428D,$8AC933C1,$E1C1460E,$8AC12B02,$A884008,$88008A42   DD $51EB4202,$7206F983,$2BDA8B37,$4FB83D8,$188B2E7C,$8904C083,$4C2831A,$8B02E983,$831A8918,$C08304C2   DD $4E98304,$7304F983,$76C985EE,$40188A20,$49421A88,$15EBF775,$8840188A,$188A421A,$421A8840,$8840188A   DD $7549421A,$8AC933F7,$E183FE4E,$FC98503,$FFFE4284,$46068AFF,$49420288,$C933F775,$E9460E8A,$FFFFFECA   DD $8B10552B,$10891445,$75FC753B,$EBC03304,$FFF8B80D,$753BFFFF,$830372FC,$5B5E04C0,$90C35D59 end; 


… и подобное колдунство для собственно сжатия. Это отлично работает (хотя у меня получилось завести это не сразу), но рекомендовать такое для production-кода не стал бы. Есть легкое неудобство при отладке…

После сжатия шрифта получаем 17 килобайт вместо 67. А могли бы и 2-3 килобайта, если бы я просто внедрил генерацию на лету..




Используем uFMOD для вывода звука


Никто не хочет играть в игры без звуков или, хотя бы, музыки. До этого конкурса у меня был опыт работы с библиотекой bass, однако, ее пришлось оставить за бортом — необходимая dll съедала аж 97 килобайт. В отчетной теме конкурса упомянули про uFMOD — миниатюрную библиотеку для вывода xm-музыки, написанную на ассемблере. Забегая вперед, скажу, что ее внедрение в проект в итоге практически не повлияло на размер ехе-файла.

Но был один маленький нюанс. На более-менее современных версиях компилятора FPC (выше 2.2.х) данная библиотека не работала. И проблема кроется в неоднозначном поведении линковщика. Сомневаюсь, что смогу максимально верно описать технические аспекты данной проблемы — иными словами почему external-функции, объявленные в заголовочном файле, не видны для подключенного тут же объектного файла. Такое поведение остается на совести разработчиков компилятора. Приведу пример «обхода» данного поведения для одной из функций.

Было так:

function waveOutClose(hwo:Pointer):LongWord; stdcall; external 'winmm.dll'; 


А пришлось оборачивать это так:

function my_waveOutClose(hwo:Pointer):LongWord; stdcall; external 'winmm.dll' name 'waveOutClose';  function _waveOutClose(hwo:Pointer):LongWord; stdcall; public name 'waveOutClose'; begin   Result := my_waveOutClose(hwo); end; 


И так для пары десятков необходимых библиотеке функций. Уточню, что я взял реализацию uFMOD через winmm, как самую легковесную и простую в использовании. Из минусов простоты — она позволяла проигрывать лишь один поток одновременно. Тем самым в моей игре появилась музыка, но от звуков пришлось отказаться.

Собственно, сам xm-трек взял вот тут, после чего утилитой от uFMOD превратил его в вот такой pas-файл, что сэкономило мне еще чуть-чуть места.


Генерация текстур


Перефразируя классика — какой demoscene-проект обойдется без генерации текстур? Изначально, я хотел опустить данную тему — уж больно просто и «в лоб» я сделал генерацию целой одной(!) текстуры. Но, быть может, кому-то из новичков пригодится подобный подход.

Практически для всех спрайтов в игре используется одна и та же текстура:



Просто полосатая текстура с «бордюром» вокруг для большей эстетики. С учетом color tint (то есть «домножая» нужный цвет на такую текстуру) получаем полосатые текстуры любых цветов.

Код генерации мне совершенно не нравится, его явно можно написать более оптимально. Более того, есть стойкое чувство, что бордюр рисуется неверно.

function TGame.GenerateTexture(aWidth, aHeight, aBorderSize: Integer): TglrTexture; var   m, m_origin: PByte;   i, j: Integer;   value: Byte; begin   m := GetMemory(aWidth * aHeight * 3);   m_origin := m;   for j := 0 to aHeight - 1 do     for i := 0 to aWidth - 1 do     begin       if (i < aBorderSize) or (j < aBorderSize)         or (i > aWidth - aBorderSize - 1) or (j > aHeight - aBorderSize - 1) then         value := 196       else         if ((i + j) mod 16) >= 8 then           value := 255         else           value := 196;       m^ := value; m+=1;       m^ := value; m+=1;       m^ := value; m+=1;     end;   Result := TglrTexture.Create(m_origin, aWidth, aHeight, tfRGB8); end; 


Для разнообразия, оттенок красного у врагов слегка варьируется с помощью random().


Досадный баг на NVidia


После сдачи работы на конкурс, ведущий собирает архив из присланных работ и выкладывает на всеобщее обозрение. Задача участников — расставить друг другу места, за исключением себя. На оценку дается три дня, поэтому после авральной разработки можно расслабиться. Но не тут-то было!

Но один за другим конкурсанты отписываются, что моя игра работает у них некорректно — «танк» игрока не виден в принципе, не видны многие вражеские танки, иногда они мелькают. Врагов можно определить только по дыму из выхлопной трубы. Такие проблемы проявляются у всех конкурсантов на видеокартах Nvidia. Более того, один из конкурсантов имеет конфигурацию идентичную моей, но у меня баг не проявляется вовсе. Кому-то помог запуск в режиме совместимости с Windows 95(!), но таких были единицы.

Я выкладывал разные билды (что уже слегка противоречит правилам конкурса), подчищая все подозрительные места в коде, советовал разные настройки, но все было тщетно. Наконец, один из участников, самый дотошный, за что ему большое спасибо (привет, pelmenka!), обнаружил причину — если выключить в панели управления Nvidia потоковую оптимизацию (threading optimization), то игра работает корректно. Досада в том, что у меня такая настройка выключена с незапамятных времен, когда она становилась причиной BSOD-а в некоторых играх.

Благодаря этой ценной информации, я смог воспроизвести баг у себя и начать устранять его, хотя в душе уже понимал, что большая часть конкурсантов уже проголосовала, да и вообще, я — сам себе злобный буратино, что не проверил игру на стандартных настройках панели управления Nvidia.

Я не буду долго описывать процесс поиска решений проблемы. Скажу только, что в процессе поиска, я понял, что:

  • Потоковая оптимизация приносит больше проблем, чем пользы. Несложный поиск «nvidia threading optimization» дает ссылки на игровые форумы, где настоятельно советуют отключать данную настройку во избежание «моргания» игр
  • Нет никакой спецификации, которая бы объясняла, что делает данная оптимизация на уровне драйверов (или есть?)
  • Некоторое количество тем от инди-разработчиков, жаловавшихся на баги при включенной потоковой опимизации. Без ответов, кроме «просто выключи ее, приятель»


Наконец, мое внимание привлекла тема, в которой жаловались на некорректную работу функции glBufferSubData при включенной Штуке, Которую Нельзя Называть. Это дало мне зацепку, и через некоторое время (дебаги, проверки) я вычленил суть проблемы:

Функция glBufferSubData обновляет данные в вершинном буфере. Основная цель данной функции — обновление «куска» буфера, но также ее следует использовать, когда необходимо обновить весь буфер целиком без реаллокации памяти (мой случай).

При включенной потоковой оптимизации драйвер NVidia по одному ему ведомым признакам иногда (всегда?) помещает вызовы данной функции в отдельный поток и немедленно возвращает управление, что приводит к плачевному результату. Сколько данных успеет «залиться» в буфер, прежде чем пойдет его отрисовка — неизвестно. И OpenGL-драйвер от Nvidia не видит в этом проблемы. Опережая ваш вопрос, я отвечу: нет, размер пересылаемых данных ничтожно мал, пара килобайт (особенно по сравнению с пропускной способностью шины), поэтому дело далеко не в жирных данных.

Спецификация OpenGL не говорит нам о том, что данная функция может быть запущена в отдельном потоке. Личная инициатива от ребят из Nvidia?

В случае однократного использования glBufferSubData за кадр вы ничего не заметите, потому что драйвер «закэширует» отрисовку данного буфера и вызовет ее, дождавшись окончания всех операций по этому буферу (вероятно).

А в моем случае один буфер используется несколько раз за кадр. То есть:

glBindBuffer(GL_ARRAY_BUFFER, BufferId); glBufferSubData(GL_ARRAY_BUFFER, 0, Size, Data); glDrawElements(...); glBindBuffer(GL_ARRAY_BUFFER, 0); ... glBindBuffer(GL_ARRAY_BUFFER, BufferId); glBufferSubData(GL_ARRAY_BUFFER, 0, OtherSize, OtherData); glDrawElements(...); glBindBuffer(GL_ARRAY_BUFFER, 0); 


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

Помогает вызов glFinish(), хотя это так себе решение. Лично я немного изменил логику обновления данных в буфере, чтобы избегать таких щекотливых ситуаций.

В: Почему бы не объединить данные и не отрисовать за один раз?
О: Между этими вызовами есть отрисовка других элементов, менять порядок отрисовки нельзя.

В: Почему бы не использовать разные буферы?
О: Данные в буфере обновляются каждый кадр, держать несколько буферов — трата ресурсов


Итог


В итоге я получил четвертое место на конкурсе, хотя вполне мог бы побороться за второе-третье. Досаду вызвал не тот факт, что я пролетел мимо «тройки» лидеров, а скорее осознание того, что я «запорол» годную игру и 7 дней своей работы одной маленькой неприятностью.

Короткое видео игрового процесса:



Суммарный размер релиза составил почти 80 килобайт. В них входит:

  • 62,0 кб — ехе
  • 17,3 кб — шрифт
  • 0,52 кб — шейдеры


Скачать релиз (все необходимые исходники прилагаются).
Исходники на github

ссылка на оригинал статьи http://habrahabr.ru/post/244417/

Строим роботанк с управлением по Wifi, камерой, пушкой, блекджеком и т.д

image

Всем привет, у меня таки возникло непреодолимое желание поделиться с миром своим достижением. Достижением является танк, который рулится по WiFi с геймпада, транслирует на пульт видео в реальном времени, передает с пульта и на пульт звук, а также имеет пушку с лазерным прицелом, из которой можно в кого-нибудь пострелять.

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




С чего все начиналось


Давным-давно была у меня мечта сделать робота на гусеничном шасси, которым можно было бы удаленно рулить. Основной проблемой было отсутствие непосредственно гусеничного шасси. В конце концов я уже решился купить радиоуправляемый танк на разборку, но мне повезло, в магазине среди хлама нашелся танк Snow Leopard (Pershing) — USA M26 с погоревшей электроникой, но полностью исправной механической частью. Это было ровно то, что нужно.

image

Вдогонку к шасси были докуплены два регулятора напряжения для коллекторных двигателей, штатив для камеры из двух сервоприводов, веб-камера с аппаратной поддержкой mjpeg и внешняя WiFi карточка TP-LINK TL-WN7200ND. Чуть позже к списку устройств добавились портативная колонка, USB звуковуха Creative SoundBlaster Play и простенький микрофон, а также пара USB хабов, чтоб все это подключить к модулю управления, которым стал Raspberry Pi. Башня с танка была демонтирована, рулить ею было очень неудобно, так как вся штатная механика была построена на обычных двигателях без обратной связи.

Сразу оговорюсь, что фотки делались, когда танк был почти готов, а не в процессе изготовления.


Питание и проводка


image

В батарейный отсек я запихал самую большую Li-Po батарею, которая туда влезла. Ей оказалась двухбаночная батарейка на 3300 mAh в твердом корпусе, которая обычно используется в модельках машин. Паять мне было лень, поэтому для всей коммутации была использована стандартная макетная плата с шагом 2.54. Позже появилась вторая на верхней крышке и шлейф, который их соединял. На каждый из двух двигателей у меня был свой регулятор напряжения, который в виде бонуса выдает стабилизированное питание около 5.6 вольт. С одного регулятора был запитан Raspberry и WiFi карта, питание со второго пошло на сервоприводы и USB хаб с периферией.


Надо заставить это двигаться


Надо было как-то это завести. Raspberry был выбран не случайно. Во-первых он позволяет поставить нормальный полноценный линух, а во-вторых имеет кучу GPIO ног, которые в том числе могут генерировать импульсный сигнал для сервоприводов и регуляторов хода. Генерировать такой сигнал можно с помощью утилиты ServoBlaster. После запуска она создает файл /dev/servoblaster, в который можно писать что-то типа 0=150, где 0 — номер канала, а 150 — длина импульса в десятках микросекунд, то есть 150 — это 1.5 миллисекунды (у большинства сервоприводов диапазон значений 700-2300 мс).
Итак, подключаем регуляторы на 7 и 11 GPIO пины и запускаем servoblaster командой

# servod --min=70 --max=230 --p1pins=7,11 


Теперь, если записать в /dev/servoblaster строки 0=230 и 1=230, то танк рванет вперед.


Подключаем камеру


Кататься взад-вперед было классно, но хотелось делать это хотя бы в соседней комнате, а в идеале вообще через интернет, поэтому надо было наладить видео в реальном времени. На просторах интернета нашелся простенький проект tinycamd. проект представляет собой сервис, который управляется по http, может делать скриншоты и менять настройки камеры. Не густо, но ничего лучше я не нашел, поэтому пришлось вспоминать С и дописывать то, чего не реализовал автор, а именно, трансляцию потока MJPEG по HTTP(кстати, как поделиться доработанным исходником с миром?). Здесь критически важно, чтобы JPEG приходил с самой камеры, процессора Raspberry на такое не хватит. В итоге я подключился к танку по ssh, открыл видеопоток через браузер, покатался по дому и был счастлив до тех пор, пока не просел канал. Было очень забавно сначала смотреть на застывший кадр, а потом получить все, что застряло в ускоренном режиме. Стриминг realtime видео через TCP — это зло.


Апгрейды, улучшения и т.п.


Далее шел долгий процесс написания серверной и клиентской части на Python’е c использованием библиотеки pygame для получения событий от геймпада, допиливание tinycamd, чтоб он посылал видео поток по UDP и установка камеры на штатив из сервоприводов, чтобы была возможность оглядеться. После чего танк отправился в первое путешествие по офису за пределы прямой видимости. И в этот момент пришло понимание, что хочется не только смотреть видео, но и иметь двусторонний аудио канал, чтобы, например, попросить коллег открыть дверь или вызвать лифт.


Звук


Для воспроизведения звука была использована дешевая карманная USB колонка, купленная в супермаркете по акции. Она была подключена вместе с простеньким микрофоном через USB звуковуху. Для работы со звуком пригодилась библиотека pyalsaaudio. После допиливания сервера и клиента появилась возможность в процессе рассекания на танке говорить и слушать.


Свет


Следующей фичей оказалась фара. В какой-то момент стало понятно, что чувствительности камеры может легко не хватать и есть шанс заехать в темноту и не выехать. Первой идеей была инфракрасная подсветка. Была собрана линейка инфракрасных светодиодов, но, как показала практика, толку от них ноль. Светят очень плохо и мало. А готовые инфракрасные прожекторы требуют 12в питание (а у меня всего 2 банки, то есть 8в максимум), много жрут тока, громоздкие и стоят дорого. В итоге было решено перейти в видимый диапазон, были куплены два мощных белых SMD светодиода и линзы к ним. Для питания фары коллегами по работе был сотворен драйвер с регуляцией по току, который включался через полевой транзистор подачей единицы на GPIO ногу Raspberry. Отныне темные комнаты перестали быть препятствием.


Батарея, точнее уровень ее заряда


На всех этапах оставалось непонятным, сколько еще можно кататься, не убив батарейку (Li-Po нельза разряжать ниже чем 3.3в на банку). Я не нашел способа замерить напряжение с помощью GPIO ног Raspberry, поэтому в качестве измерителя поставил Arduino Nano, к которому на будущее сразу подключил LCD экран c I2C адаптером. Батарейка подключается через половинный делитель на аналоговую ногу Arduino, после чего остается только откалибровать показания. Arduino по традиции общается с основным модулем через COM порт, который у Raspberry также выведен на GPIO ноги.


Какой же танк без пушки


Одной из последних деталей танка стала пушка. Пушка была куплена там же, в магазине радиоуправляемых моделей в виде запчасти. Она, правда, предназначалась для другой модели танка, но суть ее от этого не изменилась. Пушка пневматическая, имеет двигатель, взводящий пружину поршня, и контакт, который замыкается при выстреле. От горизонтального поворота пушки я пока что отказался, чтоб не снести ей весь обвес, который прицеплен на верхнюю крышку, а для вертикального использовал мощный сервопривод. Чтоб было проще рулить, я сделал поворот пушки по синхронным с поворотом камеры. То есть куда смотрим (по вертикали), туда и стреляем. Для прицеливания на ствол пушки был примотан лазерный светодиод от указки. Чтоб лишний раз не тратить батарею и не светить лазером куда не надо, нужно было сделать пушку отключаемой. Процесс выстрела также не совсем прост. Надо включить питание двигателя и ждать замыкания контакта, после чего двигатель выключить. В итоге управление выстрелом и питанием сервы и лазера было повешено на ардуину, а сигнал для сервы генерирует Raspberry. Для двигателя пушки также пришлось проводить отдельный силовой провод и включать его постепенно, используя ШИМ, так как иначе прилетает помеха по питанию и Arduino уходит в ребут. Для подачи снарядов, то есть шариков, была использована коробочка от драже TicTac с дыркой в дне.

Наверное, для первого раза хватит. Если статья понравится, буду потихоньку писать детали в следующих постах. И еще немного фоток напоследок.

imageimageimage

ссылка на оригинал статьи http://habrahabr.ru/post/244407/