Введение
В предыдущей статье был рассмотрен пример создания простейшей одномерной игры — «а-ля избегай столкновений». С такими набором способностей ШГ явно не оправдывает своего названия. По факту получается шарик-уклонист, что не звучит гордо. Так почему бы не расширить возможности «геймплея» и не добавить к игре элемент интерактивности?
Для начала предлагается перенести действие игры на плоскость. Ввести в игру условие победы — счет, при достижении которого, появится заветное для геймера «You won». Выдать ШГ пацифистское орудие для более быстрого набора очков и взаимодействия с ШП, по возможности изменить визуальное оформление проекта на более привлекательное.
Собственно, решением этих задач я занялся в рамках своего очередного проекта «выходного дня». Что получилось, читайте далее.

Шаг 1 «Задание на разработку»
-ШГ должен иметь 2 степени свободы.
-ШГ имеет возможность быстрого набора очков и замедления скорости движения ШП по средству попадания по последнему специального луча.
-Реализовать новые правила — игра ведется до 1000 очков. За простой уход от столкновения начисляется 1 очко. За удержание луча на шарике +1 очко за каждые 50*10^-3 c (в программе на Arduino установлена задержка 50 мс)
Шаг 2 «Описание подключения аналогового стика»
Стик имеет 5 пинов для подключения: пин VCC подключаетя к питанию +5V, GND — к земле на плате Arduino Uno, X — к аналоговому входу А0, Y — к А1, D — к цифровому входу D2.

Шаг 3 «Передача нескольких координат по Serial соединению»
Данные о положении стика передаются вместе, чтобы максимально быстро обеспечить обработку их Processingом без задержки по времени. Для описания текущего положения аналогового стика достаточно 7 бит XXXYYYB — 3 для координаты Х, еще 3 — для координаты Y, 1 бит — для контроля нажатия кнопки на стике. Код прикладываю ниже:
#include <stdio.h> int xPin = A0, yPin = A1, buttonPin = 2; int xPosition = 0, yPosition = 0, buttonState = 0; char strbuf[20]; // void setup() { // initialize serial communications at 9600 bps: Serial.begin(9600); pinMode(xPin, INPUT); pinMode(yPin, INPUT); //activate pull-up resistor on the push-button pin pinMode(buttonPin, INPUT_PULLUP); } void loop() { xPosition = map(analogRead(xPin),0,1023,0,639); yPosition = map(analogRead(yPin),0,1023,0,639); buttonState = digitalRead(buttonPin); sprintf(strbuf,"%03d%03d%1d",xPosition,yPosition,buttonState); Serial.println(strbuf); delay(50); }// add some delay between reads
После подключения стика и прошивки Arduino Uno, в мониторе Serial-порта можно увидеть примерно следующее.

Это и есть вектор состояния стика в определенном нами формате. Кстати, иногда «проскакивают» артефакты — вырожденные векторы из 2-х 3-х бит. Крайне неприятное явление. Из-за них игра на Processing «крашится». Возможно, это объясняется дефектом на моей плате стика, возможно — нет. В любом случае, бороться с артефактами на уровне Arduino я не стал. Для этого предусмотрено специальное условие в коде игры на Processing (см. Шаг 4). Его задача состоит в проверке целостности передаваемых данных по Serial соединению.
Шаг 4 «Кодирование игры»
Подробно комментировать код не буду. Я пытался сделать его понятным. Оценить получилось или нет можно под спойлером. На всякий случай привожу таблицу переменных:
radiusOfHero-радиус ШГ;
radiusOfEnemy — радиус ШП;
radiusOfBullet — радиус пули; выпускаемой ШГ;
Counter — счетчик очков;
speedOfEnemy — коэффициент, прямо пропорционально влияет на скорость падения ШП;
DeltaPositionOfHeroX — приращение положения ШГ по оси X, получаемое от стика;
positionOfHeroX1- конечная координата перемещения ШГ по оси Х (на конец такта считывания);
positionOfHeroX0 — начальная координата по оси X(на начало такта);
DeltapositionOfHeroY, positionOfHeroY1, positionOfHeroY0 — то же по оси Y;
strbuf — строка, в которую считываются показания состояния аналогово стика, передаваемые с Arduino Uno.
import processing.serial.*;//I/O library Serial port; PShape bot; PFont font; PImage img; int radiusOfHero=100, radiusOfEnemy, radiusOfBullet=5, Counter=0, Fire; float speedOfEnemy=1, DeltaPositionOfHeroX, positionOfHeroX1, positionOfHeroX0=640.0, DeltapositionOfHeroY, positionOfHeroY1, positionOfHeroY0=640.0, positionOfEnemyY = 0.0 ,positionOfEnemyX=0.0, positionOfBulletX=0.0,positionOfBulletY=0.0; String strbuf="3223220"; void setup() { size(640, 640); port = new Serial(this, "COM4", 9600); port.bufferUntil('\n'); bot = loadShape("2.svg"); font = loadFont("AgencyFB-Bold-200.vlw"); img = loadImage("img.png"); // Load the image into the program textFont(font,200); } void draw() { background(0); image(img, 0, 0); fill(255); text(Counter, 400,170); //==========definiton of hero========== fill(0, 200, 102); positionOfHeroX1=positionOfHeroX0+(0.05*(DeltaPositionOfHeroX-width/2)); if (positionOfHeroX1<0){positionOfHeroX1=0.0;} if (positionOfHeroX1>width){positionOfHeroX1=width;} positionOfHeroY1=positionOfHeroY0+(0.05*(DeltapositionOfHeroY-height/2)); if (positionOfHeroY1<0){positionOfHeroY1=0.0;} if (positionOfHeroY1>height){positionOfHeroY1=height;} ellipse(positionOfHeroX1, positionOfHeroY1, radiusOfHero, radiusOfHero); positionOfHeroX0=positionOfHeroX1; positionOfHeroY0=positionOfHeroY1; fill(244); positionOfBulletY= positionOfHeroY1-radiusOfHero/2; if (Fire==0){ for(int i = 0; i < (positionOfHeroY1); i++){ positionOfBulletX = positionOfHeroX1; positionOfBulletY= positionOfBulletY-height/100; ellipse(positionOfBulletX, positionOfBulletY, radiusOfBullet, radiusOfBullet); } } //===============definition of enemy=============== fill(255,0,0); radiusOfEnemy=round(random(60));{ for(int i = 0; i < height; i++) positionOfEnemyY=positionOfEnemyY+0.02*speedOfEnemy; ellipse(positionOfEnemyX, positionOfEnemyY, radiusOfEnemy*2, radiusOfEnemy*2); } if (positionOfEnemyY>height) { positionOfEnemyY=0.0; positionOfEnemyX = round(random(width)); Counter++;} //==========definition of counter========== if (Counter>1000){ text("YOU WON!", 50,height/2); } //==========clash========== if (abs(positionOfHeroX1-positionOfEnemyX) < (radiusOfHero+radiusOfEnemy)/2 & (abs(positionOfHeroY1-positionOfEnemyY) < (radiusOfHero+radiusOfEnemy)/2)){ background(255,0,0); shape(bot, positionOfHeroX1-radiusOfHero/2,positionOfHeroY1-radiusOfHero, 100, 100); Counter=-1; fill(255); textFont(font,150); text("TURN AWAY!", 0,height/2);} //==========Checking of target hit========== if (((abs(positionOfBulletX-positionOfEnemyX) < (radiusOfBullet+radiusOfEnemy)/2))& (Fire==0)) {speedOfEnemy=0.05;// decreasing of enemy speed Counter++;} else speedOfEnemy=0.2;} void serialEvent (Serial port) { if(port.available()>0){ strbuf=port.readStringUntil('\n'); if (strbuf.length()<7) {//condition to prevent artefacts strbuf="3223220"; } DeltaPositionOfHeroX=float(strbuf.substring(0, 3)); DeltapositionOfHeroY=float(strbuf.substring(3, 6)); Fire=int(strbuf.substring(6, 7)); } }

