Программируем БОС-игру
Для создания игры нам потребуется сюжет и часть предыдущего скетча Processing.
Фраза "сердце из груди выпрыгивает" и понимание, что прыгать естественнее мячику, дает нам игровую задачу: синхронизировать движение мяча с эталонным "прыгуном".
Для вычисления траектории перемещения мяча используем периодическую функцию - синус:
sin(millis()*TWO_PI*r/(60*1000)) ,где r - пульс в ударах в минуту;
Деформацию мяча имитируем изменением "высоты" эллипса:
d*(1-abs(s/3)) , где s - текущая величина синуса, а d - диаметр;
Для замыливания нижнего положения мяча добавим управление прозрачностью:
fill(g.fillColor, 255*(1-(s+1)/2)) .
Получается вот такой метод анимации движения мяча
void anim(int x, int r) { float s=sin(millis()*TWO_PI*r/(60*1000)); fill(g.fillColor, 255*(1-(s+1)/2)); ellipse(x, s*(height-d)*0.4+height/2+(height+d)*0.05, d, d*(1-abs(s/3))); }
Для управления частотой пульса эталонного мяча задействуем клавиатуру
void keyPressed() { switch(keyCode) { case UP: if (refR<120) refR+=refStep; break; case DOWN: if (refR>60) refR-=refStep; break; } }
Рисуем скромную ленту регистрации, мячики и величины пульсов
stroke(255); strokeWeight(3); int rate=0; synchronized(knocks) { for (int m : knocks) { int x=width-pxPerSec*(currTime-m)/1000; line(x, 1, x, 5); } rate=rate(); } stroke(0); fill(102, 204, 0); anim(150, rate); fill(102, 204, 0); textSize(30); text(rate, 10, 40); fill(204, 102, 0); anim(250, refR); fill(204, 102, 0); text(refR, width-textWidth(""+refR)-10, 40);
Скетч целиком
import processing.serial.*; import java.util.*; final boolean SIM=true; final int pxPerSec=50; final int d=80; final int refStep=2; ArrayDeque<Integer> knocks=new ArrayDeque<Integer>(); int timeWindow=1000*10; int refR=90; void setup() { size(400, 300); if (SIM) { thread("imitator"); } else { println(Serial.list()); Serial port=new Serial(this, "COM3", 9600); port.bufferUntil('\n'); } timeWindow=1000*width/pxPerSec; } void draw() { background(0, 0, 0); int currTime=millis(); stroke(255); strokeWeight(3); int rate=0; synchronized(knocks) { for (int m : knocks) { int x=width-pxPerSec*(currTime-m)/1000; line(x, 1, x, 5); } rate=rate(); } stroke(0); fill(102, 204, 0); anim(150, rate); fill(102, 204, 0); textSize(30); text(rate, 10, 40); fill(204, 102, 0); anim(250, refR); fill(204, 102, 0); text(refR, width-textWidth(""+refR)-10, 40); } void anim(int x, int r) { float s=sin(millis()*TWO_PI*r/(60*1000)); fill(g.fillColor, 255*(1-(s+1)/2)); ellipse(x, s*(height-d)*0.4+height/2+(height+d)*0.05, d, d*(1-abs(s/3))); } void serialEvent(Serial p) { p.readString(); addKnock(millis()); } void addKnock(int m) { synchronized(knocks) { knocks.add(m); while (m-knocks.peek ()>timeWindow) knocks.remove(); } } int rate() { if (knocks.size()>1) { return round(60f*1000*(knocks.size()-1)/(knocks.peekLast()-knocks.peekFirst())); } return 0; } void imitator() { while (true) { try { Thread.sleep((int)random(500, 1000)); } catch (Exception e) { } addKnock(millis()); } } void keyPressed() { switch(keyCode) { case UP: if (refR<120) refR+=refStep; break; case DOWN: if (refR>60) refR-=refStep; break; } }
Вот теперь в Arduino IDE перезаливаем в плату скетч с отключенным эмулятором, меняем значение SIM на false в Processing, отключаем ноутбук от розетки или от зарядника, надеваем на ухо клипсу и играем.