commit 8c45104f18f021a2847523115b1b75316112c7fd Author: username Date: Thu Jul 1 22:18:17 2021 +0900 commit diff --git a/ContinuousMovement.cs b/ContinuousMovement.cs new file mode 100644 index 0000000..4d4223f --- /dev/null +++ b/ContinuousMovement.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(); + rig = GetComponent(); + } + + // 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); + } +} diff --git a/FadeTeleportationProvider.cs b/FadeTeleportationProvider.cs new file mode 100644 index 0000000..d356c31 --- /dev/null +++ b/FadeTeleportationProvider.cs @@ -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; + } +} diff --git a/LocomotionController.cs b/LocomotionController.cs new file mode 100644 index 0000000..4de9609 --- /dev/null +++ b/LocomotionController.cs @@ -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; + } +} diff --git a/ObjectActivator.cs b/ObjectActivator.cs new file mode 100644 index 0000000..b3b8fcf --- /dev/null +++ b/ObjectActivator.cs @@ -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; + } + } +} diff --git a/SceneLoad.cs b/SceneLoad.cs new file mode 100644 index 0000000..471b9ae --- /dev/null +++ b/SceneLoad.cs @@ -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(); + } +} diff --git a/SocketTarget.cs b/SocketTarget.cs new file mode 100644 index 0000000..5fc8ae7 --- /dev/null +++ b/SocketTarget.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.XR.Interaction.Toolkit; + + +/// +/// 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 +/// +[RequireComponent(typeof(XRBaseInteractable))] +public class SocketTarget : MonoBehaviour +{ + public string SocketType; + public XRInteractableEvent SocketedEvent; + public bool DisableSocketOnSocketed; + + void Awake() + { + var interactable = GetComponent(); + + 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; + } +} diff --git a/TwoHandGrabInteractable.cs b/TwoHandGrabInteractable.cs new file mode 100644 index 0000000..62ac4ed --- /dev/null +++ b/TwoHandGrabInteractable.cs @@ -0,0 +1,108 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.XR.Interaction.Toolkit; + +public class TwoHandGrabInteractable : XRGrabInteractable +{ + public List secondHandGrabPoints = new List(); //두번째 손 잡이용 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; //첫 번째 손의 회전값 리셋 + } +} diff --git a/XRExclusiveSocketInteractor.cs b/XRExclusiveSocketInteractor.cs new file mode 100644 index 0000000..c696d9a --- /dev/null +++ b/XRExclusiveSocketInteractor.cs @@ -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이 없으면 선택 불가 + if (socketTarget == null) + return false; + + //baseClass에서도 선택할 수 있고, SocketTarget의 타입도 같으면 선택 가능하게 + return base.CanSelect(interactable) && (socketTarget.SocketType == AcceptedType); + } + + //선택 가능한 사물만이 Hover 가능하도록 + public override bool CanHover(XRBaseInteractable interactable) + { + return CanSelect(interactable); + } + +} diff --git a/XROffsetGrabInteractable.cs b/XROffsetGrabInteractable.cs new file mode 100644 index 0000000..c411a59 --- /dev/null +++ b/XROffsetGrabInteractable.cs @@ -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); + } +}