Введение
В предыдущей статье был рассмотрен пример создания простейшей одномерной игры — «а-ля избегай столкновений». С такими набором способностей ШГ явно не оправдывает своего названия. По факту получается шарик-уклонист, что не звучит гордо. Так почему бы не расширить возможности «геймплея» и не добавить к игре элемент интерактивности?
Для начала предлагается перенести действие игры на плоскость. Ввести в игру условие победы — счет, при достижении которого, появится заветное для геймера «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 бит — для контроля нажатия кнопки на стике. Код прикладываю ниже:
Код для платы Arduino Uno
#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.
Код игры на Processing
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));
}
}