This document is about: FUSION 1
SWITCH TO

VR 공유 - 로컬 리그 잡기

Level 4
이 페이지에서는 VR Shared 기술 샘플을 위한 잡기 시스템의 대안적 구현에 대해 설명합니다. 리그 설정 방법 또는 이 샘플과 관련된 기타 세부 정보를 이해하려면 먼저 기본 VR 공유 페이지를 참조하십시오.

기본 VR 공유 샘플 잡기 로직과의 차이점

이 잡기 시스템은 VR 공유 기술 샘플에 설명된 것과 매우 유사하지만 일부 상황에서 유용할 수 있는 몇 가지 차이점이 있습니다:

  • 붙잡을 수 있는 물체를 감지하는 콜라이더가 네트워크 장치 대신 하드웨어 장치에 위치합니다
  • 이러한 방식으로, 네트워크 리그가 아직 생성되지 않은 경우에도 사용할 수 있습니다(전체 오프라인 상황처럼)
  • 게다가, 의도적으로 네트워크 리그를 하드웨어 리그와 다른 위치에 배치하는 상황에서 사용될 수 있습니다. (만약 네트워크 리그 위치가 텔레포트 중에 평활화된다면...)

다운로드

이 샘플은 VR 공유 페이지 다운로드 페이지에 포함되어 있습니다. 로컬 리그 잡기를 시연하는 씬은 Scenes/AlternativeHardwareBasedGrabbingDemo 폴더에 있습니다

개요

여기서 잡기 로직은 두 부분으로 나뉩니다:

  • 하드웨어 핸드가 잡을 수 있는 객체(Grabber 및 Grabbable 클래스)에 대한 잡기/놓기 작업을 트리거 했을 때 실제 잡기/놓기를 감지한 비네트워크 로컬 부분
  • 모든 플레이어가 붙잡기 상태를 인식하고 붙잡기 손을 따르도록 실제 위치 변경을 관리하는 네트워크 부품(NetworkGrabber 및 NetworkGrabbable 클래스).
fusion vr shared local rig grabbing logic

노트: 코드에는 오프라인에서 사용할 때 로컬 부품이 다음 자체를 관리할 수 있도록 하기 위한 몇 개의 줄이 포함되어 있습니다. 예를 들어 오프라인 로비에 동일한 컴포넌트가 사용되는 경우입니다. 그러나 이 문서는 실제 네트워크 사용에 중점을 두고 있습니다.

상세 내용

잡기

각 손에 있는 HardwareHand 클래스는 업데이트할 때마다 isGrabbing 부울 값을 업데이트합니다. 사용자가 그립 버튼을 누르면 부울 값이 참이 됩니다. updateGrabWithAction 부울 값은 마우스와 키보드로 구동할 수 있는 리그 버전인 데스크톱 리그를 지원하는 데 사용됩니다(이 부울 값은 데스크톱 모드의 경우 False, VR 모드의 경우 True로 설정해야 합니다).

C#

 protected virtual void Update()
 {
    // update hand pose
    // (...)

    // update hand interaction
    if(updateGrabWithAction) isGrabbing = grabAction.action.ReadValue<float>() > grabThreshold;
}

붙잡을 수 있는 물체와의 충돌을 감지하기 위해 각 하드웨어 핸드에 간단한 박스 콜라이더가 위치하며, 이 손에 배치된 Grabber 컴포넌트가 사용합니다. 충돌이 발생하면 OnTriggerStay(Collider other) 메소드가 호출됩니다.

먼저, 물체가 이미 잡혔는지 확인합니다. 단순화를 위해 이 샘플에서는 여러 번 잡는 것이 허용되지 않습니다.

C#

// Exit if an object is already grabbed
if (grabbedObject != null)
{
    // It is already the grabbed object or another, but we don't allow shared grabbing here
    return;
}

그러고 나서 다음을 확인합니다:

  • 충돌한 물체를 잡을 수 있습니다(Grabbable 컴포넌트가 있음)
  • 사용자가 그립 버튼을 누릅니다

이러한 조건이 충족되면 Grabbable Grab 메소드 덕분에 붙잡힌 객체가 손(위 그림의 (1))을 따르도록 요청됩니다

C#

Grabbable grabbable;

if (lastCheckedCollider == other)
{
    grabbable = lastCheckColliderGrabbable;
}
else
{
    grabbable = other.GetComponentInParent<Grabbable>();
}
// To limit the number of GetComponent calls, we cache the latest checked collider grabbable result
lastCheckedCollider = other;
lastCheckColliderGrabbable = grabbable;
if (grabbable != null)
{
    if (hand.isGrabbing) Grab(grabbable);
}

잡기 동기화

Grabbable Grab() 메서드는 잡는 위치 오프셋을 저장합니다. 또한 NetworkGrabbable 관련 컴포넌트에 networkGrabbable.LocalGrab() 호출을 통해 로컬 사용자에 의한 잡기가 발생했음을 알려줍니다(위 그림의 (2)).

C#

public virtual void Grab(Grabber newGrabber, Transform grabPointTransform = null)
{
    if (onWillGrab != null) onWillGrab.Invoke(newGrabber);

    // Find grabbable position/rotation in grabber referential
    localPositionOffset = newGrabber.transform.InverseTransformPoint(transform.position);
    localRotationOffset = Quaternion.Inverse(newGrabber.transform.rotation) * transform.rotation;
    currentGrabber = newGrabber;

    if (networkGrabbable)
    {
        networkGrabbable.LocalGrab();
    }
    else
    {
        // We handle the following if we are not online (online, the DidGrab will be called by the NetworkGrabbable DidGrab, itself called on all clients by HandleGrabberChange when the grabber networked var has changed)
        DidGrab();
    }
}

NetworkGrabbable에서, LocalGrab() 호출 :

  • 먼저 객체에 대한 상태 권한에 요청: 이렇게 하면 [Networked] 속성 값을 저장할 수 있습니다
  • 상태 권한이 수신되면 [Networked] 속성에 붙잡기(그래버 및 오프셋)를 설명하는 세부 정보를 저장합니다

c#

public async virtual void LocalGrab()
{
    // Ask and wait to receive the stateAuthority to move the object
    isTakingAuthority = true;
    await Object.WaitForStateAuthority();
    isTakingAuthority = false;

    // We waited to have the state authority before setting Networked vars
    LocalPositionOffset = grabbable.localPositionOffset;
    LocalRotationOffset = grabbable.localRotationOffset;

    // Update the CurrentGrabber in order to start following position in the FixedUpdateNetwork
    CurrentGrabber = grabbable.currentGrabber.networkGrabber;
}

노트: WaitForStateAuthority는 헬퍼 확장 메소 등입니다

c#

public static async Task<bool> WaitForStateAuthority(this NetworkObject o, float maxWaitTime = 8)
{
    float waitStartTime = Time.time;
    o.RequestStateAuthority();
    while (!o.HasStateAuthority && (Time.time - waitStartTime) < maxWaitTime)
    {
        await System.Threading.Tasks.Task.Delay(1);
    }
    return o.HasStateAuthority;
}

The [Networked] 변수 변경은 모든 클라이언트들((3) 위 다이어그램)이 OnGrabberChanged를 트리거 합니다:

c#

[Networked(OnChanged = nameof(OnGrabberChanged))]
public NetworkGrabber CurrentGrabber { get; set; }

내부에서,LoadOld()LoadNew()는 호출로 CurrentGrabber 값을 이전 값과 비교할 수 있습니다:

c#

// Callback that will be called on all clients on grabber change (grabbing/ungrabbing)
public static void OnGrabberChanged(Changed<NetworkGrabbable> changed)
{
    // We load the previous state to find what was the grabber before
    changed.LoadOld();
    NetworkGrabber previousGrabber = null;
    if (changed.Behaviour.CurrentGrabber != null)
    {
        previousGrabber = changed.Behaviour.CurrentGrabber;
    }
    // We reload the current state to see the current grabber
    changed.LoadNew();

    changed.Behaviour.HandleGrabberChange(previousGrabber);
}

이러한 방식으로 모든 클라이언트는 관련이 있을 경우 DidGrab()DidUngrab()을 호출할 수 있으며, 두 메서드 모두 Grabbable DidGrab()DidUngrab() 호출로 전달됩니다(위 그림의 (4)):

c#

protected virtual void HandleGrabberChange(NetworkGrabber previousGrabber)
{
    if (previousGrabber)
    {
        DidUngrab();
    }
    if (CurrentGrabber)
    {
        DidGrab();
    }
}

protected virtual void DidGrab()
{
    grabbable.DidGrab();
    if (onDidGrab != null) onDidGrab.Invoke(CurrentGrabber);
}

protected virtual void DidUngrab()
{
    grabbable.DidUngrab();
    if (onDidUngrab != null) onDidUngrab.Invoke();
}

이러한 방식으로 Grabbable 컴포넌트는 isKinematic 값을 적절하게 설정하고(잡힐 때 객체 물리학이 비활성화됨) 릴리즈 속도를 적용할 수 있습니다

c#

public virtual void DidGrab()
{
    // While grabbed, we disable physics forces on the object, to force a position based tracking
    if (rb) rb.isKinematic = true;
}

public virtual void DidUngrab()
{
    // We restore the default isKinematic state if needed
    if (rb) rb.isKinematic = expectedIsKinematic;

    // We apply release velocity if needed
    if (rb && rb.isKinematic == false && applyVelocityOnRelease)
    {
        rb.velocity = Velocity;
        rb.angularVelocity = AngularVelocity;
    }

    ResetVelocityTracking();
}

따르기

붙잡는 동안 객체 물리학이 비활성화되므로 현재 그래버를 따르는 것은 단순히 실제 위치로 순간이동하는 것입니다:

c#

public virtual void Follow(Transform followingtransform, Transform followedTransform, Vector3 localPositionOffsetToFollowed, Quaternion localRotationOffsetTofollowed)
{
    followingtransform.position = followedTransform.TransformPoint(localPositionOffsetToFollowed);
    followingtransform.rotation = followedTransform.rotation * localRotationOffsetTofollowed;
}

온라인 상태일 때 FixedUpdateNetwork 호출 중에 다음 코드가 호출됩니다(위 다이어그램의 (5)):

c#

public override void FixedUpdateNetwork()
{
    // We only update the object position if we have the state authority
    if (!Object.HasStateAuthority) return;

    if (!IsGrabbed) return;
    // Follow grabber, adding position/rotation offsets
    grabbable.Follow(followingtransform: transform, followedTransform: CurrentGrabber.transform, LocalPositionOffset, LocalRotationOffset);
}

위치 변경은 붙잡는 사용자 클라이언트(상태 권한)에서만 수행되며, 네트워크 변환은 모든 플레이어가 위치 업데이트를 수신하도록 보장합니다.

렌더링

Render()(NetworkGrabbable 클래스에는 필요한 경우 NetworkTransform 보간을 재정의하는 OrderAfter 지침이 있음) 동안 이루어진 외삽법(위 다이어그램의 (6))과 관련하여, 여기서 두 가지 경우를 처리해야 합니다:

  • 모든 클라이언트에 대해 객체를 잡는 동안 외삽: 객체 예상 위치가 알려져 있고 손 위치에 있어야 합니다. 따라서 붙잡을 수 있는 비주얼(즉. NetworkTransform의 보간 대상)은 손이 표시되는 위치에 있어야 합니다.
  • 객체 권한이 요청되는 동안 외삽: 몇 프레임 동안 CurrentGrabber가 아직 설정되지 않고 권한 요청이 보류 중입니다([Networked] 변수는 상태 권한이 있는 동안에만 설정될 수 있습니다). 따라서 IsGrabbed는 아직 사실로 돌아오지 않으며, 실제로 따라야 할 NetworkGrabberGrabbableGrabber 컴포넌트를 통해 찾아야 합니다.

c#

public override void Render()
{
    if (isTakingAuthority && extrapolateWhileTakingAuthority)
    {
        // If we are currently taking the authority on the object due to a grab, the network info are still not set
        //  but we will extrapolate anyway (if the option extrapolateWhileTakingAuthority is true) to avoid having the grabbed object staying still until we receive the authority
        ExtrapolateWhileTakingAuthority();
        return;
    }

    // No need to extrapolate if the object is not grabbed
    if (!IsGrabbed) return;

    // Extrapolation: Make visual representation follow grabber, adding position/rotation offsets
    // We extrapolate for all users: we know that the grabbed object should follow accuratly the grabber, even if the network position might be a bit out of sync
    grabbable.Follow(followingtransform: networkTransform.InterpolationTarget.transform, followedTransform: CurrentGrabber.hand.networkTransform.InterpolationTarget.transform, LocalPositionOffset, LocalRotationOffset);
}

protected virtual void ExtrapolateWhileTakingAuthority()
{
    // No need to extrapolate if the object is not really grabbed
    if (grabbable.currentGrabber == null) return;
    NetworkGrabber networkGrabber = grabbable.currentGrabber.networkGrabber;

    // Extrapolation: Make visual representation follow grabber, adding position/rotation offsets
    // We use grabberWhileTakingAuthority instead of CurrentGrabber as we are currently waiting for the authority transfer: the network vars are not already set, so we use the temporary versions
    grabbable.Follow(followingtransform: networkTransform.InterpolationTarget.transform, followedTransform: networkGrabber.hand.networkTransform.InterpolationTarget.transform, grabbable.localPositionOffset, grabbable.localRotationOffset);
}
Back to top