AetherNet completely decouples 2D physics simulation from Unity’s native engine. A single, deterministic physics loop powered by Aether.Physics2D (a pure C# Box2D port) runs identically on a headless .NET 8 server and a Unity client — with zero runtime heap allocation.
| Package | Target | Description |
|---|---|---|
AetherNet.Shared |
.NET Standard 2.0 / .NET 8 | Core simulation: PhysicsWorldManager, collision, networking utilities, queries. Used on both server and client. |
AetherNet.Unity |
Unity (via NuGetForUnity) | MonoBehaviour components: AetherRigidbody, colliders, AetherViewManager, physics queries, scene baker, editor gizmos. |
dotnet add package AetherNet.Shared
- Install NuGetForUnity (free, open source).
- In Unity: NuGet → Manage NuGet Packages, search for AetherNet.Unity and install.
AetherNet.Sharedis pulled in automatically as a dependency.
- ⚡ Zero runtime GC allocation — pre-allocated parallel arrays throughout
- 🎯 100% deterministic fixed-timestep simulation — identical results on server and client
- 🖥️ Headless .NET 8 server — no Unity license required; loads baked JSON map files
- 💥 Unity-style collision callbacks —
OnCollisionEnter/Exit,OnTriggerEnter/Exitvia interface dispatch - 💪 Full force API —
AddForce,AddTorque,AddForceAtPositionwithForceMode - 🔍 Physics queries —
Raycast,OverlapCircle,OverlapBoxwith zero-alloc buffers - 🏗️ Editor scene baker — AetherNet → Bake Scene to JSON exports map data for the headless server
- 🎨 Scene View gizmos — box, circle, and polygon colliders drawn in the editor
- 🔗 Transport-agnostic networking — plug in Mirror, FishNet, LiteNetLib, or raw sockets
- 🔒 Rigidbody constraints —
FreezePositionX/Y,FreezeRotationapplied post-step
- Create a GameObject → Add AetherNet → View Manager.
- On entity prefabs, add AetherNet → Rigidbody + one of Box / Circle / Polygon Collider.
- Bake the scene for the server: AetherNet → Bake Scene to JSON.
using AetherNet;
public class Player : MonoBehaviour, IAetherCollisionHandler, IAetherTriggerHandler
{
private AetherRigidbody _rb;
void Awake() => _rb = GetComponent<AetherRigidbody>();
void Update()
{
if (Input.GetKey(KeyCode.Space))
_rb.AddForce(Vector2.up * 500f, ForceMode.Impulse);
}
public void OnCollisionEnter(ref CollisionData d) => Debug.Log($"Hit entity {d.EntityIdB}");
public void OnCollisionExit(ref CollisionData d) { }
public void OnTriggerEnter(ref TriggerData d) => Debug.Log($"Trigger {d.OtherEntityId}");
public void OnTriggerExit(ref TriggerData d) { }
}cd src/AetherNet.Server
dotnet run -- maps/level01.jsonvar world = new PhysicsWorldManager(WorldConfig.Default);
var loader = new MapLoader();
loader.LoadInto(world, "maps/level01.json");
var loop = new ServerTickLoop(world);
loop.SetSnapshotCallback((states, count, tick) =>
{
int bytes = StateSerializer.Serialize(states, count, sendBuffer, 0);
myTransport.BroadcastUnreliable(sendBuffer, bytes);
});
loop.Run(CancellationToken.None);| Type | Purpose |
|---|---|
INetworkStateProvider |
Hook into the tick loop — implement to broadcast state |
StateSerializer |
Zero-alloc binary write/read of EntityState[] |
StateInterpolator |
Client-side snapshot lerp for smooth rendering |
SnapshotBuffer |
Circular buffer of authoritative snapshots |
TickAcknowledger |
Bitmask ack tracking for delta compression |
See examples/LiteNetLibExample/ for a complete LiteNetLib integration.
- Fork and branch:
feature/your-featureorfix/issue. - All
AetherNet.Sharedchanges must passdotnet test. - PR checklist: build clean, tests green, no new GC allocs in hot paths.