うつろぐ

文章

Windows10でJoy-ConをProcessingから扱ってみた その1(vJoyなし編)

少し前に同級生がJoy-Conの制御を頑張っていたな~( JoyConの加速度センサーを取るための設定変更についての話 - 忘れないうちに )ということをふと思い出して、でも今switchは買えないな~とか思ったけど、そういえばJoy-Conって単体で買えるんですよね。という訳で買ってProcessingから制御してみました。

少し調べるとこちらの記事 【P5 Tips】 ProcessingをNintendo SwitchのJoy-Conで遠隔操作する|タピオカ@ジェネラティブアート|note に書かれている通り、ProcessingでJoy-ConなどのGamepadを簡単に扱えるライブラリがあるらしいです。当該記事ではMacでやってみた、ということなので、今回はWindowsで同じことをやってみて、躓いたところとかを書いていきます。

Joy-Conについて

まあいろんなとこに売ってて、新品はLRセットでだいたい8000円前後で買えます(Switchがだいたい32000円)。
ここで、このJoy-Con単体での販売には充電器がついてこないことに注意してください。別途充電器を買う必要があります。 Joy-Con充電グリップ

今回できるようになること

今回はこちらの記事 【P5 Tips】 ProcessingをNintendo SwitchのJoy-Conで遠隔操作する|タピオカ@ジェネラティブアート|note の流れに沿って、Joy-Con

・ボタン入力
・スティックの方向入力

ができるようになります。逆に、今回はvJoyを使わないので

・ジャイロ入力
・スティックのJoyStickとしての入力(深度入力)

はできません。それではやっていき

Processingで扱う前の準備

Windows10のPCにJoy-ConBluetooth接続します。このへんはこちらの記事 Windows10でSwitchのJoyConを使う方法 | hyperT'sブログ を参考にしてください。
vJoy driverから先は今回は使わないのでやらなくて大丈夫です。

問題:Joy-Conからの入力の名称が文字化けして認識できない

さっそく記事にある通りにライブラリをインポートして、設定ファイルを作成して、サンプルコードを貼り付けてやってみました。

さっそくおかしい。

具体的には、サンプルコードを実行すると

f:id:Distorted_Unchi:20180721175752p:plain

こんな画面が(出てこないはずなんだけど)出てきて、Joy-Conらしき「Wireless Gamepad」を選択すると

f:id:Distorted_Unchi:20180721180205p:plain

入力信号とプログラム上でのオブジェクトとをつなぐ画面が出てきました。Joy-Conのボタンを押すと右にある該当する入力信号の〇が光るので繋いでいくようです。よく見ると入力信号が文字化けしまくっていて、結果的に同じ文字列になってるのがいくつかあって、これ識別できるのか...?とか思いながら全部繋いで右上の「USE」を押すと、

f:id:Distorted_Unchi:20180721180812p:plain

やっと実行されました。スティックはちゃんと取れてるんだけど、やっぱり文字化けした結果同じ文字列になってしまったボタン類は正しく認識できてません。画像では+ボタンしか押してないのに、+ボタンの入力と同じ文字列の入力のボタンが全部反応してます。
結論としては僕はこの方法ではJoy-Conをちゃんと制御することはできませんでした。

原因?

恐らくはWindows10が認識したJoy-Conの入力をProcessing(のライブラリ)が受け取る際に文字化けしているものだと思われます。
また、このJoy-Conはまだ一度もSwitchと接続していない(Switchを持っていないから)ので、それが原因かもしれないです。

別の方法で認識してみた

設定ファイルからJoy-Conを認識する方法がうまくいかなかったので、今回は名称からJoy-Conを認識してみます。

ControlIO control = ControlIO.getInstance(this);
ControlDevice device = control.getDevice("Wireless Gamepad");

これで取り敢えずdeviceからJoy-Conが扱う第一歩ができたので、ここからボタンやスティックの値を取得します。
ボタンやスティックの入力文字列が文字化けしているので、入力文字列指定ではなく、入力のインデックスを特定して取得します。

//Rの場合
ControlButton buttonA = device.getButton(0); //Aは0番目のButton入力
ControlHat hat = device.getHat(16); //スティックは16番目のButton入力

ここで注意すべきは、Hat(スティック)はHatの独立したインデックスを持つのではなく、Buttonと同列のインデックスにいることです。device.getHat(0)とやるとAを取得していることになり「お前はボタンを取得してるぞ」と怒られます。ここが全然わからなかった。
Joy-Con以外のGamepadにはSliderというのが登場することもあるらしいですが、それはSlider独自のインデックスを持っているらしい。ややこしい...
つまりこういうこと。

これであとはpressed()なりgetPos()なりで値が取れるようになりました。

サンプルコード

あの記事(【P5 Tips】 ProcessingをNintendo SwitchのJoy-Conで遠隔操作する|タピオカ@ジェネラティブアート|note)のものを改変させていただきました。

import net.java.games.input.*;
import org.gamecontrolplus.*;
import org.gamecontrolplus.gui.*;

ControlIO control;
ControlDevice device;

void setup() {
  size(500, 360);
  colorMode(HSB, 360, 100, 100);
  rectMode(CENTER);
  textAlign(LEFT, CENTER);

  control = ControlIO.getInstance(this);
  device = control.getDevice("Wireless Gamepad");
  if (device == null) {
    println("No suitable device configured");
    System.exit(-1);
  }
}

void draw() {
  background(200, 60, 30);

  // ボタンの状態を表示
  for (int i=0; i<device.getNumberOfButtons()-1; i++) {
    stroke(#eeeeee);
    if (device.getButton(i).pressed()) fill(30, 60, 90); 
    else noFill();
    rect(30, 30 + i * 20, 15, 15);
    fill(#eeeeee);
    text(i, 50, 30 + i * 20);
  }

  // スティックの状態を表示
  float radius = 75;
  stroke(#eeeeee);
  noFill();
  translate(width/2, height/2);
  beginShape();
  for (int i = 0; i < 8; i++) {
    float angle = (float)i / 8 * TWO_PI;
    vertex(radius * cos(angle), radius * sin(angle));
  }  
  endShape(CLOSE);

  noStroke();
  fill(30, 60, 90);
  int pos = device.getHat(16).getPos();
  float x = 0, y = 0;
  if (pos > 0) {
    // 「横持ち」基準で上下左右が決められているため
    // 右の Joy-Con の場合は HALF_PI を足す
    // 左の Joy-Con の場合は HALF_PI を引く
    float angle = (float)pos / 8 * TWO_PI + HALF_PI;
    //float angle = (float)pos / 8 * TWO_PI - HALF_PI;
    rotate(angle);
    x = radius;
  }
  ellipse(x, y, 20, 20);
}

この方法で左右のJoy-Conを同時に認識するのに少し手間が要るお話

Joy-Conのデバイスの名称がLR双方とも「Wireless Gamepad」であるため、先ほどのデバイスの取得の方法のままだとLRを区別して認識することができません。
設定ファイルを用いてconfigしてあげれば可能なのですが、今回それが文字化けで使えないため、あるいはconfigの手間をユーザーに取らせたくない場合に、
「名称が『Wireless gamepad』であるデバイスを候補として取得しておいて、LRそれぞれ固有のボタンが押された際に区別する」という方法があります。

Joy-ConのButton(ButtonとHat)入力のインデックスの配置は以下のようになっています。

空のインデックスが存在するのがおもしろい。6,7に至ってはどっちも空、何に使われていたのか...
ここで、-/+、各スティック(押す)、SHUTTER/HOMEはLRでインデックスの被りがなく、これらが押されたことを取得すればLRを区別して認識することができます!今回ぼくは-/+でやってみました。

サンプルコード

起動して、LRの-/+を押すと、それぞれのスティック入力が可視化されます。

import net.java.games.input.*;
import org.gamecontrolplus.*;
import org.gamecontrolplus.gui.*;

ControlIO control;
ControlDevice deviceL=null, deviceR=null;
ArrayList<ControlDevice> candidates=new ArrayList<ControlDevice>();

void setup() {
  size(500, 500);

  control=ControlIO.getInstance(this);

  //control.getDevices()で接続されたデバイスの一覧がとれる
  //device.open()はそのデバイスを有効化する命令(getDevice("Wireless Gamepad")などで取得する際には自動的に呼ばれる)
  for (ControlDevice device : control.getDevices()) { 
    if (device.getName().equals("Wireless Gamepad") && device.getNumberOfButtons()==17) {
      device.open();
      candidates.add(device);
    }
  }
}

void draw() {
  background(0);

  for (ControlDevice candidate : candidates) {
    if (deviceL==null && candidate.getButton(8).pressed()) { //-:8
      deviceL=candidate;
    }
    if (deviceR==null && candidate.getButton(9).pressed()) { //+:9
      deviceR=candidate;
    }
  }

  if (deviceL!=null) {
    float angle;
    if (deviceL.getHat(16).getPos()>0) {
      angle=(deviceL.getHat(16).getPos()+6)%8/8.0*TWO_PI;

      stroke(255);
      strokeWeight(1);
      float x=width*0.33+100*cos(angle);
      float y=height*0.5+100*sin(angle);
      line(x, y, width*0.33, height*0.5);
    } else {
      angle=-1;
    }

    fill(255); 
    textSize(16); 
    textAlign(CENTER);
    text(angle, width*0.33, height*0.5);
  }
  if (deviceR!=null) {
    float angle;
    if (deviceR.getHat(16).getPos()>0) {
      angle=(deviceR.getHat(16).getPos()+2)%8/8.0*TWO_PI;

      stroke(255);
      strokeWeight(1);
      float x=width*0.66+100*cos(angle);
      float y=height*0.5+100*sin(angle);
      line(x, y, width*0.66, height*0.5);
    } else {
      angle=-1;
    }

    fill(255); 
    textSize(16); 
    textAlign(CENTER);
    text(angle, width*0.66, height*0.5);
  }
}

今回はここまで。その2があるとすればvJoyを使ってジャイロやJoyStickの取得になるけど、これらを使った制作物を公開するときにユーザーにvJoy driverのインストールを要求することになるのがよくないのでたぶんやらないかな~...