Unity-Technologies / Unity-Technologies/com.unity.netcode.gameobjects

Dedicated server: mixed-authority NetworkObject is removed from NetworkTransformUpdate, so its owner-authoritative NetworkTransform never updates server-side

未關閉
#4,159 0 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

還沒有人認領這個 Issue。

stat:import type:bug
主要語言
C#
星號
2.3k
分支
461
平均合併
3 天 16 小時
30 天內合併 PR
20

描述

Description

On a dedicated server (NetworkManager.StartServer(), i.e. IsServer && !IsConnectedClient), the server's copy of an owner-authoritative NetworkTransform never updates when the same NetworkObject also carries one or more server-authoritative NetworkTransforms on child objects. The owning client keeps sending state, the server receives and interpolates it, but the transform is never written — it stays at the position given at connection approval for the lifetime of the object. A host with the identical prefab is unaffected.

Root cause (traced in 2.13.2 source and confirmed by reading the registry at runtime, details below): NetworkManager.NetworkTransformUpdate — the collection that drives non-authority OnUpdate(), which is the only code path that applies an interpolated position to the transform — is keyed per NetworkObject with no reference count. Every NetworkTransform.InternalInitialization calls:

if (CanCommitToTransform) m_CachedNetworkManager.NetworkTransformRegistration(NetworkObject, forUpdate, false); // removes the whole NetworkObject
else                      m_CachedNetworkManager.NetworkTransformRegistration(NetworkObject, forUpdate, true);  // adds it

So on a NetworkObject with mixed authority, every authority-side transform's initialization removes the entire object, evicting its non-authority siblings. Spawn initialization runs in component order (root first), so the non-authority root adds the object and each authority-side child then removes it.

A host survives because NetworkTransform.InternalOnNetworkPostSpawn ends with:

if (!CanCommitToTransform && m_CachedNetworkManager.IsConnectedClient && SynchronizeState.IsSynchronizing)
    NonAuthorityFinalizeSynchronization();

whose InternalInitialization re-run on the non-authority root re-adds the object after the children evicted it. IsConnectedClient is false on a dedicated server, so that never runs there; and the finalize's only other caller, NotifyNetworkObjectsSynchronized, fires once at server start, never for a late-joining client's object. So the first block of that same method deliberately raises IsSynchronizing ("special case for client-server where a server is spawning an owner authoritative NetworkObject") and nothing on a pure server ever completes it.

Reproduce Steps
  1. Player prefab: root has a NetworkTransform subclass with OnIsServerAuthoritative() => false (owner authority). Add one or more child GameObjects each with a plain NetworkTransform left at AuthorityMode = Server. (Ours: two owner-authoritative on root/child, eleven server-authoritative children used as replicated pose slots.)
  2. Start a dedicated server with StartServer() (not StartHost()), client-server topology.
  3. Connect one client; its player object is spawned server-side with SpawnAsPlayerObject(clientId).
  4. On the client, move the player well away from the spawn point.
  5. On the server, read client.PlayerObject.transform.position, and check NetworkManager.NetworkTransformUpdate.ContainsKey(playerObject.NetworkObjectId).
Actual Outcome
  • Server-side position stays at the connection-approval spawn position indefinitely (we measured (0.00, 1.00, 0.00) while the client stood 110 m away).
  • NetworkTransformUpdate does not contain the player NetworkObject (ContainsKey == false).
  • Client-side the owner-authoritative transform reports CanCommitToTransform = true and produces state each tick; server-side it reports CanCommitToTransform = false, IsSpawned = true, enabled = true — willing to receive, but OnUpdate() never runs.
  • Remove the server-authoritative children (or run the same prefab on a host): position tracks correctly.
Expected Outcome

The non-authority root should remain registered for OnUpdate() regardless of how many authority-side sibling transforms share the NetworkObject, on a dedicated server exactly as on a host.

Screenshots

N/A — runtime readouts above.

Environment
  • OS: Windows 11 Pro
  • Unity Version: 6000.3.22f1
  • Netcode Version: 2.13.2 (registry)
  • Netcode Commit: n/a (registry package)
  • Netcode Topology: Client-Server, dedicated server (StartServer) — not reproducible on a host
Additional Context

Two things we ruled out while tracing it, in case they save time: SynchronizeState.IsSynchronizing is not what blocks reception — it reads True on the server before and after the workaround and no stage of the receive path (TransformStateUpdate, OnNetworkStateChanged, ApplyUpdatedState, UpdateInterpolation) gates on it; and setting the serialized AuthorityMode to Owner on the root changes nothing, since CanCommitToTransform derives from IsServerAuthoritative().

Workaround we are shipping, in our NetworkTransform subclass — it re-runs the finalize on a server that is not also a client, which re-registers the object via InternalInitialization:

protected override void InternalOnNetworkPostSpawn()
{
    base.InternalOnNetworkPostSpawn();

    var networkManager = NetworkManager;
    if (networkManager == null || !networkManager.IsServer || networkManager.IsConnectedClient)
        return;
    if (CanCommitToTransform)
        return;

    // NetworkTransform.InternalOnNetworkSessionSynchronized is NonAuthorityFinalizeSynchronization()
    // followed by an empty NetworkBehaviour base, so this is the finalize and nothing else.
    base.InternalOnNetworkSessionSynchronized();
}

It works, but only for transforms of a type we control — a plain NetworkTransform set to owner authority on such an object is still evicted. A reference count on NetworkTransformUpdate (or registering per transform rather than per object) would fix it at the source; alternatively the post-spawn finalize could run for a non-connected-client server too, since its first block already targets exactly that case.

貢獻指南

開啟貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

研究方向

從 NetworkManager.NetworkTransformUpdate 和 NetworkTransform.InternalInitialization 開始,接著追蹤 NetworkTransform.InternalOnNetworkPostSpawn 和 NonAuthorityFinalizeSynchronization。使用具有混合 authority 的 NetworkObjects 透過 StartServer() 重現,並驗證無 authority 的根物件仍保持註冊狀態,且其伺服器端位置在 dedicated server 上跟隨擁有它的用戶端。

由索引模型根據 Issue 內容生成。

評估

技術堆疊
csharp, unity
領域
networking
Issue 類型
缺陷
難度
4/5
預估耗時
3-5 天
活躍度
活躍
描述清晰度
描述清楚
新手友好度
68/100

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。