워크스페이스 탐색 튜토리얼
이 자습서에서는 핸들을 사용하여 커서 배율을 조정하는 방법을 보여 주며, 사용자가 탐색 가능한 작업 영역의 정밀도와 크기를 탐색 가능한 작업 영역 구축의 정밀도와 크기를 사용자가 제어할 수 있도록 합니다.
이 예제는 워크스페이스 스케일링 및 배치에서 생성한 씬을 기반으로 워크스페이스 스케일링 및 배치에서 생성된 씬을 기반으로 합니다. 을 제공하여 햅틱 워크스페이스에 대한 핸들 기반 제어를 제공합니다. 목표는 핸들 버튼을 사용하여 워크스페이스 오프셋을 트리거하고 을 트리거하고 커서 위치를 사용하여 변경하고, 두 번째 버튼으로 작업 공간 크기 조정을 트리거하고 핸들 롤을 사용하여 변경하는 것입니다.
이를 위해 사용자가 버튼을 누르면 WorkspaceOffsetController 스크립트는 마지막으로 알려진 커서 위치를 저장하고 마지막으로 알려진 커서 위치를 저장하고 아바타 위치 업데이트를 중지합니다. 또는 버튼을 누르면 WorkspaceScaleController가 워크스페이스 스케일과 현재 핸들 방향을 저장합니다. 버튼을 눌렀을 때 저장합니다. 두 스크립트를 함께 사용하면 커서를 이동하면 커서를 회전하는 동안 새로운 커서 오프셋이 생성되고 핸들을 회전하면 작업 영역 배율이 변경됩니다. 이 구현에서는 Z축을 중심으로 CCW 회전은 작업 영역 배율을 줄이는 것에 해당하며 그 반대의 경우도 마찬가지입니다. 사용자가 사용자가 버튼을 놓으면 두 오프셋의 변경이 중지되고 커서 아바타가 다시 한 번 한 번 더 이동합니다.
장면 설정
먼저 햅틱 워크스페이스 게임 오브젝트에 핸들 트레드 컴포넌트를 추가합니다. 인스펙터 뷰에서 인스펙터 뷰에서 커서를 핸들 스레드 아바타로 설정한 다음 커서의 자식으로 큐브를 추가합니다. 커서의 자식으로 큐브를 추가하여 회전을 시각화합니다 (자세한 내용은 빠른 시작 가이드 참조).
워크스페이스 오프셋 컨트롤러 컴포넌트
새 스크립트 만들기 WorkspaceOffsetController.cs
를 클릭하고 햅틱 작업 공간
게임 오브젝트를 추가합니다. 그런 다음 커서에 제어되는 햅틱 스레드
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()
.
선택 사항입니다: 카메라 움직임 바인딩
커서 오프셋 변경은 작업 공간과 함께 카메라를 움직여 직관적으로 할 수 있습니다. 결과적으로 결과적으로 오프셋이 크더라도 커서가 프레임 밖으로 이동하지 않습니다.
이를 달성하려면 메인 카메라에 위치 제약 컴포넌트를 추가하고 * 햅틱 워크스페이스*를 소스로 설정하고 활성화 버튼을 누릅니다.
워크스페이스 스케일 컨트롤러 컴포넌트
새 스크립트 만들기 WorkspaceScaleController.cs
를 클릭하고 햅틱 작업 공간 게임 오브젝트를 추가합니다.
그런 다음 커서에 제어되는 핸들 스레드
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);
}
핸들은 자체 축을 중심으로 한 번 이상 회전할 수 있으며, 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 패키지 관리자의 기본 포스 피드백 및 워크스페이스 컨트롤 샘플에서 임포트할 수 있습니다. 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);
}
}