feat(Car): wire NWH Vehicle Physics 2 — scripts only, scene/prefabs still TODO

Code-only first pass. The local Player.prefab (ball) is still in the scene
until the user creates the PlayerCar / RemoteCar prefab variants in the Editor.

New scripts:
- VehicleLocalSetup: attaches name label + colored capsule marker to the local
  vehicle, registers its Rigidbody with NetworkManager for broadcast.
- RemoteVehicleSync: snapshot interpolation for remote vehicles. Disables the
  NWH VehicleController + every WheelController + every AudioSource on init.
  Makes the rigidbody kinematic, MovePosition-driven from network. Disables all
  non-trigger colliders during the 1.5s spawn grace window to avoid ejecting
  overlapping locals at connect.

Adapted scripts:
- NetworkManager: RegisterLocalVehicle(Transform, Rigidbody) replaces the
  FindFirstObjectByType<PlayerController>() lookup. Remote spawn now wires
  RemoteVehicleSync instead of RemotePlayerController.
- LobbyUI: OnConnected drives VehicleLocalSetup.SetupLocal; OnDisconnected
  toggles NWH.VehicleController instead of PlayerController.
- GameManager.SetPlayerActive: toggles VehicleController, not PlayerController.
- DebugNetworkUI: live position read from VehicleController.vehicleRigidbody.
- ChatUI: drops PlayerController.ResetInputs() (NWH polls Input System each
  frame; no manual reset needed when chat opens).
- StatsTracker: drops dead _pc field; Rigidbody still gets resolved via
  GetComponent on the host GameObject (will be the vehicle on Car).

Frontend (deployed earlier on master): build_ball replaces pretty_build assets.
This commit is contained in:
2026-05-20 18:34:40 +02:00
parent 32becc12f9
commit 103f8859d4
14 changed files with 444 additions and 53 deletions

View File

@@ -30,7 +30,7 @@ public class NetworkManager : MonoBehaviour
public string LastError { get; private set; } = "";
// Expose remote players for debug UI
public Dictionary<string, RemotePlayerController> RemotePlayers => _remotePlayers;
public Dictionary<string, RemoteVehicleSync> RemotePlayers => _remotePlayers;
// Local player info (set during join)
public string LocalPlayerName { get; private set; } = "";
@@ -73,7 +73,7 @@ public class NetworkManager : MonoBehaviour
private Client _client;
private Room<GameState> _room;
private StateCallbackStrategy<GameState> _callbacks;
private readonly Dictionary<string, RemotePlayerController> _remotePlayers = new();
private readonly Dictionary<string, RemoteVehicleSync> _remotePlayers = new();
private float _broadcastTimer;
private const float BROADCAST_INTERVAL = 0.01667f; // ~60/sec
private bool _isJoining;
@@ -270,14 +270,17 @@ public class NetworkManager : MonoBehaviour
{
Vector3 spawnPos = new Vector3(player.x, player.y, player.z);
GameObject remoteBall = remotePlayerPrefab != null
? Instantiate(remotePlayerPrefab, spawnPos, Quaternion.identity)
: GameObject.CreatePrimitive(PrimitiveType.Sphere);
remoteBall.transform.position = spawnPos;
remoteBall.name = $"RemotePlayer_{player.name}_{sessionId[..6]}";
if (remotePlayerPrefab == null)
{
Debug.LogError("[Network] remotePlayerPrefab not assigned — cannot spawn remote vehicle.");
return;
}
GameObject remote = Instantiate(remotePlayerPrefab, spawnPos, Quaternion.identity);
remote.transform.position = spawnPos;
remote.name = $"RemoteVehicle_{player.name}_{sessionId[..6]}";
var controller = remoteBall.GetComponent<RemotePlayerController>()
?? remoteBall.AddComponent<RemotePlayerController>();
var controller = remote.GetComponent<RemoteVehicleSync>()
?? remote.AddComponent<RemoteVehicleSync>();
controller.Initialize(sessionId, player.name,
new Color(player.colorR, player.colorG, player.colorB));
@@ -324,20 +327,21 @@ public class NetworkManager : MonoBehaviour
// ─── Position Broadcasting ────────────────────────────────────────────
/// <summary>
/// Called by <see cref="VehicleLocalSetup"/> after the local vehicle is ready.
/// Tells us which Rigidbody to broadcast each tick.
/// </summary>
public void RegisterLocalVehicle(Transform t, Rigidbody rb)
{
_localPlayer = t;
_localPlayerRb = rb;
Debug.Log($"[Network] Local vehicle registered: {t?.name}");
}
private void BroadcastPosition()
{
if (_room == null || !IsConnected) return;
if (_localPlayer == null)
{
var pc = FindFirstObjectByType<PlayerController>();
if (pc != null)
{
_localPlayer = pc.transform;
_localPlayerRb = pc.GetComponent<Rigidbody>();
}
else return;
}
if (_localPlayer == null || _localPlayerRb == null) return;
Vector3 pos = _localPlayer.position;
Vector3 vel = _localPlayerRb != null ? _localPlayerRb.linearVelocity : Vector3.zero;