...

пятница, 18 марта 2016 г.

«Весёлый повар» на FBD шаг за шагом

Приветствую всех.

Предыдущие статьи были о небольших проектах, сделанных по одному и тому же принципу:

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

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

В качестве примера была выбрана игра «Электроника ИМ-04 — Веселый повар».

Под катом описание по шагам, как написать эту игрушку на языке программирования FBD.

*Все большие картинки под спойлером имеют ссылку на оригиналы

Постановка задачи


Итак, сегодня мы делаем игру «Веселый повар».

Суть игры: повар подбрасывает четыре предмета. Задача состоит в том, чтобы вовремя подбежать к падающему предмету, подставить сковородку и снова подбросить его вверх. За каждое удачное подбрасывание начисляется одно очко. Скорость полета предметов увеличивается с количеством набранных очков. Еще есть кот, который периодически ловит вилкой колбасу тем самым мешая повару. Если предмет падает на пол, то его хватает мышь и начисляется штрафное очко. После трех упавших на пол предметов игра прекращается.

Вот такая простая игрушка. Теперь будем ее реализовывать.

Реализация


Шаг 1. Чистый лист

Создаем контроллер и в нем новую задачу для реализации нашей игры.

Кликабельная картинка

Шаг 2. Бегающий по полю повар

Теперь нам нужен повар, который по командам с операторской станции будет бегать по полю. Для реализации этого делаем простейшую схему:

  • селектор будет подавать «команду 1» (для движения вправо) или «команду 2» (для движения влево);
  • дальше ставим два блока «Выбор» и при наличии единицы на соответствующем выходе селектора передаем на выход выбора «1» для движения вправо и "-1" для движения влево;
  • суммируем выходы с алгоритмов «Выбор» на алгоритме «Сложение», который дополним обратной связью для получения интегрального значения команд;
  • обратную связь заведем через алгоритм выбор заранее, чтобы впоследствии иметь возможность обнулять наш сумматор;
  • собираем первый и второй выходы селектора по схеме «ИЛИ» и по обратной связи подаем на сброс селектора. Таким образом получаем длительность команды в один цикл контроллера.


Кликабельная картинка

Шаг 3. Сосиски и рыба

Теперь, когда у нас есть бегающий по полю повар, пора делать еду, которой он будет жонглировать. Тут тоже все стандартно:

  • берем «Интегратор» и задаем на нем пороги верхний и нижний;
  • к порогам подключаем обычный RS-триггер, по достижении верхнего порога взводим этот триггер и по достижению нижнего сбрасываем;
  • к выходу RS-триггера подключаем алгоритм «Выбор», на котором будем выбирать скорость приращения интегратора. Ставим на вход «ЕслиДа» отрицательное значение (верхний порог достигнут и интегратор должен пойти вниз) и на вход «ЕслиНет» положительное;
  • заводим с алгоритма «Выбор» обратную связь на «Интегратор»;
  • к выходу интегратора подключаем алгоритм преобразования дискретного в целое, отбрасывая тем самым дробную часть;
  • копируем эту схему еще три раза (т.к. предметов у нас четыре штуки).


Кликабельная картинка

Шаг 4. Приделываем графический интерфейс

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

Промежуточный итог

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

Шаг 5. Ограничиваем повара

Как несложно заметить, в данный момент повар у нас управляется командами вправо-влево на единицу и может уходить куда ему заблагорассудится т.к. по выходу стоит простой сумматор, не ограниченный ничем. Но по правилам игры повар может занимать только одно из четырех положений и не может уходить за экран. Для реализации этого добавим повару рамки. Сделать это предельно просто и можно пойти двумя путями:

  1. при достижении поваром крайней правой или левой координаты («4» и «1» соответственно) заблокировать возможность нажатия на кнопку «вправо» или кнопку «влево»;
  2. при достижении поваром крайней правой или левой координаты («4» и «1» соответственно) запретить приращение или уменьшение сумматора, на котором мы храним текущую координату повара.

Если взять за основу оригинал игрушки, то второй вариант выглядит предпочтительнее, т.к. никто не мог игроку запретить нажимать на кнопочки в игрушке. Просто повар дойдя в крайнее положение больше не реагировал на команду в ту же сторону. Реализуем его. Для этого поставим два алгоритма сравнения координаты с минимальным и максимальным возможным значением. И на алгоритм «Выбор» заведем команду не напрямую, как это было реализовано изначально, а через «И» с признаком соответствующего сравнения.
Таким образом у нас получилось, что если повар находится на третьей позиции, то условие «позиция меньше четвертой» выполняется, и команда с селектора пройдет дальше на алгоритм «Выбор». Если повар находится на четвертой позиции, то условие «позиция меньше четвертой» уже не выполнится, и команда с селектора, перемножившись на алгоритме «И» с нулем, дальше не пойдет.

Кликабельная картинка

Шаг 6. Повар подбрасывает еду

Теперь добавим нашему повару возможность подбрасывать предметы. Как видно из основной схемы, сам предмет может занимать шесть положений. Этого мы добились, когда отбросили дробную часть выхода интегратора и получили целые числа от нуля до пяти. Теперь вспоминаем, что подбрасывание предмета вверх у нас осуществляется путем сбрасывания триггера и выбора положительной скорости приращения интегратора. Изначально у нас триггер сбрасывался по достижению интегратором нижнего порога. Но теперь мы уберем эту связь и поставим вместо нее простейшую схему: сложим по «И» положение повара и признак того, что координата предмета равна единицы (т.е. он находится в нижней точке). Если оба условия выполнились, то сбрасываем триггер и начинаем наращивать интегратор.

Кликабельная картинка

Шаг 7. Добавляем анимацию повару

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

  • вариант 1 — повар стоит с опущенной сковородой;
  • вариант 2 — повар стоит с поднятой сковородой.

Вот тут заключается небольшая хитрость. Нужно чтобы анимация отрабатывала, во-первых, синхронно с подбрасыванием предмета, а во-вторых, занимала четко определенное и небольшое время.

Попробуем представить, что же происходит в программе:

  • предмет падает, т.е. значение на выходе интегратора уменьшается с какой-то постоянной скоростью;
  • далее значение на выходе интегратора становится меньше двух, и это значит, что мы попали в зону, когда повар может снова подбросить предмет, подставив под него сковородку;
  • при этом (вспоминаем шестой шаг) когда у нас выполняются оба условия (координата предмета равна единице и повар стоит в этой же позиции) и происходит сброс триггера, и начинается наращивание значения на выходе интегратора все с той же постоянной скоростью;
  • как только значение на выходе интегратора станет больше двух, то предмет отрисуется в следующей координате. При этом повар должен поднять сковородку, чтобы получился визуальный эффект подбрасывания
  • но мы можем «подбросить» предмет как при значении на выходе интегратора 1,99 (тогда через долю секунды он уже отрисуется в следующей координате), так и при значении на выходе интегратора 1,01 (тогда до отрисовки его в следующей координате придется ждать довольно долгое время);
  • пока предмет не отрисовался во второй координате игроку непонятно, подбросил он его или нет.

Вот в чем основная проблема. И решить ее очень просто. При выполнении условия (наша еда в первой координате т.е. в самом низу непосредственно перед падением на пол) и повар подставил сковородку нужно принудительно вместо текущей координаты падающего предмета (не важно 1,99 или 1,01) принудительно подставить нужную нам и уже с нее начать приращение интегратора. Я предлагаю взять значение 1,8.

Теперь вспоминаем, что когда значение на выходе интегратора превысит двойку, то подброшенный предмет перерисуется в новой (второй) координате, и нам нужно еще перерисовать повара с поднятой сковородкой. Но он не должен все время стоять с поднятой рукой, а должен отпустить ее через некоторое непродолжительное время. Для этого мы поставим еще один алгоритм сравнения и будем сравнивать значение на выходе интегратора со значением 2,2. далее по «И» соберем три признака:

  • триггер сброшен и идет приращение координаты;
  • повар находится в той же позиции;
  • текущий выход интегратора больше 2;
  • текущий выход интегратора меньше 2,2.

Если все четыре условия выполнились, то к текущей координате повара добавляем четверку. Таким образом по кодам 1,2,3 и 4 мы будет отрисовывать повара с опущенной сковородкой в позиции 1,2,3 и 4 соответственно, а по кодам 5,6,7 и 8 — с поднятой сковородкой в позиции 1,2,3 и 4 соответственно.

Кликабельная картинка

Шаг 8. Прикручиваем счетчик

Тут вообще все просто. У нас уже есть признак того, что повар подбросил предмет (обработанные по «И» два условия: координата предмета равна единице и координата повара соответствует номеру предмета). Выделяем фронт и заводим на сумматор, который по стандартной схеме обвязываем обратной связью через алгоритм «Выбор», чтобы иметь возможность обнулить показания сумматора по условию.

Кликабельная картинка

Шаг 9. Кнопки «Старт» и «Стоп»

Без них никак. Нужно, чтобы человек сказал контроллеру: «Вот сейчас я готов поиграть. Запускай игру!». А потом смог сбросить результат и вернуться к исходному положению. Реализуем эту задачу.

Вообще один селектор у нас уже есть (для управления поваром) и можно было бы использовать его, добавив еще третью и четвертую команду для старта и сброса соответственно. Но чтобы не загромождать схему сделаем отдельный селектор, управляющий игрой. Точно так же заведем сброс по обратной связи через «ИЛИ» с первого и второго выхода. И поставим на выходе RS-триггер, который будем взводить по команде «Старт» и сбрасывать по команде «Сброс». Игра идет пока у нас триггер взведен.

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

Кликабельная картинка

Шаг 10. Дорабатываем графику

У нас появились новые элементы в программе. Внесем их в графическую часть и запустим игру.

Как видно из картинки, повар у нас уже двигается только в ограниченном допустимыми позициями интервале, сковородку поднимает и еду подбрасывает. Все предметы тоже уже не прыгают сами по себе, а падают и больше не появляются, если повар не успел их поймать и снова подбросить в воздух. Так же работает счетчик количества успешных попыток повара подбросить очередной падающий предмет. Это уже 80% всего функционала игры, на который в сумме затрачено минут двадцать. И в эту игру уже можно играть. Осталось реализовать всего несколько опций:

  • паузу на время падения и перезапуск упавшего предмета;
  • подсчет очков и выдачу сигнала «GameOver» при превышении максимально допустимого числа ошибок;
  • кота, ловящего колбасу;
  • ускорение игры в зависимости от количества набранных очков.

Начнем по порядку.

Шаг 11. Ставим игру на паузу при падении предмета и перезапускаем упавший предмет

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

Кликабельная картинка

Шаг 12. Считаем упавшие предметы

Сигнал по упавшему предмету у нас уже есть (он взводит триггер «Упало»). По этому сигналу наращиваем на единицу счетчик упавших предметов. Заводим по стандартной схеме обратную связь на сумматор через выбор, чтобы иметь возможность обнулять счетчик (будем это делать по команде «Сброс»). Далее сравниваем значение на сумматоре с тройкой. При достижении максимального значения выставляем признак «GameOver» и запрещаем сбрасывать триггер «Упало» автоматически через две секунды. По «ИЛИ» разрешаем сброс этого триггера только командой «Сброс» с управляющего селектора.

Кликабельная картинка

Шаг 13. Добавляем кота, ловящего колбасу

Применим стандартную схему:

  • Ставим интегратор с двумя произвольными порогами;
  • По достижению верхнего порога взводим триггер и сбрасываем по достижению нижнего порога;
  • Через выбор подаем положительную и отрицательную скорость приращения интегратора. Таким образом получаем постоянно меняющееся по «пиле» значение на выходе интегратора;
  • Берем алгоритм текущего времени и делим на выход интегратора;
  • Умножаем получившееся после деления значение на 10000 и делим с остатком на 1, тем самым получая отдельно целую и дробную часть;
  • выбираем нужный нам шанс, что кот поймал колбасу. Допустим это 30%, т.е. сравниваем остаток с 0,3;
  • складываем по «И» четыре признака: остаток меньше 0,3, колбаса перешла с 4 на третью позицию, есть признак «Идет игра» и триггер на выборе скорости интегратора «колбасы» взведен (на вход интегратора поступает отрицательное значение, т.е. колбаса падает вниз);
  • по сигналу с алгоритма «И» взводим триггер «Кот» и запускаем таймер. На вход таймаута подаем остаток, умноженный на десять (т.е. случайное количество секунд от 0 до 3);
  • по таймауту сбрасываем триггер «Кот»;
  • сигнал с триггера «Кот» с инверсией подаем на алгоритм «Выбор», где еще раз выбираем скорость, подаваемую на вход интегратора «колбасы». Т.е. пока на выходе триггера «Кот» логическая единица, на вход интегратора подается ноль и предмет зависает в воздухе;


Кликабельная картинка

Шаг 14. Делаем увеличение скорости игры в зависимости от набранных очков

Это финальный штрих. Фактически у нас все готово. Скорость определяется на алгоритме «Выбор», где задается положительная и отрицательная скорости приращения (и уменьшения соответственно) выхода интегратора. По умолчанию мы выбрали какие то числа. Для реализации функции изменения скорости мы показание счетчика успешных попыток подбросить предмет делим на произвольный коэффициент (например 50), и получившееся значение прибавляем к заданному изначально. Таким образом получается, что чем больше у нас очков, тем быстрее будут подпрыгивать сосиски.

Кликабельная картинка

Шаг 15. Дорабатываем графику для финальной проверки

Т.к. у нас появились еще несколько важных сигналов, добавляем их на нашу картинку в упрощенном виде. Получается что то в этом роде:

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

Шаг 16. Финальный вид техпрограммы и gif ее реализации по шагам

Кликабельная картинка

Шаг 17. Делаем красивую графику к игре

Для этого нам понадобится картинки из интернета. Берется любая красивая картинка с игрой «Весёлый повар». Режется на кусочки, и каждый объект (повар, сосиска, мышь и кот) оформляется отдельной картинкой. Далее эти картинки расставляются на поле. Макет графической части у нас уже есть. На нем мы отлаживали программу. Просто заменяем кружки и квадратики нужными картинками, а вместо анимации «изменение цвета» ставим анимацию «отображение». С вырезанием, редактированием и обработкой картинок пришлось повозиться. Подозреваю, что какой-нибудь фотошоп позволил бы сделать все это за 15 минут, но и в MS Paint за пару часов было нарисовано все что нужно.

Добавляем на картинку дополнительные кнопочки управления, счетчик очков и надпись «GameOver». Все готово, можно играть.

Шаг 18. Играем


* анимация сделана с частотой 1 кадр в секунду (т.к. скорость игры в начале довольно медленная) и воспроизведена с двойной скоростью.

Подведение итогов


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

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

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

Есть еще один интересный момент. В результате у нас получилась игра «Весёлый повар». Таких игр была целая куча: «Ну погоди!», «Охота», «Спасающиеся инопланетяне», «Тайны океана», «Хоккей», «Футбол» и т.д. Но все эти игры сделаны по одному и тому же принципу. Фактически менялась только графическая составляющая с минимальным изменением программы. Поэтому за пару минут из нашей программы можно реализовать любую желаемую.

Например «Ну погоди!». Перерисовывается графика. Это понятно. А с точки зрения алгоритма изменение одно: у нас предмет летит сначала снизу вверх, а потом падает обратно, а в «Ну погоди» движение только в одну сторону. Т.е. достаточно убрать RS-триггер и алгоритм «Выбор», отвечающие за скорость наращивания интегратора каждого предмета, и в место них поставить только отрицательную скорость и при появлении нового яйца устанавливать значение интегратора в максимум. Вот и все. Удаляем восемь алгоритмов, проводим восемь новых связей и игра «Ну погоди!» готова.

На этом все. Надеюсь было интересно.

Комментарии (0)

Let's block ads! (Why?)

Комментариев нет:

Отправить комментарий