This document is about: FUSION 1
SWITCH TO

VR Shared - ローカルリググラブ

Level 4
このページでは、V技術サンプル「VR Shared」Vのグラブシステムの代替実装を説明します。リグの設定方法など、本サンプルに関する詳細は、まずVR Sharedのページをご覧ください。

基本的なVR Sharedのサンプルグラビングロジックとの相違点

このグラブシステムは、VR Sharedの技術サンプルで説明したものと非常によく似ていますが、状況によっては有用ないくつかの相違点があります。

  • つかみやすいオブジェクトを検出するコライダーが、ネットワークリグではなく、ハードウェアリグにローカライズされている。
  • こうすることで、ネットワークリグがまだ生成されていないときでも使用できるようになります(完全なオフライン状態のように)。
  • また、意図的にネットワークリグをハードウェアリグと異なる位置に配置する場合(テレポート時にネットワークリグの位置が平滑化される場合など)にも使用できるようになります。

ダウンロード

このサンプルは、VR Shared page ダウンロードに含まれています。ローカルリググラビングのデモシーンは、Scenes/AlternativeHardwareBasedGrabbingDemo フォルダにあります。

概要

グラブロジックは、2つのパートに分かれています。

  • ハードウェアハンドがグラブ可能なオブジェクト(グラブする側とグラブ可能クラス)に対してグラブ/アングラブのアクションをトリガーしたときに、実際のグラブ/アングラブを検出するローカルパート(非ネットワーク化)。
  • ネットワークに接続された部分には、すべてのプレイヤーがグラブの状態を認識できるようにし、グラブするハンドに従った実際の位置変更を管理するクラス(NetworkGrabberとNetworkGrabbable)があります。
fusion vr shared local rig grabbing logic
  • 注:このコードには、オフラインで使用する際に、ローカルパートが以下を自ら管理できるようにするための行がいくつか含まれています。例えば、同じコンポーネントをオフラインのロビーに使用するようなユースケースです。しかし、このドキュメントでは、実際にネットワークで使用することに焦点を当てます。

詳細

グラブ

各手にある HardwareHand クラスは、更新のたびに isGrabbing bool を更新します : この bool は、ユーザーがグリップボタンを押したときに true になります。 なお、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);
}

グラブ同期

GrabbableGrab() メソッドは、グラブ位置のオフセットを保存します。 また、NetworkGrabbable.LocalGrab()の呼び出しにより、ローカルユーザーによるグラブが発生したこと(上図の(2))を NetworkGrabbable 関連コンポーネントに通知します。

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()を呼び出します。

  • まず、そのオブジェクトの状態権限を要求します。sそうることで、[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;
}

Networked]varの変更により、すべてのクライアントでOnGrabberChangedが発生します(上図の(3))。

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);
}

位置の変更は、グラブユーザー(状態権限者)のクライアントでのみ行われ、その後、NetworkTransformにより、すべてのプレーヤーが位置の更新を受け取るようにします。

レンダリング

Render()の中で行われる外挿(上図の(6))(NetworkGrabbableクラスは、必要に応じてNetworkTransformの外挿をオーバーライドするOrderAfter指令を持ちます)に関して、2つのケースを処理する必要があります。

  • オブジェクトが掴まれている間、すべてのクライアントに対して外挿を行います:オブジェクトの予想位置がわかっているので(手の位置にあるはずです)、グラブ可能なビジュアル(つまり NetworkTransform の補間ターゲット)は手のビジュアルの位置にある必要があります。
  • オブジェクトの権限が要求されている間、グラビングクライアントのための外挿:数フレームの間、CurrentGrabberはまだ設定されておらず、権限要求がまだ保留されています([Networked] varは状態の権限を持っている間のみ設定可能です)。そのため、IsGrabbedはまだtrueを返さず、追跡する実際のNetworkGrabberはローカルのGrabbableGrabberコンポーネントを通して見つける必要があります。

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