ワークスペース ナビゲーション チュートリアル
このチュートリアルでは、ハンドルを使ってカーソルのスケーリングを調整する方法を紹介します。 このチュートリアルでは、ハンドルを使ってカーソルのスケーリングを調整する方法を説明します。
この例では この例では、ワークスペースのスケーリングと配置で作成されたシーンを基に、ハンドルベースで を提供することによって、Haptic Workspace上で作成されたシーンを構築します。目標は、ハンドルボタンを使ってワークスペースのオフセット をトリガーし、それを変更するためにカーソル位置を使用することです。 ハンドルロールを使用して変更します。
このため、ユーザーがボタンを押すと、WorkspaceOffsetControllerスクリプトは最後に認識したカーソル位置を保存し、アバターの位置の更新を停止します。 を保存し、アバターの位置の更新を停止します。あるいは、WorkspaceScaleControllerは、ボタンが押されたときにワークスペースのスケールと現在のハンドルの向きを保存します。 が押されたときに、ワークスペースのスケールと現在のハンドルの向きを保存します。2つのスクリプトを一緒に使用した結果、カーソルを動かすと新しいカーソルオフセットが作成されます。 カーソルのオフセットが作成され、ハンドルを回転させるとワークスペースのスケーリングが変更されます。この実装では Z軸周りのCCW回転は、ワークスペースのスケーリングを縮小することに対応し、逆も同様です。ユーザーがボタンを離すと ユーザーがボタンを離すと、2つのオフセットの変化が止まり、カーソルアバターがもう一度動く もう一度移動します。
シーン設定
まず、Haptic WorkspaceGameObjectにHandleTreadコンポーネントを追加します。インスペクタビューで インスペクタビューで、Cursorを HandleThreadAvatarに設定し、カーソルの子として立方体を追加します。 カーソルの回転を視覚化するために、カーソルの子として立方体を追加します(詳細はクイックスタートガイドを参照)。
WorkspaceOffsetController コンポーネント
新しいスクリプトを作成する WorkspaceOffsetController.cs
に追加する。 ハプティック・ワークスペース
GameObject。カーソルへの参照を追加する。 ハプティック・スレッド
private Transform m_cursor;
private void Awake()
{
m_cursor = GetComponent<HapticThread>().avatar;
}
すると、次のような性質がある:
private Vector3 m_basePosition;
private Vector3 m_cursorBasePosition;
m_basePosition
は修正前のワークスケープの位置を保存します、
一方 m_cursorBasePosition
はカーソル位置を保存する。
次に active
ボタンの状態に対応するカプセル化されたフィールド。
前のフィールドを初期化し、各更新時にワークスペースのオフセットを有効にします:
public bool active
{
get => m_active;
set
{
if (value)
{
m_basePosition = transform.localPosition;
m_cursorBasePosition = m_cursor.localPosition;
}
m_active = value;
}
}
private bool m_active;
最後に Update
これは、アクティブな場合、基準位置に対するカーソル位置の変化を計算することによって、ワークスペースのオフセットを変更する。
を計算することによって、ワークスペースのオフセットを変更します。正確さを保つために、位置の変化は
によってスケーリングされる必要があることに注意してください。
private void Update()
{
if (active)
{
// Move cursor offset relative to cursor position
transform.position = m_basePosition - Vector3.Scale(m_cursor.localPosition - m_cursorBasePosition, transform.lossyScale);
}
}
ハンドルボタンをアクティブなフィールドにバインドするには ハプティック・ワークスペース そして
を追加します。ハプティック・ワークスペース)WorkspaceOffsetController.active = false
への OnButtonUp()
イベントと(ハプティック・ワークスペース)WorkspaceOffsetController.active = true
への OnButtonDown()
.
オプション:カメラの動きをバインドする
カーソルオフセットの変更は、ワークスペースと一緒にカメラを動かすことで直感的に行うことができる。その結果 その結果、オフセットが大きくても、カーソルがフレームの外に移動することはありません。
これを実現するには、メインカメラに 位置拘束コンポーネントを追加し、*を設定します。 Haptic Workspace* をソースとして設定し、Activateボタンを押します。
WorkspaceScaleController コンポーネント
新しいスクリプトを作成する WorkspaceScaleController.cs
に追加する。 ハプティック・ワークスペース GameObject。
カーソルへの参照を追加する。 ハンドルスレッド
private Transform m_cursor;
private void Awake()
{
m_cursor = GetComponent<HandleThread>().avatar;
}
次に以下の設定を追加する:
public float scalingFactor = 0.25f;
public float minimumScale = 1f;
public float maximumScale = 20f;
scalingFactor
ハンドルの回転を度単位で数値に変換する。
によって minimumScale
そして maximumScale
.
次に以下のフィールドを追加する:
private float m_baseScale;
private float m_cursorBaseAngle;
m_baseScale
は変更前のワークスケープスケールを保存します。 m_cursorBaseAngle
保存
Y軸上のカーソルの向きを保存する。
次に、以下を加える。 GetTotalDegrees
メソッドを使用する:
private float m_cursorPreviousAngle;
private int m_rotationCount;
private float GetTotalDegrees(float currentAngle, float baseAngle)
{
if (currentAngle - m_cursorPreviousAngle > 330)
m_rotationCount--;
else if (m_cursorPreviousAngle - currentAngle > 330)
m_rotationCount++;
m_cursorPreviousAngle = currentAngle;
return 360f * m_rotationCount + (currentAngle - baseAngle);
}
ハンドルは自身の軸を中心に2回以上回転することがあり、0°を超えたときに変位が急激に跳ね上がることがある。 変位の突然のジャンプにつながります。この関数は、現在の角度と直前の角度をチェックし 0°がいつどの方向に横切られたかを検出し、返される角度オフセットが正確であることを保証します。 が正確であることを保証します。
次に active
カプセル化されたフィールド。
フィールドを初期化し、各更新時にワークスペースのスケーリングを有効にします:
public bool active
{
get => m_active;
set
{
if (value)
{
m_rotationCount = 0;
m_baseScale = transform.localScale.z;
m_cursorPreviousAngle = m_cursorBaseAngle = m_cursor.localEulerAngles.z;
}
m_active = value;
}
}
private bool m_active;
最後に Update
これは、アクティブであれば、以下のトランスフォームを修正する。 ハプティック・ワークスペース ハンドルの角度
ハンドルの角度
private void Update()
{
if (active)
{
// Calculate scale relative to cursor roll on Z-axis rotation
var totalDegrees = GetTotalDegrees(m_cursor.localEulerAngles.z, m_cursorBaseAngle);
var scale = m_baseScale - totalDegrees * scalingFactor / 100f;
// Limit between minimumScale and maximumScale
scale = Mathf.Clamp(scale, minimumScale, maximumScale);
// Set cursor offset scale (same on each axis)
transform.localScale = Vector3.one * scale;
// Invert cursor scale to keep its original size
m_cursor.localScale = Vector3.one / scale;
}
}
バインディングハンドルボタン
前回同様、ハンドルボタンを active
フィールドで ハプティック・ワークスペース そして
を追加します。ハプティック・ワークスペース)WorkspaceScaleController.active = false
への OnButtonUp()
イベントと(ハプティック・ワークスペース)WorkspaceScaleController.active = true
への OnButtonDown()
.
結果
これで、ハンドル ボタンを押すことで簡単に移動できるようになりました。
ソースファイル
このサンプルで使用した最終シーンと関連ファイルは、Unity のパッケージマネージャにあるBasic Force Feedback and Workspace Controlサンプルからインポートできます。 Unity サンプルには、このチュートリアルの範囲外であった追加のクオリティオブライフの改善が含まれています:
- ワークスペースのサイズを視覚化する透明なバブル
- キーボードショートカット
- プレス
M
キーでワークスペースのみ移動 - プレス
S
キーでワークスペースの拡大縮小のみ
- プレス
- 現在のオフセット値とスケール値を表示するUI
WorkspaceOffsetController.cs
using Haply.HardwareAPI.Unity;
using UnityEngine;
public class WorkspaceOffsetController : MonoBehaviour
{
// Movable cursor with position controlled by Haptic Thread
private Transform m_cursor;
// Saved workspace and cursor values at transformation beginning
private Vector3 m_basePosition;
private Vector3 m_cursorBasePosition;
// If true, the workspace offset is set relatively to the cursor position on each Update() loop
public bool active
{
get => m_active;
set
{
if (value)
{
m_basePosition = transform.localPosition;
m_cursorBasePosition = m_cursor.localPosition;
}
m_active = value;
}
}
private bool m_active;
private void Awake()
{
// Get the moving cursor from the HapticThread
m_cursor = GetComponent<HapticThread>().avatar;
}
private void Update()
{
if (active)
{
// Update the workspace offset relative to cursor position
transform.position = m_basePosition - Vector3.Scale(m_cursor.localPosition - m_cursorBasePosition, transform.lossyScale);
}
}
}
WorkspaceScaleController.cs
using System;
using Haply.HardwareAPI.Unity;
using UnityEngine;
public class WorkspaceScaleController : MonoBehaviour
{
// Movable cursor with rotation controlled by Handle Thread
private Transform m_cursor;
[Tooltip("Sensitivity of scaling on handle rotation")]
public float scalingFactor = 3f;
public float minimumScale = 1f;
public float maximumScale = 5f;
// Saved workspace and cursor values at transformation beginning
private float m_baseScale;
private float m_cursorBaseAngle;
private float m_cursorPreviousAngle;
private int m_rotationCount;
// If enabled the workspace will be uniformly scaled relatively to cursor roll (Z-axis rotation) on each Update() loop
public bool active
{
get => m_active;
set
{
if (value)
{
m_rotationCount = 0;
m_baseScale = transform.localScale.z;
m_cursorPreviousAngle = m_cursorBaseAngle = m_cursor.localEulerAngles.z;
}
m_active = value;
}
}
private bool m_active;
private void Awake()
{
// Get the rotating cursor from the HandleThread
m_cursor = GetComponent<HandleThread>().avatar;
}
private void Update()
{
if (active)
{
// Calculate scale relative to cursor roll on Z-axis rotation
var totalDegrees = GetTotalDegrees(m_cursor.localEulerAngles.z, m_cursorBaseAngle);
var scale = m_baseScale - totalDegrees * scalingFactor / 100f;
// Limit between minimumScale and maximumScale
scale = Mathf.Clamp(scale, minimumScale, maximumScale);
// Set cursor offset scale (same on each axis)
transform.localScale = Vector3.one * scale;
// Invert cursor scale to keep its original size
m_cursor.localScale = Vector3.one / scale;
}
}
// Return the total degrees between baseAngle and currentAngle over the 360 degrees limitation
private float GetTotalDegrees(float currentAngle, float baseAngle)
{
if (currentAngle - m_cursorPreviousAngle > 330)
m_rotationCount--;
else if (m_cursorPreviousAngle - currentAngle > 330)
m_rotationCount++;
m_cursorPreviousAngle = currentAngle;
return 360f * m_rotationCount + (currentAngle - baseAngle);
}
}