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,22 @@
#if RUKHANKA_WITH_NETCODE
using Unity.NetCode;
/////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Samples
{
[UnityEngine.Scripting.Preserve]
public class GameBootstrap : ClientServerBootstrap
{
public override bool Initialize(string defaultWorldName)
{
AutoConnectPort = 7979; // Enabled auto connect
return base.Initialize(defaultWorldName); // Use the regular bootstrap
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: bdfdae841942d834bb042df3854712d0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Samples~/Samples/Scenes/13. Netcode Demo/Scripts/Game.cs
uploadId: 897522
@@ -0,0 +1,84 @@
#if RUKHANKA_WITH_NETCODE
using Unity.Collections;
using Unity.Entities;
using Unity.NetCode;
using Unity.Burst;
using UnityEngine;
namespace Rukhanka.Samples
{
public struct GoInGameRequest : IRpcCommand {}
/////////////////////////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation | WorldSystemFilterFlags.ThinClientSimulation)]
public partial struct GoInGameClientSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
var builder = new EntityQueryBuilder(Allocator.Temp)
.WithAll<NetworkId>()
.WithNone<NetworkStreamInGame>();
state.RequireForUpdate(state.GetEntityQuery(builder));
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var commandBuffer = new EntityCommandBuffer(Allocator.Temp);
foreach (var (id, entity) in SystemAPI.Query<RefRO<NetworkId>>().WithEntityAccess().WithNone<NetworkStreamInGame>())
{
commandBuffer.AddComponent<NetworkStreamInGame>(entity);
var req = commandBuffer.CreateEntity();
commandBuffer.AddComponent<GoInGameRequest>(req);
commandBuffer.AddComponent(req, new SendRpcCommandRequest { TargetConnection = entity });
}
commandBuffer.Playback(state.EntityManager);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
public partial struct GoInGameServerSystem : ISystem
{
private ComponentLookup<NetworkId> networkIdFromEntity;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
var builder = new EntityQueryBuilder(Allocator.Temp)
.WithAll<GoInGameRequest>()
.WithAll<ReceiveRpcCommandRequest>();
state.RequireForUpdate(state.GetEntityQuery(builder));
networkIdFromEntity = state.GetComponentLookup<NetworkId>(true);
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var worldName = state.WorldUnmanaged.Name;
var commandBuffer = new EntityCommandBuffer(Allocator.Temp);
networkIdFromEntity.Update(ref state);
foreach (var (reqSrc, reqEntity) in SystemAPI.Query<RefRO<ReceiveRpcCommandRequest>>().WithAll<GoInGameRequest>().WithEntityAccess())
{
commandBuffer.AddComponent<NetworkStreamInGame>(reqSrc.ValueRO.SourceConnection);
var networkId = networkIdFromEntity[reqSrc.ValueRO.SourceConnection];
Debug.Log($"'{worldName}' setting connection '{networkId.Value}' to in game");
commandBuffer.DestroyEntity(reqEntity);
}
commandBuffer.Playback(state.EntityManager);
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 4576be8e57f3a0943ac7190f541f192a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Samples~/Samples/Scenes/13. Netcode Demo/Scripts/GoInGame.cs
uploadId: 897522
@@ -0,0 +1,99 @@
#if RUKHANKA_WITH_NETCODE
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.NetCode;
using Unity.Transforms;
using static Unity.Entities.SystemAPI;
using Random = Unity.Mathematics.Random;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Samples
{
public struct ServerSpawnPrefabCommand: IRpcCommand
{
public int spawnCount;
public float3 spawnerPos;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
public partial struct ServerSpawnerSystem: ISystem
{
uint updateCounter;
BufferLookup<AnimatorControllerParameterComponent> paramBufLookup;
ComponentLookup<NetworkId> networkIdLookup;
EntityQuery prefabSpawners;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnCreate(ref SystemState ss)
{
paramBufLookup = ss.GetBufferLookup<AnimatorControllerParameterComponent>();
networkIdLookup = ss.GetComponentLookup<NetworkId>();
var eqb0 = new EntityQueryBuilder(Allocator.Temp)
.WithAll<SpawnPrefabComponent, NetworkedPrefab>();
prefabSpawners = ss.GetEntityQuery(eqb0);
}
[BurstCompile]
public void OnUpdate(ref SystemState ss)
{
var ecbs = GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>();
var ecb = ecbs.CreateCommandBuffer(ss.WorldUnmanaged);
FastAnimatorParameter animSpeed = new FastAnimatorParameter("Crowd_AnimationSpeed");
paramBufLookup.Update(ref ss);
networkIdLookup.Update(ref ss);
var spawners = prefabSpawners.ToComponentDataArray<SpawnPrefabComponent>(Allocator.Temp);
foreach (var (c0, c1, e) in Query<RefRO<ServerSpawnPrefabCommand>, RefRO<ReceiveRpcCommandRequest>>().WithEntityAccess())
{
var spc = c0.ValueRO;
var rpc = c1.ValueRO;
var random = Random.CreateFromIndex(updateCounter++);
for (var i = 0; i < spc.spawnCount; ++i)
{
var spawner = spawners[(int)(random.NextUInt() % spawners.Length)];
var entity = ss.EntityManager.Instantiate(spawner.prefabToSpawn);
var randomPos = random.NextFloat2() * 2 - 1;
var position = new float3(randomPos.x, 0, randomPos.y) * spawner.spawnRadius;
var rot = quaternion.RotateY(random.NextFloat() * math.PI * 2);
var transform = ss.EntityManager.GetComponentData<LocalTransform>(entity);
transform.Position += position + spc.spawnerPos;
transform.Rotation = rot;
ecb.SetComponent(entity, transform);
var netId = networkIdLookup[rpc.SourceConnection];
var go = new GhostOwner() { NetworkId = netId.Value };
ecb.AddComponent(entity, go);
ecb.AppendToBuffer(rpc.SourceConnection, new LinkedEntityGroup { Value = entity });
if (HasComponent<AnimatorControllerParameterIndexTableComponent>(entity))
{
var acpit = GetComponent<AnimatorControllerParameterIndexTableComponent>(entity);
paramBufLookup.TryGetBuffer(entity, out var acpc);
var randomSpeedVal = (random.NextFloat() * 2 - 1) * 0.5f + 1;
animSpeed.SetRuntimeParameterData(acpit.value, acpc, new ParameterValue() { floatValue = randomSpeedVal } );
}
}
ecb.DestroyEntity(e);
}
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 8dc2f39e57608d24bb029603c441358e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Samples~/Samples/Scenes/13. Netcode Demo/Scripts/ServerSpawner.cs
uploadId: 897522
@@ -0,0 +1,155 @@
using TMPro;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
#if RUKHANKA_WITH_NETCODE
using Unity.NetCode;
#endif
using UnityEngine;
using UnityEngine.UI;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Samples
{
class UIController_NetcodeDemo: MonoBehaviour
{
public TextMeshProUGUI spawnCountLabel;
public TextMeshProUGUI localObjectsCountLabel;
public TextMeshProUGUI predictedGhostCountLabel;
public TextMeshProUGUI interpolatedGhostCountLabel;
public TextMeshProUGUI descriptionLabel;
public Slider spawnCountSlider;
public Button spawnNetworkedBtn;
public Button spawnLocalBtn;
EntityQuery spawnerQuery, connectionQuery, localObjectsQuery, predictedGhostObjectsQuery, interpolatedGhostObjectsQuery;
EntityManager em;
/////////////////////////////////////////////////////////////////////////////////
void Start()
{
#if !RUKHANKA_WITH_NETCODE
descriptionLabel.text += $"\n\n<color=red>This sample is intended to work with 'Netcode for Entites' package and RUKHANKA_WITH_NETCODE script symbol defined! </color>";
#endif
spawnNetworkedBtn.onClick.AddListener(SpawnNetworkedPrefabs);
spawnLocalBtn.onClick.AddListener(SpawnLocalPrefabs);
var worlds = World.All;
foreach (var w in worlds)
{
if (RukhankaSystemsBootstrap.IsClientOrLocalSimulationWorld(w))
{
em = w.EntityManager;
break;
}
}
var ecb0 = new EntityQueryBuilder(Allocator.Temp)
.WithAll<SpawnPrefabComponent>()
.WithNone<NetworkedPrefab>();
spawnerQuery = em.CreateEntityQuery(ecb0);
var ecb1 = new EntityQueryBuilder(Allocator.Temp)
#if RUKHANKA_WITH_NETCODE
.WithAll<NetworkId>()
#endif
;
connectionQuery = em.CreateEntityQuery(ecb1);
var ecb2 = new EntityQueryBuilder(Allocator.Temp)
.WithAll<AnimatorControllerLayerComponent>()
#if RUKHANKA_WITH_NETCODE
.WithNone<GhostInstance>()
#endif
;
localObjectsQuery = em.CreateEntityQuery(ecb2);
var ecb3 = new EntityQueryBuilder(Allocator.Temp)
.WithAll<AnimatorControllerLayerComponent>()
#if RUKHANKA_WITH_NETCODE
.WithAll<PredictedGhost>()
#endif
;
predictedGhostObjectsQuery = em.CreateEntityQuery(ecb3);
var ecb4 = new EntityQueryBuilder(Allocator.Temp)
.WithAll<AnimatorControllerLayerComponent>()
#if RUKHANKA_WITH_NETCODE
.WithNone<PredictedGhost>()
.WithAll<GhostInstance>()
#endif
;
interpolatedGhostObjectsQuery = em.CreateEntityQuery(ecb4);
#if !RUKHANKA_WITH_NETCODE
predictedGhostCountLabel.enabled = false;
interpolatedGhostCountLabel.enabled = false;
#endif
}
/////////////////////////////////////////////////////////////////////////////////
void SpawnLocalPrefabs()
{
var spawners = spawnerQuery.ToEntityArray(Allocator.Temp);
var alreadySpawned = 0;
var spawnCount = (int)math.max(1, spawnCountSlider.value / spawners.Length);
for (var i = 0; i < spawners.Length && alreadySpawned < spawnCountSlider.value; ++i)
{
var scc = new SpawnCommandComponent()
{
spawnCount = i == spawners.Length - 1 ? (int)spawnCountSlider.value - alreadySpawned : spawnCount
};
alreadySpawned += scc.spawnCount;
em.AddComponentData(spawners[i], scc);
}
}
/////////////////////////////////////////////////////////////////////////////////
void SpawnNetworkedPrefabs()
{
#if RUKHANKA_WITH_NETCODE
var connection = connectionQuery.ToEntityArray(Allocator.Temp);
if (!connection.IsCreated || connection.Length == 0)
{
Debug.LogError($"Cannot send spawn command! No server connection!");
return;
}
var scc = new ServerSpawnPrefabCommand()
{
spawnerPos = float3.zero,
spawnCount = (int)spawnCountSlider.value
};
var e = em.CreateEntity();
em.AddComponentData(e, scc);
var rpc = new SendRpcCommandRequest() { TargetConnection = connection[0]};
em.AddComponentData(e, rpc);
#endif
}
/////////////////////////////////////////////////////////////////////////////////
void Update()
{
spawnCountLabel.text = $"{spawnCountSlider.value}";
var localObjectsCount = localObjectsQuery.CalculateEntityCount();
localObjectsCountLabel.text = $"Local objects count: {localObjectsCount}";
#if RUKHANKA_WITH_NETCODE
var predictedGhostCount = predictedGhostObjectsQuery.CalculateEntityCount();
predictedGhostCountLabel.text = $"Predicted ghosts count: {predictedGhostCount}";
var interpolatedGhostsCount = interpolatedGhostObjectsQuery.CalculateEntityCount();
interpolatedGhostCountLabel.text = $"Interpolated ghosts count: {interpolatedGhostsCount}";
#endif
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 6124b89967d417b4ba756adbf28d1bf4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Samples~/Samples/Scenes/13. Netcode Demo/Scripts/UIController_NetcodeDemo.cs
uploadId: 897522