Netcode Bootstrap

This commit is contained in:
Luis Gonzalez
2026-05-31 14:27:52 -07:00
parent 99d8d2d2a9
commit 7fa77ce821
1813 changed files with 2921554 additions and 84 deletions
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 08533a9dddc374862b7e3259cb8f872d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,11 @@
using Unity.NetCode;
namespace ProjectM.Simulation
{
/// <summary>
/// Client -&gt; server one-off request to enter gameplay. On receipt the server adds
/// NetworkStreamInGame to the connection (enabling snapshot/command flow) and spawns the
/// client's player ghost. Lives in Simulation so both worlds see the type for RPC source-gen.
/// </summary>
public struct GoInGameRequest : IRpcCommand { }
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b633994e24f874e0796d1fa93cc46679
@@ -15,8 +15,11 @@ namespace ProjectM.Simulation
{
public override bool Initialize(string defaultWorldName)
{
// 0 = do not auto-connect; worlds are still created. Set a port later to auto-connect.
AutoConnectPort = 0;
// Auto-connect in-editor: the server listens and the in-process client connects (over
// IPC) on the default BinaryWorlds host mode — one process hosts both worlds (M1 listen
// server). M3 replaces this with an explicit Unity Relay host/join flow. Set to 0 to
// disable auto-connect.
AutoConnectPort = 7979;
CreateDefaultClientServerWorlds();
return true;
}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 78b348213a4864001bf105954525fbda
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,41 @@
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.NetCode;
using Unity.Transforms;
namespace ProjectM.Simulation
{
/// <summary>
/// Predicted aim/facing: writes <see cref="PlayerFacing"/> from twin-stick Aim, falling back to
/// the movement direction when Aim is zero (controller-first directional aim). Also turns the
/// ghost transform toward the facing direction for top-down presentation. When there is no input
/// this tick the previous facing is held. Deterministic (pure math); filtered to
/// <see cref="Simulate"/> so it runs only for predicted ghosts.
/// </summary>
[UpdateInGroup(typeof(PredictedSimulationSystemGroup))]
[BurstCompile]
public partial struct PlayerAimSystem : ISystem
{
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
foreach (var (facing, transform, input) in
SystemAPI.Query<RefRW<PlayerFacing>, RefRW<LocalTransform>, RefRO<PlayerInput>>()
.WithAll<Simulate>())
{
float2 aim = input.ValueRO.Aim;
if (math.lengthsq(aim) < 1e-6f)
aim = input.ValueRO.Move; // fall back to movement heading
if (math.lengthsq(aim) < 1e-6f)
continue; // no input this tick: keep last facing
aim = math.normalize(aim);
facing.ValueRW.Direction = aim;
float3 forward = new float3(aim.x, 0f, aim.y);
transform.ValueRW.Rotation = quaternion.LookRotationSafe(forward, math.up());
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3a274d036dc034cbaa70f6a782d8784a
@@ -0,0 +1,18 @@
using Unity.Entities;
using Unity.Mathematics;
using Unity.NetCode;
namespace ProjectM.Simulation
{
/// <summary>
/// Authoritative aim/facing direction, decoupled from movement heading so twin-stick aim is
/// independent of travel direction. Written in predicted sim from PlayerInput.Aim; consumed by
/// presentation now and ability systems (M2). Replicated so interpolated remote players show
/// the correct facing.
/// </summary>
public struct PlayerFacing : IComponentData
{
/// <summary>Normalized planar facing direction (world XZ mapped to float2 x,y).</summary>
[GhostField(Quantization = 1000)] public float2 Direction;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8d0adf0e4def842a89a3017de243aca9
@@ -0,0 +1,30 @@
using Unity.Collections;
using Unity.Mathematics;
using Unity.NetCode;
namespace ProjectM.Simulation
{
/// <summary>
/// Twin-stick player input (server-authoritative, input-only clients). Gathered once per frame
/// on the owning client in <see cref="GhostInputSystemGroup"/> and streamed to the server via
/// AutoCommandTarget. Netcode source-gen produces InputBufferData&lt;PlayerInput&gt; plus the
/// copy/apply systems from this type. The [GhostField]s let remote owners be predicted; the
/// data replays deterministically under rollback.
/// </summary>
public struct PlayerInput : IInputComponentData
{
/// <summary>WASD / left-stick movement, normalized to roughly -1..1 per axis.</summary>
[GhostField(Quantization = 1000)] public float2 Move;
/// <summary>Right-stick / cursor aim direction (normalized). Zero =&gt; face movement direction.</summary>
[GhostField(Quantization = 1000)] public float2 Aim;
public FixedString512Bytes ToFixedString()
{
var s = new FixedString512Bytes();
s.Append(Move.x); s.Append(','); s.Append(Move.y); s.Append(';');
s.Append(Aim.x); s.Append(','); s.Append(Aim.y);
return s;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 72ac3666802bf49f891f49a0d03201e5
@@ -0,0 +1,17 @@
using Unity.Entities;
namespace ProjectM.Simulation
{
/// <summary>
/// Per-player movement tunables, baked from authoring. Identical on client (re-prediction) and
/// server so movement is deterministic. Not replicated.
/// </summary>
public struct PlayerMoveStats : IComponentData
{
/// <summary>Planar movement speed in units/second.</summary>
public float MoveSpeed;
/// <summary>Max turn rate (radians/second) when rotating toward the facing direction.</summary>
public float TurnRateRadiansPerSec;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: cf5fc79d6c67d4ef39ba4e7e9457dd85
@@ -0,0 +1,39 @@
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.NetCode;
using Unity.Transforms;
namespace ProjectM.Simulation
{
/// <summary>
/// Canonical predicted system: advances each player's planar (XZ) position from twin-stick Move
/// input. Runs inside the prediction loop on the owning client (re-simulated on rollback) and
/// once per tick on the server; filtered to <see cref="Simulate"/> so only predicted ghosts move.
/// Deterministic by construction: uses <c>SystemAPI.Time.DeltaTime</c> (the fixed tick step)
/// only — no wall-clock, no <c>System.Random</c>. Move is clamped to unit length so diagonal
/// keyboard movement is not faster than cardinal.
/// </summary>
[UpdateInGroup(typeof(PredictedSimulationSystemGroup))]
[BurstCompile]
public partial struct PlayerMoveSystem : ISystem
{
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
float dt = SystemAPI.Time.DeltaTime;
foreach (var (transform, input, stats) in
SystemAPI.Query<RefRW<LocalTransform>, RefRO<PlayerInput>, RefRO<PlayerMoveStats>>()
.WithAll<Simulate>())
{
float2 move = input.ValueRO.Move;
if (math.lengthsq(move) > 1f)
move = math.normalize(move);
float3 delta = new float3(move.x, 0f, move.y) * stats.ValueRO.MoveSpeed * dt;
transform.ValueRW.Position += delta;
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 35cd3eacccc2f4172b557b2807a6df22
@@ -0,0 +1,10 @@
using Unity.Entities;
namespace ProjectM.Simulation
{
/// <summary>
/// Zero-size marker identifying a player-character ghost. Lets movement/aim/ability systems
/// query players without coupling to other gameplay components. Added by PlayerBaker.
/// </summary>
public struct PlayerTag : IComponentData { }
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1f967358256a44853bfc5be66e13bf3b
@@ -3,6 +3,7 @@
"rootNamespace": "ProjectM.Simulation",
"references": [
"Unity.Entities",
"Unity.Transforms",
"Unity.Collections",
"Unity.Mathematics",
"Unity.Burst",
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 37f2ed4a443ac4a18aba505897f9e6fa
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,15 @@
using Unity.Entities;
using Unity.Mathematics;
namespace ProjectM.Simulation
{
/// <summary>
/// Singleton baked into the gameplay subscene, holding the baked player ghost prefab entity so
/// the server spawn system can instantiate it on connect. Mirrors the netcode CubeSpawner sample.
/// </summary>
public struct PlayerSpawner : IComponentData
{
public Entity PlayerPrefab;
public float3 SpawnPoint;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8d1945139846b49a7943b7149188aa45