기본적인 힘 감각 피드백 튜토리얼
이 튜토리얼에서는 구체와 같은 정적 오브젝트와의 접촉이 지닌 물리적 특성을 재현하기 위해, 강성과 감쇠를 모두 반영한 기본적인 힘 감각 시뮬레이션을 제작합니다. 이 튜토리얼을 마치면, 구체의 존재를 감지하고 다양한 힘 감각 경험에 따라 강성과 감쇠 특성을 조정할 수 있는 시뮬레이션이 완성됩니다.
서문
이 튜토리얼의 핵심 과제는강성과 감쇠를 모두 지닌 구체와의접촉으로 인해 발생하는 힘을 계산하는 함수를 개발하는 것입니다. 이 맥락에서 강성은 스프링처럼 작용하여, 압축될수록 더 큰 힘을 발생시킵니다. 반면, 감쇠는 물체의 움직임에 대한 저항을 나타내며, 물체가 빠르게 움직일수록 더 큰 저항을 가합니다.
장면 설정
입문 가이드설명된 대로 Haptic Rig를 설정합니다. 이때,햅틱 오리진 위치, 회전, 크기가 각각 (0, 0, 0)、(1, 1, 1) 로 설정되어 있는지 확인해 주십시오.
다음으로, 다음과 같은 속성을 가진 Sphere 라는 이름의 구체를 생성합니다.
- 위치:
(0, 0, -0.1)(기기 전면에서 약 10cm) - 규모:
(0.2, 0.2, 0.2)(지름 20cm의 구에 해당)
힘 감지 피드백 스크립트
구체 GameObject에,SphereForceFeedback.cs 라는 이름의 새로운 C# 스크립트를 추가합니다.
이 스크립트는 구체와 접촉할 때 ‘ Inverse3 ’ 커서에 가해지는 힘을, 강성과 감쇠를 모두 고려하여 계산합니다.
다음 속성을 사용하여 스크립트를 초기화합니다:
[SerializeField]
private Inverse3 inverse3;
[Range(0, 800)]
public float stiffness = 300f;
[Range(0, 3)]
public float damping = 1f;
private Vector3 _ballPosition;
private float _ballRadius;
private float _cursorRadius;
힘의 계산은 커서가 구체 안으로 들어갔을 때만 수행해야 합니다. 이를 통해 정의된 강성과 감쇠 특성을 지닌 물리적 객체에 닿는 느낌을 재현할 수 있습니다.
ForceCalculation 이 메서드가 이를 담당하며, 커서의 위치와 속도를 모두 고려합니다:
private Vector3 ForceCalculation(Vector3 cursorPosition, Vector3 cursorVelocity, float cursorRadius,
Vector3 otherPosition, float otherRadius)
{
var force = Vector3.zero;
var distanceVector = cursorPosition - otherPosition;
var distance = distanceVector.magnitude;
var penetration = otherRadius + cursorRadius - distance;
if (penetration > 0)
{
// Normalize the distance vector to get the direction of the force
var normal = distanceVector.normalized;
// Calculate the force based on penetration
force = normal * penetration * stiffness;
// Apply damping based on the cursor velocity
force -= cursorVelocity * damping;
}
return force;
}
private void OnDeviceStateChanged(object sender, Inverse3EventArgs args)
{
var inverse3 = args.DeviceController;
// Calculate the ball force
var force = ForceCalculation(inverse3.CursorLocalPosition, inverse3.CursorLocalVelocity,
_cursorRadius, _ballPosition, _ballRadius);
inverse3.SetCursorLocalForce(force);
}
Awake 메서드에서, 씬 데이터를 설정하기 위해 _ballPosition、_ballRadius、_cursorRadius 을 초기화합니다:
private void SaveSceneData()
{
var t = transform;
_ballPosition = t.position;
_ballRadius = t.lossyScale.x / 2f;
_cursorRadius = inverse3.Cursor.Radius;
}
private void Awake()
{
SaveSceneData();
}
OnEnable 메서드와 OnDisable 메서드별로 OnDeviceStateChanged 콜백을 등록 및 해제하고, 상호작용 중에 힘 감각 피드백을 적절히 처리해 주십시오:
protected void OnEnable()
{
inverse3.DeviceStateChanged += OnDeviceStateChanged;
}
protected void OnDisable()
{
inverse3.DeviceStateChanged -= OnDeviceStateChanged;
}
게임 플레이 체험
Inverse3 커서를 누른 상태에서 플레이 모드로 들어가 구를 터치해 보세요. 구의 존재감을 느낄 수 있으며, Unity의 인스펙터를 통해 강성과 감쇠 특성을 조정할 수 있습니다. 이를 통해 가상 오브젝트와의 상호작용을 직접 체감할 수 있습니다.

소스 파일
이 예제의 전체 장면과 관련 파일은 모두 Unity의 패키지 관리자(Package Manager)에 있는 Tutorials 샘플에서 가져올 수 있습니다.
SphereForceFeedback.cs
/*
* Copyright 2024 Haply Robotics Inc. All rights reserved.
*/
using Haply.Inverse.DeviceControllers;
using Haply.Inverse.DeviceData;
using UnityEngine;
namespace Haply.Samples.Tutorials._2_BasicForceFeedback
{
public class SphereForceFeedback : MonoBehaviour
{
public Inverse3Controller inverse3;
[Range(0, 800)]
// Stiffness of the force feedback.
public float stiffness = 300f;
[Range(0, 3)]
public float damping = 1f;
private Vector3 _ballPosition;
private float _ballRadius;
private float _cursorRadius;
/// <summary>
/// Stores the cursor and sphere transform data for access by the haptic thread.
/// </summary>
private void SaveSceneData()
{
var t = transform;
_ballPosition = t.position;
_ballRadius = t.lossyScale.x / 2f;
_cursorRadius = inverse3.Cursor.Radius;
}
/// <summary>
/// Saves the initial scene data cache.
/// </summary>
private void Awake()
{
inverse3 ??= FindObjectOfType<Inverse3Controller>();
inverse3.Ready.AddListener((device, args) => SaveSceneData());
}
/// <summary>
/// Subscribes to the DeviceStateChanged event.
/// </summary>
private void OnEnable()
{
inverse3.DeviceStateChanged += OnDeviceStateChanged;
}
/// <summary>
/// Unsubscribes from the DeviceStateChanged event.
/// </summary>
private void OnDisable()
{
inverse3.DeviceStateChanged -= OnDeviceStateChanged;
inverse3.Release();
}
/// <summary>
/// Calculates the force based on the cursor's position and another sphere position.
/// </summary>
/// <param name="cursorPosition">The position of the cursor.</param>
/// <param name="cursorVelocity">The velocity of the cursor.</param>
/// <param name="cursorRadius">The radius of the cursor.</param>
/// <param name="otherPosition">The position of the other sphere (e.g., ball).</param>
/// <param name="otherRadius">The radius of the other sphere.</param>
/// <returns>The calculated force vector.</returns>
private Vector3 ForceCalculation(Vector3 cursorPosition, Vector3 cursorVelocity, float cursorRadius,
Vector3 otherPosition, float otherRadius)
{
var force = Vector3.zero;
var distanceVector = cursorPosition - otherPosition;
var distance = distanceVector.magnitude;
var penetration = otherRadius + cursorRadius - distance;
if (penetration > 0)
{
// Normalize the distance vector to get the direction of the force
var normal = distanceVector.normalized;
// Calculate the force based on penetration
force = normal * penetration * stiffness;
// Apply damping based on the cursor velocity
force -= cursorVelocity * damping;
}
return force;
}
/// <summary>
/// Event handler that calculates and send the force to the device when the cursor's position changes.
/// </summary>
/// <param name="sender">The Inverse3 data object.</param>
/// <param name="args">The event arguments containing the device data.</param>
private void OnDeviceStateChanged(object sender, Inverse3EventArgs args)
{
var inverse3 = args.DeviceController;
// Calculate the ball force
var force = ForceCalculation(inverse3.CursorLocalPosition, inverse3.CursorLocalVelocity,
_cursorRadius, _ballPosition, _ballRadius);
inverse3.SetCursorLocalForce(force);
}
}
}