장치 매핑 튜토리얼
이 튜토리얼에서는Haply Inverse Service를 사용하여 Unity 프로젝트의 장치를 수동으로 나열, 매핑 및 연결하는 방법을 설명합니다.
개요
DeviceMapper 컴포넌트는 씬의 디바이스 컨트롤러와 연동하여,Inverse3 및 VerseGrip과 같은 Haply 디바이스의 감지, 매핑, 연결을 관리합니다.
장면 설정
먼저,GameObject > Haply > Haptic Rig (two hands) 에서 Haptic Rig를 생성합니다. 자세한 내용은 ‘시작하기 가이드’를 참조하십시오.
DeviceSelector 컴포넌트
새로운 C# 스크립트를 생성하고,DeviceSelector.cs 라는 이름을 붙이고,디바이스 매퍼 GameObject에 부착합니다.
DeviceSelector 클래스 내에서 다음 속성을 정의합니다:
public Inverse3Controller leftInverse3;
public Inverse3Controller rightInverse3;
private DeviceMapper _deviceMapper;
private string _message;
private bool _waitingForVerseGripHandednessConfirm;
Device Mapper 설정
Awake 메서드에서 _deviceMapper 속성을 초기화하고,Ready 이벤트를 구독하여 Device Mapper의 준비 완료를 처리합니다.
private void Awake()
{
_deviceMapper = GetComponent<DeviceMapper>();
_deviceMapper.autoFetchDeviceList = false; // Disable auto-fetch to manually fetch the device list
_deviceMapper.autoAssign = false; // Disable auto-assign to manually map devices
_deviceMapper.autoConnect = false; // Disable auto-connect to manually connect devices
_deviceMapper.Ready.AddListener(OnDeviceMapperReady);
}
장치 컨트롤러 가져오기
장치 컨트롤러를 가져오려면,GetInverse3Controller 메서드와 GetVerseGripController 메서드를 사용할 수 있습니다.
// Get the first Inverse3 controller in the scene
leftInverse3 = _deviceMapper.GetInverse3Controller();
// Get the first Inverse3 controller in the scene with the specified handedness
leftInverse3 = _deviceMapper.GetInverse3Controller(HandednessType.Left);
// Get the Inverse3 controller in the scene with the specified device ID
leftInverse3 = _deviceMapper.GetInverse3Controller("1309");
// Get the first VerseGrip controller in the scene
verseGripController = _deviceMapper.GetVerseGripController();
// Get the VerseGrip controller in the scene associated with the specified Inverse3 controller
leftVerseGripController = _deviceMapper.GetVerseGripController(leftInverse3);
장치 컨트롤러의 주 손 선택
기기 준비가 완료되기 전에,SelectedHandedness 속성을 사용하여 주로 사용하는 손을 선택할 수 있습니다.
DeviceMapper는 선택된 주로 사용하는 손에 따라 연결된 장치를 컨트롤러에 자동으로 할당합니다.
// Select the left handedness for the left controller
leftInverse3.SelectedHandedness = HandednessType.Left;
// Select the right handedness for the right controller
rightInverse3.SelectedHandedness = HandednessType.Right;
기기 목록 가져오기
연결된 장치 목록을 확인하려면,FetchDeviceListOnce 메서드를 사용할 수 있습니다.
// Fetch the list of connected devices
_deviceMapper.FetchDeviceListOnce();
장치 목록이 가져오면,DeviceMapper 컴포넌트는 DeviceListReceived 이벤트를 발생시킵니다.
이 이벤트를 사용하여 UI를 업데이트하거나, 기기 목록과 관련된 기타 작업을 수행할 수 있습니다.
// Subscribe to the DeviceListReceived event
_deviceMapper.DeviceListReceived += OnDeviceListReceived;
private void OnDeviceListReceived(object sender, EventArgs e)
{
// Update the UI or perform any other operations
}
장치 매핑
장치 목록이 가져오면,MapDevices 메서드를 사용하여 장치를 컨트롤러에 매핑할 수 있습니다.
// Map the devices to the controllers
if (_deviceMapper.CanMapDevices())
_deviceMapper.MapDevices();
CanMapDevices 이 메서드는 장치 목록이 완전한지, 그리고 컨트롤러가 매핑을 수행할 준비가 되어 있는지 확인합니다.
장치 연결
장치 매핑이 완료되면,Connect 이 메서드를 사용하여 기기를 WebSocket 서버에 연결하고, 실시간 데이터를 수신하여 기기를 제어할 수 있습니다.
다음 예제에서는 장치의 준비가 완료되면 커서 위치에 대한 프로브를 시작하기 위해,ProbeCursorPosition 메서드를 호출하고 있습니다.
// Connect the devices to the WebSocket server to receive real-time data
if (_deviceMapper.State == DeviceMapperState.MAPPING_COMPLETE)
_deviceMapper.Connect();
기기 교체
컨트롤러 간에 장치를 교체하려면,SwapInverse3 메서드 또는 SwapVerseGrip 메서드를 사용할 수 있습니다.
다음 예시에서는 사용자의 주 손을 확인하기 위해 오른쪽 컨트롤러의 버튼을 누르도록 요청하는 방법과, 주 손이 올바르게 설정되지 않은 경우 장치를 교체하는 방법을 보여줍니다.
OnDeviceMapperReady 메서드에서는 컨트롤러의 ButtonDown 이벤트를 구독하고, 버튼 클릭을 처리하며, 사용자에게 주로 사용하는 손을 확인해 달라고 요청합니다.
private void OnDeviceMapperReady()
{
// Get the VerseGrip controllers using the DeviceMapper (must be done in Start or on DeviceMapper Ready event)
var leftVerseGrip = _deviceMapper.GetVerseGripController(leftInverse3);
var rightVerseGrip = _deviceMapper.GetVerseGripController(rightInverse3);
leftVerseGrip.ButtonDown.AddListener(OnButtonDown);
rightVerseGrip.ButtonDown.AddListener(OnButtonDown);
// Start waiting for button press on the right controller
_message = "Press the button on the right controller to confirm handedness.";
_waitingForVerseGripHandednessConfirm = true;
}
OnButtonDown 이 메서드에서는 사용자의 주 손을 확인하기 위해, 사용자가 오른쪽 컨트롤러의 버튼을 눌렀는지 여부를 확인합니다.
private void OnButtonDown(VerseGripController verseGrip, VerseGripEventArgs args)
{
if (_waitingForVerseGripHandednessConfirm)
{
if (verseGrip != _deviceMapper.GetVerseGripController(rightInverse3))
{
_deviceMapper.SwapVerseGrip();
}
_waitingForVerseGripHandednessConfirm = false;
}
}
private void OnGUI()
{
if (_waitingForVerseGripHandednessConfirm)
{
GUILayout.Label(_message);
}
}
이 예제는 튜토리얼용이므로, 실제 운영 환경에서 사용하지 마십시오. 사용 중인 애플리케이션에서 사용자의 주손을 확인하는 적절한 방법을 찾아보시기 바랍니다.
오류 처리
장치 매핑 처리 중에 오류가 발생하면,Error 이벤트가 트리거됩니다.
이 이벤트를 사용하여 오류를 처리하고 사용자에게 피드백을 제공할 수 있습니다.
// Subscribe to the Error event
_deviceMapper.ErrorOccured += OnError;
private void OnError(object sender, ErrorEventArgs e)
{
_message = e.ErrorMessage;
switch (e.ErrorCode)
{
// Handle the error...
}
}
소스 파일
이 예제에서 사용된 마지막 장면과 모든 관련 파일은 Unity 패키지 관리자의 ‘Tutorials’ 샘플에서 가져올 수 있습니다.
DeviceSelector.cs
DeviceSelector 이 컴포넌트는 튜토리얼용이므로, 실제 운영 환경에서 사용하지 마십시오.
이 컴포넌트는 Unity 프로젝트에서 장치를 열거하고, 매핑하며, 연결하는 방법을 보여줍니다.
using Haply.Inverse;
using Haply.Inverse.DeviceControllers;
using Haply.Inverse.DeviceData;
using UnityEngine;
namespace Haply.Samples.Tutorials._7_DeviceMapping
{
public class DeviceSelector : MonoBehaviour
{
private DeviceMapper _deviceMapper;
public Inverse3Controller leftInverse3;
public Inverse3Controller rightInverse3;
private string _message;
private bool _waitingForVerseGripHandednessConfirm;
private void Awake()
{
_deviceMapper = GetComponent<DeviceMapper>();
_deviceMapper.autoAssign = false;
_deviceMapper.autoConnect = false;
_deviceMapper.autoFetchDeviceList = false;
_deviceMapper.Ready.AddListener(SetupVerseGripHandednessCheck);
_deviceMapper.Error += OnError;
leftInverse3.Ready.AddListener(OnDeviceReady);
rightInverse3.Ready.AddListener(OnDeviceReady);
}
// Start the verse grip handedness check (ask the user to press a button on the right controller)
private void SetupVerseGripHandednessCheck()
{
// Get the VerseGrip controllers using the DeviceMapper (must be done in Start or on DeviceMapper Ready event)
var leftVerseGrip = _deviceMapper.GetVerseGripController(leftInverse3);
var rightVerseGrip = _deviceMapper.GetVerseGripController(rightInverse3);
leftVerseGrip.ButtonDown.AddListener(OnButtonDown);
rightVerseGrip.ButtonDown.AddListener(OnButtonDown);
// Start waiting for button press on the right controller
_message = "Press the button on the right controller to confirm handedness.";
_waitingForVerseGripHandednessConfirm = true;
}
// Handle the button down event to confirm the verse grip handedness
private void OnButtonDown(VerseGripController verseGrip, VerseGripEventArgs args)
{
if (_waitingForVerseGripHandednessConfirm && args.Button is VerseGripButton.Button0 or VerseGripButton.Button1)
{
if (verseGrip == _deviceMapper.GetVerseGripController(rightInverse3))
{
_message = "VerseGrip handedness confirmed!";
}
else
{
_message = "Wrong controller button pressed. Swap the controllers.";
_deviceMapper.SwapVerseGrip();
}
_waitingForVerseGripHandednessConfirm = false;
}
else
{
_message = $"Button {args.Button} pressed on {verseGrip.DeviceId}";
}
}
// Handle the device ready event to start probing the cursor position
private void OnDeviceReady(Inverse3Controller device, Inverse3EventArgs _)
{
// Start probing the cursor position when the device is ready
device.ProbeCursorPosition();
}
// Handle errors from the device mapper
private void OnError(object sender, DeviceMapperErrorEventArgs e)
{
_message = e.ErrorMessage;
}
// Display the device list
private void DeviceListGUI()
{
if (_deviceMapper.GetNumInverse3() + _deviceMapper.GetNumVerseGrip() > 0)
{
GUILayout.Label("Connected devices:");
foreach (var device in _deviceMapper.GetInverse3Devices())
{
GUILayout.Label($"- {device}");
}
foreach (var device in _deviceMapper.GetVerseGripDevices())
{
GUILayout.Label($"- {device}");
}
}
}
// Display the device mapper state and actions.
private void DeviceMapperGUI()
{
switch (_deviceMapper.State)
{
case DeviceMapperState.UNINITIALIZED:
case DeviceMapperState.INITIALIZED:
if (GUILayout.Button(new GUIContent("List devices",
"Fetch the device list by HTTP request")))
{
_deviceMapper.FetchDeviceListOnce();
_message = "Fetching device list...";
}
break;
case DeviceMapperState.DEVICE_LIST_COMPLETE:
if (GUILayout.Button(new GUIContent("Map devices",
"Map the devices to the controllers according to the selected handedness")))
{
_deviceMapper.MapDevices();
_message = "Mapping devices...";
}
break;
case DeviceMapperState.MAPPING_COMPLETE:
if (!_deviceMapper.IsReady)
{
if (GUILayout.Button(new GUIContent("Connect",
"Connect the devices to WebSocket server to receive real-time data")))
{
_deviceMapper.Connect();
_message = "Connecting...";
}
}
break;
case DeviceMapperState.DEVICE_LIST_IN_PROGRESS:
case DeviceMapperState.MAPPING_IN_PROGRESS:
case DeviceMapperState.CONNECTED:
var style = new GUIStyle(GUI.skin.label) { normal = { textColor = Color.yellow } };
GUILayout.Label(_message, style);
break;
case DeviceMapperState.ERROR:
var errorStyle = new GUIStyle(GUI.skin.label) { normal = { textColor = Color.red } };
GUILayout.Label(_message, errorStyle);
if (GUILayout.Button(new GUIContent("Retry")))
{
_deviceMapper.Reset();
}
break;
}
}
// Display the device controller GUI for the inverse3 controller.
private void Inverse3ControllerGUI(Inverse3Controller controller)
{
// Before the device is assigned, we can select the handedness
if (!controller.Assigned)
{
GUILayout.Label("Inverse3Controller \u2192 <not assigned>", GUILayout.Width(800));
if (GUILayout.Button($"Filter: {controller.SelectedHandedness}"))
{
switch (controller.SelectedHandedness)
{
case HandednessType.Left:
controller.SelectedHandedness = HandednessType.Right;
break;
case HandednessType.Right:
controller.SelectedHandedness = HandednessType.Any;
break;
case HandednessType.Any:
controller.SelectedHandedness = HandednessType.Left;
break;
}
}
}
// Once the device is assigned, devices can be swapped
else
{
GUILayout.Label($"Inverse3Controller \u2192 #{controller.DeviceId}.{controller.Handedness}", GUILayout.Width(800));
// Swap the two inverse3 controllers' assigned devices
if (GUILayout.Button(new GUIContent("Swap inverse3",
"Swap the two inverse3 controller's assigned devices")))
{
_deviceMapper.SwapInverse3();
_waitingForVerseGripHandednessConfirm = true;
}
}
// Enable or disable the cursor position update
var probing= GUILayout.Toggle(controller.IsProbeCursorPosition, "Probe cursor position");
if (controller.IsReady && probing != controller.IsProbeCursorPosition)
{
controller.ProbeCursorPosition(probing);
}
}
private void VerseGripControllerGUI(VerseGripController controller)
{
// Before the device is assigned, we can select the VerseGrip type
if (!controller.Assigned)
{
GUILayout.Label("VerseGripController \u2192 <not assigned>", GUILayout.Width(800));
if (GUILayout.Button($"Filter: {controller.verseGripTypeFilter}"))
{
switch (controller.verseGripTypeFilter)
{
case VerseGripType.Wired:
controller.verseGripTypeFilter = VerseGripType.Wireless;
break;
case VerseGripType.Wireless:
controller.verseGripTypeFilter = VerseGripType.Any;
break;
case VerseGripType.Any:
controller.verseGripTypeFilter = VerseGripType.Wired;
break;
}
}
}
// Once the device is ready, devices can be swapped
else
{
GUILayout.Label($"VerseGripController \u2192 #{controller.DeviceId}.{controller.VerseGripType}", GUILayout.Width(800));
// Swap the two verse grip controllers' assigned devices
if (GUILayout.Button("Swap verse grip"))
{
_deviceMapper.SwapVerseGrip();
_waitingForVerseGripHandednessConfirm = true;
}
}
}
// Display the GUI
private void OnGUI()
{
// Show the device mapper state and actions
GUILayout.BeginArea(new Rect(10, 10, 400, 200), new GUIStyle(GUI.skin.box));
DeviceMapperGUI();
GUILayout.Space(10);
// Show the device list
DeviceListGUI();
GUILayout.EndArea();
// Show the left inverse3 controller
var leftRect = new Rect(0, Screen.height - 200, 300, 200);
leftRect.x = 10;
GUILayout.BeginArea(leftRect, new GUIStyle(GUI.skin.box));
GUILayout.Label(leftInverse3.gameObject.name, GUILayout.Width(600));
Inverse3ControllerGUI(leftInverse3);
GUILayout.Space(10);
// Show the associated verse grip controller
var leftVerseGripController = _deviceMapper.GetVerseGripController(leftInverse3);
VerseGripControllerGUI(leftVerseGripController);
GUILayout.EndArea();
// Show the right controller
var rightRect = new Rect(0, Screen.height - 200, 300, 200);
rightRect.x = Screen.width - rightRect.width - 10;
GUILayout.BeginArea(rightRect, new GUIStyle(GUI.skin.box));
GUILayout.Label(rightInverse3.gameObject.name, GUILayout.Width(600));
Inverse3ControllerGUI(rightInverse3);
GUILayout.Space(10);
// Show the associated verse grip controller
var rightVerseGripController = _deviceMapper.GetVerseGripController(rightInverse3);
VerseGripControllerGUI(rightVerseGripController);
GUILayout.EndArea();
}
}
}