장치 매핑 튜토리얼
이 튜토리얼에서는 Unity 프로젝트에서 ‘Haply ’ 역방향 서비스를사용하여 장치를 수동으로 나열, 매핑 및 연결하는 방법을 설명합니다.
개요
DeviceMapper 컴포넌트는 씬의 Device Controller와연결되어 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 이벤트를 구독하여 디바이스 매퍼의 준비 상태를 처리합니다.
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();
}
}
}