Files
2026-06-02 08:56:26 -07:00

105 lines
4.4 KiB
C#

using Unity.Burst;
using Unity.Burst.Intrinsics;
using Unity.CharacterController;
using Unity.Entities;
using Unity.Physics;
using Unity.Transforms;
namespace ProjectM.Simulation
{
/// <summary>
/// Fixed-step kinematic character update. Runs in <see cref="KinematicCharacterPhysicsUpdateGroup"/>
/// (which sits after the physics build); under Netcode-for-Entities the physics groups are relocated into
/// <see cref="Unity.NetCode.PredictedFixedStepSimulationSystemGroup"/>, so the character integrates inside
/// the predicted loop — deterministic, rollback-safe, identical on server + owning client. Builds a
/// <see cref="KinematicCharacterDataAccess"/> per entity and runs the processor's PhysicsUpdate. Filtered
/// to <see cref="Simulate"/> so only predicted ghosts step.
/// </summary>
[UpdateInGroup(typeof(KinematicCharacterPhysicsUpdateGroup))]
[BurstCompile]
public partial struct CharacterPhysicsUpdateSystem : ISystem
{
EntityQuery m_CharacterQuery;
CharacterUpdateContext m_Context;
KinematicCharacterUpdateContext m_BaseContext;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
m_CharacterQuery = KinematicCharacterUtilities.GetBaseCharacterQueryBuilder()
.WithAll<CharacterComponent, CharacterControl>()
.Build(ref state);
m_Context = new CharacterUpdateContext();
m_Context.OnSystemCreate(ref state);
m_BaseContext = new KinematicCharacterUpdateContext();
m_BaseContext.OnSystemCreate(ref state);
state.RequireForUpdate(m_CharacterQuery);
state.RequireForUpdate<PhysicsWorldSingleton>();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
m_Context.OnSystemUpdate(ref state);
m_BaseContext.OnSystemUpdate(ref state, SystemAPI.Time, SystemAPI.GetSingleton<PhysicsWorldSingleton>());
var job = new CharacterPhysicsUpdateJob
{
Context = m_Context,
BaseContext = m_BaseContext,
};
state.Dependency = job.Schedule(state.Dependency);
}
[BurstCompile]
[WithAll(typeof(Simulate))]
public partial struct CharacterPhysicsUpdateJob : IJobEntity, IJobEntityChunkBeginEnd
{
public CharacterUpdateContext Context;
public KinematicCharacterUpdateContext BaseContext;
void Execute(
Entity entity,
RefRW<LocalTransform> localTransform,
RefRW<KinematicCharacterProperties> characterProperties,
RefRW<KinematicCharacterBody> characterBody,
RefRW<PhysicsCollider> physicsCollider,
RefRW<CharacterComponent> characterComponent,
RefRW<CharacterControl> characterControl,
DynamicBuffer<KinematicCharacterHit> characterHitsBuffer,
DynamicBuffer<StatefulKinematicCharacterHit> statefulHitsBuffer,
DynamicBuffer<KinematicCharacterDeferredImpulse> deferredImpulsesBuffer,
DynamicBuffer<KinematicVelocityProjectionHit> velocityProjectionHits)
{
var processor = new CharacterProcessor
{
CharacterDataAccess = new KinematicCharacterDataAccess(
entity,
localTransform,
characterProperties,
characterBody,
physicsCollider,
characterHitsBuffer,
statefulHitsBuffer,
deferredImpulsesBuffer,
velocityProjectionHits),
CharacterComponent = characterComponent,
CharacterControl = characterControl,
};
processor.PhysicsUpdate(ref Context, ref BaseContext);
}
public bool OnChunkBegin(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
{
BaseContext.EnsureCreationOfTmpCollections();
return true;
}
public void OnChunkEnd(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask, bool chunkWasExecuted) { }
}
}
}