【Unity(C#)】Oculus Integrationで手のマテリアル切り替え

##デモ

さっそくデモです。
![OQGetColor.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/d6f14979-b4d6-fea6-4b81-4cd93c876efa.gif)

オブジェクトに触れると手のマテリアルが触れたオブジェクトと同じものに変わります。

この情報だけでコードが頭の中に完成してしまう人も少なくないかと思いますが、
なぜか、望んだ結果が得られず原因特定に時間を要したのでメモしておきます。

##望んだ結果が得られなかったコード

```C#:触れられるオブジェクト側にアタッチ
public class HandTriggerEnterEvent : MonoBehaviour
{
    private MeshRenderer _meshRenderer;
    
    private void Start()
    { 
        _mshRenderer = this.gameObject.GetComponent<MeshRenderer>();
    }
    
    private void OnTriggerEnter(Collider other)
    {
        //この書き方については今回の本題ではないのでスルー安定
        SkinnedMeshRenderer skinnedMeshRenderer = other.gameObject.transform.parent.parent.parent.GetComponent<SkinnedMeshRenderer>();
        
        if (skinnedMeshRenderer != null)
        {
            Debug.Log("Get Color");
            
            //マテリアルを手に適用
            skinnedMeshRenderer.material = _meshRenderer.material;
        }
        else
        {
            Debug.Log("skinnedMeshRenderer is Null");
        }
    }
}
```

一見すると触れられたらマテリアルを手に反映させる
ただそれだけのコードで問題ないように思えますが、
マテリアルは変化しません。

##原因

原因は`OVRHandPrefab`にアタッチされた`OVRMeshRenderer`のUpdate内のコードにありました。

```C#
private void Update()
{
    IsDataValid = false;
    IsDataHighConfidence = false;
    ShouldUseSystemGestureMaterial = false;

    if (IsInitialized)
    {
        bool shouldRender = false;

        if (_dataProvider != null)
        {
            var data = _dataProvider.GetMeshRendererData();

            IsDataValid = data.IsDataValid;
            IsDataHighConfidence = data.IsDataHighConfidence;
            ShouldUseSystemGestureMaterial = data.ShouldUseSystemGestureMaterial;

            shouldRender = data.IsDataValid && data.IsDataHighConfidence;
        }

        if (_confidenceBehavior == ConfidenceBehavior.ToggleRenderer)
        {
            if (_skinnedMeshRenderer != null && _skinnedMeshRenderer.enabled != shouldRender)
            {
                _skinnedMeshRenderer.enabled = shouldRender;
            }
        }

        if (_systemGestureBehavior == SystemGestureBehavior.SwapMaterial)
        {
            if (_skinnedMeshRenderer != null)
            {
                if (ShouldUseSystemGestureMaterial && _systemGestureMaterial != null && _skinnedMeshRenderer.sharedMaterial != _systemGestureMaterial)
                {
                    _skinnedMeshRenderer.sharedMaterial = _systemGestureMaterial;
                }
                else if (!ShouldUseSystemGestureMaterial && _originalMaterial != null && _skinnedMeshRenderer.sharedMaterial != _originalMaterial)
                {
                    _skinnedMeshRenderer.sharedMaterial = _originalMaterial;
                }
            }
        }
    }
#if UNITY_EDITOR
    else
    {
        if (OVRInput.IsControllerConnected(OVRInput.Controller.Hands))
        {
            Initialize();
        }
    }
#endif
}

```

`SystemGestureBehavior.SwapMaterial`というステートに設定している場合、
マテリアルが変更されてもすぐに`_originalMaterial`に戻されてしまいます。

なので、Inspectorでデフォルト設定されている`System Gesture Behavior`の`SwapMaterial`を`None`に変更すればOKでした。

<img width="324" alt="SystemGestureBehavior.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/3c44202e-a7c7-f1c9-f725-903c434eb4d7.png">

余談ですが、`Confidence Behavior`は
デフォルトで`Toggle Renderer`に設定されているパラメーターで、
手のトラッキング状況に応じてRendererをオンオフして切り替えてくれます。

##SystemGestureBehavior
`System Gesture Behavior`は、OculusQuestのハンドトラッキング機能に
デフォルトで備わっている
"ホーム画面に戻るジェスチャーの予備動作(手のひらを自身に向ける)"に関わる機能です。

`SwapMaterial`を設定している場合、
"ホーム画面に戻るジェスチャーの予備動作(手のひらを自身に向ける)"を行うと
手のマテリアルが`System Gesture Material`に設定したマテリアルに変更されます。
![OQBackHomeGesture.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/c3fb1e76-0873-69a4-0dd5-952265d01c84.gif)

もし、この`System Gesture Behavior`の`SwapMaterial`も利用しつつ
他のマテリアルとの入れ替えも実装したいとなった場合、
下記のように少しコードを書き換える必要があります。

```C#:OVRMeshRendererのUpdate内のコード
if (_systemGestureBehavior == SystemGestureBehavior.SwapMaterial)
{
    if (_skinnedMeshRenderer != null)
    {
        if (ShouldUseSystemGestureMaterial && _systemGestureMaterial != null && _skinnedMeshRenderer.sharedMaterial != _systemGestureMaterial)
        {
            Debug.Log("Get Current Material");
            _originalMaterial = GetComponent<SkinnedMeshRenderer>().sharedMaterial;
            _skinnedMeshRenderer.sharedMaterial = _systemGestureMaterial;
        }
        else if (!ShouldUseSystemGestureMaterial && _originalMaterial != null && _skinnedMeshRenderer.sharedMaterial == _systemGestureMaterial)
        {
            _skinnedMeshRenderer.sharedMaterial = _originalMaterial;
        }
    }
}
```

##参考リンク
[SkinnedMeshRenderer material from OVRHand prefab is overwritten every frame](https://forums.oculusvr.com/developer/discussion/87797/skinnedmeshrenderer-material-from-ovrhand-prefab-is-overwritten-every-frame)