1. Add haptics on hover and select

  • Right/Left Hand Controller を同時に選択して Haptic Impulse Player をアタッチ
    • 以下のように左右それぞれに XRI (Right/Left)/Haptic Device を設定
  • 続いて Simple Haptic Feedback を Interactor にアタッチする
    • 今回は左右の Right/Left Ray Interactor にアタッチ
    • Play Select EnteredPlay Hover Entered を有効化する
      • Duration はバイブレーションの継続時間

Haptic Impulse Player

Player が Haptic を再生する

  • Audio と同じで、この Player が Output に対して Haptic を再生している
  • Inspector からは見えないがコードとしては以下の感じになっている。
[SerializeField]
XRInputHapticImpulseProvider m_HapticOutput = new XRInputHapticImpulseProvider("Haptic");
 
public bool SendHapticImpulse(float amplitude, float duration, float frequency)  
{
    return m_HapticOutput.GetChannelGroup()?.GetChannel()?.SendHapticImpulse(amplitude * m_AmplitudeMultiplier, duration, frequency) ?? false;  
}

Feedback は XR Interactable 用

  • Simple Haptic Feedbackは XR Interactable のイベントに自動でハプティクスを紐付けるコンポーネント。
  • 以下のように、InteractorHaptic Impulse Player を受け取る
  • 内部では、受け取った Interactor の Select/Hover の Entered/Exited に自身のイベントを紐づけるような Observer/Subscriber の形になっている
  • よって、Interactor が XR Interactable に対して Hover / Select のイベントが発火すると、Simple Haptic Feedback にもその通知が来て、Player の再生が実行される。

出力は Input System 経由で行う

  • 少し不思議な感じがするが、Input System の Input Action の一つとして出力デバイスが設定される
  • 先ほど出てきた XRInputHapticImpulseProvider がそれに該当する。
  • 呼び出しとしては以下のような流れ
SendHapticImpulse() 呼び出し
    │
    ▼
InputAction からデバイスを解決
    │
    ▼
IHapticDevice.SendHapticImpulse()  ← Input System のインターフェース
    │
    ▼
OpenXR Runtime / プラットフォームドライバー
    │
    ▼
物理コントローラー
Haptic Impulse Player
    │
    └─ Haptic Output(XRHapticImpulseProvider型)
            │
            └─ XRInputHapticImpulseProvider(ScriptableObjectアセット)
                        │
                        └─ Haptic Action
                                └─ Input Action Asset
                                        └─ XRI Right Hand/Haptic Device 等
  • このチュートリアルでは、Input System の Action (Input Action Reference) として実装されているが、以下のように Create メニューからも作成可能となっている
    • その場合は、Object として参照を渡す
  • Input System でも同様に行えるのは、Input System がそもそも「入力を受け取るもの」ではなく、「デバイスとの双方向チャネル」であるから
    • Input System 内部で、 IHapticDevice がハプティックフィードバック対応デバイスとして認識されており、内部でデバイスを解決している
    • 便利!

2. Add audio on hover or select

  • Left/Right Hand ControllerSimple Audio Feedback を追加する
    • Interactor SourceRight/Left Ray Interactor をセット
    • Play Select EnteredSelect Entered Clip に Audio フォルダの音源をセット
      • Play Hover Entered にセットしてもOK

3. Add 3D audio from the fireplace

4. Add a reverb zone

5. Experiment with spatializer plugins

7. Extension activities

1. Add realistic bouncing sounds

ボールの跳ねる音を追加。ここでは衝突する素材に応じて音を変えるのではなく、速度に応じて音の大きさを変えるまで。

[RequireComponent(typeof(AudioSource)), RequireComponent(typeof(SphereCollider)), RequireComponent(typeof(Rigidbody))]
public class BallBounce : MonoBehaviour
{
    [SerializeField]
    private AudioClip audioClip;
    
    private float minVelocity = 0.5f;  // この速度以下は音を鳴らさない
    private float maxVelocity = 5f;    // この速度で最大音量
    
    private AudioSource audioSource;
    private Rigidbody rb;
    
    private void Awake()
    {
        audioSource = GetComponent<AudioSource>();
        rb = GetComponent<Rigidbody>();
    }
 
    private void OnCollisionEnter(Collision other)
    {
        var volumeScale = Mathf.InverseLerp(minVelocity, maxVelocity, rb.linearVelocity.magnitude);
        audioSource.PlayOneShot(audioClip, volumeScale);
    }
}
  • magnitude の範囲をどのように 0-1 に収めるかはいくつか方法があると思いますが、ここでは単純な方法で。
    • UE5 だと MapRange みたいのを探してしまうので、InverseLerp がなかなか出てこなかった。。

2. Add a speaker

レコードプレイヤーを置いて、そこから音を鳴らし、レコード盤を回す。

  • ほとんど、暖炉のときの設定を真似ています。値は適当であまり妥当性はありません。

レコード盤を回すのは特に工夫もなく以下のようにしました。とりあえずそれっぽく回ります。

public class RecordPlayer : MonoBehaviour
{
    [SerializeField] private GameObject record;
    
    // Update is called once per frame
    private void Update()
    {
        record.transform.Rotate(new Vector3(0, 30 * Time.deltaTime, 0));
    }
}