Unityでの開発において、WebGL用のマウス入力イベントの扱い方

前回は、Unityで回転可能なオブジェクトを作成する方法を紹介しました。オブジェクトは任意の角度からマウスでクリックしてドラッグすることで回転されます。前回の記事のリンクは下記になります。
https://qiita.com/NestVisual/items/9a97903eb9ea8918bcbe

![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/626158/a5647a51-f1e6-3f27-a279-50c068287272.png)

今回は、WebGL用のマウス入力イベントの扱い方について説明させて頂きます。
まずは問題となる部分から触れていきます。今回作成した機能はエディタでも、PCビルドでも問題なく動作していましたが、プロジェクトのターゲットプラットフォームをWebGL用のアプリケーションでビルドした際に問題が発生しました。ウェブブラウザには、マウスカーソルでできることに制限があり、システムターゲットのアプリケーションにはない制限があります。その一つが、コードによるカーソルの移動や位置合わせができないことです。

このアプリケーションでは、カーソルは非表示になっており、2つの機能があります。デフォルトでは、マウスはファーストパーソン・シューティングゲームのようにカメラを回転させます。 しかし、カーソルがオブジェクトをクリックしてドラッグすると、カメラは回転せず、代わりにオブジェクトが回転します。 エディタでは、マウスの位置は常にアプリケーションウィンドウの中央にある十字の位置になるので、これは問題なく動作します。これは以下のFirstPersonControllerにあるコードのおかげです。
Cursor.lockState = CursorLockMode.Locked;
また、'Esc'キーを押してアプリケーションのフォーカスを解除した後、再フォーカスすると、下記のようなことが起こります。

WebGL ビルドを実行すると、カーソルは少なくとも所定の位置にロックされますが、アプリケーション中央に再配置されることはありません。カーソル自体が見えないので、これは残念なことです。十字線と同じ位置には対応していない可能性が高いです。

回避方法としまして、カーソルの位置に関係なく、アプリケーション中央からレイキャストができる事が分かりました。但しこれは簡単そうに聞こえますが、この動作を直接変更はできません。独自のカーソルコントローラクラスを設定する必要があります。そこで、このクラスをUnityのMonoBehaviourマウスイベントコールバックメソッドの代わりに使います。まず、必要なマウスイベントのためのメソッドを定義したシンプルなインターフェースを作成します。 

```C#:IMouseInteractive.cs
interface IMouseInteractive
{
   void OnCursorDown();
   void OnCursorUp();
   void OnCursorDrag();
}
```

次に、MonoBehaviour に加えて、RotatableObject.cs にこのインターフェイスを実装させ、それを継承したメソッドを実装する必要があります。

```C#:RotatableObject.cs
public class RotatableObject : MonoBehaviour, IMouseInteractive
```

IMouseInteractiveメソッドの実装は、MonoBehaviourのマウスコールバックを使う場合と同じなので、メソッド名の「Mouse」の部分を「Cursor」に変更するだけです。

```C#:RotatableObject.cs
public void OnCursorDown()
{
    if (characterController == null)    return;
    characterController.IsLookingWithMouse = false;
}

public void OnCursorUp()
{
    if (characterController == null)    return;
    characterController.IsLookingWithMouse = true;
}
  
public void OnCursorDrag()
{
    if (characterController == null || Camera.main == null)   return;

    float rotX = Input.GetAxis("Mouse X") * userRotationSpeed * Mathf.Deg2Rad;
    float rotY = Input.GetAxis("Mouse Y") * userRotationSpeed * Mathf.Deg2Rad;

    transform.Rotate(Camera.main.transform.up, -rotX, Space.World);
    transform.Rotate(Camera.main.transform.right, rotY, Space.World);
}
```


次のステップでは、カーソルイベントを適切に発生させるCursorControllerクラスを作成します。入力を扱っているので、Updateメソッドで以下のメソッドを呼び出します。最初に CalculateCursorData()が呼ばれます。現在のカーソルの状態を記述するのに必要な値を計算します。そして、これらの値を使用して、マウスの機能が実行されているかどうかを判断します。各マウス機能は別のメソッドに構造化されています。

```C#:CursorController.cs
using UnityEngine;

public class CursorController : MonoBehaviour
{
   private IMouseInteractive hoveredObject;
   private IMouseInteractive selectedObject;
   private bool isHovering;

   private void Update()
   {
       if (Camera.main == null)     return;

       CalculateCursorData();
       CheckMouseDown();
       CheckLeftMouseUp();
       CheckMouseDrag();
   }
  
   private void CalculateCursorData()
   {
       RaycastHit hitInfo = new RaycastHit();
       Ray ray = Camera.main.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f));
       bool rayHitSomething = Physics.Raycast(ray, out hitInfo);
      
       bool hasRigidBodyAndIsInteractive    = (hitInfo.rigidbody != null) && (hitInfo.rigidbody.GetComponent<IMouseInteractive>() != null);
       bool hasColliderAndIsInteractive     = (hitInfo.collider != null) && (hitInfo.collider.GetComponent<IMouseInteractive>() != null);
       isHovering = (rayHitSomething) && (hasRigidBodyAndIsInteractive || hasColliderAndIsInteractive);
      
       if (isHovering)
       {
           if (hitInfo.rigidbody != null)     hoveredObject = hitInfo.collider.attachedRigidbody.GetComponent<IMouseInteractive>();
           else if (hitInfo.collider != null) hoveredObject = hitInfo.collider.GetComponent<IMouseInteractive>();
           else                               hoveredObject = null;
       }
       else
       {
           hoveredObject = null;
       }
   }
  
   private void CheckMouseDown()
   {
       if (!isHovering || !Input.GetMouseButtonDown(0)) return;
       selectedObject = hoveredObject;
       hoveredObject.OnCursorDown();
   }

   private void CheckLeftMouseUp()
   {
       if (selectedObject == null || !Input.GetMouseButtonUp(0)) return;
       selectedObject.OnCursorUp();
       selectedObject = null;
   }
  
   private void CheckMouseDrag()
   {
       if (selectedObject != null && Input.GetMouseButton(0))     selectedObject.OnCursorDrag();
   }
}
```


以上になります！これで今までと同じような機能を持ちつつ、WebGLアプリケーションにも対応する事が出来ました。カーソルコントローラを拡張して「CheckMouseEnter」や「CheckMouseExit」などの機能を追加することもできますが、今回は最低限の部分を実装しました。