学祭での一般向け研究室公開のために、
Leap Motionで遊べるProcessingスケッチを作成しました。



1108145443

Leap Motionで両手を認識し、それぞれの指をポインタとして白く小さな円で表示します。
様々な色で縁取りされた大きな円の中にポインタが入ると、
縁取りと同じ色のパーティクルが花火のようにはじけます。
また、縁取りは大きな円を一周すると消えてしまいます。

マウスで同じように遊べるスケッチをOpenProcessingに用意しました。
Fireworks_Circles- OpenProcessing
Leap Motionは10本の指を認識できますがマウスなので当然1つしか認識できません。
そういった点でも、Leap Motionは表現の幅を広げてくれます。

作り方
花火の表現は、もともとは別のスケッチで使用していたものでした。
Fireworks- OpenProcessing
これに対して、大きな円の動作やLeap Motionの動作について書き加えました。
Leap Motionの導入については、以下のページを参考にしました。
Leap MotionをProcessingでつかってみた - AP.TV - アライアンス・ポートBLOG
PC制御UIの最先端Leapアプリ開発入門(2):ProcessingやJavaScriptでLeap Motionを動かす (1/3) - @IT
Leap Motionの動作は、サンプルコードの「active_fingers」を参考にしました。
実際のコードは、この記事の最後に掲載しています。

大きな円の縁取りについてはarc()を使用しています。
arc()の範囲を0からTWO_PIまで増加させていき、
TWO_PIに達したらアルファ値を下げて消しています。
これにより、「時間内にさわらないと消える」というゲーム性を持たせました。

Leap Motionを実際に動作させてみると、小さなモニター上に表示させるよりも、
大きなモニターの方が精度が良いように思えました。
よって、展示の際には大きなモニターをお借りし、
その下にLeap Motionを置いて動作させました。
また、モニターの端の方にポインタを持っていく時に認識が切れることが多かったので、
ポインタのターゲットとなる大きな円が端の方に表示されないように調整しました。

展示してみて
学祭での展示だったので、子どもたちにたくさん遊んでもらいました。
「おもしろい!」と言ってもらえたのが何よりも励みになりました。
特に、小学生の女の子が「自分の好きな色の円を他よりも優先的にさわる」というルールを
自分で考えて遊んでくれたのが印象的でした。
「時間内にさわらないと消える」というゲーム性を持たせたことで、
さらに楽しい遊び方を考えてくれたのがうれしかったです。
また、Leap Motionは真上ではなく少し手前に手をかざす必要があるのですが、
そこをうまく伝えきれなかったことが何回かありました。
実際にやって見せても真上にかざす子が多かったので、もっと丁寧に説明したり、
あるいはLeap Motionを少し遠めに置いておくといった工夫が必要だったと思います。
しかし、Kinectほど認識がシビアではないので、
ある程度の適当さでかざしてもきちんと認識されたり、
認識が切れてもすぐにやりなおせたりするところが優秀だなーと思いました。

コード
import com.onformative.leap.*;
import com.leapmotion.leap.Finger;

LeapMotionP5 leap;

ArrayList flowers;
ArrayList circles;

float hue;

void setup() {
size(displayWidth, displayHeight);
colorMode(HSB, 360, 100, 100);
background(0);
smooth();
noCursor();

leap = new LeapMotionP5(this);

flowers = new ArrayList();

circles = new ArrayList();

for (int p = 0;p < 5;p++) {
hue = random(360);

circles.add(new Circle(hue));
}
}

void draw() {
background(0);

Circle circle;
Flower flower;

for (int j = 0; j < circles.size();j++) {
circle = circles.get(j);
circle.display();
if (circle.arc_flg == true) {
circle.alp -= 20;
if (circle.alp < 0) {
circles.remove(j);
hue = random(360);
circles.add(new Circle(hue));
}
}
}

for (Finger finger : leap.getFingerList()) {
PVector fingerPos = leap.getTip(finger);

noStroke();
fill(360);
ellipse(fingerPos.x, fingerPos.y, 10, 10);

for (int j = 0; j < circles.size();j++) {
circle = circles.get(j);

if (dist(fingerPos.x, fingerPos.y, circle.x, circle.y) < circle.r/2) {
flowers.add(new Flower(fingerPos.x, fingerPos.y, circle.hue));

circles.remove(j);
hue = random(360);
circles.add(new Circle(hue));
}
}
}

for (int i = 0; i flower = flowers.get(i);
flower.display();
if (flower.finished()) {
flowers.remove(i);
}
}
}

public void stop() {
leap.stop();
}

//大きな円
class Circle {
float r = random(100, 150);
float x = random(100, width-100);
float y = random(150, height-150);

float alp = 255;

float arc_t = 0;
float arc_t_spd = random(0.01, 0.05);

float hue;

boolean arc_flg = false;

Circle(float tmpHue) {
hue = tmpHue;
}

void display() {
noStroke();
fill(360, alp-100);
ellipse(x, y, r, r);
strokeCap(SQUARE);
strokeWeight(10);
stroke(hue, 100, 100, alp);
noFill();
arc(x, y, r, r, 0, arc_t);

arc_t += arc_t_spd;

if (arc_t > TWO_PI) {
arc_flg = true;
}
}
}

//花火
class Flower {
int dot_val = (int)random(100, 200);

Dot[] dots = new Dot[dot_val];

float x;
float y;
float hue;

float alp = 255;
float alp_spd = random(3, 6);

Flower(float tmpX, float tmpY, float tmpHue) {
x = tmpX;
y = tmpY;
hue = tmpHue;

for (int i = 0; i < dots.length;i++) {
dots[i] = new Dot(hue);
}
}

void display() {
pushMatrix();
translate(x, y);
for (int i = 0; i < dots.length;i++) {
dots[i].move();
dots[i].display(alp);
}

alp -= alp_spd;

popMatrix();
}

boolean finished() {
if (alp < 0) {
return true;
}
else {
return false;
}
}
}

//パーティクル
class Dot {
float hue;

float R = random(10, 100);
float theta = random(TWO_PI);

float x;
float y;

float goal_x = R * cos(theta);
float goal_y = R * sin(theta);

float easing_x = random(0.03, 0.07);
float easing_y = random(0.03, 0.07);

float sat = random(30, 100);
float brt = random(30, 100);

Dot(float tmpHue) {
x = 0;
y = 0;
hue = tmpHue;
}

void move() {
x += (goal_x - x) * easing_x;
y += (goal_y - y) * easing_y;
}

void display(float alp) {
strokeCap(ROUND);
strokeWeight(5);
stroke(hue, sat, brt, alp);
point(x, y);
}
}