Tutorial zur Arbeitsbereich-Navigation
Dieses Tutorial zeigt, wie man die Skalierung des Cursors mit Hilfe des Griffs anpasst, um dem Benutzer die Kontrolle über die Genauigkeit und Größe des navigierbaren Arbeitsbereichs.
Dieses Beispiel baut auf der Szene auf, die in Arbeitsbereichskalierung und -platzierung, indem es eine griffbasierte Steuerung über den haptischen Arbeitsbereich. Ziel ist es, mit der Grifftaste eine Verschiebung des Arbeitsbereichs auszulösen auszulösen und die Cursorposition zu verwenden, um sie zu ändern, und die zweite, um die Größenänderung des Arbeitsbereichs auszulösen und mit Hilfe der Handle-Rolle zu ändern.
Zu diesem Zweck speichert das Skript WorkspaceOffsetController die letzte bekannte Cursorposition, wenn der Benutzer eine Taste drückt. bekannte Cursorposition und stoppt die Aktualisierung der Avatarposition. Alternativ dazu speichert das Skript WorkspaceScaleController die Skalierung des Arbeitsbereichs und die aktuelle Griffausrichtung, wenn eine Taste gedrückt wird. Wenn die beiden Skripte zusammen verwendet werden, wird durch das Bewegen des Cursors ein neuer Cursor-Offset, während das Drehen des Griffs die Skalierung des Arbeitsbereichs ändert. In dieser Implementierung, entspricht eine Drehung um die Z-Achse im Uhrzeigersinn einer Verkleinerung der Arbeitsbereichsskalierung und vice versa. Wenn der Benutzer die Taste loslässt, ändern sich die beiden Offsets nicht mehr und der Cursor-Avatar bewegt sich noch einmal.
Szene einrichten
Fügen Sie zunächst die Komponente HandleTread zum GameObject Haptic Workspace hinzu. In der Inspektoransicht den Cursor als HandleThread Avatar ein und fügen Sie dann einen Würfel als Kind des Cursors einen Würfel hinzu, um seine Drehung zu visualisieren (siehe Schnellstartanleitung für weitere Details).
Die Komponente WorkspaceOffsetController
Ein neues Skript erstellen WorkspaceOffsetController.cs
und fügen Sie sie dem Haptischer Arbeitsbereich
GameObject. Fügen Sie dann einen Verweis auf den Cursor hinzu, der von der Option HapticThread
private Transform m_cursor;
private void Awake()
{
m_cursor = GetComponent<HapticThread>().avatar;
}
Dann gelten die folgenden Eigenschaften:
private Vector3 m_basePosition;
private Vector3 m_cursorBasePosition;
m_basePosition
speichert die Position der Arbeitsfläche vor jeder Änderung,
während m_cursorBasePosition
speichert die Cursorposition.
Als nächstes fügen Sie die active
gekapseltes Feld, das dem Schaltflächenstatus entspricht und die vorherigen Felder initialisiert
vorherige Felder initialisiert und die Verschiebung des Arbeitsbereichs bei jeder Aktualisierung aktiviert:
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;
Schließlich fügen Sie Update
die, wenn sie aktiv ist, den Offset des Arbeitsbereichs durch Berechnung der Änderung der
der Cursorposition relativ zur Basisposition berechnet. Beachten Sie, dass die Positionsänderung durch die Arbeitsbereichstransformation skaliert werden muss
die Arbeitsbereichstransformation skaliert werden muss, um genau zu sein.
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);
}
}
Um die Griffschaltfläche an das aktive Feld zu binden, wählen Sie die Schaltfläche Haptischer Arbeitsbereich und in der
Inspektor-Panel, fügen Sie (Haptischer Arbeitsbereich)WorkspaceOffsetController.active = false
zu OnButtonUp()
Ereignis und (Haptischer Arbeitsbereich)WorkspaceOffsetController.active = true
zu OnButtonDown()
.
Optional: Binden der Kamerabewegung
Die Änderung des Cursor-Offsets kann intuitiv erfolgen, indem die Kamera mit dem Arbeitsbereich mitbewegt wird. Das hat zur Folge wird der Cursor auch bei einem großen Versatz nicht aus dem Rahmen fallen.
Um dies zu erreichen, fügen Sie der Hauptkamera eine Positionsbeschränkungskomponente hinzu, setzen Sie die * Haptischen Arbeitsbereich* als Quelle ein und drücken Sie die Schaltfläche Aktivieren.
Die Komponente WorkspaceScaleController
Ein neues Skript erstellen WorkspaceScaleController.cs
und fügen Sie sie dem Haptischer Arbeitsbereich GameObject.
Fügen Sie dann einen Verweis auf den Cursor hinzu, der von der Option HandleThread
private Transform m_cursor;
private void Awake()
{
m_cursor = GetComponent<HandleThread>().avatar;
}
Fügen Sie dann die folgenden Einstellungen hinzu:
public float scalingFactor = 0.25f;
public float minimumScale = 1f;
public float maximumScale = 20f;
scalingFactor
wandelt die Drehung des Griffs in Grad in eine numerische Skala um, die an
durch den minimumScale
und die maximumScale
.
Fügen Sie dann die folgenden Felder hinzu:
private float m_baseScale;
private float m_cursorBaseAngle;
m_baseScale
speichert den Maßstab der Arbeitslandschaft vor jeder Änderung, während m_cursorBaseAngle
speichert
die Ausrichtung des Cursors auf der Y-Achse.
Als nächstes fügen Sie GetTotalDegrees
Methode:
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);
}
Der Griff kann sich mehr als einmal um seine eigene Achse drehen, was zu plötzlichen Sprüngen in der Verschiebung beim Überschreiten von 0° führen kann. Diese Funktion prüft den aktuellen Winkel mit dem vorherigen Winkel und erkennt, wann und in welcher Richtung 0° überschritten wurde und garantiert, dass die zurückgegebene Winkelverschiebung genau ist, auch wenn mehr als eine vollständige Drehung stattgefunden hat.
Als nächstes wird die active
gekapseltes Feld, das dem Zustand der Schaltfläche entspricht und die vorherigen
Felder initialisiert und die Skalierung des Arbeitsbereichs bei jeder Aktualisierung aktiviert:
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;
Schließlich fügen Sie Update
die, wenn sie aktiv ist, die Transformationen der Haptischer Arbeitsbereich basierend auf
dem Griffwinkel.
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;
}
}
Knopf für den Bindegriff
Um die Grifftaste an die Schaltfläche zu binden, müssen Sie wie bisher active
wählen Sie das Feld Haptischer Arbeitsbereich und im
fügen Sie im Inspektionsbereich (Haptischer Arbeitsbereich)WorkspaceScaleController.active = false
zu OnButtonUp()
Ereignis und (Haptischer Arbeitsbereich)WorkspaceScaleController.active = true
zu OnButtonDown()
.
Ergebnis
Jetzt können Sie ganz einfach in der Szene navigieren, indem Sie den Arbeitsbereich skalieren und verschieben, indem Sie die Taste Handle Taste.
Quelldateien
Die endgültige Szene und alle zugehörigen Dateien, die in diesem Beispiel verwendet werden, können aus dem Basic Force Feedback and Workspace Control-Beispiel im Paketmanager von Unity importiert werden. Das Unity-Beispiel enthält zusätzliche Verbesserungen der Lebensqualität, die außerhalb des Rahmens dieses Tutorials lagen:
- Eine transparente Blase zur Visualisierung der Größe des Arbeitsbereichs
- Tastaturkurzbefehle
- Drücken Sie
M
Taste bewegt nur den Arbeitsbereich - Drücken Sie
S
Taste lässt den Arbeitsbereich nur skalieren
- Drücken Sie
- Eine Benutzeroberfläche zur Anzeige der aktuellen Offset- und Skalenwerte
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);
}
}