This document is about: FUSION 1
SWITCH TO

VR Shared

Level 4

概述

Fusion VR共享 展示了一個快速且方便的方法,以透過VR開始多人玩家遊戲或應用程式。

共享的或主機端/伺服器拓撲之間的選擇,必須由您的遊戲特性決定。在這個範例中,使用 共享模式

這個範例的目的在於闡明處理VR裝置的方式,並且提供一個基礎的傳送及拿取的示例。

fusion vr shared

在您開始之前

  • 專案已透過Unity 2021.3.7f1及Fusion 1.1.3f組建599進行開發
  • 為了運行範例,首先在PhotonEngine儀表板中建立一個Fusion應用程式帳號,並且將它貼上到即時設定(可從Fusion選單中到達)中的App Id Fusion欄位之中。然後載入Launch場景並且按下Play

下載

版本 發佈日期 下載
1.1.8 Sep 21, 2023 Fusion VR Shared 1.1.8 Build 276

處理輸入

中繼任務

  • 傳送:按下A、B、X、Y,或任何搖桿以顯示一個指標。您將在放開時傳送到任何已接受的目標
  • 拿取:首先將您的手放在物件上,然後使用控制器拿取按鈕來拿取它

滑鼠

在專案中包含一個基礎的桌面裝置。它意味著您可以使用滑鼠來進行基礎的互動。

  • 移動:按下您的滑鼠左鍵以顯示一個指標。您將在放開時傳送到任何已接受的目標
  • 旋轉:持續按下滑鼠右鍵,並且移動滑鼠以旋轉視角
  • 拿取:在一個物件上按下您的滑鼠左鍵以拿取它。

連線管理器

Connection Manager遊戲物件上安裝NetworkRunnerConnection Manager負責設置遊戲設定並且開始連線

C#

private async void Start()
{
    // Launch the connection at start
    if (connectOnStart) await Connect();
}

 public async Task Connect()
 {
    // Create the scene manager if it does not exist
    if (sceneManager == null) sceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>();

    if (onWillConnect != null) onWillConnect.Invoke();

    // Start or join (depends on gamemode) a session with a specific name
    var args = new StartGameArgs()
    {
        GameMode = mode,
        SessionName = roomName,
        Scene = SceneManager.GetActiveScene().buildIndex,
        SceneManager = sceneManager
    };
    await runner.StartGame(args);
}

執行INetworkRunnerCallbacks將允許Fusion NetworkRunner來和Connection Manager類別互動。在這個範例中,OnPlayerJoined回調用於在玩家加入遊戲階段時繁衍本機使用者預製件。

C#

public void OnPlayerJoined(NetworkRunner runner, PlayerRef player)
{
    if (player == runner.LocalPlayer)
    {
        // Spawn the user prefab for the local user
        NetworkObject networkPlayerObject = runner.Spawn(userPrefab, position: transform.position, rotation: transform.rotation, player, (runner, obj) => {
        });
    }
}

為了確保已建立的玩家物件在中斷連線時被終結,請確保檢查您的預製件的網路物件的 "共享模式設定 > 當狀態授權離開時終結"。

裝置

概述

在一個沉浸式的應用程式中,裝置描述了代表一個使用者時所需的所有可移動部件,通常是雙手、頭,以及遊玩區域(舉例而言,在使用者傳送時,是可以移動的個人空間),

在一個已連線工作階段,每位使用者由一個已連線裝置所代表,其中透過網路來同步它的各個部件位置。

fusion vr shared rigs logic

在組織及同步裝置部件方面,有幾種架構是可行且有效的。在此,一個單一NetworkObject代表一個使用者,其中包含多個巢狀NetworkTransforms,每個裝置部件附有一個。

在代表本機玩家的網路裝置的特定案例方面,這個裝置必須由硬體輸入來驅動。為了簡化這個流程,已建立了一個獨立的、非連線的裝置,稱為「Hardware rig」。它使用傳統的Unity元件以收集硬體輸入(比如TrackedPoseDriver)。

詳細資訊

裝置

RigInput架構中包含了驅動裝置的所有參數(其在空間中的位置及手的姿勢)。

C#

public struct RigInput : INetworkInput
{
    public Vector3 playAreaPosition;
    public Quaternion playAreaRotation;
    public Vector3 leftHandPosition;
    public Quaternion leftHandRotation;
    public Vector3 rightHandPosition;
    public Quaternion rightHandRotation;
    public Vector3 headsetPosition;
    public Quaternion headsetRotation;
    public HandCommand leftHandCommand;
    public HandCommand rightHandCommand;
}

Fusion NetworkRunner輪詢使用者輸入時,HardwareRig類別更新架構。為了做到這點,它從多種硬體裝置部件來收集輸入參數。

C#


public virtual void OnInput(NetworkRunner runner, NetworkInput input)
{
    RigInput rigInput = PrepareRigInput();
    input.Set(rigInput);
}

protected virtual RigInput PrepareRigInput()
{
    RigInput rigInput = new RigInput();
    rigInput.playAreaPosition = transform.position;
    rigInput.playAreaRotation = transform.rotation;
    rigInput.leftHandPosition = leftHand.transform.position;
    rigInput.leftHandRotation = leftHand.transform.rotation;
    rigInput.rightHandPosition = rightHand.transform.position;
    rigInput.rightHandRotation = rightHand.transform.rotation;
    rigInput.headsetPosition = headset.transform.position;
    rigInput.headsetRotation = headset.transform.rotation;
    rigInput.leftHandCommand = leftHand.handCommand;
    rigInput.rightHandCommand = rightHand.handCommand;
    return rigInput;
}

請注意,在共享模式中,不是強制需要使用一個網路Input介面。但是如果稍後需要移轉到主機端或伺服器模式,這樣做將簡化程式碼重構。

然後,與本機使用者相關的已連線裝置接收這個輸入,並且設置每個已連線裝置部件為簡單地遵循來自配對的硬體裝置部件的輸入參數。

NetworkRig元件,位於使用者預製件,針對所有巢狀裝置部件來管理這個追蹤。

在共享模式中,只在本機使用者,狀態授權上將這個輸入轉送到NetworkRig。為了確保這些更改在代理上被複製(在其他玩家應用程式上的這個玩家物件的執行個體),則必須完成其他事情:

  • 針對裝置部件位置及旋轉,這些裝置部件有NetworkTransform元件,當更新Transform位置或旋轉時其已經處理這個同步
  • 針對應用程式特定的資料,比如手姿勢,設定已連線變數(附有[Networked]標籤的屬性),並且在它們的值更改上觸發回調,以處理它們的新值。

C#

// As we are in shared topology, having the StateAuthority means we are the local user
public bool IsLocalNetworkRig => Object.HasStateAuthority;
public override void Spawned()
{
    base.Spawned();
    if (IsLocalNetworkRig)
    {
        hardwareRig = FindObjectOfType<HardwareRig>();
        if (hardwareRig == null) Debug.LogError("Missing HardwareRig in the scene");
    }
}

public override void FixedUpdateNetwork()
        {
            base.FixedUpdateNetwork();
            // update the rig at each network tick
            if (GetInput<RigInput>(out var input))
            {
                ApplyInputToRigParts(input);
                ApplyInputToHandPoses(input);
            }
        }

protected virtual void ApplyInputToRigParts(RigInput input)
        {
            transform.position = input.playAreaPosition;
            transform.rotation = input.playAreaRotation;
            leftHand.transform.position = input.leftHandPosition;
            leftHand.transform.rotation = input.leftHandRotation;
            rightHand.transform.position = input.rightHandPosition;
            rightHand.transform.rotation = input.rightHandRotation;
            headset.transform.position = input.headsetPosition;
            headset.transform.rotation = input.headsetRotation;
        }

protected virtual void ApplyInputToHandPoses(RigInput input)
        {
            // we update the hand pose info. It will trigger on network hands OnHandCommandChange on all clients, and update the hand representation accordingly
            leftHand.HandCommand = input.leftHandCommand;
            rightHand.HandCommand = input.rightHandCommand;
        }

除了在FixedUpdateNetwork()期間移動網路裝置部件位置之外,NetworkRig元件也處理本機外插:在Render()期間,針對在這個物件上有輸入授權的本機使用者,處理各種裝置部件的NetworkTransforms的圖形代表的內插補點目標,將使用最新的本機硬體裝置部件資料來移動。

它確保了本機使用者總是有它們自己的手的最新的位置(以避免潛在的不穩定),就算畫面重新整理率比網路刷新率更高的情況也是如此。

在類別之前的[OrderAfter]標籤確保將在NetworkTransform方法之後調用NetworkRig Render(),以便NetworkRig可以覆寫NetworkTransform擁有的內插補點目標的處理。

C#

public override void Render()
{
    base.Render();
    if (IsLocalNetworkRig)
    {
        // Extrapolate for local user :
        // we want to have the visual at the good position as soon as possible, so we force the visuals to follow the most fresh hardware positions
        // To update the visual object, and not the actual networked position, we move the interpolation targets
        networkTransform.InterpolationTarget.position = hardwareRig.transform.position;
        networkTransform.InterpolationTarget.rotation = hardwareRig.transform.rotation;
        leftHand.networkTransform.InterpolationTarget.position = hardwareRig.leftHand.transform.position;
        leftHand.networkTransform.InterpolationTarget.rotation = hardwareRig.leftHand.transform.rotation;
        rightHand.networkTransform.InterpolationTarget.position = hardwareRig.rightHand.transform.position;
        rightHand.networkTransform.InterpolationTarget.rotation = hardwareRig.rightHand.transform.rotation;
        headset.networkTransform.InterpolationTarget.position = hardwareRig.headset.transform.position;
        headset.networkTransform.InterpolationTarget.rotation = hardwareRig.headset.transform.rotation;
    }
}

頭戴式裝置

NetworkHeadset類別非常簡單:它針對NetworkRig類別提供一個存取到頭戴式裝置NetworkTransform

C#

        public class NetworkHeadset : NetworkBehaviour
        {
            [HideInInspector]
            public NetworkTransform networkTransform;
            private void Awake()
            {
                if (networkTransform == null) networkTransform = GetComponent<NetworkTransform>();
            }
        }

如同NetworkHeadset類別,NetworkHand針對NetworkRig類別提供存取到手Network Transform

為了同步手姿勢,已在HardwareHand類別中建立一個稱為HandCommand的網路架構。

C#

// Structure representing the inputs driving a hand pose
[System.Serializable]
public struct HandCommand : INetworkStruct
{
    public float thumbTouchedCommand;
    public float indexTouchedCommand;
    public float gripCommand;
    public float triggerCommand;
    // Optionnal commands
    public int poseCommand;
    public float pinchCommand;// Can be computed from triggerCommand by default
}

這個HandCommand架構用於IHandRepresentation介面之中,該介面設定各種手屬性,其中包含手姿勢。NetworkHand可以有一個下層IHandRepresentation,並將轉傳手姿勢資料給此下層。

C#

    public interface IHandRepresentation
    {
        public void SetHandCommand(HandCommand command);
        public GameObject gameObject { get; }
        public void SetHandColor(Color color);
        public void SetHandMaterial(Material material);
        public void DisplayMesh(bool shouldDisplay);
        public bool IsMeshDisplayed { get; }
    }

OSFHandRepresentation類別位於各個手上,其執行這個介面以利用所提供的手動畫工具(ApplyCommand(HandCommand command)功能)來修改手指位置。

fusion vr shared hand representation
fusion vr shared hand animator

現在,讓我們看一下它被同步的方式。

HandCommand架構隨手指的位置而更新為HardwareHandUpdate()

C#

protected virtual void Update()
{
    // update hand pose
    handCommand.thumbTouchedCommand = thumbAction.action.ReadValue<float>();
    handCommand.indexTouchedCommand = indexAction.action.ReadValue<float>();
    handCommand.gripCommand = gripAction.action.ReadValue<float>();
    handCommand.triggerCommand = triggerAction.action.ReadValue<float>();
    handCommand.poseCommand = handPose;
    handCommand.pinchCommand = 0;
    // update hand interaction
    isGrabbing = grabAction.action.ReadValue<float>() > grabThreshold;
}

在各個NetworkRig FixedUpdateNetwork(),在本機使用者上更新手姿勢資料,以及其他裝置輸入。

C#

public override void FixedUpdateNetwork()
{
    base.FixedUpdateNetwork();

    // update the rig at each network tick
    if (GetInput<RigInput>(out var input))
    {
        ApplyInputToRigParts(input);
        ApplyInputToHandPoses(input);
    }
}


protected virtual void ApplyInputToHandPoses(RigInput input)
{
    // we update the hand pose info. It will trigger on network hands OnHandCommandChange on all clients, and update the hand representation accordingly
    leftHand.HandCommand = input.leftHandCommand;
    rightHand.HandCommand = input.rightHandCommand;
}

NetworkHand元件位於使用者預製件的各個手上,其管理手代表更新。

為了做到這點,該類別含有一個HandCommand已連線架構。

C#

[Networked(OnChanged = nameof(OnHandCommandChange))]
public HandCommand HandCommand { get; set; }

因為HandCommand是一個已連線變數,每當已連線架構改變時,在所有玩家上調用OnHandCommandChange()回調,並且相應地更新手代表。

C#

public static void OnHandCommandChange(Changed<NetworkHand> changed)
{
    // Will be called on all clients when the local user change the hand pose structure
    // We trigger here the actual animation update
    changed.Behaviour.UpdateHandRepresentationWithNetworkState();
}

C#

void UpdateHandRepresentationWithNetworkState()
{
    if (handRepresentation != null) handRepresentation.SetHandCommand(HandCommand);
}

類似於NetworkRig針對裝置部件位置所做的,在Render()期間,NetworkHand也使用本機硬體手來處理外插及手姿勢的更新。

C#

public override void Render()
{
    base.Render();
    if (IsLocalNetworkRig)
    {
        // Extrapolate for local user : we want to have the visual at the good position as soon as possible, so we force the visuals to follow the most fresh hand pose
        UpdateRepresentationWithLocalHardwareState();
    }
}

C#

void UpdateRepresentationWithLocalHardwareState()
{
    if (handRepresentation != null) handRepresentation.SetHandCommand(LocalHardwareHand.handCommand);
}

傳送及運動

fusion vr shared teleport

RayBeamer類別位於各個硬體裝置手之上,其負責在使用者按下一個按鈕時顯示一個射線。當使用者放開按鈕時,如果射線目標是有效的,那麼將觸發一個事件。

C#

   if (onRelease != null) onRelease.Invoke(lastHitCollider, lastHit);

位於硬體裝置上的Rig Locomotion類別將聽取這個事件。

C#

       beamer.onRelease.AddListener(OnBeamRelease);

然後,它調用裝置傳送協同程式…

C#

protected virtual void OnBeamRelease(Collider lastHitCollider, Vector3 position)
{
[...]
    if (ValidLocomotionSurface(lastHitCollider))
    {
        StartCoroutine(rig.FadedTeleport(position));
    }
}

其更新硬體裝置位置,並要求在硬體頭戴式裝置上可用的一個Fader元件,在傳送時淡入及淡出檢視(以避免虛擬實境暈眩)。

C#

public virtual IEnumerator FadedTeleport(Vector3 position)
{
    if (headset.fader) yield return headset.fader.FadeIn();
    Teleport(position);
    if (headset.fader) yield return headset.fader.WaitBlinkDuration();
    if (headset.fader) yield return headset.fader.FadeOut();
}


public virtual void Teleport(Vector3 position)
{
    Vector3 headsetOffet = headset.transform.position - transform.position;
    headsetOffet.y = 0;
    Vector3 previousPosition = transform.position;
    transform.position = position - headsetOffet;
    if (onTeleport != null) onTeleport.Invoke(previousPosition, transform.position);
}

如同前述所見,將利用OnInput回調,透過網路來同步這個在硬體裝置位置上的修改。

同樣的策略也適用於裝置旋轉,其中CheckSnapTurn()觸發一個裝置修改。

C#

IEnumerator Rotate(float angle)
{
    timeStarted = Time.time;
    rotating = true;
    yield return rig.FadedRotate(angle);
    rotating = false;
}

public virtual IEnumerator FadedRotate(float angle)
{
    if (headset.fader) yield return headset.fader.FadeIn();
    Rotate(angle);
    if (headset.fader) yield return headset.fader.WaitBlinkDuration();
    if (headset.fader) yield return headset.fader.FadeOut();
}

public virtual void Rotate(float angle)
{
    transform.RotateAround(headset.transform.position, transform.up, angle);
}

拿取

fusion vr shared grab

概述

這裡的拿取邏輯基於兩個已連線元件,NetworkHandColliderGrabberNetworkHandColliderGrabbable

  • NetworkHandColliderGrabber當硬體手已經在一個可拿取物件上觸發一個拿取動作時觸發拿取及取消拿取
  • NetworkHandColliderGrabbable以網路變數來同步其他網路及拿取資訊,這樣可拿取物件在各個玩家應用程式上跟隨其拿取者。

注意事項:雖然裝置部件位置及手姿勢處理,與主機端或伺服器拓撲中的操作非常相似,但這裡的處理拿取方式非常特定於共享拓撲,以使其盡可能簡單地讀取

這個目前的頁面描述了一個非常簡單及容易執行的拿取系統,它與網路裝置緊密連結。在這裡可以找到一個更依賴硬體裝置的替代執行方式:VR共享——本機裝置拿取

詳細資訊

fusion vr shared remote grab grabbing logic

拿取

HardwareHand類別位於各個手上,其在各個更新時更新isGrabbing布林值:當使用者按下底框按鈕時,布林值為真。 請注意,updateGrabWithAction布林值用於支援桌面裝置,這是一個裝置的版本,其可透過滑鼠及鍵盤來驅動(這個布林值必須針對桌面模式設定為False,針對VR模式設定為True

C#

 protected virtual void Update()
 {
    // update hand pose
    handCommand.thumbTouchedCommand = thumbAction.action.ReadValue<float>();
    handCommand.indexTouchedCommand = indexAction.action.ReadValue<float>();
    handCommand.gripCommand = gripAction.action.ReadValue<float>();
    handCommand.triggerCommand = triggerAction.action.ReadValue<float>();
    handCommand.poseCommand = handPose;
    handCommand.pinchCommand = 0;
    // update hand interaction
    if(updateGrabWithAction) isGrabbing = grabAction.action.ReadValue<float>() > grabThreshold;
}

為了偵測與可拿取物件的碰撞,各個網路手上都有一個簡單的方盒碰撞器。

為了在網路上同步拿取動作,在各個網路手上新增一個NetworkHandColliderGrabber類別。當一個碰撞發生時,調用OnTriggerStay(Collider other)方法。

首先,因為碰撞器位於各個網路手上,因此需要限制與本機手有關的碰撞,而不是與另一位玩家有關的碰撞。

C#

// We only trigger grabbing for our local hands
if (!hand.IsLocalNetworkRig || !hand.LocalHardwareHand) return;

然後,它檢查一個物件是否已經被拿取。為了簡化性,在這個範例中不允許多個拿取。

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

然後它檢查:

  • 被碰撞的物件可以被拿取(它有一個NetworkHandColliderGrabbable元件)
  • 使用者按下底框按鈕

如果滿足這些條件,利用NetworkHandColliderGrabbable Grab方法以要求可拿取的物件來跟隨手(在上述圖表中的(1)

C#

NetworkHandColliderGrabbable grabbable;
if (lastCheckedCollider == other)
{
    grabbable = lastCheckColliderGrabbable;
}
else
{
    grabbable = other.GetComponentInParent<NetworkHandColliderGrabbable>();
}

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

拿取同步

因為範例使用共享模式,每個玩家可以在物件上請求狀態授權,並且更改描述拿取狀態的網路變數。因此,玩家試著拿取物件時,可以沒有在被拿取物件上的授權。所以,NetworkHandColliderGrabbable Grab方法在儲存目前拿取器(以及拿取點位移)之前首先請求狀態授權。當IsGrabbed為真時,跟隨物件位置是啟用的,也就是當設定了CurrentGrabber時。

C#

public async void Grab(NetworkHandColliderGrabber newGrabber)
{
    if (onWillGrab != null) onWillGrab.Invoke(newGrabber);

    // Find grabbable position/rotation in grabber referential
    localPositionOffsetWhileTakingAuthority = newGrabber.transform.InverseTransformPoint(transform.position);
    localRotationOffsetWhileTakingAuthority = Quaternion.Inverse(newGrabber.transform.rotation) * transform.rotation;
    grabberWhileTakingAuthority = newGrabber;

    // 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 = localPositionOffsetWhileTakingAuthority;
    LocalRotationOffset = localRotationOffsetWhileTakingAuthority;

    // Update the CurrentGrabber in order to start following position in the FixedUpdateNetwork
    CurrentGrabber = grabberWhileTakingAuthority;
}

請注意,CurrentGrabberLocalPositionOffsetLocalRotationOffset被宣告為一個已連線變數。它意味著所有玩家將在它們被更新時收到它們的新值(上述圖表中的(2)),以及意味著在改變時回調將允許所有玩家在拿取及取消拿取事件時配置物件(主要在需要時編輯/恢復其運動學上的狀態(上述圖表中的(3)))。

注意事項: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;
}

跟隨

NetworkHandColliderGrabbable FixedUpdateNetwork()中,當玩家有物件授權並且正在拿取物件時,更新物件位置以跟隨拿取手(上述圖表中的(4))。之後它上面的NetworkTransform元件將確保針對所有玩家來同步位置。

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
    Follow(followingtransform: transform, followedTransform: CurrentGrabber.transform, LocalPositionOffset, LocalRotationOffset);
}

c#

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

轉譯

類似於NetworkRigNetworkHandNetworkHandColliderGrabbable處理外插,並且在最近位置的Render()期間更新被拿取物件的視覺效果的位置,甚至是在網路刷新之間(上述圖表中的(5))。在類別中的各種[OrderAfter]標籤確保NetworkGrabbble Render()將在NetworkTransform方法後被調用,以在這些類別中覆寫NetworkTransform的內插補點目標的處理。

然而相較於先前的外插,這個外插有2個獨特性:

  • 首先,外插不限制於本機使用者。當拿取一個物件時,每個使用者「知道」它應該跟隨拿取手(利用描述拿取的已連線變數):就算被拿取物件的網路位置及拿取器可能有一點不同步,視覺效果必須匹配(以避免物件在代理上輕微地漂浮在手周圍)。
  • 其次,在取得授權時已經新增一個選項(預設啟用)到外插,以提供最佳使用者體驗:它避免了被拿取物件在收到授權之前保持靜止(即使持續時間很短,使用者也可以在VR中稍微感知到)。 所以,在請求授權時,拿取器及拿取點位置被儲存在暫時性本機變數中,以使用這些資料來有一個特定的外插。

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
    Follow(followingtransform: networkTransform.InterpolationTarget.transform, followedTransform: CurrentGrabber.hand.networkTransform.InterpolationTarget.transform, LocalPositionOffset, LocalRotationOffset);
}

void ExtrapolateWhileTakingAuthority()
{
    // No need to extrapolate if the object is not really grabbed
    if (grabberWhileTakingAuthority == null) return;

    // 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
    Follow(followingtransform: networkTransform.InterpolationTarget.transform, followedTransform: grabberWhileTakingAuthority.hand.networkTransform.InterpolationTarget.transform, localPositionOffsetWhileTakingAuthority, localRotationOffsetWhileTakingAuthority);
}

第三方

下一步

這裡是一些您可以在這個專案上練習的修改或改進的建議:

Back to top