'전체 글'에 해당되는 글 1800건

  1. 2024.01.10 VR 핸드 시계 와 UI 메뉴
  2. 2024.01.08 XR Poke Interactor (1월 2주차 방송 예정)
  3. 2024.01.07 Push Button 커스텀
  4. 2024.01.04 [OpenXR] XR Ray Interactor / XR Interaction Toolkit 2.4.3 (2024-01-04방송)
  5. 2024.01.04 [OpenXR] XR Interaction Toolkit 2.4.3 / XR Ray Interaction
  6. 2024.01.03 Oculus Integration SDK Hand & Materials

VR 핸드 시계 와 UI 메뉴

VR/XR Interaction Toolkit 2024. 1. 10. 13:59
반응형

 

https://youtu.be/CDrSGeiA1a8?list=PLTFRwWXfOIYBIPKhWi-ZO_ITXqtNuqj6j

손등을 바라보면 시계가 나오는 기능 만들기 

 

내적을 사용해 바라보는 방향과 손목의 방향을 계산후 일정 각도 이하면 UI를 보여줍니다.

 

ControllerHand스크립트를 생성하고 Left Controller에 부착후 다음과 같이 작성 합니다.

 

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
using TMPro;
public class ControllerHand : MonoBehaviour
{
    [SerializeField] private Transform palm;
    [SerializeField] private Transform centerEye;
    [SerializeField] private TMP_Text txtAngle;

    [SerializeField] private GameObject ui;
    void Start()
    {
        
    }

    private void Update()
    {
        this.DrawPalmRay();
        this.DrawWorldForwardRay();

        var angle = this.CalcAngle();
        this.DisplayUI(angle);
    }

    private void DisplayUI(float angle)
    {
        if (angle <= 20)
        {
            ActiveUI();
        }
        else
        {
            DeactiveUI();
        }
    }

    private void ActiveUI()
    {
        ui.SetActive(true);
    }

    private void DeactiveUI()
    {
        ui.SetActive(false);
    }

    private float CalcAngle()
    {
        var dot = Vector3.Dot(-centerEye.forward, -palm.right);
        var angle = Mathf.Acos(dot) * Mathf.Rad2Deg;
        txtAngle.text = angle.ToString("0.00");
        return angle;
    }

    private void DrawWorldForwardRay()
    {
        var ray = new Ray(palm.transform.position, -centerEye.forward);
        //Debug.DrawRay(ray.origin, ray.direction, Color.blue, 0);
        DrawArrow.ForDebug(ray.origin, ray.direction, 0, Color.blue, ArrowType.Default);
    }

    private void DrawPalmRay()
    {
        var ray = new Ray(palm.transform.position, -palm.right);
        DrawArrow.ForDebug(ray.origin, ray.direction, 0, Color.red, ArrowType.Default);
        //Debug.DrawRay(ray.origin, ray.direction, Color.red, 0);
    }
}

 

 

UI구성 하기 

UI는 시 분 초로 보여주고 날짜와 오전/오후 표시를 위해 여러개의 텍스를 사용합니다.

using UnityEngine;
using TMPro;

public class DigitalClock : MonoBehaviour
{
    [SerializeField] private TMP_Text h0;
    [SerializeField] private TMP_Text h1;
    [SerializeField] private TMP_Text m0;
    [SerializeField] private TMP_Text m1;
    [SerializeField] private TMP_Text s0;
    [SerializeField] private TMP_Text s1;
    
    [SerializeField] private TMP_Text d0;
    [SerializeField] private TMP_Text d1;
    [SerializeField] private TMP_Text month0;
    [SerializeField] private TMP_Text month1;
    [SerializeField] private TMP_Text y0;
    [SerializeField] private TMP_Text y1;
    
    [SerializeField] private TMP_Text ampm;
    private bool use12HourFormat = true; // 12시간 형식 사용 여부
    
    void Start()
    {
        
    }

     void Update()
    {
        // 현재 시간을 얻어옵니다.
        System.DateTime currentTime = System.DateTime.Now;

        // 시, 분, 초를 따로 얻어옵니다.
        int hour = currentTime.Hour;
        int minute = currentTime.Minute;
        int second = currentTime.Second;

        // AM 또는 PM을 가져오는 메서드
        string amPm = GetAmPm(hour);

        // 시간을 12시간 또는 24시간 형식으로 포맷팅합니다.
        string formattedHour = use12HourFormat ? Format12Hour(hour) : hour.ToString("D2");

        // 시간, 분, 초를 두 자리 숫자로 포맷팅합니다.
        string formattedTime = string.Format("{0}:{1:D2}:{2:D2} {3}", formattedHour, minute, second, amPm);

        // 현재 날짜를 가져옵니다.
        int year = GetYear(currentTime);
        int month = GetMonth(currentTime);
        int day = GetDay(currentTime);
        
        var strHour = string.Format("{0:D2}", hour);
        h0.text = strHour[0].ToString();
        h1.text = strHour[1].ToString();
        
        var strMin = string.Format("{0:D2}", minute);
        m0.text = strMin[0].ToString();
        m1.text = strMin[1].ToString();
        
        var strSec = string.Format("{0:D2}", second);
        s0.text = strSec[0].ToString();
        s1.text = strSec[1].ToString();
        
        var strYear = string.Format("{0:D2}", year);
        y0.text = strYear[2].ToString();
        y1.text = strYear[3].ToString();
        
        var strMonth = string.Format("{0:D2}", month);
        month0.text = strMonth[0].ToString();
        month1.text = strMonth[1].ToString();
        
        var strDay = string.Format("{0:D2}", day);
        d0.text = strDay[0].ToString();
        d1.text = strDay[1].ToString();

        ampm.text = GetAmPm(hour);
    }

    // AM 또는 PM을 가져오는 메서드
    string GetAmPm(int hour)
    {
        return hour >= 12 ? "PM" : "AM";
    }

    // 12시간 형식으로 시간을 포맷팅하는 메서드
    string Format12Hour(int hour)
    {
        if (hour == 0)
        {
            return "12";
        }
        else if (hour > 12)
        {
            return (hour - 12).ToString("D2");
        }
        else
        {
            return hour.ToString("D2");
        }
    }

    // 년도를 가져오는 메서드
    int GetYear(System.DateTime dateTime)
    {
        return dateTime.Year;
    }

    // 월을 가져오는 메서드
    int GetMonth(System.DateTime dateTime)
    {
        return dateTime.Month;
    }

    // 일을 가져오는 메서드
    int GetDay(System.DateTime dateTime)
    {
        return dateTime.Day;
    }
}

 

사용된 리소스 폰트 에셋 

digital-7.zip
0.04MB

 

 

 


참고 자료 

https://learn.microsoft.com/ko-kr/windows/mixed-reality/design/hand-menu

 

손 메뉴 - Mixed Reality

손 메뉴를 사용하면 자주 사용되는 함수에 대한 손 연결 UI를 빠르게 가져올 수 있습니다.

learn.microsoft.com

 

반응형
:

XR Poke Interactor (1월 2주차 방송 예정)

VR/XR Interaction Toolkit 2024. 1. 8. 10:08
반응형

 

찌르기 상호 작용은 다음과 같은 다양한 곳에서 사용 됩니다. 

 

특히 UI에서 많이 활용될수 있습니다.

 

이번 시간에는 Poke Interaction을 구현 하는 방법에 대해 알아보고 버튼을 누르는 예제를 만들어 보겠습니다. 


 

1단계 : 핸드 설정 

 

 

새 씬을 만들고 메인 카메라를 제거 하고 XR Origin을 생성후 Left, Right Controller를 선택해 XR Controller를 제외한 나머지 컴포넌트를 지워 줍니다.

 

컨트롤러로 테스트 해도 되지만 저는 손으로 해보겠습니다. 

Left / Right Controller를 선택후 다음과 같은 구조로 만들어 주고 

 

 

Offset에 알맞는 핸드 프리팹을 넣어 줍니다.

 

모델에 따라 위치와 회전의 조절이 필요 할수 있습니다.

각 손의 Offset을 선택후 정면을 바라보게 수정 합니다.

 

 

Left, Right Controller를 선택후 Hand Model을 none으로 설정 합니다.

 

이제 실행후 결과를 확인 합니다.

 

저는 다음과 같이 왼손 그립을 찌르기 애니메이션으로 변경 해보았습니다.

 

 


 

2단계 : Poke Interactor 설정 

 

Poke Interactor를 프로젝트 창에서 검색후 프리팹을 찾아 Left/Right Controller에 넣어 줍니다.

 

두 Poke Interactor를 선택해 Unpack 해주고 

 

 

실린더는 제거 합니다.

 

Poke Point를 선택하고 

 

빨강 메터리얼로 변경합니다. 

 

이렇게 하면 시각 적으로 포크 하는 위치를 확인 할수 있어 편리 합니다.

 

 

왼쪽 Poke Point를 l_index_finger_tip_makter (모델이 다르다면 손 끝)에 옮겨두고 

위치를 초기화 합니다.

 

 

 

오른손의 Poke Point는 손바닥쯤 위치 시킵니다.

실행후 Poke Point의 위치를 살펴 봅니다.

 

 

 


 

3단계 : 인터렉터블 오브젝트 만들기 

 

다음과 같이 빈 오브젝트 Cube / Visuals 자식으로 Cube 를 생성해 줍니다.

Mesh를 선택해 크기를 조절하고 

 

위치를 적당히 맞춰줍니다.

 

 

Cube오브젝트를 선택하고 XR Simple Interactable 컴포넌트를 부착 합니다.

 

이는 Interactable 객체의 가장 간단한 버전입니다. 이는 단순히 XRBaseInteractable의 구체적인 구현을 제공합니다. 기본 상호 작용 동작 없이 상호 작용 이벤트에 응답하는 방법으로 사용하기 위한 것입니다.

 

 

 

Interaction Affordance를 검색해 Cube자식으로 넣어줍니다.

 

Color Affordance를 선택하고 Material Property Block Helpter의 Renderer 속성에 Cube/Visuals/Mesh를 넣어 줍니다.

XR Interaction Toolkit 패키지는 사용자가 상호 작용 상태에 대한 시각적 및 청각적 피드백을 생성할 수 있는 어포던스 시스템을 제공합니다.

 

 

 

 

Left Controller 아래 있는 Poke Interactor를 선택하고 

Require Poke Filter를 체크 해제 합니다.

 

 

이제 실행하고 결과를 확인 합니다 

큐브는 왼손 포크 상호 작용에 대해 반응 하고 오른손은 반응 하지 않습니다.

 

 


 

4단계 : 찌르기 상호 작용 이벤트 

 


ControllerHand 스크립트를 생성후 

 

다음과 같이 작성 합니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;

public class ControllerHand : MonoBehaviour
{
    [SerializeField] private XRPokeInteractor _pokeInteractor;
    
    
    void Start()
    {
        _pokeInteractor.hoverEntered.AddListener((args) =>
        {
            Debug.LogFormat("hoverEntered: {0}", args.interactableObject);
        });
        
        _pokeInteractor.hoverExited.AddListener((args) =>
        {
            Debug.LogFormat("hoverExited: {0}", args.interactableObject);
        });
        
        _pokeInteractor.selectEntered.AddListener((args) =>
        {
            Debug.LogFormat("selectEntered: {0}", args.interactableObject);
        });
        
        _pokeInteractor.selectExited.AddListener((args) =>
        {
            Debug.LogFormat("selectExited: {0}", args.interactableObject);
        });
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

 

왼손과 오른손 컨트롤러를 선택해 부착 후 Poke Interactor를 프로퍼티에 넣어 줍니다.

오른손쪽도 동일하게

 

 

실행후 결과를 확인 합니다.

https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.4/manual/interactor-events.html

 

 

인터랙터 이벤트

 

이는 UI 버튼 누름에 반응하는 것과 같은 방식으로 에디터에 연결될 수 있는 이벤트입니다.

이는 Interactable과 상호작용할 수 있는 객체인 Interactor에 적용됩니다.

 

 

PropertyDescription

Hover Entered 이 Interactor가 Interactable 위에 마우스를 올리기 시작할 때 호출되는 이벤트입니다. 각 리스너에 전달된 HoverEnterEventArgs는 이벤트가 호출되는 동안에만 유효하며 이에 대한 참조를 보유하지 않습니다.
Hover Exited 이 Interactor가 Interactable 위에 마우스를 올리면 호출되는 이벤트입니다. 각 리스너에 전달된 HoverExitEventArgs는 이벤트가 호출되는 동안에만 유효하며 이에 대한 참조를 보유하지 않습니다.
Select Entered 이 Interactor가 Interactable을 선택하기 시작할 때 호출되는 이벤트입니다. 각 리스너에 전달된 SelectEnterEventArgs는 이벤트가 호출되는 동안에만 유효하며 이에 대한 참조를 보유하지 않습니다.
Select Exited 이 Interactor가 Interactable 선택을 종료할 때 호출되는 이벤트입니다. 각 리스너에 전달된 SelectEnterEventArgs는 이벤트가 호출되는 동안에만 유효하며 이에 대한 참조를 보유하지 않습니다.

 


 

버튼 누르기 인터렉션 

 

PushButton.unitypackage
0.04MB

 

 

 

 

Push Button 프리팹을 찾아 씬으로 가져온후 위치를 설정하고 실행해 봅니다.

 

 

 

푸시 버튼의 구조는 다음과 같습니다.

 

Push Button

┗ Visuals 

        ┗ Plate 

        ┗ StopOffset 

                ┗  Stop 

 

 

 

 

Push Button에 붙어 있는 컴포넌트 

 

 

Visuals와 StopOffset은 빈오브젝트 

 

나머지는 모두 메쉬입니다.

 

Plate 

 

 

Stop

 

 

 

 

Push Button을 선택 하고 Collider의 Center를 0으로 설정한후 

 

Poke Filter를 비활성화 > 활성화 해봅니다.

 

그러면 정확히 콜라이더의 중앙에서 콜라이더 상단까지 빨간색 라인이 그려지는것을 확인 할수 있습니다.

 

 

플레이를 해보면 포크 포인트가 닫는 위치부터 빨간색 레이의 아래쪽 끝부분까지 푸쉬가 되는것을 확인 할수 있습니다.

또한 콜라이더에서 나갔을 경우 원래 위치로 돌아갑니다.

 

 

 

XR Poke Follow Affordance 의 Return To Initial Position을 체크 해제 하면 원래 초기 위치로 돌아가지 않습니다.


 

 

 

Clamp To Max Distance 

Whethrer to keep the Poke Follow Transform from moving past a maximum distance from the poke target.

Poke Follow Transform이 찌르기 대상으로부터 최대 거리를 넘어서 이동하지 않도록 할지 여부입니다.

 

 

 

Max Distance 

The maximum distance from thie transform that the Poke Follow Transform can move.

Poke Follow Transform이 이동할 수 있는 변형으로부터의 최대 거리입니다.

 

 

 

 

m_MaxDistance는 일종의 거리 제한 또는 최대 거리를 나타내는 변수

게임 오브젝트가 어떤 대상에 따라 움직일 때, 이 대상과의 거리를 제한하기 위해 사용

m_MaxDistance 변수는 대상과의 거리를 제한하기 위해 사용되며, 이 거리가 설정된 최대 거리보다 크면 해당 거리로 제한

 

Vector3.ClampMagnitude는 주어진 3D 벡터를 특정 길이로 제한하는 데 사용되는 Unity 엔진의 함수입니다. 이 함수는 벡터의 크기(또는 길이)를 제한하고, 만약 제한된 크기보다 크면 해당 크기로 조절합니다.

함수 시그니처는 다음과 같습니다:

public static Vector3 ClampMagnitude(Vector3 vector, float maxLength);
  • vector: 크기를 제한하려는 3D 벡터입니다.
  • maxLength: 벡터의 최대 길이(크기)를 나타내는 값입니다. 만약 vector의 크기가 maxLength보다 크다면, vector는 maxLength로 스케일링(크기 조절)됩니다.

예를 들어, Vector3.ClampMagnitude를 사용하여 특정 벡터의 크기를 최대 1로 제한하려면 다음과 같이 호출할 수 있습니다:

 

Vector3 originalVector = new Vector3(2.0f, 0.0f, 0.0f); Vector3 clampedVector = Vector3.ClampMagnitude(originalVector, 1.0f);

 

이 경우, clampedVector(1.0f, 0.0f, 0.0f)이 될 것이며, 원래 벡터의 크기가 최대 길이보다 크기 때문에 길이가 1.0으로 제한됩니다.

 

 

 

 

 

 

 

 

 

 

이 둘을 같이 쓸때 의미가 있습니다.

 

이 코드 조각은 주어진 조건에 따라 targetPosition을 조정하고, 최대 거리(m_MaxDistance)를 초과하지 않도록 제한하는 역할을 합니다. 코드를 하나씩 설명하겠습니다.

  1. pokeTarget.InverseTransformPoint(data.axisAlignedPokeInteractionPoint):
    • pokeTarget의 로컬 좌표계로 변환된 data.axisAlignedPokeInteractionPoint를 targetPosition 변수에 저장합니다.
    • pokeTarget는 게임 오브젝트나 트랜스폼을 나타내며, data.axisAlignedPokeInteractionPoint는 다른 곳에서 계산된 위치 좌표를 의미합니다.
  2. if (m_ClampToMaxDistance && targetPosition.sqrMagnitude > m_MaxDistance * m_MaxDistance):
    • m_ClampToMaxDistance는 설정된 최대 거리를 사용할 것인지 여부를 나타내는 불리언 변수입니다.
    • targetPosition.sqrMagnitude는 targetPosition의 크기(거리)를 나타내는 값의 제곱입니다. 제곱을 사용하는 이유는 벡터의 크기를 비교할 때 제곱된 크기를 비교하면 계산이 빠르기 때문입니다.
    • m_MaxDistance * m_MaxDistance는 설정된 최대 거리의 제곱값입니다. 이 값과 targetPosition.sqrMagnitude을 비교합니다.
    • 조건문은 만약 m_ClampToMaxDistance가 참이고, targetPosition의 크기(거리)가 설정된 최대 거리의 제곱값보다 크다면 아래의 코드 블록을 실행합니다.
  3. targetPosition = Vector3.ClampMagnitude(targetPosition, m_MaxDistance):
    • 조건이 충족되면, Vector3.ClampMagnitude 함수를 사용하여 targetPosition을 m_MaxDistance로 제한합니다.
    • 이것은 targetPosition이 최대 거리를 초과하지 않도록 하는 역할을 합니다.
    • targetPosition의 크기(거리)가 m_MaxDistance보다 크면, 이 함수는 targetPosition을 길이가 m_MaxDistance인 벡터로 스케일링(크기를 조절)하여 제한된 크기로 만듭니다.

이 코드는 주로 게임 오브젝트나 트랜스폼을 대상으로 하는 상호 작용에서 사용되며, 해당 대상과의 거리가 설정된 최대 거리를 초과하지 않도록 보장합니다.

 

 

 

테스트 방법 

 

Push Button 자식으로 스피어를 하나 만들어 위치를 0, 0, 0 으로 설정 합니다. (콜라이더제거)

 

 

 

 EmergencyStopOffset을 선택하고 자식으로  스피어를 만들어 주고 위치를 0, 0, 0으로 합니다.

 

 

Push Button을 선택하고 자식으로 스피어를 하나 만들어 위치를 0, 0.045, 0 으로 설정 합니다.

 

 

 

플레이 해보면 콜라이더의 상단 마지막까지만 올라오는 모습을 확인 합니다.

 

 

 

즉, 콜라이더의 상단을 벗어날수는 없습니다.

 

 

 

 

 

이번에는 Max Distance의 값을 0.025로 변경하고 

Max Distance 스피어를 선택해 y 값을 0.025로 변경 합니다.

 

실행후 결과를 확인 합니다. 

포크 포인트가 콜라이더에 닿았을때 EmergencyStopOffset가 어디까지 움직이는지 확인해보면 됩니다.

 

 

정확히 0.025만큼 내려가는것을 확인할수 있습니다.

 

 

그래서 다음 과 같이 포크 포인트가 콜라이더에 닿았을때 버튼이 살짝 위로 올라가는 현상을 없애려면 

 

 

EmergencyStopOffset의 Y값 만큼 Max Distance가 설정 되어야 합니다.

 

EmergencyStopOffset오브젝트를 선택하고 Y 값을 복사 합니다.

 

 

 

Max Distance오브젝트를 선택하고 

복사한 위치로 설정 합니다.

 

 

Push Button을 선택해 XR Poke Follow Affordance컴포넌트의 Max Distance의 값을 0.03799997로 변경 합니다.

 

 

 

실행후 결과를 확인 합니다.

 

 


 

처음부터 만들어 보기 

 

이제 분석했으니 메쉬만 가지고 처음부터 구성 해보록 하겠습니다.

 

 

 

빈오브젝트 Push Button을 생성합니다

자식으로 Visuals 빈오브젝트를 생성 합니다.

자식으로 StopOffset과 Plate 빈오브젝트를 생성 합니다.

Stop Plate를 선택하고 Mesh Filter와 Mesh Renderer를 추가 합니다.

 

Mesh와 Material을 추가 합니다.

 

Stop Offset자식으로 빈오브젝트 Stop Mesh를 생성 합니다.

 

Mesh와 Materials를 넣어 줍니다.

 

 

 

 

Stop Offset의 피봇이 상단으로 오게 수정 합니다.

Stop Mesh를 선택하고 아래로 내려 줍니다.

 

Stop Offset이 0일경우가 완전히 눌러졌을때이므로 Plate를 선택해 아래로 내려 줍니다.

 

 

 

Push Button을 선택해 

Box Collider, XR Simple Interactable, XR Poke Filter, XR Poke Follow Affordance를 추가 합니다.

 

 

 

이때 Draw On Box Collider컴포넌트를 추가 해서 하면 편리 합니다.

 

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
 
[ExecuteAlways]
public class DrawOnBoxCollider : MonoBehaviour
{
    public enum DrawStates
    {
        DrawCube,
        DrawWireCube
    }
 
 
    public Transform boxColliderToDrawOn;
    [Range(0f, 1f)]
    public float colorTransparency;
    public bool draw = false;
    public DrawStates drawStates;
 
    private BoxCollider boxCollider = null;
 
    // Start is called before the first frame update
    void Start()
    {
        if (boxColliderToDrawOn != null)
        {
            boxCollider = boxColliderToDrawOn.GetComponent<BoxCollider>();
        }
 
        drawStates = DrawStates.DrawCube;
    }
 
    // Update is called once per frame
    void Update()
    {
       
    }
 
    private void DrawGizmosOnRunTime(Color color)
    {
        if (boxCollider != null && draw)
        {
            Gizmos.color = color;
            Matrix4x4 rotationMatrix = Matrix4x4.TRS(boxCollider.transform.position, boxCollider.transform.rotation, boxCollider.transform.lossyScale);
            Gizmos.matrix = rotationMatrix;
 
            switch (drawStates)
            {
                case DrawStates.DrawCube:
                    Gizmos.DrawCube(boxCollider.center, boxCollider.size);
                    break;
 
                case DrawStates.DrawWireCube:
                    Gizmos.DrawWireCube(boxCollider.center, boxCollider.size);
                    break;
            }
        }
    }
 
    private void OnDrawGizmos()
    {
        Color c = Color.red;
        c.a = colorTransparency;
        DrawGizmosOnRunTime(c);
    }
}

 

 

Stop Offset을 선택해 콜러이더 위쪽 상단으로 옮겨줍니다.

 

이것에 마춰 Plate도 이동 시킵니다.

 

 

Push Button 자식으로 디버깅용 Sphere를 만들어 콜라이더 상단으로 위치 시킵니다.

 

정확한 위치를 위해 Stop Offset의 Y값을 사용하십시오 

 

 

Push Button을 선택하고 XR Poke Filter의 Poke Configuration의 Poke Direction을 Negative Y로 수정 합니다.

 

 

XR Poke Follow Affordance를 선택해 Clamp To Max Distance를 체크 하고 

Max Distance 의 값을 0.0512로 설정 합니다. 

이 값은 Stop Offset의 Y값 입니다.

 

 

이제 완벽 하게 동작 하는 Push Button을 감상하며 영상을 마치도록 하겠습니다.

 

 

 

 

 


 

레퍼런스 

 

 

https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.4/manual/xr-poke-interactor.html

 

XR Poke Interactor | XR Interaction Toolkit | 2.4.3

XR Poke Interactor Interactor used for interacting with interactables through poking. Property Description Interaction Manager The XRInteractionManager that this Interactor will communicate with (will find one if None). Interaction Layer Mask Allows intera

docs.unity3d.com

https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.3/manual/xr-poke-filter.html

 

XR Poke Filter | XR Interaction Toolkit | 2.3.2

XR Poke Filter Filter component that allows for basic poke functionality and to define constraints for when the interactable will be selected. Property Description Poke Data  Use Asset Enable to reference an externally defined PokeThresholdData asset usi

docs.unity3d.com

https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.4/changelog/CHANGELOG.html

 

Changelog | XR Interaction Toolkit | 2.4.3

Changelog All notable changes to this package will be documented in this file. The format is based on Keep a Changelog and this project adheres to Semantic Versioning. [2.4.3] - 2023-07-21 Fixed Fixed Starter Asset validation check that would trigger a rac

docs.unity3d.com

https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.3/manual/affordance-system.html

 

Affordance system | XR Interaction Toolkit | 2.3.2

Affordance system The XR Interaction Toolkit package provides an affordance system which enables users to create visual and auditory feedback to interaction states. The general flow of information starts by adding an Affordance State Provider to the scene

docs.unity3d.com

 

반응형
:

Push Button 커스텀

VR/XR Interaction Toolkit 2024. 1. 7. 03:17
반응형

 

 

 

 

 


Push Button의 기본 구성 요소 (컴포넌트)

BoxColider (IsTrigger체크해제)

 

XRSimpleInteractable : XRBaseInteractable

호버 및 선택과 같은 상호 작용 상태에 대한 이벤트를 제공하는 가장 간단한 Interactable 개체입니다.

 

XRPokeFilter

기본 찌르기 기능을 허용하고 상호 작용 가능 항목이 선택되는 시기에 대한 제약 조건을 정의하는 필터 구성 요소입니다.

 

XRPokeFollowAffordance

XRPokeFilter와 같은 IPokeStateDataProvider에 대한 애니메이션 어포던스를 따릅니다.
찌른 위치를 따르는 버튼과 같은 누른 변환을 애니메이션하는 데 사용됩니다.


포크가 트리거될 수 있도록 하는 포크 ​​상호 작용 가능 상호 작용 축을 따른 거리입니다.

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
using UnityEngine.XR.Interaction.Toolkit.Filtering;
using UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets;

public class PushButtonController : MonoBehaviour
{
    public enum HoverType
    {
        NONE, ON, OFF
    }
    private XRSimpleInteractable _interactable;
    private XRPokeFollowAffordance _affordance;
    private bool isOn = false;
    private BoxCollider _boxCollider;
    private Vector3 initBoxColliderCenter;
    private HoverType _hoverType;
    private XRPokeFilter filter;
    void Start()
    {
        _interactable = GetComponent<XRSimpleInteractable>();
        _affordance = GetComponent<XRPokeFollowAffordance>();
        _boxCollider = GetComponent<BoxCollider>();
        filter = GetComponent<XRPokeFilter>();
        
        initBoxColliderCenter = _boxCollider.center;
        
        _interactable.hoverEntered.AddListener(arg0 =>
        {
            if (isOn)
            {   
                _hoverType = HoverType.OFF;    
            }
            else
            {
                _hoverType = HoverType.ON;
                _affordance.returnToInitialPosition = false;
            }
            
        });
        
        _interactable.hoverExited.AddListener((arg0) =>
        {
            if (isOn == false)
            {
                _affordance.returnToInitialPosition = true;
                _boxCollider.center = initBoxColliderCenter;
                _hoverType = HoverType.NONE;
            }
        });
        
    }

    private void Update()
    {
        if (_interactable.isHovered && isOn == false)
        {
            //-0.045+0.0398
            _boxCollider.center = new Vector3(0, -0.045f + _affordance.pokeFollowTransform.localPosition.y, 0);
        }

        if (_interactable.isHovered && _affordance.pokeFollowTransform.localPosition == Vector3.zero)
        {
            if (_hoverType == HoverType.ON)
            {
                isOn = true;
                Debug.Log("On");
            }
            else if(_hoverType == HoverType.OFF)
            {
                isOn = false;
                Debug.Log("Off");
                // if(coroutine != null) StopCoroutine(coroutine);
                // coroutine= StartCoroutine(ReturnToInitPosition());
                _hoverType = HoverType.NONE;
            }
        }
    }

    // private Coroutine coroutine;
    //
    // private IEnumerator ReturnToInitPosition()
    // {
    //     while (true)
    //     {
    //         _affordance.pokeFollowTransform.localPosition = Vector3.Lerp(_affordance.pokeFollowTransform.localPosition, new Vector3(0, 0.045f, 0), Time.deltaTime);
    //         if (_affordance.pokeFollowTransform.localPosition == new Vector3(0, 0.045f, 0))
    //             break;
    //         yield return null;
    //     }
    //     Debug.LogFormat("move complete");
    //     _boxCollider.center = initBoxColliderCenter;
    // }
}

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
using UnityEngine.XR.Interaction.Toolkit.Filtering;
using UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets;

public class PushButtonController : MonoBehaviour
{
    public enum HoverType
    {
        NONE, ON, OFF
    }
    private XRSimpleInteractable _interactable;
    private XRPokeFollowAffordance _affordance;
    private BoxCollider _boxCollider;
    private Vector3 initBoxColliderCenter;
    private HoverType _hoverType;
    private bool _isOn;
    void Start()
    {
        _interactable = GetComponent<XRSimpleInteractable>();
        _affordance = GetComponent<XRPokeFollowAffordance>();
        _boxCollider = GetComponent<BoxCollider>();
        
        initBoxColliderCenter = _boxCollider.center;
        
        _interactable.hoverEntered.AddListener(arg0 =>
        {
            if (_isOn)
            {   
                _hoverType = HoverType.OFF;    
            }
            else
            {
                _hoverType = HoverType.ON;
                _affordance.returnToInitialPosition = false;
            }
            
        });
        
        _interactable.hoverExited.AddListener((arg0) =>
        {
            if (_isOn == false)
            {
                _affordance.returnToInitialPosition = true;
                _boxCollider.center = initBoxColliderCenter;
                _hoverType = HoverType.NONE;
            }
        });
        
    }

    private void Update()
    {
        if (_interactable.isHovered && _isOn == false)
        {
            //-0.045+0.0398
            _boxCollider.center = new Vector3(0, -0.045f + _affordance.pokeFollowTransform.localPosition.y, 0);
        }

        if (_interactable.isHovered && _affordance.pokeFollowTransform.localPosition == Vector3.zero)
        {
            if (_hoverType == HoverType.ON)
            {
                _isOn = true;
                Debug.Log("On");
            }
            else if(_hoverType == HoverType.OFF)
            {
                _isOn = false;
                Debug.Log("Off");
                _hoverType = HoverType.NONE;
            }
        }
    }
}

45보다 작을때
45보다 클떄

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
using UnityEngine.XR.Interaction.Toolkit.Filtering;
using UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets;

public class PushButtonController : MonoBehaviour
{
    public enum HoverType
    {
        NONE, ON, OFF
    }
    private XRSimpleInteractable _interactable;
    private XRPokeFollowAffordance _affordance;
    private BoxCollider _boxCollider;
    private Vector3 initBoxColliderCenter;
    private HoverType _hoverType;
    private bool _isOn;
    void Start()
    {
        _interactable = GetComponent<XRSimpleInteractable>();
        _affordance = GetComponent<XRPokeFollowAffordance>();
        _boxCollider = GetComponent<BoxCollider>();
        
        initBoxColliderCenter = _boxCollider.center;
        
        _interactable.hoverEntered.AddListener(arg0 =>
        {
            var poke = arg0.interactorObject as XRPokeInteractor;
            var ray = new Ray(poke.attachTransform.position, poke.attachTransform.forward);
            //Debug.DrawRay(ray.origin, ray.direction, Color.red, 5f);
            var dot = Vector3.Dot(poke.attachTransform.forward, -_boxCollider.transform.up);
            Debug.LogFormat("{0}", dot);
                
            var ray2 = new Ray(_boxCollider.transform.position, -_boxCollider.transform.up);
            //Debug.DrawRay(ray2.origin, ray2.direction, Color.green, 5f);

            // 코사인 값으로부터 각도(라디안)를 계산한 후, 라디안을 도로 변환
            var angle = Mathf.Acos(dot) * Mathf.Rad2Deg;

            Debug.LogFormat("Dot: {0}, Angle: {1} degrees", dot, angle);
                
            if (angle > 45)
            {
                Debug.LogFormat("45보다 큽니다.");
            }
            else
            {
                Debug.LogFormat("45보다 작습니다.");
                if (_isOn)
                {   
                    _hoverType = HoverType.OFF;    
                }
                else
                {
                    _hoverType = HoverType.ON;
                    _affordance.returnToInitialPosition = false;

                
                }
            }
            //EditorApplication.isPaused = true;
            
        });
        
        _interactable.hoverExited.AddListener((arg0) =>
        {
            if (_isOn == false)
            {
                _affordance.returnToInitialPosition = true;
                _boxCollider.center = initBoxColliderCenter;
                _hoverType = HoverType.NONE;
            }
        });
        
    }

    private void Update()
    {
        if (_interactable.isHovered && _isOn == false)
        {
            //-0.045+0.0398
            _boxCollider.center = new Vector3(0, -0.045f + _affordance.pokeFollowTransform.localPosition.y, 0);
        }

        if (_interactable.isHovered && _affordance.pokeFollowTransform.localPosition == Vector3.zero)
        {
            if (_hoverType == HoverType.ON)
            {
                _isOn = true;
                Debug.Log("On");
            }
            else if(_hoverType == HoverType.OFF)
            {
                _isOn = false;
                Debug.Log("Off");
                _hoverType = HoverType.NONE;
            }
        }
    }
}

 

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
using UnityEngine.XR.Interaction.Toolkit.Filtering;
using UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets;

public class PushButtonController : MonoBehaviour
{
    public enum HoverType
    {
        NONE, ON, OFF
    }
    private XRSimpleInteractable _interactable;
    private XRPokeFollowAffordance _affordance;
    private BoxCollider _boxCollider;
    private Vector3 initBoxColliderCenter;
    private HoverType _hoverType;
    private bool _isOn;
    private Vector3 previousPosition;
    [SerializeField] private Transform attachTransform;
    void Start()
    {
        _interactable = GetComponent<XRSimpleInteractable>();
        _affordance = GetComponent<XRPokeFollowAffordance>();
        _boxCollider = GetComponent<BoxCollider>();
        
        initBoxColliderCenter = _boxCollider.center;
        // 초기 위치 설정
        previousPosition = attachTransform.position;
        
        _interactable.hoverEntered.AddListener(arg0 =>
        {
            var poke = arg0.interactorObject as XRPokeInteractor;
            
            
            // 콜리전 발생 시 이전 위치와 현재 위치를 비교하여 이동 방향 계산
            Debug.LogFormat("previousPosition: {0}", previousPosition);
            Debug.LogFormat("poke.attachTransform.position: {0}", poke.attachTransform.position);
            Vector3 direction = previousPosition - poke.attachTransform.position;// - previousPosition;
            
            var ray = new Ray(poke.attachTransform.position, direction);
            //Debug.DrawRay(ray.origin, ray.direction, Color.red, 5f);
            DrawArrow.ForDebug(ray.origin, ray.direction, 5f, Color.red, ArrowType.Solid);
            
            var dot = Vector3.Dot(poke.attachTransform.forward, -_boxCollider.transform.up);
            //Debug.LogFormat("{0}", dot);
                
            var ray2 = new Ray(_boxCollider.transform.position, -_boxCollider.transform.up);
            Debug.DrawRay(ray2.origin, ray2.direction, Color.green, 5f);

            // 코사인 값으로부터 각도(라디안)를 계산한 후, 라디안을 도로 변환
            var angle = Mathf.Acos(dot) * Mathf.Rad2Deg;

            Debug.LogFormat("Dot: {0}, Angle: {1} degrees", dot, angle);
                
            if (angle > 45)
            {
                Debug.LogFormat("45보다 큽니다.");
            }
            else
            {
                Debug.LogFormat("45보다 작습니다.");
                if (_isOn)
                {   
                    _hoverType = HoverType.OFF;    
                }
                else
                {
                    _hoverType = HoverType.ON;
                    _affordance.returnToInitialPosition = false;

                
                }
            }
            EditorApplication.isPaused = true;
            // 다시 이전 위치를 현재 위치로 업데이트
            //previousPosition = transform.position;
        });
        
        _interactable.hoverExited.AddListener((arg0) =>
        {
            if (_isOn == false)
            {
                _affordance.returnToInitialPosition = true;
                _boxCollider.center = initBoxColliderCenter;
                _hoverType = HoverType.NONE;
            }
        });
        
    }

    private void Update()
    {
        // 매 프레임마다 현재 위치를 업데이트
        previousPosition = transform.position;
        
        if (_interactable.isHovered && _isOn == false)
        {
            //-0.045+0.0398
            _boxCollider.center = new Vector3(0, -0.045f + _affordance.pokeFollowTransform.localPosition.y, 0);
        }

        if (_interactable.isHovered && _affordance.pokeFollowTransform.localPosition == Vector3.zero)
        {
            if (_hoverType == HoverType.ON)
            {
                _isOn = true;
                Debug.Log("On");
            }
            else if(_hoverType == HoverType.OFF)
            {
                _isOn = false;
                Debug.Log("Off");
                _hoverType = HoverType.NONE;
            }
        }
    }
}

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
using UnityEngine.XR.Interaction.Toolkit.Filtering;
using UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets;

public class PushButtonController : MonoBehaviour
{
    public enum HoverType
    {
        NONE, ON, OFF
    }
    private XRSimpleInteractable _interactable;
    private XRPokeFollowAffordance _affordance;
    private BoxCollider _boxCollider;
    private Vector3 initBoxColliderCenter;
    private HoverType _hoverType;
    private bool _isOn;
    private Vector3 previousPosition;
    [SerializeField] private Transform attachTransform;
    void Start()
    {
        _interactable = GetComponent<XRSimpleInteractable>();
        _affordance = GetComponent<XRPokeFollowAffordance>();
        _boxCollider = GetComponent<BoxCollider>();
        
        initBoxColliderCenter = _boxCollider.center;
        // 초기 위치 설정
        previousPosition = attachTransform.position;
        
        _interactable.hoverEntered.AddListener(arg0 =>
        {
            var poke = arg0.interactorObject as XRPokeInteractor;
            
            
            // 콜리전 발생 시 이전 위치와 현재 위치를 비교하여 이동 방향 계산
            Debug.LogFormat("previousPosition: {0}", previousPosition);
            Debug.LogFormat("poke.attachTransform.position: {0}", poke.attachTransform.position);
            Vector3 direction = (previousPosition - poke.attachTransform.position).normalized;// - previousPosition;
            
            var ray = new Ray(_boxCollider.transform.position, direction);
            //Debug.DrawRay(ray.origin, ray.direction, Color.red, 5f);
            DrawArrow.ForDebug(ray.origin, ray.direction, 5f, Color.red, ArrowType.Solid);
            
            var dot = Vector3.Dot(direction, -_boxCollider.transform.up);
            //Debug.LogFormat("{0}", dot);
                
            var ray2 = new Ray(_boxCollider.transform.position, -_boxCollider.transform.up);
            Debug.DrawRay(ray2.origin, ray2.direction, Color.green, 5f);

            // 코사인 값으로부터 각도(라디안)를 계산한 후, 라디안을 도로 변환
            var angle = Mathf.Acos(dot) * Mathf.Rad2Deg;

            Debug.LogFormat("Dot: {0}, Angle: {1} degrees", dot, angle);
                
            if (angle > 45)
            {
                Debug.LogFormat("45보다 큽니다.");
            }
            else
            {
                Debug.LogFormat("45보다 작습니다.");
                if (_isOn)
                {   
                    _hoverType = HoverType.OFF;    
                }
                else
                {
                    _hoverType = HoverType.ON;
                    _affordance.returnToInitialPosition = false;

                
                }
            }
            //EditorApplication.isPaused = true;
            
            // 다시 이전 위치를 현재 위치로 업데이트
            previousPosition = transform.position;
        });
        
        _interactable.hoverExited.AddListener((arg0) =>
        {
            if (_isOn == false)
            {
                _affordance.returnToInitialPosition = true;
                _boxCollider.center = initBoxColliderCenter;
                _hoverType = HoverType.NONE;
            }
        });
        
    }

    private void Update()
    {
        // 매 프레임마다 현재 위치를 업데이트
        previousPosition = transform.position;
        
        if (_interactable.isHovered && _isOn == false)
        {
            //-0.045+0.0398
            _boxCollider.center = new Vector3(0, -0.045f + _affordance.pokeFollowTransform.localPosition.y, 0);
        }

        if (_interactable.isHovered && _affordance.pokeFollowTransform.localPosition == Vector3.zero)
        {
            if (_hoverType == HoverType.ON)
            {
                _isOn = true;
                Debug.Log("On");
            }
            else if(_hoverType == HoverType.OFF)
            {
                _isOn = false;
                Debug.Log("Off");
                _hoverType = HoverType.NONE;
            }
        }
    }
}

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
using UnityEngine.XR.Interaction.Toolkit.Filtering;
using UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets;

public class PushButtonController : MonoBehaviour
{
    public enum HoverType
    {
        NONE, ON, OFF
    }
    private XRSimpleInteractable _interactable;
    private XRPokeFollowAffordance _affordance;
    private BoxCollider _boxCollider;
    private Vector3 initBoxColliderCenter;
    private HoverType _hoverType;
    private bool _isOn;
    private Vector3 previousPosition;
    [SerializeField] private Transform attachTransform;


    private bool isHoverEntered = false;
    
    void Start()
    {
        _interactable = GetComponent<XRSimpleInteractable>();
        _affordance = GetComponent<XRPokeFollowAffordance>();
        _boxCollider = GetComponent<BoxCollider>();
        
        initBoxColliderCenter = _boxCollider.center;
        // 초기 위치 설정
        previousPosition = attachTransform.position;
        
        _interactable.hoverEntered.AddListener(arg0 =>
        {
            var poke = arg0.interactorObject as XRPokeInteractor;

            if (isHoverEntered == false)
            {
                isHoverEntered = true;
                if (_hoverType == HoverType.NONE)
                {
                    // 콜리전 발생 시 이전 위치와 현재 위치를 비교하여 이동 방향 계산
                    Debug.LogFormat("previousPosition: {0}", previousPosition);
                    Debug.LogFormat("poke.attachTransform.position: {0}", poke.attachTransform.position);
                    Vector3 direction = (previousPosition - poke.attachTransform.position).normalized;// - previousPosition;
            
                    var ray = new Ray(_boxCollider.transform.position, direction);
                    //Debug.DrawRay(ray.origin, ray.direction, Color.red, 5f);
                    DrawArrow.ForDebug(ray.origin, ray.direction, 5f, Color.red, ArrowType.Solid);
            
                    var dot = Vector3.Dot(direction, -_boxCollider.transform.up);
                    //Debug.LogFormat("{0}", dot);
                
                    var ray2 = new Ray(_boxCollider.transform.position, -_boxCollider.transform.up);
                    Debug.DrawRay(ray2.origin, ray2.direction, Color.green, 5f);

                    // 코사인 값으로부터 각도(라디안)를 계산한 후, 라디안을 도로 변환
                    var angle = Mathf.Acos(dot) * Mathf.Rad2Deg;

                    Debug.LogFormat("Dot: {0}, Angle: {1} degrees", dot, angle);
                
                    if (angle > 45)
                    {
                        Debug.LogFormat("45보다 큽니다.");
                    }
                    else
                    {
                        Debug.LogFormat("45보다 작습니다.");
                        if (_isOn)
                        {   
                            _hoverType = HoverType.OFF;    
                        }
                        else
                        {
                            _hoverType = HoverType.ON;
                            _affordance.returnToInitialPosition = false;

                
                        }
                    }
                    //EditorApplication.isPaused = true;
            
                    // 다시 이전 위치를 현재 위치로 업데이트
                    previousPosition = transform.position;
                }
            }

            

           
        });
        
        _interactable.hoverExited.AddListener((arg0) =>
        {
            if (_isOn == false)
            {
                _affordance.returnToInitialPosition = true;
                _boxCollider.center = initBoxColliderCenter;
            }
            
            _hoverType = HoverType.NONE;
            isHoverEntered = false;
        });
        
    }

    private void Update()
    {
        // 매 프레임마다 현재 위치를 업데이트
        previousPosition = transform.position;
        
        if (_interactable.isHovered && _isOn == false)
        {
            //-0.045+0.0398
            _boxCollider.center = new Vector3(0, -0.045f + _affordance.pokeFollowTransform.localPosition.y, 0);
        }

        if (_interactable.isHovered && _affordance.pokeFollowTransform.localPosition == Vector3.zero)
        {
            if (_hoverType == HoverType.ON)
            {
                if (_isOn == false)
                {
                    _isOn = true;
                    Debug.Log("On");
                }
            }
            else if(_hoverType == HoverType.OFF)
            {
                if (_isOn == true)
                {
                    _isOn = false;
                    Debug.Log("Off");
                    _hoverType = HoverType.NONE;
                }
            }
        }
    }
}

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
using UnityEngine.XR.Interaction.Toolkit.Filtering;
using UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets;

public class PushButtonController : MonoBehaviour
{
    public enum HoverType
    {
        NONE, ON, OFF
    }
    private XRSimpleInteractable _interactable;
    private XRPokeFollowAffordance _affordance;
    private BoxCollider _boxCollider;
    private Vector3 initBoxColliderCenter;
    private HoverType _hoverType;
    private bool _isOn;
    private Vector3 previousPosition;
    [SerializeField] private Transform attachTransform;
    private bool isHoverEntered = false;
    private bool isValidated = false;
    
    void Start()
    {
        _interactable = GetComponent<XRSimpleInteractable>();
        _affordance = GetComponent<XRPokeFollowAffordance>();
        _boxCollider = GetComponent<BoxCollider>();
        
        initBoxColliderCenter = _boxCollider.center;
        // 초기 위치 설정
        previousPosition = attachTransform.position;
        
        _interactable.hoverEntered.AddListener(arg0 =>
        {
            var poke = arg0.interactorObject as XRPokeInteractor;

            if (isHoverEntered == false)
            {
                isHoverEntered = true;
                if (_hoverType == HoverType.NONE)
                {
                    // 콜리전 발생 시 이전 위치와 현재 위치를 비교하여 이동 방향 계산
                    Debug.LogFormat("previousPosition: {0}", previousPosition);
                    Debug.LogFormat("poke.attachTransform.position: {0}", poke.attachTransform.position);
                    Vector3 direction = (previousPosition - poke.attachTransform.position).normalized;// - previousPosition;
            
                    var ray = new Ray(_boxCollider.transform.position, direction);
                    //Debug.DrawRay(ray.origin, ray.direction, Color.red, 5f);
                    DrawArrow.ForDebug(ray.origin, ray.direction, 5f, Color.red, ArrowType.Solid);
            
                    var dot = Vector3.Dot(direction, -_boxCollider.transform.up);
                    //Debug.LogFormat("{0}", dot);
                
                    var ray2 = new Ray(_boxCollider.transform.position, -_boxCollider.transform.up);
                    Debug.DrawRay(ray2.origin, ray2.direction, Color.green, 5f);

                    // 코사인 값으로부터 각도(라디안)를 계산한 후, 라디안을 도로 변환
                    var angle = Mathf.Acos(dot) * Mathf.Rad2Deg;

                    Debug.LogFormat("Dot: {0}, Angle: {1} degrees", dot, angle);
                
                    if (angle > 45)
                    {
                        Debug.LogFormat("45보다 큽니다.");
                        _affordance.enabled = false;
                        isValidated = false;
                    }
                    else
                    {
                        Debug.LogFormat("45보다 작습니다.");
                        _affordance.enabled = true;
                        if (_isOn)
                        {   
                            _hoverType = HoverType.OFF;    
                        }
                        else
                        {
                            _hoverType = HoverType.ON;
                            _affordance.returnToInitialPosition = false;
                        }

                        isValidated = true;
                    }
                    //EditorApplication.isPaused = true;
            
                    // 다시 이전 위치를 현재 위치로 업데이트
                    previousPosition = transform.position;
                }
            }

            

           
        });
        
        _interactable.hoverExited.AddListener((arg0) =>
        {
            if (isHoverEntered)
            {
                Debug.LogFormat("{0}, {1}", isValidated, _isOn);
                
                if (isValidated)
                {
                    if (_isOn == false)
                    {
                        _affordance.returnToInitialPosition = true;
                        _boxCollider.center = initBoxColliderCenter;
                    }    
                }
            
                _hoverType = HoverType.NONE;
                isHoverEntered = false;
                isValidated = false;
            }

            
        });
        
    }

    private void Update()
    {
        // 매 프레임마다 현재 위치를 업데이트
        previousPosition = transform.position;

        if (isValidated == false) return;
        
        if (_interactable.isHovered && _isOn == false)
        {
            //-0.045+0.0398
            _boxCollider.center = new Vector3(0, -0.045f + _affordance.pokeFollowTransform.localPosition.y, 0);
        }

        if (_interactable.isHovered && _affordance.pokeFollowTransform.localPosition == Vector3.zero)
        {
            if (_hoverType == HoverType.ON)
            {
                if (_isOn == false)
                {
                    _isOn = true;
                    Debug.Log("On");
                }
            }
            else if(_hoverType == HoverType.OFF)
            {
                if (_isOn == true)
                {
                    _isOn = false;
                    Debug.Log("Off");
                    _hoverType = HoverType.NONE;
                }
            }
        }
    }
}

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
using UnityEngine.XR.Interaction.Toolkit.Filtering;
using UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets;

public class PushButtonController : MonoBehaviour
{
public enum HoverType
{
NONE, ON, OFF
}
private XRSimpleInteractable _interactable;
private XRPokeFollowAffordance _affordance;
private BoxCollider _boxCollider;
private Vector3 initBoxColliderCenter;
private HoverType _hoverType;
private bool _isOn;
private Vector3 previousPosition;
[SerializeField] private Transform attachTransform;
private bool isHoverEntered = false;
private bool isValidated = false;

void Start()
{
_interactable = GetComponent<XRSimpleInteractable>();
_affordance = GetComponent<XRPokeFollowAffordance>();
_boxCollider = GetComponent<BoxCollider>();

initBoxColliderCenter = _boxCollider.center;
// 초기 위치 설정
previousPosition = attachTransform.position;

_interactable.hoverEntered.AddListener(arg0 =>
{
var poke = arg0.interactorObject as XRPokeInteractor;

if (isHoverEntered == false)
{
isHoverEntered = true;
if (_hoverType == HoverType.NONE)
{
// 콜리전 발생 시 이전 위치와 현재 위치를 비교하여 이동 방향 계산
Debug.LogFormat("previousPosition: {0}", previousPosition);
Debug.LogFormat("poke.attachTransform.position: {0}", poke.attachTransform.position);
Vector3 direction = (previousPosition - poke.attachTransform.position).normalized;// - previousPosition;

var ray = new Ray(_boxCollider.transform.position, direction);
Debug.DrawRay(ray.origin, ray.direction, Color.red, 0.1f);
//DrawArrow.ForDebug(ray.origin, ray.direction, 5f, Color.red, ArrowType.Solid);

var dot = Vector3.Dot(direction, -_boxCollider.transform.up);
//Debug.LogFormat("{0}", dot);

var ray2 = new Ray(_boxCollider.transform.position, -_boxCollider.transform.up);
Debug.DrawRay(ray2.origin, ray2.direction, Color.green, 5f);

// 코사인 값으로부터 각도(라디안)를 계산한 후, 라디안을 도로 변환
var angle = Mathf.Acos(dot) * Mathf.Rad2Deg;

Debug.LogFormat("Dot: {0}, Angle: {1} degrees", dot, angle);

if (angle > 45)
{
Debug.LogFormat("45보다 큽니다.");
_affordance.enabled = false;
isValidated = false;
}
else
{
Debug.LogFormat("45보다 작습니다.");
_affordance.enabled = true;
if (_isOn)
{
_hoverType = HoverType.OFF;
}
else
{
_hoverType = HoverType.ON;
_affordance.returnToInitialPosition = false;
}

isValidated = true;
}
//EditorApplication.isPaused = true;

// 다시 이전 위치를 현재 위치로 업데이트
previousPosition = transform.position;
}
}




});

_interactable.hoverExited.AddListener((arg0) =>
{
if (isHoverEntered)
{
Debug.LogFormat("{0}, {1}", isValidated, _isOn);

if (isValidated)
{
if (_isOn == false)
{
_affordance.returnToInitialPosition = true;
_boxCollider.center = initBoxColliderCenter;
}
}

_hoverType = HoverType.NONE;
isHoverEntered = false;
isValidated = false;
}


});

}

private void Update()
{
// 매 프레임마다 현재 위치를 업데이트
previousPosition = transform.position;

if (isValidated == false) return;

if (_interactable.isHovered && _isOn == false)
{
//-0.045+0.0398
_boxCollider.center = new Vector3(0, -0.045f + _affordance.pokeFollowTransform.localPosition.y, 0);
}

if (_interactable.isHovered && _affordance.pokeFollowTransform.localPosition == Vector3.zero)
{
if (_hoverType == HoverType.ON)
{
if (_isOn == false)
{
_isOn = true;
Debug.Log("On");
}
}
else if(_hoverType == HoverType.OFF)
{
if (_isOn == true)
{
_isOn = false;
Debug.Log("Off");
_hoverType = HoverType.NONE;
}
}
}
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
using UnityEngine.XR.Interaction.Toolkit.Filtering;
using UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets;

public class PushButtonController : MonoBehaviour
{
    public enum HoverType
    {
        NONE, ON, OFF
    }
    private XRSimpleInteractable _interactable;
    private XRPokeFollowAffordance _affordance;
    private BoxCollider _boxCollider;
    private Vector3 initBoxColliderCenter;
    private HoverType _hoverType;
    private bool _isOn;
    private Vector3 previousPosition;
    [SerializeField] private Transform attachTransform;
    private bool isHoverEntered = false;
    private bool isValidated = false;
    
    void Start()
    {
        _interactable = GetComponent<XRSimpleInteractable>();
        _affordance = GetComponent<XRPokeFollowAffordance>();
        _boxCollider = GetComponent<BoxCollider>();
        
        initBoxColliderCenter = _boxCollider.center;
        // 초기 위치 설정
        previousPosition = attachTransform.position;
        
        _interactable.hoverEntered.AddListener(arg0 =>
        {
            var poke = arg0.interactorObject as XRPokeInteractor;

            if (isHoverEntered == false)
            {
                isHoverEntered = true;
                if (_hoverType == HoverType.NONE)
                {
                    // 콜리전 발생 시 이전 위치와 현재 위치를 비교하여 이동 방향 계산
                    Debug.LogFormat("previousPosition: {0}", previousPosition);
                    Debug.LogFormat("poke.attachTransform.position: {0}", poke.attachTransform.position);
                    Vector3 direction = (previousPosition - poke.attachTransform.position).normalized;// - previousPosition;
            
                    var ray = new Ray(_boxCollider.transform.position, direction);
                    Debug.DrawRay(ray.origin, ray.direction, Color.red, 0.1f);
                    //DrawArrow.ForDebug(ray.origin, ray.direction, 5f, Color.red, ArrowType.Solid);
            
                    var dot = Vector3.Dot(direction, -_boxCollider.transform.up);
                    //Debug.LogFormat("{0}", dot);
                
                    var ray2 = new Ray(_boxCollider.transform.position, -_boxCollider.transform.up);
                    Debug.DrawRay(ray2.origin, ray2.direction, Color.green, 5f);

                    // 코사인 값으로부터 각도(라디안)를 계산한 후, 라디안을 도로 변환
                    var angle = Mathf.Acos(dot) * Mathf.Rad2Deg;

                    Debug.LogFormat("Dot: {0}, Angle: {1} degrees", dot, angle);
                
                    if (angle > 45)
                    {
                        Debug.LogFormat("45보다 큽니다.");
                        _affordance.enabled = false;
                        isValidated = false;
                    }
                    else
                    {
                        Debug.LogFormat("45보다 작습니다.");
                        _affordance.enabled = true;
                        if (_isOn)
                        {   
                            _hoverType = HoverType.OFF;    
                        }
                        else
                        {
                            _hoverType = HoverType.ON;
                            _affordance.returnToInitialPosition = false;
                        }

                        isValidated = true;
                    }
                    //EditorApplication.isPaused = true;
            
                    // 다시 이전 위치를 현재 위치로 업데이트
                    previousPosition = transform.position;
                }
            }

            

           
        });
        
        _interactable.hoverExited.AddListener((arg0) =>
        {
            if (isHoverEntered)
            {
                Debug.LogFormat("{0}, {1}", isValidated, _isOn);
                
                if (isValidated)
                {
                    if (_isOn == false)
                    {
                        _affordance.returnToInitialPosition = true;
                        _boxCollider.center = initBoxColliderCenter;
                    }    
                }
            
                _hoverType = HoverType.NONE;
                isHoverEntered = false;
                isValidated = false;
            }

            
        });
        
    }

    private void Update()
    {
        // 매 프레임마다 현재 위치를 업데이트
        previousPosition = transform.position;

        if (isValidated == false) return;
        
        if (_interactable.isHovered && _isOn == false)
        {
            //-0.045+0.0398
            _boxCollider.center = new Vector3(0, -0.045f + _affordance.pokeFollowTransform.localPosition.y, 0);
        }

        if (_interactable.isHovered && _affordance.pokeFollowTransform.localPosition == Vector3.zero)
        {
            if (_hoverType == HoverType.ON)
            {
                if (_isOn == false)
                {
                    _isOn = true;
                    Debug.Log("On");
                }
            }
            else if(_hoverType == HoverType.OFF)
            {
                if (_isOn == true)
                {
                    _isOn = false;
                    Debug.Log("Off");
                    _hoverType = HoverType.NONE;
                }
            }
        }
    }
}

 

using UnityEngine;
using System.Collections;
#if UNITY_EDITOR
using UnityEditor;
#endif
// adapted from http://wiki.unity3d.com/index.php/DrawArrow 


public enum ArrowType
{
  Default,
	Thin,
	Double,
	Triple,
	Solid,
	Fat,
	ThreeD,
}

public static class DrawArrow
{
	public static void ForGizmo(Vector3 pos, Vector3 direction, Color? color = null,  bool doubled = false, float arrowHeadLength = 0.2f, float arrowHeadAngle = 20.0f)
	{
		Gizmos.color = color ?? Color.white;

		//arrow shaft
		Gizmos.DrawRay(pos, direction);
 
		if (direction != Vector3.zero)
		{
			//arrow head
			Vector3 right = Quaternion.LookRotation(direction) * Quaternion.Euler(0,180+arrowHeadAngle,0) * new Vector3(0,0,1);
			Vector3 left = Quaternion.LookRotation(direction) * Quaternion.Euler(0,180-arrowHeadAngle,0) * new Vector3(0,0,1);
			Gizmos.DrawRay(pos + direction, right * arrowHeadLength);
			Gizmos.DrawRay(pos + direction, left * arrowHeadLength);
		}
	}
 
	public static void ForDebug(Vector3 pos, Vector3 direction, float duration = 0.5f, Color? color = null, ArrowType type = ArrowType.Default, float arrowHeadLength = 0.2f, float arrowHeadAngle = 30.0f, bool sceneCamFollows = false)
	{
		Color actualColor = color ?? Color.white;
		duration = duration/Time.timeScale;
		
		float width = 0.01f;

		Vector3 directlyRight = Vector3.zero;
		Vector3 directlyLeft = Vector3.zero;
		Vector3 directlyBack = Vector3.zero;
		Vector3 headRight = Vector3.zero;
		Vector3 headLeft = Vector3.zero;

		if (direction != Vector3.zero)
		{
			directlyRight = Quaternion.LookRotation(direction) * Quaternion.Euler(0,180+90,0) * new Vector3(0,0,1);
			directlyLeft = Quaternion.LookRotation(direction) * Quaternion.Euler(0,180-90,0) * new Vector3(0,0,1);
			directlyBack = Quaternion.LookRotation(direction) * Quaternion.Euler(0,180,0) * new Vector3(0,0,1);
			headRight = Quaternion.LookRotation(direction) * Quaternion.Euler(0,180+arrowHeadAngle,0) * new Vector3(0,0,1);
			headLeft = Quaternion.LookRotation(direction) * Quaternion.Euler(0,180-arrowHeadAngle,0) * new Vector3(0,0,1);
		}		

		//draw arrow head
		Debug.DrawRay(pos + direction, headRight * arrowHeadLength, actualColor, duration);
		Debug.DrawRay(pos + direction, headLeft * arrowHeadLength, actualColor, duration);
		
		switch (type) {
		case ArrowType.Default:
			Debug.DrawRay(pos, direction, actualColor, duration); //draw center line
			break;
		case ArrowType.Double:
			Debug.DrawRay(pos + directlyRight * width, direction * (1-width), actualColor, duration); //draw line slightly to right
			Debug.DrawRay(pos +  directlyLeft * width, direction * (1-width), actualColor, duration); //draw line slightly to left

			//draw second arrow head
			Debug.DrawRay(pos + directlyBack * width + direction, headRight * arrowHeadLength, actualColor, duration);
			Debug.DrawRay(pos + directlyBack * width + direction, headLeft * arrowHeadLength, actualColor, duration);
			
			break;
		case ArrowType.Triple:
			Debug.DrawRay(pos, direction, actualColor, duration); //draw center line
			Debug.DrawRay(pos + directlyRight * width, direction * (1-width), actualColor, duration); //draw line slightly to right
			Debug.DrawRay(pos +  directlyLeft * width, direction * (1-width), actualColor, duration); //draw line slightly to left
			break;
		case ArrowType.Fat:
			break;
		case ArrowType.Solid:
			int increments = 20;
			for (int i=0;i<increments;i++)
			{
				float displacement = Mathf.Lerp(-width, +width, i/(float)increments);
				//draw arrow body
				Debug.DrawRay(pos + directlyRight * displacement, direction, actualColor, duration); //draw line slightly to right
				Debug.DrawRay(pos +  directlyLeft * displacement, direction, actualColor, duration); //draw line slightly to left
				//draw arrow head
				Debug.DrawRay((pos + direction) + directlyRight * displacement, headRight * arrowHeadLength, actualColor, duration);
				Debug.DrawRay((pos + direction) + directlyRight * displacement, headLeft * arrowHeadLength, actualColor, duration);
			}
			break;
		case ArrowType.Thin:
			Debug.DrawRay(pos, direction, actualColor, duration); //draw center line
			break;
		case ArrowType.ThreeD:
			break;
		}

/*#if UNITY_EDITOR
    //snap the Scene view camera to a spot where it is looking directly at this arrow.
		if (sceneCamFollows)
			SceneViewCameraFollower.activateAt(pos + direction, duration, "_arrow");
#endif*/
	}

	public static void randomStar(Vector3 center, Color color)
	{
		//special: refuse to draw at 0,0.
		if (center == Vector3.zero) return;
			for(int i=0;i<2;i++)
				DrawArrow.ForGizmo(center, UnityEngine.Random.onUnitSphere * 1, color, false, 0.1f, 30.0f);
	}
	
	public static void comparePositions(Transform t1, Transform t2)
	{
		//direct from one to the other:
		ForDebug(t1.position, t2.position - t1.position);

		//direction
		//Vector3 moveDirection = (t2.position-t1.position).normalized;
	}
}

 

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
 
[ExecuteAlways]
public class DrawOnBoxCollider : MonoBehaviour
{
    public enum DrawStates
    {
        DrawCube,
        DrawWireCube
    }
 
 
    public Transform boxColliderToDrawOn;
    [Range(0f, 1f)]
    public float colorTransparency;
    public bool draw = false;
    public DrawStates drawStates;
 
    private BoxCollider boxCollider = null;
 
    // Start is called before the first frame update
    void Start()
    {
        if (boxColliderToDrawOn != null)
        {
            boxCollider = boxColliderToDrawOn.GetComponent<BoxCollider>();
        }
 
        drawStates = DrawStates.DrawCube;
    }
 
    // Update is called once per frame
    void Update()
    {
       
    }
 
    private void DrawGizmosOnRunTime(Color color)
    {
        if (boxCollider != null && draw)
        {
            Gizmos.color = color;
            Matrix4x4 rotationMatrix = Matrix4x4.TRS(boxCollider.transform.position, boxCollider.transform.rotation, boxCollider.transform.lossyScale);
            Gizmos.matrix = rotationMatrix;
 
            switch (drawStates)
            {
                case DrawStates.DrawCube:
                    Gizmos.DrawCube(boxCollider.center, boxCollider.size);
                    break;
 
                case DrawStates.DrawWireCube:
                    Gizmos.DrawWireCube(boxCollider.center, boxCollider.size);
                    break;
            }
        }
    }
 
    private void OnDrawGizmos()
    {
        Color c = Color.red;
        c.a = colorTransparency;
        DrawGizmosOnRunTime(c);
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
using UnityEngine.XR.Interaction.Toolkit.Filtering;
using UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets;

public class PushButtonController : MonoBehaviour
{
    public enum HoverType
    {
        NONE, ON, OFF
    }
    private XRSimpleInteractable _interactable;
    private XRPokeFollowAffordance _affordance;
    private BoxCollider _boxCollider;
    private Vector3 initBoxColliderCenter;
    private HoverType _hoverType;
    private bool _isOn;
    private Vector3 previousPosition;
    [SerializeField] private Transform attachTransform;
    private bool isHoverEntered = false;
    private bool isValidated = false;
    
    void Start()
    {
        _interactable = GetComponent<XRSimpleInteractable>();
        _affordance = GetComponent<XRPokeFollowAffordance>();
        _boxCollider = GetComponent<BoxCollider>();
        
        initBoxColliderCenter = _boxCollider.center;
        // 초기 위치 설정
        previousPosition = attachTransform.position;
        
        _interactable.hoverEntered.AddListener(arg0 =>
        {
            var poke = arg0.interactorObject as XRPokeInteractor;

            if (isHoverEntered == false)
            {
                isHoverEntered = true;
                if (_hoverType == HoverType.NONE)
                {
                    // 콜리전 발생 시 이전 위치와 현재 위치를 비교하여 이동 방향 계산
                    Debug.LogFormat("previousPosition: {0}", previousPosition);
                    Debug.LogFormat("poke.attachTransform.position: {0}", poke.attachTransform.position);
                    Vector3 direction = (previousPosition - poke.attachTransform.position).normalized;// - previousPosition;
            
                    var ray = new Ray(_boxCollider.transform.position, direction);
                    Debug.DrawRay(ray.origin, ray.direction, Color.red, 0.1f);
                    //DrawArrow.ForDebug(ray.origin, ray.direction, 5f, Color.red, ArrowType.Solid);
            
                    var dot = Vector3.Dot(direction, -_boxCollider.transform.up);
                    //Debug.LogFormat("{0}", dot);
                
                    var ray2 = new Ray(_boxCollider.transform.position, -_boxCollider.transform.up);
                    Debug.DrawRay(ray2.origin, ray2.direction, Color.green, 5f);

                    // 코사인 값으로부터 각도(라디안)를 계산한 후, 라디안을 도로 변환
                    var angle = Mathf.Acos(dot) * Mathf.Rad2Deg;

                    Debug.LogFormat("Dot: {0}, Angle: {1} degrees", dot, angle);
                
                    if (angle > 45)
                    {
                        Debug.LogFormat("45보다 큽니다.");
                        _affordance.enabled = false;
                        isValidated = false;
                    }
                    else
                    {
                        Debug.LogFormat("45보다 작습니다.");
                        _affordance.enabled = true;
                        if (_isOn)
                        {   
                            _hoverType = HoverType.OFF;    
                        }
                        else
                        {
                            _hoverType = HoverType.ON;
                            _affordance.returnToInitialPosition = false;
                        }

                        isValidated = true;
                    }
                    //EditorApplication.isPaused = true;
            
                    // 다시 이전 위치를 현재 위치로 업데이트
                    previousPosition = transform.position;
                }
            }

            

           
        });
        
        _interactable.hoverExited.AddListener((arg0) =>
        {
            if (isHoverEntered)
            {
                Debug.LogFormat("{0}, {1}", isValidated, _isOn);
                
                if (isValidated)
                {
                    if (_isOn == false)
                    {
                        _affordance.returnToInitialPosition = true;
                        _boxCollider.center = initBoxColliderCenter;
                    }    
                }
            
                _hoverType = HoverType.NONE;
                isHoverEntered = false;
                isValidated = false;
            }

            
        });
        
    }

    private void Update()
    {
        // 매 프레임마다 현재 위치를 업데이트
        previousPosition = transform.position;

        if (isValidated == false) return;
        
        if (_interactable.isHovered && _isOn == false)
        {
            //-0.045+0.0398
            _boxCollider.center = new Vector3(0, -0.045f + _affordance.pokeFollowTransform.localPosition.y, 0);
        }

        if (_interactable.isHovered && _affordance.pokeFollowTransform.localPosition == Vector3.zero)
        {
            if (_hoverType == HoverType.ON)
            {
                if (_isOn == false)
                {
                    _isOn = true;
                    Debug.Log("On");
                }
            }
            else if(_hoverType == HoverType.OFF)
            {
                if (_isOn == true)
                {
                    _isOn = false;
                    Debug.Log("Off");
                    _hoverType = HoverType.NONE;
                }
            }
        }
    }
}
반응형
:

[OpenXR] XR Ray Interactor / XR Interaction Toolkit 2.4.3 (2024-01-04방송)

VR/XR Interaction Toolkit 2024. 1. 4. 17:57
반응형

https://youtu.be/Gmh4mTqo92g?list=PLTFRwWXfOIYBIPKhWi-ZO_ITXqtNuqj6j

 

리소스 
https://assetstore.unity.com/packages/3d/props/food/japanese-food-sushi-free-158209

 

Japanese food - Sushi Free | 3D 음식 | Unity Asset Store

Elevate your workflow with the Japanese food - Sushi Free asset from Brushzim. Find this & other 음식 options on the Unity Asset Store.

assetstore.unity.com

https://assetstore.unity.com/packages/3d/props/electronics/flashlight-18972

 

Flashlight | 3D 전자제품 | Unity Asset Store

Elevate your workflow with the Flashlight asset from RRFreelance. Find this & other 전자제품 options on the Unity Asset Store.

assetstore.unity.com


 
지난 시간에 이어 오늘은 XR Ray Interactor 예제를 만들어 보겠습니다.
 
새 씬을 만들고 메인 카메라를 제거후 XR Origin을 생성 합니다.
 

 
Left / Right Controller를 선택후 XR Controller를 제외한 나머지 컴포넌트들을 지웁니다.

 
 
Left Controller를 선택후 빈오브젝트 (Visual)를 생성하고 컨트롤러 모델을 부착 합니다.

 
XR Controller의 Model에 컨트롤러 모델을 넣어 줍니다.

 
오른손도 동일하게 작업 합니다.

 
실행후 결과를 확인 합니다.
 
 

 
 


 
 
2단계 
 
프로젝트 창에서 Ray Interactor를 검색해 프리팹을 Left / Right Controller자식으로 넣어줍니다.

 
 
마키 만들기 
 
다음과 같은 구조로 만들어 줍니다.

 
위치를 적당히 잡아 줍니다.

 
 
 
Maki를 선택하고 Rigidbody를 부착후 use Gravity를 비활성화 합니다.

 
이어서 XR Grab Interactable컴포넌트를 부착 합니다.

 
 
Throw On Detach는 체크 해제 합니다.

 
 
마키 메쉬를 선택후 메쉬 콜라이더를 부착 하고 convex를 체크 합니다.

 
 
실행후 결과를 확인 합니다.
 
 

 
 


 
 
3단계 
 
다음과 같은 구조로 손전등을 만들어 줍니다.

 
위치와 크기를 조절 합니다.

 
 
 
Flashlight를 선택하고 빈오브젝트 (Collider) 를 생성 하고 BoxCollider를 부착 합니다.

 
 
Flashlight를 선택하고 Rigidbody를 부착 하고 use gravity는 체크 해제 합니다.

 
이어서 XR Grab Interactable컴포넌트를 부착 합니다.
Throw on Detach는 체크 해제 합니다.

 
 
 
실행후 결과를 확인 합니다.
 

 
 
Left Controller의 Ray Interactor를 선택 하고 

 
Ray Interactor컴포넌트의 Force Grab을 체크 하고 Anchor Control은 체크 해제 합니다.

 
Force Grab
 
Force grab moves the object to your hand rather than interacting with it at a distance.
 
Anchor Control
Allows the user to move the attach anchor point using the joystick.
 
실행하고 결과를 확인 합니다.
 
 

 
 
 
Hide Controller on Select 를 체크 하면 그립 했을때 컨트롤러를 숨깁니다.

 
 
손전등의 메쉬를 선택해 X 축으로 90도 회전 합니다.

 
콜라이더도 위와 같은 방법으로 회전 시킵니다.

 
 
실행후 결과를 확인 합니다.

 


 
Flashlight 스크립트를 생성하고 다음과 같이 작성후 Flashlight게임 오브젝트에 부착 합니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;

public class Flashlight : MonoBehaviour
{
    public void Activated(ActivateEventArgs args)
    {
        Debug.LogFormat("Activated: {0}", args.interactorObject);
    }

    public void Deactivated(DeactivateEventArgs args)
    {
        Debug.LogFormat("Deactivated: {0}", args.interactorObject);
    }
}

 
 

 
 
 
Flashlight를 선택하고 이벤트가 발생하면 호출되는 메서드를 연결해줍니다.
Activated 이벤트는 그립하고 트리거를 입력 하면 호출 됩니다. (Deactivated는 반대)

 
 
 
실행후 손전등을 그립 하고 인덱스 트리거를 땡겨 봅니다.

 
 
이제 Flashlight를 다음과 같이 수정 합니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;

public class Flashlight : MonoBehaviour
{
    public Material material; // 렌더러의 메터리얼
    public GameObject light;

    private bool isOn = false;
    public void Activated(ActivateEventArgs args)
    {
        this.isOn = !isOn;

        if (isOn)
        {
            this.ActivateEmission();
            this.light.SetActive(true);
        }
        else
        {
            DeactivateEmission();
            this.light.SetActive(false);
        }
    }
    
    public void ActivateEmission()
    {
        if (material != null)
        {
            material.EnableKeyword("_EMISSION");
            material.SetColor("_EmissionColor", Color.yellow); // 원하는 색상으로 설정 가능
        }
    }
    
    public void DeactivateEmission()
    {
        if (material != null)
        {
            material.DisableKeyword("_EMISSION");
        }
    }
}

 
 
메터리얼을 넣어주고 

 
스폿 라이트를 생성 합니다.

다음과 같이 설정하고 

 
색상과 Shape를 잘 설정해서 테스트 해봅니다.

 
Flshlight를 선택해 프로퍼티에 넣어줍니다.

 
스폿 라이트를 비활성화 하고 

 
이제 실행후 결과를 확인 합니다.
 

 


참고 
https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.4/manual/xr-ray-interactor.html

반응형
:

[OpenXR] XR Interaction Toolkit 2.4.3 / XR Ray Interaction

VR/XR Interaction Toolkit 2024. 1. 4. 01:04
반응형

 
https://youtube.com/playlist?list=PLTFRwWXfOIYBIPKhWi-ZO_ITXqtNuqj6j&si=XhyHS26tNDP6u7CA

XR Interaction Toolkit 2.4.3

XR Interaction Toolkit 2.4.3 OpenXR Oculus

www.youtube.com


멀리 있는 Interactable과 상호작용하는 데 사용되는 Interactor입니다.
이는 이 인터랙터에 대한 현재 유효한 타겟 세트를 업데이트하는 레이 캐스트를 통해 처리됩니다.
 
https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.4/manual/xr-ray-interactor.html

XR Ray Interactor | XR Interaction Toolkit | 2.4.3

XR Ray Interactor Interactor used for interacting with Interactables at a distance. This is handled via ray casts that update the current set of valid targets for this interactor. Property Description Interaction Manager The XRInteractionManager that this

docs.unity3d.com

 
 
새 씬을 만들고 메인 카메라를 제거후 XR Origin을 생성 

 
기본으로 Left / Right Controller에 Ray Interactor가 붙어 있음 
 
프로젝트 창에도 프리팹으로 지원 

 
 
샘플을 보면 여러 인터렉터들을 사용할수 있기 때문에 Left Controller자식으로 붙어 있는 모습 

 
 
Left Controller 에는 다음과 같은 컴포넌트들이 붙어 있음 

 
 
 
우리는 XR Ray Interactor와 XR Interactio Group만 일단 알아볼거기 때문에 
 
Left / Right Controller를 제거후 XR Controller컴포넌트이외 컴포넌트들은 제거 한다 

 
프로젝트 창에서 Ray Interactor를 검색해 Left, Right Controller에 넣어 준다 

 
실행후 결과를 확인 한다 
 

 
 
지난 시간에 사용했던 스시로 테스트 해본다 
 

 
 

 
 


Force Grab강제 잡기는 멀리서 개체와 상호 작용하는 대신 개체를 손으로 이동시킵니다.

 
 

 
 


 

Anchor Control사용자가 조이스틱을 사용하여 부착 앵커 포인트를 이동할 수 있습니다.

 

 


Translate Speed앵커가 변환되는 속도입니다. 앵커 제어가 활성화된 경우에만 사용 및 표시됩니다.

 

 
 


Rotate Reference Frame부착 앵커 포인트를 회전할 때 위쪽 축을 정의하는 선택적 참조 프레임입니다. 설정되지 않은 경우 연결 변환의 로컬 위쪽 축을 기준으로 회전합니다. 앵커 제어가 활성화된 경우에만 사용 및 표시됩니다.

 
 

설정 안했을때

 
 

 

설정했을떄

 


 

Rotation Mode앵커 회전을 제어하는 방법을 지정합니다. 앵커 제어가 활성화된 경우에만 사용 및 표시됩니다.

 

 Rotate Over Time회전 입력이 활성화된 동안 시간에 따른 앵커 회전을 제어하려면 회전 모드를 시간에 따른 회전으로 설정하세요.
 Match Direction회전 모드를 방향 일치로 설정하여 앵커 회전을 2차원 회전 입력 방향과 일치시킵니다.

 

Match Direction

 


 

Rotate Speed앵커가 회전하는 속도입니다. 앵커 제어가 활성화되고 회전 모드가 시간에 따른 회전으로 설정된 경우에만 사용 및 표시됩니다.
Match Direction모드에서는 안보임

 


 
 

Attach TransformInteractable의 연결 지점으로 사용되는 변환입니다.
자동으로 인스턴스화되고 Awake if None으로 설정됩니다.
이를 설정하면 이전 개체가 자동으로 삭제되지 않습니다.

 

비워두면 자동으로 생성됨

 
 
 
설정해본다 

 

 
일단 레이가 나오는 시작지점부터 달라짐 

 
 


 
 

Ray Origin Transform광선 투사의 시작 위치와 방향입니다.
자동으로 인스턴스화되고 Awake if None으로 설정되며 XRBaseInteractor.attachTransform의 포즈로 초기화됩니다.
이를 설정하면 이전 개체가 자동으로 삭제되지 않습니다.
그냥 자동으로 만들어짐

 
생성해서 넣어보자 

 

 
 
 
 


 

Disable Visuals When Blocked In Group이 인터랙터가 상호 작용 그룹의 일부이고
그룹 내 다른 인터랙터의 활성 상호 작용으로 인해 상호 작용할 수 없는 경우 시각적 개체를 비활성화할지 여부입니다.

 
 
 
 


Line TypeThe type of ray cast.
 Straight Line설정된 광선 길이로 장면에 단일 광선 투사를 수행하려면 선 유형을 직선으로 설정합니다.
 Projectile Curve발사체 곡선을 생성하기 위해 발사체의 궤적을 샘플링하려면 선 유형을 발사체 곡선으로 설정합니다.
 Bezier Curve제어점과 끝점을 사용하여 2차 베지어 곡선을 생성하려면 선 유형을 베지어 곡선으로 설정하세요.

 
 

Max Raycast Distance선 유형이 직선인 경우에만 사용 및 표시됩니다. 이 값을 늘리면 선이 더 멀리 도달하게 됩니다.

 
 
 


 
 

 

Reference Frame선 유형이 투영 곡선 또는 베지어 곡선인 경우에만 사용 및 표시됩니다. 
지면과 위쪽을 정의하는 곡선의 참조 프레임입니다. 
시작 시 설정되지 않은 경우 XROrigin.Origin GameObject를 찾으려고 시도하고, 존재하지 않는 경우 기본적으로 글로벌 up 및 원점을 사용합니다.
Velocity선 유형이 발사체 곡선인 경우에만 사용 및 표시됩니다. 발사체의 초기 속도. 이 값을 늘리면 곡선이 더 멀리 도달하게 됩니다.

 

Acceleration선 유형이 발사체 곡선인 경우에만 사용 및 표시됩니다. 참조 프레임에서 발사체의 중력입니다.

 

Additional Flight Time선 유형이 발사체 곡선인 경우에만 사용 및 표시됩니다. 발사체가 조정된 지면에 착륙한 후 추가 비행 시간입니다. 이 값을 늘리면 끝점의 높이가 낮아집니다.

 

Sample Frequency선 유형이 투영 곡선 또는 베지어 곡선인 경우에만 사용 및 표시됩니다. 
Unity가 곡선 경로를 근사화하는 데 사용하는 샘플 포인트 수입니다. 
값이 클수록 광선 투사 수로 인해 성능이 저하되는 대신 더 나은 품질을 얻을 수 있습니다. n 값은 레이 캐스팅을 위해 n - 1개의 선 세그먼트를 생성합니다. 
유효한 값은 항상 2이므로 직선 유형을 사용할 때 이 속성은 사용되지 않습니다.

 
 


 
 
 

 


 
 

Raycast Mask광선 투사 대상을 제한하는 데 사용되는 레이어 마스크입니다.

 
오른손 왼손 레이어 마스크 적용해본다 
 
오른손 

왼손 


 

 
 
마키를 선택하고 

레이어 Test로 변경하고 
 

 
해당 레이어가 아니면 레이가 줄어듬 

 
https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.4/manual/xr-interactor-line-visual.html

XR Interactor Line Visual | XR Interaction Toolkit | 2.4.3

XR Interactor Line Visual Interactor helper object aligns a LineRenderer with the Interactor. Property Description Line Width Controls the width of the line. Width Curve Controls the relative width of the line from start to end. Override Line Origin Contro

docs.unity3d.com

Auto Adjust Line Length유효한 적중이나 선택이 발생하지 않을 때 시간이 지남에 따라 선 길이가 줄어들지 여부를 결정합니다.

 
체크 해제 하면 라인이 안줄어 듬 

 


 
 

Raycast Trigger Interaction레이캐스트를 통한 트리거 충돌체와의 상호 작용 유형입니다.

 
3가지 옵션이 있음 

 
이건 머선 옵션인지 잘 몰겠네...

Is Trigger를 체크 하고

 
Raycast Trigger Interaction을 Collide로 변경하면 

 
감지가 되야 하는거 아닌가?

 
 
레이어도 문제 없고 

 
 

/// <summary>
/// Whether ray cast should include or ignore hits on trigger colliders that are snap volume colliders,
/// even if the ray cast is set to ignore triggers.
/// If you are not using gaze assistance or XR Interactable Snap Volume components, you should set this property
/// to <see cref="QuerySnapVolumeInteraction.Ignore"/> to avoid the performance cost.
/// </summary>
/// <remarks>
/// When set to <see cref="QuerySnapVolumeInteraction.Collide"/> when <see cref="raycastTriggerInteraction"/> is set to ignore trigger colliders
/// (when set to <see cref="QueryTriggerInteraction.Ignore"/> or when set to <see cref="QueryTriggerInteraction.UseGlobal"/>
/// while <see cref="Physics.queriesHitTriggers"/> is <see langword="false"/>),
/// the ray cast query will be modified to include trigger colliders, but then this behavior will ignore any trigger collider
/// hits that are not snap volumes.
/// <br />
/// When set to <see cref="QuerySnapVolumeInteraction.Ignore"/> when <see cref="raycastTriggerInteraction"/> is set to hit trigger colliders
/// (when set to <see cref="QueryTriggerInteraction.Collide"/> or when set to <see cref="QueryTriggerInteraction.UseGlobal"/>
/// while <see cref="Physics.queriesHitTriggers"/> is <see langword="true"/>),
/// this behavior will ignore any trigger collider hits that are snap volumes.
/// </remarks>
/// <seealso cref="raycastTriggerInteraction"/>
/// <seealso cref="XRInteractableSnapVolume.snapCollider"/>

/// <요약>
         /// 레이 캐스트가 스냅 볼륨 충돌체인 트리거 충돌체에 대한 적중을 포함할지 아니면 무시할지 여부,
         /// 레이캐스트가 트리거를 무시하도록 설정된 경우에도 마찬가지입니다.
         /// 응시 지원 또는 XR Interactable Snap Volume 구성 요소를 사용하지 않는 경우 이 속성을 설정해야 합니다.
         /// 성능 비용을 방지하려면 <see cref="QuerySnapVolumeInteraction.Ignore"/>로 설정하세요.
         /// </summary>
         /// <비고>
         /// <see cref="raycastTriggerInteraction"/>이 트리거 충돌자를 무시하도록 설정된 경우 <see cref="QuerySnapVolumeInteraction.Collide"/>로 설정된 경우
         /// (<see cref="QueryTriggerInteraction.Ignore"/>로 설정된 경우 또는 <see cref="QueryTriggerInteraction.UseGlobal"/>로 설정된 경우)
         /// <see cref="Physics.queriesHitTriggers"/>가 <see langword="false"/>인 동안),
         /// 레이캐스트 쿼리는 트리거 충돌체를 포함하도록 수정되지만 이 동작은 모든 트리거 충돌체를 무시합니다.
         /// 스냅 볼륨이 아닌 적중입니다.
         /// <br />
         /// <see cref="raycastTriggerInteraction"/>이 트리거 충돌을 적중하도록 설정된 경우 <see cref="QuerySnapVolumeInteraction.Ignore"/>로 설정된 경우
         /// ( <see cref="QueryTriggerInteraction.Collide"/>로 설정된 경우 또는 <see cref="QueryTriggerInteraction.UseGlobal"/>로 설정된 경우
         /// <see cref="Physics.queriesHitTriggers"/>가 <see langword="true"/>인 동안),
         /// 이 동작은 스냅 볼륨인 트리거 충돌체 적중을 무시합니다.
         /// </비고>
         /// <see also cref="raycastTriggerInteraction"/>
         /// <see also cref="XRInteractableSnapVolume.snapCollider"/>
 
 

 
 
아니 근데 왜 콜라이더를 벗어났는데도 감지를 하는거냐는거지
 

 
어딘가 오프셋이 있나본데
스시도 동일함 
 
띠용...Hit Detection Type이 Sphere Cast였음 ㅋㅋㅋ 하하핳....
 

 
다시 바꾸고 

 
테스트 
오키 잘 동작 하고 

 
 
다시 트리거 테스트 

 

 
진짜 노이해...

 

https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.4/manual/xr-ray-interactor.html

 

 

When set to QuerySnapVolumeInteraction.Collide when raycastTriggerInteraction is set to ignore trigger colliders
(when set to QueryTriggerInteraction.Ignore or when set to QueryTriggerInteraction.UseGlobal
while Physics.queriesHitTriggers is false.
the ray cast query will be modified to include trigger colliders, but then this behavior will ignore any trigger collider
hits that are not snap volumes.
 
raycastTriggerInteraction이 트리거 충돌체를 무시하도록 설정된 경우 QuerySnapVolumeInteraction.Collide로 설정된 경우
(QueryTriggerInteraction.Ignore로 설정된 경우 또는 QueryTriggerInteraction.UseGlobal로 설정된 경우
Physics.queriesHitTriggers는 false입니다.
레이 캐스트 쿼리는 트리거 충돌체를 포함하도록 수정되지만 이 동작은 트리거 충돌체를 무시합니다.
스냅 볼륨이 아닌 적중.
 
 
 
 
Whether ray cast should include or ignore hits on trigger colliders that are snap volume colliders, even if the ray cast is set to ignore triggers.
If you are not using gaze assistance or XR Interactable Snap Volume components, you should set this property QuerySnapVolumeInteraction.Ignore to avoid the performance cost.
 
레이 캐스트가 트리거를 무시하도록 설정된 경우에도 레이 캐스트가 스냅 볼륨 충돌자인 트리거 충돌기의 적중을 포함해야 하는지 아니면 무시해야 하는지 여부입니다.
시선 지원 또는 XR Interactable Snap Volume 구성 요소를 사용하지 않는 경우 성능 비용을 방지하려면 이 속성 QuerySnapVolumeInteraction.Ignore를 설정해야 합니다.
 
 
 
 
When set to QuerySnapVolumeInteraction.Ignore when raycastTriggerInteraction is set to hit trigger colliders
(when set to QueryTriggerInteraction.Collide or when set to QueryTriggerInteraction.UseGlobal while Physics.queriesHitTriggers is true this behavior will ignore any trigger collider hits that are snap volumes.
 
QuerySnapVolumeInteraction.Ignore로 설정된 경우 raycastTriggerInteraction이 트리거 충돌체를 적중하도록 설정된 경우
(QueryTriggerInteraction.Collide로 설정되거나 Physics.queriesHitTriggers가 true인 동안 QueryTriggerInteraction.UseGlobal로 설정된 경우 이 동작은 스냅 볼륨인 모든 트리거 충돌체 적중을 무시합니다.
 
 
아무래도 볼륨이랑 연관이 있는듯 하다 ...


 
 
 
 

Raycast Snap Volume Interaction레이 캐스트가 트리거를 무시하도록 설정된 경우에도 레이 캐스트가 스냅 볼륨 충돌자인 트리거 충돌기의 적중을 포함해야 하는지 아니면 무시해야 하는지 여부입니다. 응시 지원 또는 XR Interactable Snap Volume 구성 요소를 사용하지 않는 경우 성능 비용을 방지하려면 이 속성을 무시로 설정해야 합니다.

 

 
https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.4/manual/xr-interactable-snap-volume.html

XR Interactable Snap Volume | XR Interaction Toolkit | 2.4.3

XR Interactable Snap Volume Component that enables an XR Ray Interactor to visually snap to an associated interactable. This component requires a collider that is a trigger. This behavior automatically registers itself with the XR Interaction Manager to cr

docs.unity3d.com

 
XR Ray Interactor가 연관된 상호작용 가능 항목에 시각적으로 스냅될 수 있도록 하는 구성 요소입니다. 이 구성 요소에는 트리거인 충돌체가 필요합니다. 이 동작은 자동으로 XR Interaction Manager에 등록되어 Snap Collider와 Interactable Object 간의 연결을 생성합니다. 이 구성 요소는 일반적으로 상호 작용 가능 항목의 하위 GameObject에 추가되어야 합니다.
 
Snap Collider
충돌/충돌 시 상호작용 가능 항목과 연결할 트리거 충돌체입니다. 광선은 여기에서 Snap To Collider로 스냅됩니다.

 

Disable Snap Collider When Selected
상호 작용 가능 항목을 선택하거나 선택 취소할 때 Snap Collider를 자동으로 비활성화하거나 활성화합니다.

 
트리거모드에서도 된다 이거고 

 
 
 
 


 

Hit Detection Type레이 캐스트에 사용할 적중 감지 유형입니다.

 

Raycast적중 감지 유형을 Raycast로 설정하여 Physics Raycast를 사용하여 충돌을 감지합니다.
 Sphere CastPhysics Sphere Cast를 사용하여 충돌을 감지하려면 적중 감지 유형을 Sphere Cast로 설정하세요.

 

 
자세히 보면 스피어가 보임 

 
 


 

Hit Closest OnlyUnity가 가장 가까운 Interactable만 상호 작용의 유효한 대상으로 간주하는지 여부입니다. 가장 가까운 Interactable만 호버 이벤트를 수신하도록 하려면 이 옵션을 활성화합니다. 그렇지 않으면 모든 적중 Interactable이 유효한 것으로 간주되고 이 Interactor는 다중 호버링됩니다.

 

 
 


 

Blend Visual Line PointsUnity가 광선 캐스팅에 사용하는 라인 샘플 포인트를 컨트롤러의 현재 포즈와 혼합합니다. 이를 사용하면 라인이 뒤처지지 않고 컨트롤러와 시각적으로 연결된 상태를 유지할 수 있습니다. 컨트롤러가 입력 대기 시간을 줄이기 위해 렌더링하기 전에 직접 추적 입력을 샘플링하도록 구성되면 컨트롤러는 레이 캐스팅에 사용되는 샘플 곡선의 시작점을 기준으로 새로운 위치 또는 회전에 있을 수 있습니다. 값이 false이면 선이 광선 투사 선의 끝을 향해 구부러지거나 휘어지는 대신 고정 참조 프레임에 시각적으로 유지됩니다.

 
 
Unity가 광선 캐스팅에 사용하는 라인 샘플 포인트를 컨트롤러의 현재 포즈와 혼합합니다. 
 
이를 사용하면 라인이 뒤처지지 않고 컨트롤러와 시각적으로 연결된 상태를 유지할 수 있습니다. 
 
컨트롤러가 입력 대기 시간을 줄이기 위해 렌더링하기 전에 직접 추적 입력을 샘플링하도록 구성되면 컨트롤러는 레이 캐스팅에 사용되는 샘플 곡선의 시작점을 기준으로 새로운 위치 또는 회전에 있을 수 있습니다. 
 
값이 false이면 선이 광선 투사 선의 끝을 향해 구부러지거나 휘어지는 대신 고정 참조 프레임에 시각적으로 유지됩니다.
 
 

 
 


Select Action TriggerUnity가 컨트롤러의 입력 동작 선택을 해석하는 방법을 선택합니다. 버튼이 현재 눌려져 있는지 또는 활성 상태를 토글하는지 여부와 같이 이 인터랙터가 선택할 수 있는지 여부를 결정하기 위해 다양한 입력 스타일을 제어합니다. 이것이 상태로 설정되고 여러 상호작용자가 InteractableSelectMode.Single로 설정된 상호작용 가능 항목을 선택하면 상호작용 가능 항목 선택이 매 프레임마다 상호작용자 간에 앞뒤로 전달되는 바람직하지 않은 동작이 발생할 수 있습니다. 이로 인해 선택 상호 작용 이벤트가 각 프레임을 실행하게 될 수도 있습니다. 이는 기본 권장 옵션인 State Change로 설정하여 해결할 수 있습니다.

 

StateUnity는 버튼을 누르고 있는 동안 입력이 활성화된 것으로 간주합니다. 사용자는 상호 작용이 가능해지기 전에 버튼을 누르고 있을 수 있으며 가능할 때 상호 작용을 계속 트리거할 수 있습니다.
 State ChangeUnity는 버튼을 누른 프레임에서만 입력이 활성화된 것으로 간주하고 성공하면 입력이 해제될 때까지 계속 연결된 상태를 유지합니다. 사용자는 상호작용이 가능한 동안 버튼을 눌러야 상호작용이 실행됩니다. 상호작용이 가능해지기 전에 버튼을 누르기 시작하면 상호작용이 실행되지 않습니다.
 Toggle상호 작용은 입력을 누른 프레임에서 시작되고 입력을 두 번째로 누를 때까지 계속 사용됩니다.
 Sticky상호 작용은 입력이 눌려진 프레임에서 시작되고 두 번째 입력이 해제될 때까지 계속 사용됩니다.

 

 
Unity가 컨트롤러의 입력 동작 선택을 해석하는 방법을 선택합니다. 
 
버튼이 현재 눌려져 있는지 또는 활성 상태를 토글하는지 여부와 같이 이 인터랙터가 선택할 수 있는지 여부를 결정하기 위해 다양한 입력 스타일을 제어합니다. 
 
이것이 State로 설정되고 여러 Interactor가 InteractableSelectMode.Single로 설정된 상호작용 가능 항목을 선택하면 상호작용 가능 항목 선택이 매 프레임마다 상호작용자 간에 앞뒤로 전달되는 바람직하지 않은 동작이 발생할 수 있습니다. 
 
이로 인해 선택 상호 작용 이벤트가 각 프레임을 실행하게 될 수도 있습니다. 
 
이는 기본 권장 옵션인 State Change로 설정하여 해결할 수 있습니다.
 
 
 
이걸 테스트 해보기는 쉽지 않겠는걸...


 
이건 이전시간에 테스트 해봤음 텔레포트 할때 잡고 있을거냐 놓을거냐 이거임 

Keep Selected Target Valid더 이상 유효한 대상이 아닌 경우에도 상호작용 가능 항목을 처음 선택한 후 계속 선택할지 여부입니다. Interactable이 유효한 대상 목록에 포함되지 않은 경우에도 XRInteractionManager가 선택을 유지하도록 하려면 활성화합니다. 유효한 대상 목록에 없는 경우 상호 작용 관리자가 선택 항목을 지우도록 하려면 비활성화합니다. 이를 비활성화하는 일반적인 용도는 순간 이동에 사용되는 Ray Interactor가 현재 가리키고 있지 않을 때 순간 이동 Interactable을 더 이상 선택하지 않도록 만드는 것입니다.

 


 
 

Hide Controller On Select이 인터랙터가 선택 시 컨트롤러 모델을 숨겨야 하는지 여부를 제어합니다.

뭐지요?
 
안숨겨 지는디요?
 
 

 
이것 때문인가요?

 
 

 
네 맞습니다요 

 

 


Allow Hovered Activate현재 선택 항목이 없을 때 이 인터랙터를 마우스로 가리키지만 선택되지 않은 상호 작용 가능 항목에 활성화 및 비활성화 이벤트를 보낼지 여부를 제어합니다. 

기본적으로 인터랙터는 선택한 인터랙티브 개체에만 활성화 및 비활성화 이벤트를 보냅니다.

 

 
 
인터랙터는 선택한 인터랙티브 개체에만 활성화 및 비활성화 이벤트를 보냅니다.
 
By default, the interactor will only send activate and deactivate events to interactables that it's selected.
 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;

public class Maki : XRGrabInteractable
{
    protected override void OnActivated(ActivateEventArgs args)
    {
        Debug.LogFormat("OnActivated: {0}", args.interactorObject);
        base.OnActivated(args);
    }

    protected override void OnDeactivated(DeactivateEventArgs args)
    {
        Debug.LogFormat("OnDeactivated: {0}", args.interactorObject);
        base.OnDeactivated(args);
    }
}

 

https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.4/api/UnityEngine.XR.Interaction.Toolkit.XRBaseInteractable.html

 
 
XRBaseControllerInteractor는 Interactor가 이 Interactable에서 활성화 이벤트를 시작할 때 이 메서드를 호출합니다.
 
활성화/비활성화 이벤트?

 

   /// <summary>
        /// <see cref="XRBaseControllerInteractor"/> calls this method when the
        /// Interactor begins an activation event on this Interactable.
        /// </summary>
        /// <param name="args">Event data containing the Interactor that is sending the activate event.</param>
        /// <remarks>
        /// <paramref name="args"/> is only valid during this method call, do not hold a reference to it.
        /// </remarks>
        /// <seealso cref="OnDeactivated"/>
        protected virtual void OnActivated(ActivateEventArgs args)
        {
            m_Activated?.Invoke(args);

#pragma warning disable 618 // Calling deprecated method to help with backwards compatibility with existing user code.
            OnActivate(args.interactor);
#pragma warning restore 618
        }

        /// <summary>
        /// <see cref="XRBaseControllerInteractor"/> calls this method when the
        /// Interactor ends an activation event on this Interactable.
        /// </summary>
        /// <param name="args">Event data containing the Interactor that is sending the deactivate event.</param>
        /// <remarks>
        /// <paramref name="args"/> is only valid during this method call, do not hold a reference to it.
        /// </remarks>
        /// <seealso cref="OnActivated"/>
        protected virtual void OnDeactivated(DeactivateEventArgs args)
        {
            m_Deactivated?.Invoke(args);

#pragma warning disable 618 // Calling deprecated method to help with backwards compatibility with existing user code.
            OnDeactivate(args.interactor);
#pragma warning restore 618
        }

 
XRBaseControllerInteractor calls this method when the Interactor begins an activation event on this Interactable.

 
 
https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.0/manual/interactable-events.html

Interactable Events | XR Interaction Toolkit | 2.0.4

Interactable Events These are events that can be hooked into in the editor the same way you would respond to a UI button press. These apply to Interactables - objects that Interactors can interact with. Property Description First Hover Entered The event th

docs.unity3d.com

 

ActivatedThe event that is called when the selecting Interactor activates this Interactable.
Not to be confused with activating or deactivating a GameObject with GameObject.SetActive. This is a generic event when an Interactor wants to activate an Interactable, such as from a trigger pull on a controller.
The ActivateEventArgs passed to each listener is only valid while the event is invoked, do not hold a reference to it.
DeactivatedThe event that is called when an Interactor deactivates this Interactable.
Not to be confused with activating or deactivating a GameObject with GameObject.SetActive. This is a generic event when an Interactor wants to deactivate an Interactable, such as from a trigger release on a controller.
The DeactivateEventArgs passed to each listener is only valid while the event is invoked, do not hold a reference to it.

 
Activated
 
The event that is called when the selecting Interactor activates this Interactable. Not to be confused with activating or deactivating a GameObject with GameObject.SetActive. This is a generic event when an Interactor wants to activate an Interactable, such as from a trigger pull on a controller. The ActivateEventArgs passed to each listener is only valid while the event is invoked, do not hold a reference to it.
 
Interactable을 활성화할 때?
 

 

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;

public class Maki : XRGrabInteractable
{
    protected override void OnSelectEntering(SelectEnterEventArgs args)
    {
        Debug.LogFormat("<color=yellow>OnSelectEntering</color>");
        base.OnSelectEntering(args);
    }

    protected override void OnSelectEntered(SelectEnterEventArgs args)
    {
        Debug.LogFormat("<color=yellow>OnSelectEntered</color>");
        base.OnSelectEntered(args);
    }

    protected override void OnActivated(ActivateEventArgs args)
    {
        Debug.LogFormat("<color=lime>OnActivated</color>");
        base.OnActivated(args);
    }

    protected override void OnHoverEntering(HoverEnterEventArgs args)
    {
        Debug.LogFormat("<color=yellow>OnHoverEntering</color>");
        base.OnHoverEntering(args);
    }

    protected override void OnHoverEntered(HoverEnterEventArgs args)
    {
        Debug.LogFormat("<color=yellow>OnHoverEntered</color>");
        base.OnHoverEntered(args);
    }
}

 
 
왜 OnActivated만 호출이 안되는거지?
 

 
 
Send activate and deactivate events to interactables that this interactor is hovered over
이 인터랙터 위에 마우스를 올려놓은 상호작용 가능 항목에 활성화 및 비활성화 이벤트를 보냅니다.
 
but not selected when there is no current selection 
하지만 현재 선택 항목이 없으면 선택되지 않습니다.

https://forum.unity.com/threads/xr-simple-interaction-activate.1154033/

 

아..
 
핸드 트리거로 잡고 인덱스 트리거 땡기면 호출 되는구만..
 
For the Activate events on the Interactable (Activated/Deactivated), the object must be selected first. So if you pick up an object by selecting it with the grip, and then pull the trigger to activate, it will fire the Activated event. This is for something like a flashlight that you can pick up and then use the trigger to toggle the light.
 
grip : 핸드 트리거 
trigger  : 인덱스 트리거 
 
용어 헷갈리네 Oculus SDK 하다 와서 ㅋㅋ


 
일단 속성은 여기 까지 파고 실습 예제 만들기 
 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;

public class Flashlight : MonoBehaviour
{
    public Material material; // 렌더러의 메터리얼
    public GameObject light;

    private bool isOn = false;
    public void Activated(ActivateEventArgs args)
    {
        Debug.LogFormat("Activated: {0}", args.interactorObject);
        this.isOn = !isOn;

        if (isOn)
        {
            this.ActivateEmission();
            this.light.SetActive(true);
        }
        else
        {
            DeactivateEmission();
            this.light.SetActive(false);
        }
    }

    public void Deactivated(DeactivateEventArgs args)
    {
        // Debug.LogFormat("Deactivated: {0}", args.interactorObject);
        // DeactivateEmission();
        // this.light.SetActive(false);
    }
    // Emission을 활성화하는 함수
    public void ActivateEmission()
    {
        if (material != null)
        {
            material.EnableKeyword("_EMISSION");
            material.SetColor("_EmissionColor", Color.yellow); // 원하는 색상으로 설정 가능
        }
    }

    // Emission을 비활성화하는 함수
    public void DeactivateEmission()
    {
        if (material != null)
        {
            material.DisableKeyword("_EMISSION");
        }
    }
}

 


 
연관 
https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.4/manual/xr-interaction-group.html
 
 
 

반응형
:

Oculus Integration SDK Hand & Materials

VR/Oculus Integration 2024. 1. 3. 18:23
반응형

https://youtube.com/playlist?list=PLTFRwWXfOIYB9hru6EjeVS0E-4jNiOuQQ&si=zoJ3NoYX6YrGnpg1

Unity VR with Oculus Integration 57.0

Unity VR with Oculus Integration

www.youtube.com

OculusHand_L_R.unitypackage
0.43MB
Skybox_Gradient.unitypackage
0.00MB
OculusMaterials.unitypackage
0.96MB
Oculus_Animated_Hands.unitypackage
0.99MB
반응형

'VR > Oculus Integration' 카테고리의 다른 글

탁구채로 탁구공 튕기기  (0) 2023.12.22
활시위 당기는 법  (0) 2023.12.22
OneGrabPhysicsJointTransformer  (0) 2023.12.20
Grabbable (Two Grab Transformers)  (0) 2023.12.19
Grabbable  (0) 2023.12.18
: