【Unity】Joy-Conをポインタデバイスとして使う

[総コン Advent Calendar 2022](https://adventar.org/calendars/7897) 19日目の記事です。

# 今回目指すもの
![tcqqa-odkty.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/29b8d544-089f-9937-3e34-bd787019ce78.gif)
JoyconLibの導入からポインターのUIの表示まで行います。

# 環境
JoyconLib06 2018.2
Unity 2021.3.8 (2019以降なら問題ない気はします)
MacOS Big Sur ver 11.6

# 対象読者
Unity初心者

# JoyconLibの導入
## JoyconLibのダウンロード
https://github.com/Looking-Glass/JoyconLib

Joy-ConをUnityで使えるようにパッケージを製作してくださっている方がいるので、これを用います。上のリンクから、JoyConLibのpackageをダウンロードします。
![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f313836353635302f34353531303632372d613062622d386135622d336631362d6236663531623666633338382e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/3f49aee7-2317-12dc-617b-804616450485.png)
![スクリーンショット 2022-12-03 16.59.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/b911aadd-5040-721f-4b4e-a90ba85c52d8.png)

## Unityプロジェクトの作成
UnityHubを開いて3Dプロジェクトを作成します。
![スクリーンショット 2022-12-03 17.02.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/b289cc54-aa0a-782e-30db-6914af9c12ce.png)

## UnityプロジェクトへPackageの導入
画面左上から Assets -> Import Package -> Custom Package を選択します。
![スクリーンショット 2022-12-03 17.08.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/1095068f-8692-be70-647d-c567beb96993.png)

先ほどダウンロードしたパッケージファイルを選択して、Openしましょう。
![スクリーンショット 2022-12-03 17.10.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/0f4474e8-7963-a87b-c9af-871ddc5f86ff.png)

Unity側で新しくウィンドウが出るので、Importをクリックします。
![スクリーンショット 2022-12-03 17.10.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/31e6c305-89c5-4f13-b6ad-d66375ae9c4c.png)
これでPackageが導入できました。

# Joy-ConをPCに接続する
Macのシステム環境設定を開いて、Bluetoothの項目を開きます。
![スクリーンショット 2022-12-03 17.24.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/190bd17d-7703-9a8f-153e-ea62e49f2631.png)

Joy-Con側面のペアリングボタンを長押ししてペアリングモードにします。
![1667353293627-bCHeB31iZXのコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/bd91ae9e-31f7-1dba-2e38-a2641a200743.png)

出てきたデバイス一覧から「Joy-Con(R)」をクリックして接続します。
「Joy-Con(L)」も同じように接続します。
接続済み表記になれば問題なく接続できていると思います。
![スクリーンショット 2022-12-17 17.20.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/711c35d5-ce33-faba-eda0-96d5b4f050b4.png)
![スクリーンショット 2022-12-17 17.21.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/972eb1ca-015d-cd39-903d-ad52354f619e.png)

# サンプルシーンの起動
ProjectビューからAssets -> ScenesをクリックしてScenesフォルダを開きます。
![スクリーンショット 2022-12-03 17.15.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/4777894d-e9d4-f1fd-640e-351fef04c9da.png)

Scene1をクリックして、JoyconLibのサンプルシーンに切り替えます。
![スクリーンショット 2022-12-03 17.16.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/90173025-2ddf-5c9e-d792-fce846a1c6de.png)
![スクリーンショット 2022-12-17 17.45.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/b18ed642-7537-a957-7d70-12cf5fd63a88.png)

Unityの実行ボタンを押してサンプルシーンを実行します。
このような感じで青いCubeがJoy-Conに連動して動けば問題ないと思われます。
![Videotogif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/97ff8d03-c253-9c0b-6594-8052f08516f6.gif)

## ちょっとした解説
今回動かした青色の立方体を動かしているのは、JoyconDemo.csというスクリプトです。
JoyconDemo.csを見てみましょう。
画面左のCubeオブジェクトをクリックして、画面右のInspectorビューにあるJoyconDemoをダブルクリックする。もしくは画面下のAssetsフォルダ内にあるJoyconDemo.csをダブルクリックすることで、c#ファイルを編集できます。
![スクリーンショット 2022-12-18 23.48.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/33ee5799-6486-9e72-5096-1eb79935d6d5.png)

VisualStudioで開くと、このようなコードが書かれています。
静的に定義したJoyconManagerクラスのインスタンスから、接続しているJoy-Conのリストを取得し、Joy-Conの右と左をそれぞれJoyconクラスの変数jに代入して、Joy-Conからの情報を受け取る。のようなコードが書いてあるようです。
JoyconManagerクラスのインスタンスを静的に定義しているので、Hierarchyビューにもあるように、どうやら、JoyconManagerスクリプトを付けたオブジェクトがシーン内に1つ必要そうですね。
今回はこれを参考に進めていきます。
![スクリーンショット 2022-12-18 23.57.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/d35e6576-a605-45a1-e78d-17cb95b3276a.png)

# ポインタデバイスの実装(この記事の肝)
ポインターを実現するためには、
「あるオブジェクトの向きベクトルと、垂直な平面との交点を求めれば良い」です。
先ほどのサンプルの実行でJoy-Conの傾きが取得できそうなことはわかったと思います。
Joy-Conの傾きを反映させたオブジェクトの向きベクトルを利用して実装します。
![IMG_2319.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/ae0f7804-a0b5-3d1d-cc6e-c478f00fad7d.jpeg)

これから、実際にJoyconをポインタとして使うためのコードと設定を書いていきます。

## Sceneの作成
Assets/Sceneフォルダに移動して、右クリック
Create -> Scene をクリックして、新しくSceneを作成します。名前はPointerとしました。
![スクリーンショット 2022-12-19 0.13.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/5935c39a-a996-faaa-9ea9-0c1db402f1bc.png)
Sceneアイコンをダブルクリックして新しく作ったSceneの編集に切り替えます。
![スクリーンショット 2022-12-19 0.13.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/393ea902-4aad-e003-6e37-27b15a177737.png)

## JoyConManagerオブジェクトの作成
Hierarchyビューの何もないところを右クリックして、CreateEmptyを選択します。
オブジェクト名を「JoyConManager」として、空のオブジェクトを作成します。
![スクリーンショット 2022-12-19 0.16.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/0a563f2b-9e0f-6188-7525-3d6e0433ec9c.png)

HierarchyビューにできたJoyConManagerをクリックして、InspectorビューのAddComponentからJoyConManager.csをアタッチします。
![スクリーンショット 2022-12-19 0.20.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/caa70c52-6486-a32e-62c4-344cc386ad38.png)
![スクリーンショット 2022-12-19 0.20.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/eecfe995-3429-e93b-1b90-3b4cf28eaa7c.png)
![スクリーンショット 2022-12-19 0.28.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/b87471c0-71ea-0d0b-5c18-7a3ea92fdee6.png)

## Cube,Plane,Sphereオブジェクトの作成
先ほどと同じようにオブジェクトを作成します。
create -> 3DObject -> Cube　を選択してCube(JoyConCube)を作成します。
同様にPlane(JoyConPlane), Sphere(JoyConPlane)を作成します。
![スクリーンショット 2022-12-19 0.40.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/54bd3115-4abb-9532-d0bc-d9797def003f.png)

## Cubeオブジェクト用のスクリプト JoyConCube.cs を作成
JoyConCubeオブジェクトを選択して、AddComponentをします。
New Scriptをクリックして、名前はJoyConCubeなどにして、スクリプトを作成&アタッチします。
![スクリーンショット 2022-12-21 13.01.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/f8a8d853-441c-c3c6-7ec1-016cf011f9f9.png)
![スクリーンショット 2022-12-21 13.02.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/2668a71c-7ece-decb-52a1-d5b1cb4c577f.png)
![スクリーンショット 2022-12-21 13.03.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/2cd0a580-ab77-b53d-22b0-729a94b01389.png)

JoyconDemo.csを参考にして、以下のような必要最低限のコードを書きます。
傾きの回転がCubeオブジェクトに反映されればOKです。
```csharp:JoyConCube.cs
using UnityEngine;
using System.Collections.Generic;

public class JoyConCube : MonoBehaviour
{
	private List<Joycon> joycons;

	// Values made available via Unity
	public float[] stick;
	public Vector3 gyro;
	public Vector3 accel;
	public int jc_ind = 0;
	public Quaternion orientation;

	void Start()
	{
		gyro = new Vector3(0, 0, 0);
		accel = new Vector3(0, 0, 0);
		joycons = JoyconManager.Instance.j;
		if (joycons.Count < jc_ind + 1)
		{
			Destroy(gameObject);
		}
	}

	// Update is called once per frame
	void Update()
	{
		// make sure the Joycon only gets checked if attached
		if (joycons.Count > 0)
		{
			Joycon j = joycons[jc_ind];

			// Bボタンでセンター位置のリセット
			if (j.GetButtonDown(Joycon.Button.DPAD_DOWN))
			{
				j.Recenter();
			}

			gyro = j.GetGyro();
			accel = j.GetAccel();

			orientation = j.GetVector();
			gameObject.transform.rotation = orientation;
		}
	}
}
```

## JoyConSphere.csの作成
Joy-Conのポインターの先で面との交点となるSphereを動かすJoyConSphere.csを作ります。
先ほどと同様にして、JoyConSphereオブジェクトにJoyConSphere.csを作成&アタッチします。
以下の記事を参考にして、コードを書きます。

https://qiita.com/edo_m18/items/c8808f318f5abfa8af1e

```csharp:JoyConSphere.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class JoyConSphere : MonoBehaviour
{
    public GameObject StartPoint;
    public GameObject Plane;

    // Update is called once per frame
    void Update()
    {
        var n = Plane.transform.up;
        var x = Plane.transform.position;
        var x0 = StartPoint.transform.position;
        var m = StartPoint.transform.forward;
        var h = Vector3.Dot(n, x);

        var intersectPoint = x0 + ((h - Vector3.Dot(n, x0)) / (Vector3.Dot(n, m))) * m;

        transform.position = intersectPoint;
    }
}
```

unityエディタに戻って、JoyConSphereオブジェクトのInspectorビューで、
上のコードでpublicとして定義したStartPointとPlaneを設定します。
StartPointに、JoyConCubeオブジェクトを
Planeに、JoyConPlaneオブジェクトをドラッグ&ドロップしてアタッチします。
![スクリーンショット 2022-12-21 13.36.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/ba0b5a0a-cd09-8dd8-a9ff-d9c26fe52d0b.png)

## オブジェクトの位置の微修正
このまま実行するとJoyConCubeオブジェクトはデフォルトで真上を向いているので、それに合わせて各オブジェクトの位置を変えます。

JoyConSphereオブジェクトのtransformのPositionのYの座標を10にします。
![スクリーンショット 2022-12-21 13.49.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/ca81616a-f97c-fc37-dec8-784eed209d80.png)
JoyConPlaneオブジェクトのtransformのPositionのYの座標を10にします。
transformのRotationのXの座標を180にします。
![スクリーンショット 2022-12-21 13.51.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/86e5db08-ded1-0620-f5b1-db26db8fa4ca.png)

## 実行してみる
ここで一度実行してみましょう。SphereがCubeの向きの延長上に動けばOKです。
![317y7-i3shc.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/67b46f41-9eba-b626-3fb2-dabd277acdb1.gif)
次に、Sphereの座標を利用してUIに組み込みます。

# UI用の画像の作成と、ポインタに同期して動かす
UIとして画面に表示させるようにします。

## UI画像の作成
Hierarchyビューの何もないところで右クリックをして、UI->RawImage を選択してポインタ用の画像を作ります。
![スクリーンショット 2022-12-21 14.12.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/1b3f3a2b-09d1-1b03-1e3f-465d8df9f331.png)

RawImageのRectTransfromからWidth,Heightをそれぞれ25くらいで、色は赤色などにしましょう。
![スクリーンショット 2022-12-21 14.16.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/681fa4eb-85ea-9634-e773-0cda33d3ba69.png)

## ポインタに同期して動かす
RawImageにPointerImage.csなどのスクリプトを作成・アタッチします。以下のようなコードを書きます。
```csharp:PointerImage.cs
using UnityEngine;

public class PointerImage : MonoBehaviour
{
    public Transform SphereTransfrom;
    private RectTransform rectTransform;
    // 任意の倍率、大きくすると小さい動きで大きく動く
    private float scale = 50.0f;

    void Start()
    {
        rectTransform = GetComponent<RectTransform>();
    }

    void Update()
    {
        float x = SphereTransfrom.position.x * scale;
        float y = SphereTransfrom.position.z * -scale;
        rectTransform.anchoredPosition = new Vector3(x, y, 0);
    }
}
```
untiyエディタでPointerImage(Script)のSphereTransform欄に、JoyConSphereオブジェクトをドラッグ&ドロップしてアタッチします。これで全ての工程が終わりました。

# 完成
![ilri2-juse4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/e0031187-cc2f-c33f-7470-cbc36916e34c.gif)
こんな感じで上手く動けばポインタデバイスとしての表示までできました。
感度はPointerImage.csのscaleの値をいじるか、JoyConPlaneのy座標をもっと遠くに設定するかなどすれば変えられます。
あとは、ボタンを押して決定できたりすればゲームに組み込めますね！!

# おまけ
実はこの記事は僕が作ったゲーム「一限侍」の実装解説でもありました。
ゲーム内では、このような感じでUIボタンを配置してポインタで選択・決定をできるようにしました。
![t61c1-718j8.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1865650/25773dda-d77e-bdb2-b7b7-d454e95b28b6.gif)
また時間があれば斬る動作の実装の記事も書こうと思います。

https://twitter.com/amanatsu_toko/status/1587419228092895232

# 参考にした記事
https://tech.mof-mof.co.jp/blog/unity-joycon-introduce/

https://qiita.com/edo_m18/items/c8808f318f5abfa8af1e