Introduction

前回の記事 で作ったサンプルを改造して、レイでヒットしたキューブを掴む処理を追加してみる。

対象は

  • Unity
    • 2021.3.5f1
  • Oculus Integration
    • 41.0

サンプルソースは、GitHub に置きました。

How to implement?

調べると

  • OVRGrabber
  • OVRGrabbable

ってのを使えばいいらしいが、よくわからんので 0 から実装する。

参考にさせていただいたのは下記。

ただし、この記事は

  • Oculus Go をターゲットにし、Go のコントローラ特性を利用してオブジェクトを操作している
  • 放り投げる動作をサポート

している。

今回はシンプルに

  • オブジェクトをレイの挙動に従って動かすこと
  • 慣性とか重力加速度は考えない
    • オブジェクトは浮いているものとする

という条件で実装する。

キューブ

まずは動かす物体としてキューブを追加する。

Cube

Cube

Inspector をみると下記の属性・コンポーネントが存在する。

  • Transform
    • ゲームオブジェクトの位置・回転・スケール(拡大・縮小)を制御
  • Mesh Filter
    • メッシュを参照する。メッシュレンダラーは、メッシュフィルターが参照するメッシュをレンダリングする、らしいがよくわからない
  • Mesh Renderer
    • メッシュのレンダリング。そのまま?
  • Box Collider
    • 立方体をした基本的なコライダー
      • Collider コンポーネントは、物理衝突のためのオブジェクト形状を定義するもの。要するに衝突や接触判定をサポートするという理解
        • Transform と同じように、X、Y、Z があるから、コライダーの範囲と表示上のサイズを別にできるきがする

なので、このコライダーを意識しながら、「掴まれる」機能を追加していく。

はずだが、今回は意識しなくてもいい。
コライダーを無効にしたり、スケールを変更しない限りにおいてだが。
コライダーを無効にすると、後述の Physics.Raycast でヒットしなくなるので。

あと、色を付けた。
Materials フォルダを作成し、Material を追加。
Albedo を赤に変更後、Cube にドラッグアンドドロップで完成。

Cube

レイで掴む

レイを制御するスクリプト LaserPointerBehavior を改造する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
using UnityEngine;

public class LaserPointerBehaviour : MonoBehaviour
{

#region SerializeFields

[SerializeField]
private Transform _HandAnchor;

[SerializeField]
private OVRInput.Controller _TargetController;

[SerializeField]
private OVRInput.Button _GrabTriggerButton;

[SerializeField]
private float _MaxDistance = 100.0f;

[SerializeField]
private uint _Responsiveness = 1;

[SerializeField]
private LineRenderer _LaserPointerRenderer;

[SerializeField]
private Transform _TrackingSpace;

#endregion

#region Fields

private bool _IsGrabbedObject = false;

private GameObject _GrabbedObject = null;

private Vector3? _PreviousControllerPosition;

#endregion

#region Methods

#region Magic Methods

void Update()
{
var connected = OVRInput.IsControllerConnected(this._TargetController);
if (!connected)
return;

// Cast ray from controller
var pointerRay = new Ray(this._HandAnchor.position, this._HandAnchor.forward);

// 0 is start point
this._LaserPointerRenderer.SetPosition(0, pointerRay.origin);

var hit = Physics.Raycast(pointerRay, out var hitInfo, this._MaxDistance);
if (hit)
{
// Set end point to hit position if ray intersects with a collider
this._LaserPointerRenderer.SetPosition(1, hitInfo.point);

// Grab object
var grabTriggerOn = OVRInput.Get(this._GrabTriggerButton);
if (grabTriggerOn)
{
var hitObj = hitInfo.collider.gameObject;

// Check whether that grabbed object is ground or not
if (hitObj.name != "Plane")
{
this._GrabbedObject = hitObj;
this._IsGrabbedObject = true;
}

// Avoid to check _GrabbedObject is null or not
// https://github.com/JetBrains/resharper-unity/wiki/Avoid-null-comparisons-against-UnityEngine.Object-subclasses
if (this._IsGrabbedObject)
{
// Move object
var controllerPosition = OVRInput.GetLocalControllerPosition(this._TargetController);
controllerPosition = this._TrackingSpace.TransformPoint(controllerPosition);
if (this._PreviousControllerPosition.HasValue)
{
// Get movement from previous controller position
var diff = controllerPosition - this._PreviousControllerPosition.Value;
this._GrabbedObject.transform.position += (diff * this._Responsiveness);
}

this._PreviousControllerPosition = controllerPosition;
}
}
}
else
{
// Extend MaxDistance in the direction if ray does not intersect with a collider
this._LaserPointerRenderer.SetPosition(1, pointerRay.origin + pointerRay.direction * this._MaxDistance);
}

// Check GetUp rather than grabTriggerOn is false!!
var grabTriggerOff = OVRInput.GetUp(this._GrabTriggerButton);
if (grabTriggerOff)
{
this._GrabbedObject = null;
this._PreviousControllerPosition = null;
}
}

#endregion

#endregion

}
  1. RayCast でレイがコライダーにヒットしたかどうかを調べる
  2. ヒットしたコライダーを持つゲームオブジェクトを取得
  3. トリガーがオンかどうか調べる
    1 . 直前のフレームのコントローラの座標と現フレームのコントローラの差を移動ベクトルとして計算
    1. ゲームオブジェクトに移動ベクトルを加算
  4. トリガーがオフかどうか調べる
    1. 掴んでいるゲームオブジェクトの情報を破棄

特にコントローラの位置を使って移動量を計算することで、奥行方向にもキューブを動かせるようになります。
最初は hitInfo.point を使っていたのですが、これだとゲームオブジェクトの表面の座標になるので、前後のフレームを比べた時に奥行方向がとれないので没になりました。

_Responsiveness はキューブを移動する際の応答性。
値を大きくすると、コントローラの動きに対してキューブが大きく移動します。

Result

Build して実行すると、コントローラーとレーザーが表示されるようになった。

Cube

Source Code

https://github.com/takuya-takeuchi/Demo/tree/master/Unity/Oculus/02_GrabCube