using Unity.Burst; using Unity.Entities; using Unity.Physics; using Unity.Physics.Systems; namespace Unity.NetCode { /// /// A system which builds the physics world based on the entity world. The world will contain a /// rigid body for every entity which has a rigid body component, and a joint for every entity /// which has a joint component. /// [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)] [BurstCompile] [UpdateInGroup(typeof(PhysicsBuildWorldGroup))] [CreateAfter(typeof(BuildPhysicsWorld))] public partial struct CustomBuildPhysicsWorld : ISystem { private SystemHandle buildPhysicSystemHandle; private int currentPhysicStep; [BurstCompile] public void OnCreate(ref SystemState state) { buildPhysicSystemHandle = state.WorldUnmanaged.GetExistingUnmanagedSystem(); //It is necesary to create temp PhysicsWorldData here for sake of setting up the correct dependencies. //The PhysicsWorldData itself is then retrieved from the BuildPhysicsWorld all the time. var physicsData = new PhysicsWorldData(ref state, new PhysicsWorldIndex()); physicsData.Dispose(); state.EntityManager.AddComponentData(state.SystemHandle, new BuildPhysicsWorldSettings()); //The sytem always start disabled and will be enabled/disabled based on the PhysicsCustomLoop component //settings by the ConfigureBuildPhysicsWorld. state.Enabled = false; } [BurstCompile] public void OnUpdate(ref SystemState state) { //First we need to need to complete any pending dependencies before building or updating the //physics world. In particular the BuildPhysicsWorldData may have some physics jobs that need to be //completed but for which the dependency cannot be tracked automatically. They are tracked by the internal // InputDepdency handled and it is possible to wait for them here using the exposed CompleteInputDependency method. ref var buildPhysicsData = ref state.EntityManager.GetComponentDataRW(buildPhysicSystemHandle).ValueRW; buildPhysicsData.CompleteInputDependency(); float timeStep = SystemAPI.Time.DeltaTime; if (!SystemAPI.TryGetSingleton(out PhysicsStep stepComponent)) stepComponent = PhysicsStep.Default; //The BuildPhysicsWorldSettings govern how the build system construct the physics world. In particular, //either a full rebuild or an "incremental" one, that just update the current brodphase by enlargin the AABB //by using the last calculated physics step PhysicsVelocity and gravity. //Notice that in case of prediction, the PhysicsVelocity may be actually a replicated value from the server //and not the last predicted value, being used as both input-output. var settings = state.EntityManager.GetComponentData(state.SystemHandle); // In case we just want to update the broadphase and motion data we check if any new // physics object has been created or destroyed (either, static, dynamic or joint) and we force a full rebuild // in that case. if (settings.UpdateBroadphaseAndMotion == 1) { var dynamicObject = buildPhysicsData.PhysicsData.DynamicEntityGroup.CalculateEntityCount(); //+1 to account for the default static body object var staticObject = buildPhysicsData.PhysicsData.StaticEntityGroup.CalculateEntityCount() + 1; var joints = buildPhysicsData.PhysicsData.JointEntityGroup.CalculateEntityCount(); //If entity count changed we forcibly rebuild (and we log this) if (buildPhysicsData.PhysicsData.PhysicsWorld.NumDynamicBodies != dynamicObject || buildPhysicsData.PhysicsData.PhysicsWorld.NumStaticBodies != staticObject || buildPhysicsData.PhysicsData.PhysicsWorld.NumJoints != joints) { settings.UpdateBroadphaseAndMotion = 0; } } // The following code either does a full rebuild (BuildPhysicsWorld) // or does an partial update of the Motion and Broaphase. // The update of the motions and the broaphase are not executed // by the same method (i.e ScheduleUpdateBroadphase) because this way there is a little bit more // flexibility for checking if and what we actually need to update. Technically, only simulated physics entities can have LocalTransform, // and PhysicsVelocity expoerted, meaning that "kinematic-like" motion data in general does not change, and // can be potentially not need an update. //Immediate mode could be a good choice to use when the number of physics entities is small. //In this sammple, this is executed on the main thread but it is possible to also execute that //in a job. if (settings.UseImmediateMode != 0) { state.CompleteDependency(); if (settings.UpdateBroadphaseAndMotion == 0) { PhysicsWorldBuilder.BuildPhysicsWorldImmediate(ref state, ref buildPhysicsData.PhysicsData, timeStep, stepComponent.Gravity, state.LastSystemVersion); } else { buildPhysicsData.PhysicsData.Update(ref state); PhysicsWorldBuilder.UpdateMotionDataImmediate(ref state, ref buildPhysicsData.PhysicsData); //This method internally check if necessary to update also the static tree PhysicsWorldBuilder.UpdateBroadphaseImmediate(ref buildPhysicsData.PhysicsData, timeStep, stepComponent.Gravity, state.LastSystemVersion); } } else { //Full rebuild of the physics world. if (settings.UpdateBroadphaseAndMotion == 0) { state.Dependency = PhysicsWorldBuilder.SchedulePhysicsWorldBuild(ref state, ref buildPhysicsData.PhysicsData, state.Dependency, timeStep, stepComponent.MultiThreaded > 0, stepComponent.Gravity, state.LastSystemVersion); } else { buildPhysicsData.PhysicsData.Update(ref state); state.Dependency = PhysicsWorldBuilder.ScheduleUpdateMotionData(ref state, ref buildPhysicsData.PhysicsData, state.Dependency); //This method internally check if necessary to update also the static tree state.Dependency = PhysicsWorldBuilder.ScheduleUpdateBroadphase( ref buildPhysicsData.PhysicsData, timeStep, stepComponent.Gravity, state.LastSystemVersion, state.Dependency, stepComponent.MultiThreaded > 0); } } SystemAPI.SetSingleton(new PhysicsWorldSingleton { PhysicsWorld = buildPhysicsData.PhysicsData.PhysicsWorld, PhysicsWorldIndex = buildPhysicsData.WorldFilter }); } } }