Character Sequence (Decimal)는 일련의 숫자로 표현된 문자열을 나타내는 방법 중 하나입니다. 이것은 주로 문자열을 숫자로 나타내는 데 사용되며, 각 문자에 대해 해당 문자의 유니코드 코드 포인트 값을 10진수로 표현합니다.
예를 들어, "Hello"라는 문자열을 Character Sequence (Decimal)로 나타내면 다음과 같이 될 수 있습니다:
H: 72 (유니코드 코드 포인트)
e: 101 (유니코드 코드 포인트)
l: 108 (유니코드 코드 포인트)
l: 108 (유니코드 코드 포인트)
o: 111 (유니코드 코드 포인트)
따라서 "Hello"는 Character Sequence (Decimal)로 표현하면 72 101 108 108 111과 같습니다.
이러한 형식은 컴퓨터 프로그래밍에서 문자열 처리나 텍스트 인코딩과 관련된 작업에서 유용하게 사용될 수 있습니다. 코드 포인트를 10진수로 표현함으로써 문자열을 숫자로 다루거나 변환하는 데 도움을 줄 수 있습니다.
32-126,44032-55203,12593-12643,8200-9900
주어진 숫자 범위들은 각각 다른 유니코드 코드 포인트 범위를 나타냅니다. 각 범위의 시작과 끝에 대한 설명은 다음과 같습니다:
32-126: 이 범위는 ASCII 문자의 범위입니다. 이 범위에는 영문 대소문자, 숫자, 구두점 및 일부 특수 문자가 포함되어 있습니다.
44032-55203: 이 범위는 한글 문자의 범위입니다. 한글 문자들은 대부분 이 범위 내에서 유니코드 코드 포인트로 나타납니다.
12593-12643: 이 범위는 한글 자모음의 범위입니다. 한글 문자는 자음과 모음의 결합으로 구성되며, 이 범위에 해당하는 코드 포인트들은 자모음을 표현합니다.
8200-9900: 이 범위는 다양한 유니코드 문자와 기호를 포함하는 범위입니다. 이 범위에는 다양한 문자와 기호가 있을 수 있으며, 어떤 구체적인 문자나 기호를 나타내는지는 코드 포인트에 따라 다릅니다.
각 코드 포인트 범위는 해당 범위에 속하는 문자나 기호를 나타내는 데 사용됩니다. 이러한 코드 포인트를 이용하여 특정 문자나 기호를 참조하거나 처리할 수 있습니다.
1. 버튼 만들기
빈 오브젝트 Flat UI를 만들고 우클릭후 XR > UI Canvas를 생성 합니다.
Canvas를 선택후 버튼을 만들어줍니다.
Button을 선택하고 다음과 같이
Transition을 Color Tint로 설정후
Normal Color
2096F3
Highlighted Color
1870B6
Pressed Color
58B0F6
Selected Color
2096F3
로 설정 합니다.
Navigation을 None으로 설정 합니다.
Unity의 Button 컴포넌트의 Navigation 프로퍼티는 UI 요소 간의 탐색 순서와 포커스 동작을 정의하는 데 사용됩니다. Navigation은 주로 UI 요소 사이의 상호 작용 및 키보드 또는 컨트롤러로 포커스를 이동하는 데 유용합니다.
Navigation 프로퍼티는 아래와 같이 구성되어 있습니다:
Mode (모드): 이 속성은 Navigation의 동작 모드를 정의합니다. 다음과 같은 모드가 있습니다.
None (없음): 탐색이 비활성화되며 요소 사이의 포커스 이동이 불가능합니다.
Horizontal (수평): 요소 간의 좌우 방향 탐색을 허용합니다.
Vertical (수직): 요소 간의 상하 방향 탐색을 허용합니다.
Automatic (자동): Unity는 요소 간의 상대적 위치에 따라 가장 적절한 방향으로 자동으로 탐색합니다.
Select On Up/Down/Left/Right (위/아래/왼쪽/오른쪽에서 선택): 이 속성들은 요소의 상, 하, 좌, 우 방향에서 포커스 이동을 정의합니다. 예를 들어, "Select On Up"을 설정하면 요소가 위쪽 방향으로 포커스를 이동할 때 어떤 요소가 선택되어야 하는지를 지정할 수 있습니다.
Default Select (기본 선택): 이 속성은 요소가 활성화될 때 포커스를 어느 요소에 설정할지를 결정합니다. 보통 첫 번째 요소로 설정하여 초기에 포커스가 어디로 이동할지를 정할 수 있습니다.
Navigation 프로퍼티는 주로 UI 요소 간의 상호 작용 및 접근성을 향상시키기 위해 사용됩니다. 예를 들어, 게임 메뉴에서 버튼 간의 탐색을 제어하거나, 키보드 또는 컨트롤러를 사용하여 UI 요소를 탐색하고 선택하는 데 활용할 수 있습니다. 이를 통해 사용자 경험을 개선하고 UI를 보다 사용자 친화적으로 만들 수 있습니다.
포커스가 UI 요소에서 벗어나고 다시 해당 UI 요소로 포커스가 돌아올 때 Navigation과 연관이 있습니다.
이러한 상황에서 Navigation은 UI 요소 간의 포커스 이동을 제어하고 정의하는 데 사용됩니다.
다음은 포커스가 UI에서 벗어났다가 다시 포커싱될 때 Navigation이 어떻게 작용하는지의 예시입니다:
포커스가 벗어남: 예를 들어, 현재 포커스가 버튼 A에 있고 사용자가 특정 동작을 수행하여 버튼 A에서 포커스가 벗어날 때, Navigation 설정에 따라 포커스 이동이 발생할 수 있습니다.
포커스가 돌아옴: 이후 사용자가 다시 해당 UI 요소로 포커스를 가져오면, Navigation 설정에 따라 어떤 요소로 포커스가 이동해야 하는지가 결정됩니다. 예를 들어, 버튼 A에서 다른 버튼 B로 포커스가 이동하도록 Navigation이 설정되어 있다면, 포커스가 돌아올 때 버튼 B로 이동할 것입니다.
Navigation은 UI 요소 간의 탐색 및 포커스 이동을 정의하는 데 중요한 역할을 합니다. 사용자가 UI를 상호 작용하거나 키보드 또는 컨트롤러로 포커스를 이동할 때, Navigation 설정에 따라 UI 요소 간의 탐색 경로가 결정되며 사용자 경험을 향상시키는 데 도움이 됩니다.
Navigation 기능은 다음과 같은 상황에서 필요하며 사용됩니다:
접근성 향상: Navigation은 웹 사이트나 애플리케이션에서 포커스 이동을 통해 접근성을 향상시킵니다. 특히 시각 장애인이나 키보드 또는 스크린 리더를 사용하는 사용자에게 중요합니다. Navigation을 효과적으로 구성하면 사용자가 키보드 또는 스크린 리더를 통해 웹 페이지 또는 앱을 쉽게 탐색하고 상호 작용할 수 있습니다.
유저 인터페이스 (UI) 요소 간의 포커스 이동: 사용자는 키보드 또는 컨트롤러를 사용하여 UI 요소 간에 포커스를 이동할 수 있어야 합니다. 예를 들어, 버튼, 입력 필드, 메뉴 항목, 링크 등의 UI 요소를 포커스할 때 Navigation 설정에 따라 어떤 요소로 이동할지가 결정됩니다.
게임 메뉴 및 인터페이스: 게임에서 Navigation은 주로 게임 메뉴, 대화 상자, 인게임 인터페이스 요소 (예: 아이템 목록, 버튼) 등을 다룰 때 사용됩니다. 특히 콘솔 게임 또는 휴대폰 게임과 같은 플랫폼에서는 Navigation이 키보드나 컨트롤러를 사용하여 UI 요소 간의 탐색을 관리하는 데 중요합니다.
포커스 제어: 사용자 경험을 향상시키기 위해 포커스를 정확하게 제어하고 사용자가 예상한 대로 UI 요소 간을 이동하도록 돕습니다. 예를 들어, 사용자가 엔터 키를 누를 때 버튼에 대한 클릭 동작을 수행하기 위해 Navigation을 설정할 수 있습니다.
Navigation 설정은 사용자의 편의성과 접근성을 고려하여 설계되며, UI 요소 간의 포커스 이동이나 상호 작용을 원활하게 만드는 데 도움이 됩니다. 게임 및 애플리케이션 개발에서 이 기능을 적절하게 활용하면 사용자 경험을 향상시키고 사용자가 UI를 더 쉽게 다룰 수 있게 됩니다.
실무적인 측면에서 Navigation의 필요성과 사용 예시를 살펴보겠습니다. Unity를 사용한 게임 개발을 예로 들겠습니다.
게임 메뉴 탐색:
시작 화면: 게임이 시작될 때 사용자는 주로 "시작", "설정", "크레딧" 등의 메뉴 옵션 중 하나를 선택할 수 있어야 합니다. Navigation 설정을 사용하여 각 메뉴 옵션으로 포커스 이동을 관리할 수 있습니다. 예를 들어, "아래" 키를 누르면 "설정" 메뉴로 포커스가 이동하고, "위" 키를 누르면 "시작" 메뉴로 포커스가 이동하도록 설정할 수 있습니다.
설정 메뉴: 사용자가 "설정" 메뉴에 들어가면, Navigation을 사용하여 화면 내의 설정 옵션 간에 포커스를 이동할 수 있습니다. 예를 들어, "음량 조절"과 "해상도 설정" 사이를 탐색하거나 "뒤로 가기" 버튼을 사용하여 메뉴를 빠져나올 수 있도록 구성할 수 있습니다.
대화 상자와 인터페이스 요소:
게임 내 대화 상자: 게임에서 캐릭터 간 대화 또는 스토리 진행을 위해 대화 상자를 사용할 때, Navigation을 이용하여 대화 상자 내의 "다음" 버튼 또는 "선택지"를 포커스할 수 있습니다. 이렇게 하면 플레이어가 키보드 또는 컨트롤러를 사용하여 대화를 진행할 때 편리하게 탐색할 수 있습니다.
아이템 목록: 게임에서 아이템을 관리하는 인터페이스에서 Navigation을 사용하여 아이템 목록 간의 포커스 이동을 정의할 수 있습니다. 플레이어가 아이템을 선택하거나 사용할 때, Navigation 설정을 활용하여 사용자 경험을 향상시킬 수 있습니다.
접근성 향상:
웹사이트 또는 애플리케이션: 웹 개발에서도 Navigation은 접근성을 향상시키는 데 중요한 역할을 합니다. 특히 키보드로 웹 사이트를 탐색하는 사용자에게 중요합니다. Navigation을 사용하여 메뉴, 링크, 양식 입력 필드 등의 웹 요소 간의 포커스 이동을 관리하면 사용자가 쉽게 웹 사이트를 탐색하고 상호 작용할 수 있습니다.
이러한 예시에서 Navigation은 사용자 경험을 개선하고 인터페이스를 보다 직관적으로 만드는 데 사용됩니다. UI 요소 간의 탐색을 관리하여 사용자가 원하는 작업을 수행하기 쉽도록 도와줍니다.
버튼은 찌르기 상호 작용을 할수도 있습니다.
이때는XR > UI Canvas를 선택합니다.
이름을 변경하고
Graphic Raycaster컴포넌트를 제거 합니다.
Image와 Button컴포넌트를 추가 합니다.
이미지의 알파를 0으로 설정 하고
Canvas Renderer 의 Cull Transparent Mesh는 체크 해제 합니다.
버튼의 Transition 색을 변경하고 Navigation을 None으로 설정 합니다.
자식으로 Image를 생성하고
앵커 프리셋을 스트레치 해주고 Pose Z 를 -10으로 설정 합니다.
이미지는 다음과 같이 설정하고
버튼을 선택해 Target Graphic에 넣어줍니다.
이어서 XR Poke Follow Affordance를 부착 하고
Poke Follow Transform에 Image를 넣어주고 Clamp To Max Distance를 체크 후 Max Distance를 10으로 설정 합니다.
프로젝트 창에서 Poke Interactor를 찾아 Left Controller에 부착 하고
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를 만들어 콜라이더 상단으로 위치 시킵니다.
호버 및 선택과 같은 상호 작용 상태에 대한 이벤트를 제공하는 가장 간단한 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;
}
}
}
}
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;
}
}
}
}
}
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");
}
}
}
기본으로 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차원 회전 입력 방향과 일치시킵니다.
Rotate Speed
앵커가 회전하는 속도입니다. 앵커 제어가 활성화되고 회전 모드가 시간에 따른 회전으로 설정된 경우에만 사용 및 표시됩니다.
Attach Transform
Interactable의 연결 지점으로 사용되는 변환입니다. 자동으로 인스턴스화되고 Awake if None으로 설정됩니다. 이를 설정하면 이전 개체가 자동으로 삭제되지 않습니다.
설정해본다
일단 레이가 나오는 시작지점부터 달라짐
Ray Origin Transform
광선 투사의 시작 위치와 방향입니다. 자동으로 인스턴스화되고 Awake if None으로 설정되며 XRBaseInteractor.attachTransform의 포즈로 초기화됩니다. 이를 설정하면 이전 개체가 자동으로 삭제되지 않습니다.
생성해서 넣어보자
Disable Visuals When Blocked In Group
이 인터랙터가 상호 작용 그룹의 일부이고 그룹 내 다른 인터랙터의 활성 상호 작용으로 인해 상호 작용할 수 없는 경우 시각적 개체를 비활성화할지 여부입니다.
Line Type
The 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 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"/>
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 구성 요소를 사용하지 않는 경우 성능 비용을 방지하려면 이 속성을 무시로 설정해야 합니다.
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 Cast
Physics Sphere Cast를 사용하여 충돌을 감지하려면 적중 감지 유형을 Sphere Cast로 설정하세요.
자세히 보면 스피어가 보임
Hit Closest Only
Unity가 가장 가까운 Interactable만 상호 작용의 유효한 대상으로 간주하는지 여부입니다. 가장 가까운 Interactable만 호버 이벤트를 수신하도록 하려면 이 옵션을 활성화합니다. 그렇지 않으면 모든 적중 Interactable이 유효한 것으로 간주되고 이 Interactor는 다중 호버링됩니다.
Blend Visual Line Points
Unity가 광선 캐스팅에 사용하는 라인 샘플 포인트를 컨트롤러의 현재 포즈와 혼합합니다. 이를 사용하면 라인이 뒤처지지 않고 컨트롤러와 시각적으로 연결된 상태를 유지할 수 있습니다. 컨트롤러가 입력 대기 시간을 줄이기 위해 렌더링하기 전에 직접 추적 입력을 샘플링하도록 구성되면 컨트롤러는 레이 캐스팅에 사용되는 샘플 곡선의 시작점을 기준으로 새로운 위치 또는 회전에 있을 수 있습니다. 값이 false이면 선이 광선 투사 선의 끝을 향해 구부러지거나 휘어지는 대신 고정 참조 프레임에 시각적으로 유지됩니다.
Unity가 광선 캐스팅에 사용하는 라인 샘플 포인트를 컨트롤러의 현재 포즈와 혼합합니다.
이를 사용하면 라인이 뒤처지지 않고 컨트롤러와 시각적으로 연결된 상태를 유지할 수 있습니다.
컨트롤러가 입력 대기 시간을 줄이기 위해 렌더링하기 전에 직접 추적 입력을 샘플링하도록 구성되면 컨트롤러는 레이 캐스팅에 사용되는 샘플 곡선의 시작점을 기준으로 새로운 위치 또는 회전에 있을 수 있습니다.
값이 false이면 선이 광선 투사 선의 끝을 향해 구부러지거나 휘어지는 대신 고정 참조 프레임에 시각적으로 유지됩니다.
Select Action Trigger
Unity가 컨트롤러의 입력 동작 선택을 해석하는 방법을 선택합니다. 버튼이 현재 눌려져 있는지 또는 활성 상태를 토글하는지 여부와 같이 이 인터랙터가 선택할 수 있는지 여부를 결정하기 위해 다양한 입력 스타일을 제어합니다. 이것이 상태로 설정되고 여러 상호작용자가 InteractableSelectMode.Single로 설정된 상호작용 가능 항목을 선택하면 상호작용 가능 항목 선택이 매 프레임마다 상호작용자 간에 앞뒤로 전달되는 바람직하지 않은 동작이 발생할 수 있습니다. 이로 인해 선택 상호 작용 이벤트가 각 프레임을 실행하게 될 수도 있습니다. 이는 기본 권장 옵션인 State Change로 설정하여 해결할 수 있습니다.
State
Unity는 버튼을 누르고 있는 동안 입력이 활성화된 것으로 간주합니다. 사용자는 상호 작용이 가능해지기 전에 버튼을 누르고 있을 수 있으며 가능할 때 상호 작용을 계속 트리거할 수 있습니다.
State Change
Unity는 버튼을 누른 프레임에서만 입력이 활성화된 것으로 간주하고 성공하면 입력이 해제될 때까지 계속 연결된 상태를 유지합니다. 사용자는 상호작용이 가능한 동안 버튼을 눌러야 상호작용이 실행됩니다. 상호작용이 가능해지기 전에 버튼을 누르기 시작하면 상호작용이 실행되지 않습니다.
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);
}
}
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.
The event that is called when the selecting Interactor activates this Interactable. Not to be confused with activating or deactivating aGameObjectwithGameObject.SetActive. This is a generic event when an Interactor wants to activate an Interactable, such as from a trigger pull on a controller. TheActivateEventArgspassed to each listener is only valid while the event is invoked, do not hold a reference to it.
Deactivated
The 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 하지만 현재 선택 항목이 없으면 선택되지 않습니다.
아..
핸드 트리거로 잡고 인덱스 트리거 땡기면 호출 되는구만..
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");
}
}
}