commit
8c45104f18
9 changed files with 503 additions and 0 deletions
-
83ContinuousMovement.cs
-
80FadeTeleportationProvider.cs
-
48LocomotionController.cs
-
43ObjectActivator.cs
-
17SceneLoad.cs
-
50SocketTarget.cs
-
108TwoHandGrabInteractable.cs
-
30XRExclusiveSocketInteractor.cs
-
44XROffsetGrabInteractable.cs
@ -0,0 +1,83 @@ |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using UnityEngine; |
|||
using UnityEngine.XR; |
|||
using UnityEngine.XR.Interaction.Toolkit; |
|||
|
|||
public class ContinuousMovement : MonoBehaviour |
|||
{ |
|||
public float gravity = -9.81f; //중력 상수
|
|||
public float speed = 1f; |
|||
public float additionalHeight = 0.2f; //눈~정수리 위까지의 추가 키
|
|||
public XRNode inputSource; //인풋을 가져올 XRNode => Left Hand로 설정
|
|||
public LayerMask groundLayer; //히트 체크할 땅의 레이어
|
|||
|
|||
private float fallingSpeed; //떨어지는 속도
|
|||
private Vector2 inputAxis; //조이스틱 인풋값
|
|||
private CharacterController character; //이동용 컴포넌트
|
|||
private XRRig rig; //카메라 바라보는 방향 가져오기 위해 레퍼런스 가져옴
|
|||
|
|||
|
|||
// Start is called before the first frame update
|
|||
void Start() |
|||
{ |
|||
character = GetComponent<CharacterController>(); |
|||
rig = GetComponent<XRRig>(); |
|||
} |
|||
|
|||
// Update is called once per frame
|
|||
void Update() |
|||
{ |
|||
//XRNode로 device가져와서 인풋값 받기
|
|||
InputDevice device = InputDevices.GetDeviceAtXRNode(inputSource); |
|||
device.TryGetFeatureValue(CommonUsages.primary2DAxis, out inputAxis); |
|||
} |
|||
|
|||
//CharacterController는 물리적으로 반응하며 움직이기 떄문에 FixedUpdate에서 처리해줘야 함
|
|||
private void FixedUpdate() //0.02 * 50 = 1
|
|||
{ |
|||
CapsuleFollowHeadset(); |
|||
|
|||
//※사원수를 쓰는 이유 : https://hoodymong.tistory.com/3
|
|||
//카메라의 y 앵글값(=Yaw)을 사원수(Quaternion)으로 변환
|
|||
Quaternion headYaw = Quaternion.Euler(0f, rig.cameraGameObject.transform.eulerAngles.y, 0f); |
|||
|
|||
//Quaternion x Vector3 하면 회전
|
|||
Vector3 direction = headYaw * new Vector3(inputAxis.x, 0f, inputAxis.y); |
|||
|
|||
character.Move(direction * Time.fixedDeltaTime * speed); |
|||
|
|||
//중력 적용 : 발이 땅에 붙어있지 않을 때만 하강 속도가 증가하도록
|
|||
bool isGrounded = CheckIfGrounded(); |
|||
|
|||
if (isGrounded) |
|||
fallingSpeed = 0f; |
|||
else |
|||
fallingSpeed += gravity * Time.fixedDeltaTime; |
|||
|
|||
character.Move(Vector3.up * fallingSpeed * Time.fixedDeltaTime); |
|||
} |
|||
|
|||
//발이 땅에 붙었는지?를 리턴하는 함수
|
|||
private bool CheckIfGrounded() |
|||
{ |
|||
Vector3 rayStart = transform.TransformPoint(character.center); //CharacterController의 중심점을 월드 스페이스 변환
|
|||
float rayLength = character.center.y + 0.01f; //광선 길이는 CC의 키/2 보다 살짝만 더 길게해서 발 밑을 확실히 체크하도록 해줌
|
|||
bool hasHit = Physics.SphereCast(rayStart, character.radius, Vector3.down, out RaycastHit hitInfo, rayLength, groundLayer); |
|||
|
|||
return hasHit; |
|||
} |
|||
|
|||
//캐릭터 컨트롤러의 캡슐이 내 머리 위치 따라오도록 설정
|
|||
void CapsuleFollowHeadset() |
|||
{ |
|||
//카메라의 위치를 자기 자신 기준 local 좌표로 보정하여 저장
|
|||
Vector3 capsuleCenter = transform.InverseTransformPoint(rig.cameraGameObject.transform.position); |
|||
|
|||
//Capsule의 높이를 rig의 높이 + 약간의 여유값에 맞춰줌
|
|||
character.height = rig.cameraInRigSpaceHeight + additionalHeight; |
|||
|
|||
//수평(x, z) 좌표는 capsuleCenter를 따라가고, y는 character.height의 절반으로 해서 발을 땅에 붙이도록 해줌
|
|||
character.center = new Vector3(capsuleCenter.x, character.height / 2f, capsuleCenter.z); |
|||
} |
|||
} |
|||
@ -0,0 +1,80 @@ |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using UnityEngine; |
|||
using UnityEngine.UI; |
|||
using UnityEngine.XR.Interaction.Toolkit; |
|||
using DG.Tweening; //DOTween 네임스페이스
|
|||
using UnityEngine.Assertions; |
|||
|
|||
public class FadeTeleportationProvider : TeleportationProvider |
|||
{ |
|||
public Image fadeScreen; |
|||
public Color fadeColor = Color.black; |
|||
public float fadeDuration = 0.5f; |
|||
|
|||
// Start is called before the first frame update
|
|||
void Start() |
|||
{ |
|||
startLocomotion += FadeTeleportationProvider_startLocomotion; |
|||
endLocomotion += FadeTeleportationProvider_endLocomotion; |
|||
fadeScreen.color = fadeColor; |
|||
} |
|||
|
|||
private void FadeTeleportationProvider_startLocomotion(LocomotionSystem obj) |
|||
{ |
|||
print("Start Locomotion"); |
|||
//페이드아웃이 끝나면 Teleport 함수를 실행해라
|
|||
fadeScreen.DOFade(1f, fadeDuration).OnComplete(Teleport); |
|||
} |
|||
|
|||
private void FadeTeleportationProvider_endLocomotion(LocomotionSystem obj) |
|||
{ |
|||
print("End Locomotion"); |
|||
fadeScreen.DOFade(0f, fadeDuration); |
|||
} |
|||
|
|||
protected override void Update() |
|||
{ |
|||
//텔레포트 요청
|
|||
if (!validRequest || !BeginLocomotion()) |
|||
return; |
|||
} |
|||
|
|||
void Teleport() |
|||
{ |
|||
print("Begin TeleporT!!!"); |
|||
|
|||
//실제 텔레포트 하는 부분
|
|||
var xrRig = system.xrRig; |
|||
if (xrRig != null) |
|||
{ |
|||
switch (currentRequest.matchOrientation) |
|||
{ |
|||
case MatchOrientation.WorldSpaceUp: |
|||
xrRig.MatchRigUp(Vector3.up); |
|||
break; |
|||
case MatchOrientation.TargetUp: |
|||
xrRig.MatchRigUp(currentRequest.destinationRotation * Vector3.up); |
|||
break; |
|||
case MatchOrientation.TargetUpAndForward: |
|||
xrRig.MatchRigUpCameraForward(currentRequest.destinationRotation * Vector3.up, currentRequest.destinationRotation * Vector3.forward); |
|||
break; |
|||
case MatchOrientation.None: |
|||
// Change nothing. Maintain current rig rotation.
|
|||
break; |
|||
default: |
|||
Assert.IsTrue(false, $"Unhandled {nameof(MatchOrientation)}={currentRequest.matchOrientation}."); |
|||
break; |
|||
} |
|||
|
|||
var heightAdjustment = xrRig.rig.transform.up * xrRig.cameraInRigSpaceHeight; |
|||
|
|||
var cameraDestination = currentRequest.destinationPosition + heightAdjustment; |
|||
|
|||
xrRig.MoveCameraToWorldLocation(cameraDestination); |
|||
} |
|||
|
|||
EndLocomotion(); |
|||
validRequest = false; |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using UnityEngine; |
|||
using UnityEngine.XR.Interaction.Toolkit; |
|||
|
|||
public class LocomotionController : MonoBehaviour |
|||
{ |
|||
public XRController leftTeleportRay; |
|||
public XRController rightTeleportRay; |
|||
public InputHelpers.Button teleportActivationButton; //텔레포트 활성화 버튼
|
|||
public float activationThreshold = 0.1f; //살짝만 누르면 텔레포트 활성화 되도록
|
|||
|
|||
//각 손의 텔레포트 활성화 할것인가? => UnityEvent 에서 설정
|
|||
public bool EnableLeftTeleport { get; set; } = true; |
|||
public bool EnableRightTeleport { get; set; } = true; |
|||
|
|||
public XRRayInteractor leftRayInteractor; |
|||
public XRRayInteractor rightRayInteractor; |
|||
|
|||
// Update is called once per frame
|
|||
void Update() |
|||
{ |
|||
//각 컨트롤러의 텔레포트를 활성화/비활성화
|
|||
if (leftTeleportRay) |
|||
{ |
|||
//InteractorRay가 무엇에 닿았는가?
|
|||
bool isLeftInteractorRayHovering = leftRayInteractor.TryGetHitInfo(out Vector3 pos, out Vector3 norm, out int posInLine, out bool isValid); |
|||
leftTeleportRay.gameObject.SetActive(CheckIfActivated(leftTeleportRay) && EnableLeftTeleport && !isLeftInteractorRayHovering); |
|||
} |
|||
|
|||
|
|||
if (rightTeleportRay) |
|||
{ |
|||
bool isRightInteractorRayHovering = rightRayInteractor.TryGetHitInfo(out Vector3 pos, out Vector3 norm, out int posInLine, out bool isValid); |
|||
rightTeleportRay.gameObject.SetActive(CheckIfActivated(rightTeleportRay) && EnableRightTeleport && !isRightInteractorRayHovering); |
|||
} |
|||
|
|||
} |
|||
|
|||
//특정 컨트롤러의 특정 버튼이 눌렀는가? 를 리턴하는 함수
|
|||
public bool CheckIfActivated(XRController controller) |
|||
{ |
|||
//controller의 활성화 버튼을 activationThreshold 이상으로 눌렀으면, 그 값을 저장하고 리턴
|
|||
InputHelpers.IsPressed(controller.inputDevice, teleportActivationButton, out bool isActivated, activationThreshold); |
|||
|
|||
return isActivated; |
|||
} |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using UnityEngine; |
|||
using UnityEngine.Events; |
|||
|
|||
public class ObjectActivator : MonoBehaviour |
|||
{ |
|||
public bool Toggle; |
|||
public UnityEvent OnActivated; |
|||
public UnityEvent OnDeactivated; |
|||
public AudioClip audioClip; |
|||
public AudioSource audioSource; |
|||
|
|||
bool m_Activated = false; |
|||
|
|||
public void Activated() |
|||
{ |
|||
if (Toggle) |
|||
{ |
|||
if (m_Activated) |
|||
OnDeactivated.Invoke(); |
|||
else |
|||
OnActivated.Invoke(); |
|||
m_Activated = !m_Activated; |
|||
audioSource.PlayOneShot(audioClip); |
|||
} |
|||
else |
|||
{ |
|||
OnActivated.Invoke(); |
|||
m_Activated = true; |
|||
|
|||
} |
|||
} |
|||
|
|||
public void Deactivated() |
|||
{ |
|||
if (!Toggle) |
|||
{ |
|||
OnDeactivated.Invoke(); |
|||
m_Activated = false; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using UnityEngine; |
|||
using UnityEngine.SceneManagement; |
|||
|
|||
public class SceneLoad : MonoBehaviour |
|||
{ |
|||
public void ChangeScene(string sceneName) |
|||
{ |
|||
SceneManager.LoadScene(sceneName); |
|||
} |
|||
|
|||
public void OnApplicationQuit() |
|||
{ |
|||
Application.Quit(); |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using UnityEngine; |
|||
using UnityEngine.XR.Interaction.Toolkit; |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Special script that work with the XRExclusiveSocket script. This allow to define a SocketType and if that SocketType
|
|||
/// does not match the XRExclusiveSocket SocketType, this won't be accepted by the socket as a valid target
|
|||
/// </summary>
|
|||
[RequireComponent(typeof(XRBaseInteractable))] |
|||
public class SocketTarget : MonoBehaviour |
|||
{ |
|||
public string SocketType; |
|||
public XRInteractableEvent SocketedEvent; |
|||
public bool DisableSocketOnSocketed; |
|||
|
|||
void Awake() |
|||
{ |
|||
var interactable = GetComponent<XRBaseInteractable>(); |
|||
|
|||
interactable.onSelectEntered.AddListener(SelectedSwitch); |
|||
} |
|||
|
|||
public void SelectedSwitch(XRBaseInteractor interactor) |
|||
{ |
|||
var socketInteractor = interactor as XRExclusiveSocketInteractor; |
|||
|
|||
if(socketInteractor == null) |
|||
return; |
|||
|
|||
if(SocketType != socketInteractor.AcceptedType) |
|||
return; |
|||
|
|||
if (DisableSocketOnSocketed) |
|||
{ |
|||
//TODO : find a better way, delay feel very wrong
|
|||
StartCoroutine(DisableSocketDelayed(socketInteractor)); |
|||
} |
|||
|
|||
SocketedEvent.Invoke(interactor); |
|||
} |
|||
|
|||
IEnumerator DisableSocketDelayed(XRExclusiveSocketInteractor interactor) |
|||
{ |
|||
yield return new WaitForSeconds(0.5f); |
|||
interactor.socketActive = false; |
|||
} |
|||
} |
|||
@ -0,0 +1,108 @@ |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using UnityEngine; |
|||
using UnityEngine.XR.Interaction.Toolkit; |
|||
|
|||
public class TwoHandGrabInteractable : XRGrabInteractable |
|||
{ |
|||
public List<XRSimpleInteractable> secondHandGrabPoints = new List<XRSimpleInteractable>(); //두번째 손 잡이용 XRSimpleInteractable들
|
|||
private XRBaseInteractor secondInteractor; //두 번째 손의 Interactor
|
|||
private Quaternion attachInitialRotation; //첫 번째 손의 원래 회전값
|
|||
|
|||
public enum TwoHandRotationType { None, First, Second } //어느 손의 기준으로 총을 회전할 것인가?
|
|||
public TwoHandRotationType twoHandRotationType; |
|||
|
|||
private Quaternion initialRotationOffset; //두번째 손으로 잡을 때, 회전각의 차이값을 저장
|
|||
|
|||
void Start() |
|||
{ |
|||
//두 번째 손을 잡았을 때 / 손았을 때 실행할 리스너 함수 등록
|
|||
foreach (var item in secondHandGrabPoints) |
|||
{ |
|||
item.onSelectEntered.AddListener(OnSecondHandGrab); |
|||
item.onSelectExited.AddListener(OnSecondHandRelease); |
|||
} |
|||
} |
|||
|
|||
//사물을 잡고 있는 동안 계쏙 실행
|
|||
public override void ProcessInteractable(XRInteractionUpdateOrder.UpdatePhase updatePhase) |
|||
{ |
|||
//양 손으로 잡고 있을 때만 회전값을 계산하도록
|
|||
if(selectingInteractor && secondInteractor) |
|||
{ |
|||
//회전값을 계산
|
|||
//오브젝트의 Pivot중심점이 아닌, 첫 번째 손을 잡은 AttachTransform을 기준으로 회전하기 위해 잡은 손의 attachTransform의 회전각을 변경해줌
|
|||
selectingInteractor.attachTransform.rotation = GetTwoHandRotation() * initialRotationOffset; |
|||
} |
|||
|
|||
base.ProcessInteractable(updatePhase); |
|||
} |
|||
|
|||
|
|||
private Quaternion GetTwoHandRotation() |
|||
{ |
|||
Quaternion targetRotation; |
|||
|
|||
switch (twoHandRotationType) |
|||
{ |
|||
default: |
|||
case TwoHandRotationType.None: |
|||
//회전하지 않음
|
|||
targetRotation = Quaternion.LookRotation(secondInteractor.transform.position - selectingInteractor.transform.position); |
|||
break; |
|||
case TwoHandRotationType.First: |
|||
targetRotation = Quaternion.LookRotation(secondInteractor.transform.position - selectingInteractor.transform.position, selectingInteractor.attachTransform.up); |
|||
break; |
|||
case TwoHandRotationType.Second: |
|||
targetRotation = Quaternion.LookRotation(secondInteractor.transform.position - selectingInteractor.transform.position, secondInteractor.attachTransform.up); |
|||
break; |
|||
} |
|||
|
|||
return targetRotation; |
|||
} |
|||
|
|||
public override bool IsSelectableBy(XRBaseInteractor interactor) |
|||
{ |
|||
//이미 다른 Interactor가 잡고 있는가?
|
|||
//현재 잡고 있는 Interactor가 있고, 그게 지금 새로 잡으려는 interactor가 아닌 경우에만 true
|
|||
bool isAlreadyGrabbed = selectingInteractor && !interactor.Equals(selectingInteractor); |
|||
|
|||
//'다른 Interactor'가 잡고 있지 않을 때'라는 조건 추가
|
|||
return base.IsSelectableBy(interactor) && !isAlreadyGrabbed; |
|||
} |
|||
|
|||
public void OnSecondHandGrab(XRBaseInteractor interactor) |
|||
{ |
|||
print("Second Hand Grab"); |
|||
secondInteractor = interactor; //두번째 Interactor 설정
|
|||
|
|||
//Quaternion의 회전값의 차이를 구하기 위해, 한 Quaternion의 Inverse값에 다른 Quaternion을 곱해줌
|
|||
initialRotationOffset = Quaternion.Inverse(GetTwoHandRotation()) * selectingInteractor.attachTransform.rotation; |
|||
} |
|||
|
|||
public void OnSecondHandRelease(XRBaseInteractor interactor) |
|||
{ |
|||
print("Second Hand Release"); |
|||
secondInteractor = null; //두번쨰 Interactor 비워줌
|
|||
} |
|||
|
|||
protected override void OnSelectEntered(XRBaseInteractor interactor) |
|||
{ |
|||
base.OnSelectEntered(interactor); |
|||
|
|||
print("First Hand Enter"); |
|||
|
|||
attachInitialRotation = interactor.attachTransform.localRotation; //첫 번째 손의 원래 회전값 저장
|
|||
} |
|||
|
|||
protected override void OnSelectExited(XRBaseInteractor interactor) |
|||
{ |
|||
base.OnSelectExited(interactor); |
|||
|
|||
print("First Hand Exit"); |
|||
|
|||
secondInteractor = null; //첫번째 손을 놓으면, 두번째 손도 놓게
|
|||
|
|||
interactor.attachTransform.localRotation = attachInitialRotation; //첫 번째 손의 회전값 리셋
|
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using UnityEngine; |
|||
using UnityEngine.XR.Interaction.Toolkit; |
|||
|
|||
public class XRExclusiveSocketInteractor : XRSocketInteractor |
|||
{ |
|||
public string AcceptedType; //SocketTarget의 SocketType과 비교할 값
|
|||
|
|||
|
|||
public override bool CanSelect(XRBaseInteractable interactable) |
|||
{ |
|||
//이 소켓과 인터렉션하려는 사물의 SocketTarget을 가져오고
|
|||
SocketTarget socketTarget = interactable.GetComponent<SocketTarget>(); |
|||
|
|||
//SocketTarget이 없으면 선택 불가
|
|||
if (socketTarget == null) |
|||
return false; |
|||
|
|||
//baseClass에서도 선택할 수 있고, SocketTarget의 타입도 같으면 선택 가능하게
|
|||
return base.CanSelect(interactable) && (socketTarget.SocketType == AcceptedType); |
|||
} |
|||
|
|||
//선택 가능한 사물만이 Hover 가능하도록
|
|||
public override bool CanHover(XRBaseInteractable interactable) |
|||
{ |
|||
return CanSelect(interactable); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using UnityEngine; |
|||
using UnityEngine.XR.Interaction.Toolkit; |
|||
|
|||
public class XROffsetGrabInteractable : XRGrabInteractable |
|||
{ |
|||
//Pivot을 리셋시키기 위해 원래 로컬 위치, 회전 저장
|
|||
private Vector3 initialAttachLocalPos; |
|||
private Quaternion initialAttachLocalRot; |
|||
|
|||
protected virtual void Start() |
|||
{ |
|||
//attachTransform 이 없다면 만들어준다.
|
|||
if(!attachTransform) |
|||
{ |
|||
GameObject pivot = new GameObject("Attach Pivot"); //피봇용 빈 게임오브젝트를 만들어서
|
|||
pivot.transform.SetParent(transform, false); //자신의 자식으로 넣고, 위치를 0, 0, 0 으로
|
|||
attachTransform = pivot.transform; |
|||
} |
|||
|
|||
initialAttachLocalPos = attachTransform.localPosition; |
|||
initialAttachLocalRot = attachTransform.localRotation; |
|||
} |
|||
|
|||
//잡는 순간 실행되는 함수 오버라이드
|
|||
protected override void OnSelectEntering(XRBaseInteractor interactor) |
|||
{ |
|||
//직접 손으로 잡으면 Offset Grabbing
|
|||
if(interactor is XRDirectInteractor) |
|||
{ |
|||
attachTransform.position = interactor.attachTransform.position; |
|||
attachTransform.rotation = interactor.attachTransform.rotation; |
|||
} |
|||
else |
|||
{ |
|||
//그 외에는 Pivot값을 원래대로 리셋시켜줌
|
|||
attachTransform.localPosition = initialAttachLocalPos; |
|||
attachTransform.localRotation = initialAttachLocalRot; |
|||
} |
|||
|
|||
base.OnSelectEntering(interactor); |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue