シンプルな位置制御チュートリアル
このチュートリアルでは、入門ガイド で紹介した概念をもとに、動的なゲームオブジェクトを使って、Unity シーン内で Inverse3 デバイスのカーソル位置を制御する方法を説明します。
はじめに
Inverse3 デバイスは、力制御(Force Control)と位置制御(Position Control)の2つの制御モードをサポートしています。
前者はカーソルの位置に基づいて力を調整するのに対し、後者はカーソルの位置を直接操作します。
このチュートリアルでは、動的シーンにおける力フィードバックのチュートリアル のシーン設定を活用し、FixedUpdate メソッド内で CursorSetPosition を使用して、デバイスのカーソルが動くゲームオブジェクトに追従するようにします。
シーンの設定
動的シーンにおける力フィードバックのチュートリアル で説明したシーン構成から始めます。このシーンには、Haptic Rig と、MovableObject コンポーネントで制御される Moving Ball ゲームオブジェクトが含まれています。
SpherePositionControl コンポーネントの実装
Moving Ball ゲームオブジェクト上の SphereForceFeedback コンポーネントを、新しい C# スクリプト SpherePositionControl.cs に置き換えます。
SpherePositionControl クラス内で、以下のプロパティを定義します:
public Inverse3 inverse3;
public float minSyncDistance = 0.05f;
private bool _isCursorSynchronized;
- inverse3: インスペクターで設定する、Inverse3 デバイスコンポーネントへの参照です。
- minSyncDistance: カーソルの同期を開始するための最小距離のしきい値です。
- _isCursorSynchronized: カーソルの動きが Moving Ball と同期しているかどうかを示すフラグです。
同期ロジック
Moving Ball への近接度に基づいて、カーソルの同期を開始および停止するメソッドを実装します:
private void StartSynchronizeCursor()
{
var cursorPosition = inverse3.Cursor.transform.position;
GetComponent<MovableObject>().SetTargetPosition(cursorPosition, teleport: true);
_isCursorSynchronized = true;
}
private void StopSynchronizeCursor()
{
_isCursorSynchronized = !inverse3.Release();
}
ここでは
MovableObject.SetTargetPosition(position, teleport:true)を使用します。これにより、MovingBall がカーソル位置にテレポートされます。
Update メソッドと FixedUpdate メソッド
Update メソッドでは、カーソルと Moving Ball の距離に基づいて、カーソルの同期を切り替えます:
private void Update()
{
var distance = Vector3.Distance(inverse3.CursorPosition, transform.position);
if (!_isCursorSynchronized && distance <= minSyncDistance)
{
StartSynchronizeCursor();
}
else if (_isCursorSynchronized && distance > minSyncDistance)
{
StopSynchronizeCursor();
}
}
同期が有効な場合は、FixedUpdate 内でカーソルの位置を Moving Ball の位置に一致するように更新します:
private void FixedUpdate()
{
if (_isCursorSynchronized)
{
inverse3.SetCursorPosition(transform.position);
}
}
位置制御がグラフィックスのレンダリング性能に左右されず、安定したフレームレートで動作するよう、FixedUpdate メソッドを使用してください。
初期化
Awake() 内で、利き手にかかわらず Inverse3 デバイスからアクセスできる位置に Moving Ball が開始されるようにしてください:
private void Awake()
{
inverse3.Ready.AddListener(device =>
{
GetComponent<MovableObject>().SetTargetPosition(((Inverse3)device).WorkspaceCenterLocalPosition, teleport: true);
});
}
ゲームプレイ体験
Inverse3 デバイスをしっかりと固定し、十分なスペースを確保してください。 プレイモードを有効にし、カーソルを Moving Ball に近づけると、Inverse3 が Moving Ball の動きに追従します。 キーボード入力で Moving Ball を移動させ、カーソルがその位置を追跡できることを確認できます。

ソースファイル
このチュートリアルの完全なシーンおよび関連ファイルは、Unity Package Manager 内の Tutorials サンプルにあります。
これには MovableObject スクリプトが含まれます。このスクリプトは、複数の例でキーボード入力によるゲームオブジェクトの移動制御に使用されています。
SpherePositionControl.cs
/*
* Copyright 2024 Haply Robotics Inc. All rights reserved.
*/
using Haply.Inverse.DeviceControllers;
using Haply.Samples.Tutorials.Utils;
using UnityEngine;
namespace Haply.Samples.Tutorials._5_SimplePositionControl
{
/// <summary>
/// Controls the Inverse3 cursor position based on the current position of this GameObject.
/// When the GameObject is within a specified distance from the cursor, it initiates synchronized control,
/// allowing the cursor to follow the GameObject's movements.
/// </summary>
[RequireComponent(typeof(MovableObject))]
public class SpherePositionControl : MonoBehaviour
{
public Inverse3Controller inverse3;
[Tooltip("Minimum distance required to initiate synchronized control between this GameObject and the Inverse3 cursor.")]
[Range(0, 1)]
public float minSyncDistance = 0.05f;
private bool _isCursorSynchronized;
private void Awake()
{
// Ensure inverse3 is set, finding it in the scene if necessary.
inverse3 ??= FindFirstObjectByType<Inverse3Controller>();
// When inverse3 is ready, so the handedness is defined
inverse3.Ready.AddListener((device, args) =>
{
// Teleport the sphere to its workspace center to ensure it can be reached,
// regardless of whether the device is left or right-handed. This ensures the GameObject starts in a
// position that is accessible by the Inverse3 device.
GetComponent<MovableObject>().SetTargetPosition(device.WorkspaceCenterLocalPosition, teleport:true);
});
}
private void OnDisable()
{
// Ensure movement synchronization is disabled when the component is disabled.
StopSynchronizeCursor();
inverse3.Release();
}
private void Update()
{
if (!inverse3.IsReady)
{
return;
}
// Calculate the distance between the Inverse3 position and this object's position.
var distance = Vector3.Distance(inverse3.CursorPosition, transform.position);
// Enable synchronized movement if within the minimum sync distance and not already synced.
if (!_isCursorSynchronized && distance <= minSyncDistance)
{
StartSynchronizeCursor();
}
// Disable synchronized movement if outside the minimum sync distance and currently synced.
else if (_isCursorSynchronized && distance > minSyncDistance)
{
StopSynchronizeCursor();
}
}
private void FixedUpdate()
{
if (_isCursorSynchronized)
{
// If in sync, set the Inverse3 cursor position to this object's position.
inverse3.SetCursorPosition(transform.position);
}
else
{
inverse3.ProbeCursorPosition(true);
}
}
private void StartSynchronizeCursor()
{
// Get the current cursor position.
var cursorPosition = inverse3.Cursor.transform.position;
// Teleport this object to the cursor position to avoid a sudden jump when position control starts.
GetComponent<MovableObject>().SetTargetPosition(cursorPosition, teleport:true);
// Start synchronizing the movement of this object with the cursor.
_isCursorSynchronized = true;
}
private void StopSynchronizeCursor()
{
// Stop synchronizing the movement.
_isCursorSynchronized = !inverse3.Release();
}
}
}