c58a17b8af
Field report: walking onto hilly-looking colliders (the new convex rock hulls are ramps) raised the character's Y, and with gravity zero + SnapToGround off nothing ever brought it back down - the character floated and its shadow drifted away. Fix inside CharacterProcessor.PhysicsUpdate: capture the pre-update Y and restore it (plus zero RelativeVelocity.y) after the CC sequence - the character update itself can never change the movement plane, while spawn/teleport writes made outside the update stay honored. Deterministic + idempotent (rollback-safe), identical on server + predicted client. Verified live: player shoved into a cover-hull sloped edge decollided ~1 u horizontally with Y bit-exact at 1.0000 (the case that previously rode up the slope); 466/466 EditMode, console clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
223 lines
11 KiB
C#
223 lines
11 KiB
C#
using Unity.CharacterController;
|
|
using Unity.Entities;
|
|
using Unity.Mathematics;
|
|
using Unity.Physics;
|
|
|
|
namespace ProjectM.Simulation
|
|
{
|
|
/// <summary>
|
|
/// Per-update "global" data the character processor needs (lookups, singletons). Empty for the minimal
|
|
/// top-down character — kept so the CC update signature is satisfied and future lookups have a home.
|
|
/// </summary>
|
|
public struct CharacterUpdateContext
|
|
{
|
|
public void OnSystemCreate(ref SystemState state) { }
|
|
public void OnSystemUpdate(ref SystemState state) { }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Top-down kinematic character processor (CC 1.4.2 pattern: <see cref="IKinematicCharacterProcessor{T}"/>
|
|
/// + a <see cref="KinematicCharacterDataAccess"/> built in the job, driving the static
|
|
/// <see cref="KinematicCharacterUtilities"/> Update_* sequence). Stripped of all FPS features: no jump,
|
|
/// no air movement, no gravity, no view. <see cref="PhysicsUpdate"/> runs the canonical CC update
|
|
/// sequence; <see cref="HandleVelocityControl"/> lerps RelativeVelocity toward the desired planar
|
|
/// velocity <see cref="PlayerControlSystem"/> wrote. Rotation/facing is owned by <see cref="PlayerAimSystem"/>.
|
|
/// </summary>
|
|
public struct CharacterProcessor : IKinematicCharacterProcessor<CharacterUpdateContext>
|
|
{
|
|
public KinematicCharacterDataAccess CharacterDataAccess;
|
|
public RefRW<CharacterComponent> CharacterComponent;
|
|
public RefRW<CharacterControl> CharacterControl;
|
|
|
|
public void PhysicsUpdate(ref CharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext)
|
|
{
|
|
ref CharacterComponent characterComponent = ref CharacterComponent.ValueRW;
|
|
ref KinematicCharacterBody characterBody = ref CharacterDataAccess.CharacterBody.ValueRW;
|
|
ref float3 characterPosition = ref CharacterDataAccess.LocalTransform.ValueRW.Position;
|
|
float planeY = characterPosition.y; // authored movement plane — restored after the update (see below)
|
|
|
|
KinematicCharacterUtilities.Update_Initialize(
|
|
in this, ref context, ref baseContext,
|
|
ref characterBody,
|
|
CharacterDataAccess.CharacterHitsBuffer,
|
|
CharacterDataAccess.DeferredImpulsesBuffer,
|
|
CharacterDataAccess.VelocityProjectionHits,
|
|
baseContext.Time.DeltaTime);
|
|
|
|
KinematicCharacterUtilities.Update_ParentMovement(
|
|
in this, ref context, ref baseContext,
|
|
CharacterDataAccess.CharacterEntity,
|
|
ref characterBody,
|
|
CharacterDataAccess.CharacterProperties.ValueRO,
|
|
CharacterDataAccess.PhysicsCollider.ValueRO,
|
|
CharacterDataAccess.LocalTransform.ValueRO,
|
|
ref characterPosition,
|
|
characterBody.WasGroundedBeforeCharacterUpdate);
|
|
|
|
KinematicCharacterUtilities.Update_Grounding(
|
|
in this, ref context, ref baseContext,
|
|
ref characterBody,
|
|
CharacterDataAccess.CharacterEntity,
|
|
CharacterDataAccess.CharacterProperties.ValueRO,
|
|
CharacterDataAccess.PhysicsCollider.ValueRO,
|
|
CharacterDataAccess.LocalTransform.ValueRO,
|
|
CharacterDataAccess.VelocityProjectionHits,
|
|
CharacterDataAccess.CharacterHitsBuffer,
|
|
ref characterPosition);
|
|
|
|
HandleVelocityControl(ref context, ref baseContext);
|
|
|
|
KinematicCharacterUtilities.Update_PreventGroundingFromFutureSlopeChange(
|
|
in this, ref context, ref baseContext,
|
|
CharacterDataAccess.CharacterEntity,
|
|
ref characterBody,
|
|
CharacterDataAccess.CharacterProperties.ValueRO,
|
|
CharacterDataAccess.PhysicsCollider.ValueRO,
|
|
in characterComponent.StepAndSlopeHandling);
|
|
|
|
KinematicCharacterUtilities.Update_GroundPushing(
|
|
in this, ref context, ref baseContext,
|
|
ref characterBody,
|
|
CharacterDataAccess.CharacterProperties.ValueRO,
|
|
CharacterDataAccess.LocalTransform.ValueRO,
|
|
CharacterDataAccess.DeferredImpulsesBuffer,
|
|
float3.zero);
|
|
|
|
KinematicCharacterUtilities.Update_MovementAndDecollisions(
|
|
in this, ref context, ref baseContext,
|
|
CharacterDataAccess.CharacterEntity,
|
|
ref characterBody,
|
|
CharacterDataAccess.CharacterProperties.ValueRO,
|
|
CharacterDataAccess.PhysicsCollider.ValueRO,
|
|
CharacterDataAccess.LocalTransform.ValueRO,
|
|
CharacterDataAccess.VelocityProjectionHits,
|
|
CharacterDataAccess.CharacterHitsBuffer,
|
|
CharacterDataAccess.DeferredImpulsesBuffer,
|
|
ref characterPosition);
|
|
|
|
KinematicCharacterUtilities.Update_MovingPlatformDetection(ref baseContext, ref characterBody);
|
|
KinematicCharacterUtilities.Update_ParentMomentum(ref baseContext, ref characterBody,
|
|
CharacterDataAccess.LocalTransform.ValueRO.Position);
|
|
KinematicCharacterUtilities.Update_ProcessStatefulCharacterHits(
|
|
CharacterDataAccess.CharacterHitsBuffer,
|
|
CharacterDataAccess.StatefulHitsBuffer);
|
|
|
|
// Top-down planar lock: the world is one flat plane and gravity is zero, so any Y gained by
|
|
// step-handling/velocity-projection up a sloped collider (convex rock hulls etc.) would persist
|
|
// FOREVER — the character ends up floating and its shadow drifts away (07-09 field report).
|
|
// Restoring the pre-update Y forbids the CC update itself from ever changing the plane while
|
|
// spawn/teleport writes (made outside this update) stay honored. Deterministic + idempotent.
|
|
characterPosition.y = planeY;
|
|
characterBody.RelativeVelocity.y = 0f;
|
|
}
|
|
|
|
void HandleVelocityControl(ref CharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext)
|
|
{
|
|
float deltaTime = baseContext.Time.DeltaTime;
|
|
ref KinematicCharacterBody characterBody = ref CharacterDataAccess.CharacterBody.ValueRW;
|
|
ref CharacterComponent characterComponent = ref CharacterComponent.ValueRW;
|
|
CharacterControl characterControl = CharacterControl.ValueRO;
|
|
|
|
// Planar twin-stick: smoothly approach the desired world velocity (already speed-scaled, Y==0).
|
|
float3 targetVelocity = characterControl.MoveVelocity;
|
|
CharacterControlUtilities.StandardGroundMove_Interpolated(
|
|
ref characterBody.RelativeVelocity,
|
|
targetVelocity,
|
|
characterComponent.GroundedMovementSharpness,
|
|
deltaTime,
|
|
math.up(),
|
|
math.up());
|
|
}
|
|
|
|
public void VariableUpdate(ref CharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext) { }
|
|
|
|
public void UpdateGroundingUp(ref CharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext)
|
|
{
|
|
ref KinematicCharacterBody characterBody = ref CharacterDataAccess.CharacterBody.ValueRW;
|
|
KinematicCharacterUtilities.Default_UpdateGroundingUp(
|
|
ref characterBody,
|
|
CharacterDataAccess.LocalTransform.ValueRO.Rotation);
|
|
}
|
|
|
|
public bool CanCollideWithHit(ref CharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in BasicHit hit)
|
|
{
|
|
return PhysicsUtilities.IsCollidable(hit.Material);
|
|
}
|
|
|
|
public bool IsGroundedOnHit(ref CharacterUpdateContext context, ref KinematicCharacterUpdateContext baseContext, in BasicHit hit, int groundingEvaluationType)
|
|
{
|
|
CharacterComponent characterComponent = CharacterComponent.ValueRO;
|
|
return KinematicCharacterUtilities.Default_IsGroundedOnHit(
|
|
in this, ref context, ref baseContext,
|
|
CharacterDataAccess.CharacterEntity,
|
|
CharacterDataAccess.PhysicsCollider.ValueRO,
|
|
CharacterDataAccess.CharacterBody.ValueRO,
|
|
CharacterDataAccess.CharacterProperties.ValueRO,
|
|
in hit,
|
|
in characterComponent.StepAndSlopeHandling,
|
|
groundingEvaluationType);
|
|
}
|
|
|
|
public void OnMovementHit(
|
|
ref CharacterUpdateContext context,
|
|
ref KinematicCharacterUpdateContext baseContext,
|
|
ref KinematicCharacterHit hit,
|
|
ref float3 remainingMovementDirection,
|
|
ref float remainingMovementLength,
|
|
float3 originalVelocityDirection,
|
|
float hitDistance)
|
|
{
|
|
ref KinematicCharacterBody characterBody = ref CharacterDataAccess.CharacterBody.ValueRW;
|
|
ref float3 characterPosition = ref CharacterDataAccess.LocalTransform.ValueRW.Position;
|
|
CharacterComponent characterComponent = CharacterComponent.ValueRO;
|
|
|
|
KinematicCharacterUtilities.Default_OnMovementHit(
|
|
in this, ref context, ref baseContext,
|
|
ref characterBody,
|
|
CharacterDataAccess.CharacterEntity,
|
|
CharacterDataAccess.CharacterProperties.ValueRO,
|
|
CharacterDataAccess.PhysicsCollider.ValueRO,
|
|
CharacterDataAccess.LocalTransform.ValueRO,
|
|
ref characterPosition,
|
|
CharacterDataAccess.VelocityProjectionHits,
|
|
ref hit,
|
|
ref remainingMovementDirection,
|
|
ref remainingMovementLength,
|
|
originalVelocityDirection,
|
|
hitDistance,
|
|
characterComponent.StepAndSlopeHandling.StepHandling,
|
|
characterComponent.StepAndSlopeHandling.MaxStepHeight,
|
|
characterComponent.StepAndSlopeHandling.CharacterWidthForStepGroundingCheck);
|
|
}
|
|
|
|
public void OverrideDynamicHitMasses(
|
|
ref CharacterUpdateContext context,
|
|
ref KinematicCharacterUpdateContext baseContext,
|
|
ref PhysicsMass characterMass,
|
|
ref PhysicsMass otherMass,
|
|
BasicHit hit)
|
|
{
|
|
}
|
|
|
|
public void ProjectVelocityOnHits(
|
|
ref CharacterUpdateContext context,
|
|
ref KinematicCharacterUpdateContext baseContext,
|
|
ref float3 velocity,
|
|
ref bool characterIsGrounded,
|
|
ref BasicHit characterGroundHit,
|
|
in DynamicBuffer<KinematicVelocityProjectionHit> velocityProjectionHits,
|
|
float3 originalVelocityDirection)
|
|
{
|
|
CharacterComponent characterComponent = CharacterComponent.ValueRO;
|
|
KinematicCharacterUtilities.Default_ProjectVelocityOnHits(
|
|
ref velocity,
|
|
ref characterIsGrounded,
|
|
ref characterGroundHit,
|
|
in velocityProjectionHits,
|
|
originalVelocityDirection,
|
|
characterComponent.StepAndSlopeHandling.ConstrainVelocityToGroundPlane,
|
|
in CharacterDataAccess.CharacterBody.ValueRO);
|
|
}
|
|
}
|
|
}
|