Add NGO networking with owner-authoritative player position sync

Install Netcode for GameObjects 2.13.0 + Unity Transport 2.6.0 and wire up
the first networking milestone: syncing player position/rotation across clients.

- NetworkBootstrap: Host/Client/Server controls (OnGUI + H/C hotkeys) for
  local clone testing over loopback 127.0.0.1:7777.
- NetworkPlayerSetup: per-avatar owner gating. The local owner binds the
  GameModeManager and cameras to itself; remote avatars disable their
  controller/CharacterController and receive transform via NetworkTransform.
- GameModeManager.BindLocalPlayer: central runtime rebind to the locally
  spawned player.
- FirstPersonController.SetPlayer: explicit runtime rebind hook.
- Player prefab: NetworkObject + NetworkTransform (owner-authoritative,
  position + rotation) + NetworkPlayerSetup; registered as PlayerPrefab.
- Scene: NetworkManager + UnityTransport added; the fixed scene player
  instance removed in favor of spawn-on-connect.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 4.8 committed 2026-06-27 14:33:18 +02:00
1 parent c0ec65058b
commit 4fa443f630
13 files changed
+435 -91

No files matched your search

+31
View File
@@ -0,0 +1,31 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e651dbb3fbac04af2b8f5abf007ddc23, type: 3}
m_Name: DefaultNetworkPrefabs
m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkPrefabsList
IsDefault: 1
List:
- Override: 0
Prefab: {fileID: 3510981371713521867, guid: cf153a3aea3d5da4f9d4cdbff0be8180, type: 3}
SourcePrefabToOverride: {fileID: 0}
SourceHashToOverride: 0
OverridingTargetPrefab: {fileID: 0}
- Override: 0
Prefab: {fileID: 5508335275054701003, guid: 6b0b58e8352625343951db00beb8cb0a, type: 3}
SourcePrefabToOverride: {fileID: 0}
SourceHashToOverride: 0
OverridingTargetPrefab: {fileID: 0}
- Override: 0
Prefab: {fileID: 5384024074013944205, guid: 97017ed3bb40aff4e885440d9a7336ee, type: 3}
SourcePrefabToOverride: {fileID: 0}
SourceHashToOverride: 0
OverridingTargetPrefab: {fileID: 0}
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 231a4db0603e8994f9e13e19108d58d1
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
+88
View File
@@ -45,6 +45,9 @@ GameObject:
- component: {fileID: 4587432968399885263} - component: {fileID: 4587432968399885263}
- component: {fileID: 3810343734243857883} - component: {fileID: 3810343734243857883}
- component: {fileID: 273411532115212066} - component: {fileID: 273411532115212066}
- component: {fileID: 1557561356852236266}
- component: {fileID: 6847253018511389861}
- component: {fileID: 8574096554986372233}
m_Layer: 0 m_Layer: 0
m_Name: Player m_Name: Player
m_TagString: Player m_TagString: Player
@@ -208,6 +211,91 @@ MonoBehaviour:
m_EditorClassIdentifier: Assembly-CSharp::PlayerScaler m_EditorClassIdentifier: Assembly-CSharp::PlayerScaler
scale: 1 scale: 1
scaleJumpAndGravity: 1 scaleJumpAndGravity: 1
--- !u!114 &1557561356852236266
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5384024074013944205}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkObject
GlobalObjectIdHash: 0
InScenePlacedSourceGlobalObjectIdHash: 0
DeferredDespawnTick: 0
Ownership: 1
AlwaysReplicateAsRoot: 0
SynchronizeTransform: 1
<InScenePlaced>k__BackingField: 0
ActiveSceneSynchronization: 0
SceneMigrationSynchronization: 1
SpawnWithObservers: 1
DontDestroyWithOwner: 0
AutoObjectParentSync: 1
SyncOwnerTransformWhenParented: 1
AllowOwnerToParent: 0
--- !u!114 &6847253018511389861
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5384024074013944205}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e96cb6065543e43c4a752faaa1468eb1, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.Components.NetworkTransform
ShowTopMostFoldoutHeaderGroup: 1
NetworkTransformExpanded: 0
AutoOwnerAuthorityTickOffset: 1
PositionInterpolationType: 0
RotationInterpolationType: 0
ScaleInterpolationType: 0
PositionLerpSmoothing: 1
PositionMaxInterpolationTime: 0.1
RotationLerpSmoothing: 1
RotationMaxInterpolationTime: 0.1
ScaleLerpSmoothing: 1
ScaleMaxInterpolationTime: 0.1
AuthorityMode: 1
TickSyncChildren: 0
UseUnreliableDeltas: 0
SyncPositionX: 1
SyncPositionY: 1
SyncPositionZ: 1
SyncRotAngleX: 1
SyncRotAngleY: 1
SyncRotAngleZ: 1
SyncScaleX: 0
SyncScaleY: 0
SyncScaleZ: 0
PositionThreshold: 0.001
RotAngleThreshold: 0.01
ScaleThreshold: 0.01
UseQuaternionSynchronization: 0
UseQuaternionCompression: 0
UseHalfFloatPrecision: 0
InLocalSpace: 0
SwitchTransformSpaceWhenParented: 0
Interpolate: 1
SlerpPosition: 0
--- !u!114 &8574096554986372233
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5384024074013944205}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fce6d601a5d475943bc43d2abd75570d, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::NetworkPlayerSetup
ShowTopMostFoldoutHeaderGroup: 1
--- !u!1 &6840099669803454162 --- !u!1 &6840099669803454162
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
+127 -89
View File
@@ -735,7 +735,7 @@ MonoBehaviour:
m_StreamingVersion: 20241001 m_StreamingVersion: 20241001
m_LegacyPriority: 0 m_LegacyPriority: 0
Target: Target:
TrackingTarget: {fileID: 5297240775790447668} TrackingTarget: {fileID: 0}
LookAtTarget: {fileID: 0} LookAtTarget: {fileID: 0}
CustomLookAtTarget: 0 CustomLookAtTarget: 0
Lens: Lens:
@@ -1218,6 +1218,127 @@ Transform:
m_Children: [] m_Children: []
m_Father: {fileID: 347639557} m_Father: {fileID: 347639557}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &284910513
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 284910517}
- component: {fileID: 284910516}
- component: {fileID: 284910515}
- component: {fileID: 284910514}
m_Layer: 0
m_Name: NetworkManager
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &284910514
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 284910513}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 519095f0598dfdf4ebe457561f77771c, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::NetworkBootstrap
showGui: 1
enableHotkeys: 1
--- !u!114 &284910515
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 284910513}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6960e84d07fb87f47956e7a81d71c4e6, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.Transports.UTP.UnityTransport
m_ProtocolType: 0
m_UseWebSockets: 0
m_UseEncryption: 0
m_MaxPacketQueueSize: 128
m_MaxPayloadSize: 6144
m_HeartbeatTimeoutMS: 500
m_ConnectTimeoutMS: 1000
m_MaxConnectAttempts: 60
m_DisconnectTimeoutMS: 30000
ConnectionData:
Address: 127.0.0.1
Port: 7777
WebSocketPath:
ServerListenAddress: 0.0.0.0
ClientBindPort: 0
DebugSimulator:
PacketDelayMS: 0
PacketJitterMS: 0
PacketDropRate: 0
--- !u!114 &284910516
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 284910513}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 593a2fe42fa9d37498c96f9a383b6521, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkManager
NetworkManagerExpanded: 0
NetworkConfig:
ProtocolVersion: 0
NetworkTransport: {fileID: 284910515}
PlayerPrefab: {fileID: 5384024074013944205, guid: 97017ed3bb40aff4e885440d9a7336ee, type: 3}
Prefabs:
NetworkPrefabsLists:
- {fileID: 11400000, guid: 231a4db0603e8994f9e13e19108d58d1, type: 2}
TickRate: 30
ClientConnectionBufferTimeout: 10
ConnectionApproval: 0
ConnectionData:
EnableTimeResync: 0
TimeResyncInterval: 30
EnsureNetworkVariableLengthSafety: 0
EnableSceneManagement: 1
ForceSamePrefabs: 1
RecycleNetworkIds: 1
NetworkIdRecycleDelay: 120
RpcHashSize: 0
LoadSceneTimeOut: 120
SpawnTimeout: 10
EnableNetworkLogs: 1
NetworkTopology: 0
UseCMBService: 0
AutoSpawnPlayerPrefabClientSide: 1
NetworkProfilingMetrics: 1
OldPrefabList: []
RunInBackground: 1
LogLevel: 1
--- !u!4 &284910517
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 284910513}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &286369291 --- !u!1 &286369291
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -2541,9 +2662,9 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 43b8f1dc43642294f99fab0aaabf6197, type: 3} m_Script: {fileID: 11500000, guid: 43b8f1dc43642294f99fab0aaabf6197, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: Assembly-CSharp::FirstPersonController m_EditorClassIdentifier: Assembly-CSharp::FirstPersonController
cameraTarget: {fileID: 897939465} cameraTarget: {fileID: 0}
hiddenRenderers: hiddenRenderers:
- {fileID: 1294962561} - {fileID: 0}
horizontalSensitivity: 0.1 horizontalSensitivity: 0.1
verticalSensitivity: 0.075 verticalSensitivity: 0.075
invertY: 0 invertY: 0
@@ -3737,7 +3858,7 @@ Transform:
m_GameObject: {fileID: 829281440} m_GameObject: {fileID: 829281440}
serializedVersion: 2 serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: -2, y: 0.5, z: 1} m_LocalPosition: {x: -2, y: 0.5, z: 1.5}
m_LocalScale: {x: 1, y: 1, z: 1} m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0 m_ConstrainProportionsScale: 0
m_Children: [] m_Children: []
@@ -4063,11 +4184,6 @@ MeshCollider:
m_Convex: 0 m_Convex: 0
m_CookingOptions: 30 m_CookingOptions: 30
m_Mesh: {fileID: 4300000, guid: 6d5becbf770f24fa3ab1ceaba45fcefa, type: 3} m_Mesh: {fileID: 4300000, guid: 6d5becbf770f24fa3ab1ceaba45fcefa, type: 3}
--- !u!4 &897939465 stripped
Transform:
m_CorrespondingSourceObject: {fileID: 5751241787949053935, guid: cf153a3aea3d5da4f9d4cdbff0be8180, type: 3}
m_PrefabInstance: {fileID: 5297240775790447667}
m_PrefabAsset: {fileID: 0}
--- !u!1001 &924705986 --- !u!1001 &924705986
PrefabInstance: PrefabInstance:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -4633,17 +4749,6 @@ Transform:
m_CorrespondingSourceObject: {fileID: 411130, guid: a4bb788560e5d4617b970cd301097f38, type: 3} m_CorrespondingSourceObject: {fileID: 411130, guid: a4bb788560e5d4617b970cd301097f38, type: 3}
m_PrefabInstance: {fileID: 1039758568} m_PrefabInstance: {fileID: 1039758568}
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
--- !u!114 &1141039642 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 8381518232634759850, guid: cf153a3aea3d5da4f9d4cdbff0be8180, type: 3}
m_PrefabInstance: {fileID: 5297240775790447667}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 463b6be548abf464287096e1e92fcd01, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::PlayerCharacterController
--- !u!1001 &1148993634 --- !u!1001 &1148993634
PrefabInstance: PrefabInstance:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -5113,11 +5218,6 @@ Transform:
m_Children: [] m_Children: []
m_Father: {fileID: 0} m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!137 &1294962561 stripped
SkinnedMeshRenderer:
m_CorrespondingSourceObject: {fileID: 8001664246499114791, guid: cf153a3aea3d5da4f9d4cdbff0be8180, type: 3}
m_PrefabInstance: {fileID: 5297240775790447667}
m_PrefabAsset: {fileID: 0}
--- !u!1001 &1317768121 --- !u!1001 &1317768121
PrefabInstance: PrefabInstance:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -5904,7 +6004,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 730466b26610d95498d22ab748d855a7, type: 3} m_Script: {fileID: 11500000, guid: 730466b26610d95498d22ab748d855a7, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: Assembly-CSharp::GameModeManager m_EditorClassIdentifier: Assembly-CSharp::GameModeManager
player: {fileID: 1141039642} player: {fileID: 0}
cameraInput: {fileID: 172071431} cameraInput: {fileID: 172071431}
painter: {fileID: 0} painter: {fileID: 0}
freeCam: {fileID: 1282734304} freeCam: {fileID: 1282734304}
@@ -7782,68 +7882,6 @@ Transform:
m_CorrespondingSourceObject: {fileID: 4922444305696846, guid: ac1cc01cb5dbf5d4f869c403b38f8a21, type: 3} m_CorrespondingSourceObject: {fileID: 4922444305696846, guid: ac1cc01cb5dbf5d4f869c403b38f8a21, type: 3}
m_PrefabInstance: {fileID: 1424346480} m_PrefabInstance: {fileID: 1424346480}
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
--- !u!1001 &5297240775790447667
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 0}
m_Modifications:
- target: {fileID: 1950906664192868687, guid: cf153a3aea3d5da4f9d4cdbff0be8180, type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1950906664192868687, guid: cf153a3aea3d5da4f9d4cdbff0be8180, type: 3}
propertyPath: m_LocalPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1950906664192868687, guid: cf153a3aea3d5da4f9d4cdbff0be8180, type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1950906664192868687, guid: cf153a3aea3d5da4f9d4cdbff0be8180, type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 1950906664192868687, guid: cf153a3aea3d5da4f9d4cdbff0be8180, type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1950906664192868687, guid: cf153a3aea3d5da4f9d4cdbff0be8180, type: 3}
propertyPath: m_LocalRotation.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1950906664192868687, guid: cf153a3aea3d5da4f9d4cdbff0be8180, type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1950906664192868687, guid: cf153a3aea3d5da4f9d4cdbff0be8180, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1950906664192868687, guid: cf153a3aea3d5da4f9d4cdbff0be8180, type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1950906664192868687, guid: cf153a3aea3d5da4f9d4cdbff0be8180, type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3510981371713521867, guid: cf153a3aea3d5da4f9d4cdbff0be8180, type: 3}
propertyPath: m_Name
value: Player
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: cf153a3aea3d5da4f9d4cdbff0be8180, type: 3}
--- !u!4 &5297240775790447668 stripped
Transform:
m_CorrespondingSourceObject: {fileID: 5067936922952835876, guid: cf153a3aea3d5da4f9d4cdbff0be8180, type: 3}
m_PrefabInstance: {fileID: 5297240775790447667}
m_PrefabAsset: {fileID: 0}
--- !u!1660057539 &9223372036854775807 --- !u!1660057539 &9223372036854775807
SceneRoots: SceneRoots:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -7857,7 +7895,7 @@ SceneRoots:
- {fileID: 832575519} - {fileID: 832575519}
- {fileID: 410087041} - {fileID: 410087041}
- {fileID: 2025317892} - {fileID: 2025317892}
- {fileID: 5297240775790447667}
- {fileID: 686187287} - {fileID: 686187287}
- {fileID: 489277934} - {fileID: 489277934}
- {fileID: 347639557} - {fileID: 347639557}
- {fileID: 284910517}
+16
View File
@@ -175,6 +175,22 @@ public class FirstPersonController : MonoBehaviour
if (r) r.enabled = !hidden; if (r) r.enabled = !hidden;
} }
/// <summary>
/// Explicitly binds this rig to the locally-owned networked player (overriding the scene-wide
/// auto-resolve, which could otherwise latch onto a remote avatar). Called by the GameModeManager
/// when the local player spawns.
/// </summary>
public void SetPlayer(PlayerCharacterController p)
{
if (!p) return;
_player = p;
_poseController = p.GetComponent<PlayerPoseController>();
Transform t = p.transform.Find("CameraTarget");
if (t) cameraTarget = t;
hiddenRenderers = p.GetComponentsInChildren<SkinnedMeshRenderer>(true);
_resolved = true;
}
void ResolveReferences() void ResolveReferences()
{ {
if (_resolved) return; if (_resolved) return;
+36
View File
@@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
using Unity.Cinemachine;
#if ENABLE_INPUT_SYSTEM #if ENABLE_INPUT_SYSTEM
using UnityEngine.InputSystem; using UnityEngine.InputSystem;
#endif #endif
@@ -108,6 +109,41 @@ public class GameModeManager : MonoBehaviour
m.Bind(this); m.Bind(this);
} }
/// <summary>
/// Binds the scene singletons (this manager, the orbit/first-person cameras, the painter) to the
/// locally-owned networked player after it spawns, then enters Player Control. Called from
/// <see cref="NetworkPlayerSetup"/> on the owning client. Before this runs the scene has no player,
/// so the cycle sits in a player-less mode (Paint); this promotes it once the avatar exists.
/// </summary>
public void BindLocalPlayer(PlayerCharacterController localPlayer)
{
if (!localPlayer) return;
player = localPlayer;
// The painter lives on the player (per-avatar), so bind the local one.
if (!painter) painter = localPlayer.GetComponentInChildren<MeshPainter>(true);
Transform camTarget = localPlayer.transform.Find("CameraTarget");
// Re-point the third-person orbit camera (Cinemachine Follow/LookAt) at the local avatar.
if (cameraInput)
{
var vcam = cameraInput.GetComponent<CinemachineCamera>();
if (vcam) { vcam.Follow = camTarget; vcam.LookAt = camTarget; }
}
if (firstPerson)
firstPerson.SetPlayer(localPlayer);
// Tear down the pre-spawn (player-less) mode and rebuild with Player Control available.
ActiveMode?.Exit();
_activeIndex = -1;
BuildModes();
int idx = _modes.FindIndex(m => m is PlayerControlMode);
SwitchTo(idx >= 0 ? idx : Mathf.Clamp(startModeIndex, 0, _modes.Count - 1));
}
private void Update() private void Update()
{ {
if (_modes.Count == 0) return; if (_modes.Count == 0) return;
+69
View File
@@ -0,0 +1,69 @@
using Unity.Netcode;
using UnityEngine;
#if ENABLE_INPUT_SYSTEM
using UnityEngine.InputSystem;
#endif
/// <summary>
/// Dev-only connection harness for local multiplayer testing across the junction-mirror clones:
/// one instance clicks Host, the others click Client (loopback 127.0.0.1 via the configured
/// UnityTransport). Draws a small on-screen panel and exposes hotkeys (H = Host, C = Client,
/// J = Server, K = Shutdown). Purely a testing scaffold — replace with real lobby/menu UI later.
/// Attach to the same GameObject as the NetworkManager.
/// </summary>
[RequireComponent(typeof(NetworkManager))]
[DisallowMultipleComponent]
public class NetworkBootstrap : MonoBehaviour
{
[Tooltip("Draw the on-screen Host/Client panel (top-left).")]
public bool showGui = true;
[Tooltip("Allow the H/C/J/K keyboard shortcuts.")]
public bool enableHotkeys = true;
private NetworkManager _net;
private void Awake() => _net = GetComponent<NetworkManager>();
private void Update()
{
if (!enableHotkeys) return;
#if ENABLE_INPUT_SYSTEM
var kb = Keyboard.current;
if (kb == null || _net == null) return;
if (!_net.IsListening)
{
if (kb.hKey.wasPressedThisFrame) _net.StartHost();
else if (kb.cKey.wasPressedThisFrame) _net.StartClient();
else if (kb.jKey.wasPressedThisFrame) _net.StartServer();
}
else if (kb.kKey.wasPressedThisFrame)
{
_net.Shutdown();
}
#endif
}
private void OnGUI()
{
if (!showGui || _net == null) return;
GUILayout.BeginArea(new Rect(10, 10, 220, 160), GUI.skin.box);
if (!_net.IsListening)
{
GUILayout.Label("Netcode (dev)");
if (GUILayout.Button("Host (H)")) _net.StartHost();
if (GUILayout.Button("Client (C)")) _net.StartClient();
if (GUILayout.Button("Server (J)")) _net.StartServer();
}
else
{
string role = _net.IsHost ? "Host" : _net.IsServer ? "Server" : "Client";
GUILayout.Label($"Role: {role}");
GUILayout.Label($"ClientId: {_net.LocalClientId}");
GUILayout.Label($"Clients: {(_net.IsServer ? _net.ConnectedClients.Count : (_net.IsConnectedClient ? 1 : 0))}");
if (GUILayout.Button("Shutdown (K)")) _net.Shutdown();
}
GUILayout.EndArea();
}
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 519095f0598dfdf4ebe457561f77771c
+32
View File
@@ -0,0 +1,32 @@
using Unity.Netcode;
using UnityEngine;
/// <summary>
/// Per-avatar network glue on the spawned player. The local owner binds the scene singletons
/// (GameModeManager, cameras) to itself via <see cref="GameModeManager.BindLocalPlayer"/>; remote
/// avatars stop simulating so their transform is driven purely by the NetworkTransform replication.
/// Position-sync milestone: remote avatars still show their mesh/idle pose, just no local control.
/// </summary>
[RequireComponent(typeof(PlayerCharacterController))]
[DisallowMultipleComponent]
public class NetworkPlayerSetup : NetworkBehaviour
{
public override void OnNetworkSpawn()
{
var controller = GetComponent<PlayerCharacterController>();
if (IsOwner)
{
var manager = FindFirstObjectByType<GameModeManager>(FindObjectsInactive.Include);
if (manager)
manager.BindLocalPlayer(controller);
return;
}
// Remote avatar: NetworkTransform owns the transform here, so disable the local
// simulation (input/movement) and the CharacterController's own depenetration.
if (controller) controller.enabled = false;
var cc = GetComponent<CharacterController>();
if (cc) cc.enabled = false;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: fce6d601a5d475943bc43d2abd75570d
+1
View File
@@ -5,6 +5,7 @@
"com.unity.ide.visualstudio": "2.0.27", "com.unity.ide.visualstudio": "2.0.27",
"com.unity.inputsystem": "1.19.0", "com.unity.inputsystem": "1.19.0",
"com.unity.multiplayer.center": "1.0.1", "com.unity.multiplayer.center": "1.0.1",
"com.unity.netcode.gameobjects": "2.13.0",
"com.unity.render-pipelines.universal": "17.3.0", "com.unity.render-pipelines.universal": "17.3.0",
"com.unity.timeline": "1.8.12", "com.unity.timeline": "1.8.12",
"com.unity.ugui": "2.0.0", "com.unity.ugui": "2.0.0",
+22 -1
View File
@@ -89,9 +89,19 @@
"com.unity.modules.uielements": "1.0.0" "com.unity.modules.uielements": "1.0.0"
} }
}, },
"com.unity.netcode.gameobjects": {
"version": "2.13.0",
"depth": 0,
"source": "registry",
"dependencies": {
"com.unity.transport": "2.6.0",
"com.unity.nuget.mono-cecil": "1.11.4"
},
"url": "https://packages.unity.com"
},
"com.unity.nuget.mono-cecil": { "com.unity.nuget.mono-cecil": {
"version": "1.11.6", "version": "1.11.6",
"depth": 3, "depth": 1,
"source": "registry", "source": "registry",
"dependencies": {}, "dependencies": {},
"url": "https://packages.unity.com" "url": "https://packages.unity.com"
@@ -201,6 +211,17 @@
}, },
"url": "https://packages.unity.com" "url": "https://packages.unity.com"
}, },
"com.unity.transport": {
"version": "2.6.0",
"depth": 1,
"source": "registry",
"dependencies": {
"com.unity.burst": "1.8.24",
"com.unity.collections": "2.2.1",
"com.unity.mathematics": "1.3.2"
},
"url": "https://packages.unity.com"
},
"com.unity.ugui": { "com.unity.ugui": {
"version": "2.0.0", "version": "2.0.0",
"depth": 0, "depth": 0,
+1 -1
View File
@@ -12,7 +12,7 @@ PlayerSettings:
targetDevice: 2 targetDevice: 2
useOnDemandResources: 0 useOnDemandResources: 0
accelerometerFrequency: 60 accelerometerFrequency: 60
companyName: DefaultCompany companyName: Tausi Development
productName: Chameleon productName: Chameleon
defaultCursor: {fileID: 0} defaultCursor: {fileID: 0}
cursorHotspot: {x: 0, y: 0} cursorHotspot: {x: 0, y: 0}