15-05-2026, 04:16 PM
I only got back to this the last hour; but it seems to work doing it manual mode with inspiration of what you said. (Also posting it for any future ECSer)
I think the only thing now that I need to modify the asset now for is to add a setting to be able to scale the inertia wind when set in World mode, which might be a nice setting to have in general. For reference; the code below `ApplyInertialForcesJob`.
Code:
public struct ObiRuntimeData : IComponentData
{
public NetworkTick lastFixedTick;
public double elapsedTime;
public float deltaTime;
}
[BurstCompile]
[UpdateInGroup(typeof(PredictedFixedStepSimulationSystemGroup), OrderFirst = true)]
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
public partial struct RecordLastPredictedFixedTickForObiSystem : ISystem
{
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
SystemAPI.TryGetSingleton<NetworkTime>(out var networkTime);
if (!networkTime.IsFinalFullPredictionTick)
{
return;
}
ref var obiRuntimeData = ref SystemAPI.GetSingletonRW<ObiRuntimeData>().ValueRW;
obiRuntimeData.lastFixedTick = networkTime.ServerTick;
obiRuntimeData.elapsedTime = SystemAPI.Time.ElapsedTime;
obiRuntimeData.deltaTime = SystemAPI.Time.DeltaTime;
}
}
[UpdateInGroup(typeof(SimulationSystemGroup), OrderLast = true)] // Seems ObiBones write back in LateUpdate, so can't have in PresentationGroup
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
public partial struct ObiManualUpdateSystem : ISystem
{
private NetworkTick _lastFixedTick;
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<ClientServerTickRate>();
state.EntityManager.CreateSingleton<ObiRuntimeData>();
}
public void OnUpdate(ref SystemState state)
{
var system = state.World.GetExistingSystemManaged<GhostPresentationGameObjectSystem>();
SystemAPI.TryGetSingleton<ObiRuntimeData>(out var obiRuntimeData);
var timeAhead = (float)(SystemAPI.Time.ElapsedTime - obiRuntimeData.elapsedTime);
var timeStep = (float)obiRuntimeData.deltaTime;
if (timeAhead < 0f || timeStep == 0f)
{
return;
}
// Assumption we do 1:1 physics ticks with tick rate
var steps = _lastFixedTick.IsValid ? obiRuntimeData.lastFixedTick.TicksSince(_lastFixedTick) : 1;
_lastFixedTick = obiRuntimeData.lastFixedTick;
foreach (var (_, localTransformRef, entity) in SystemAPI.Query<RefRW<ObiSolverData>, RefRO<LocalTransform>>().WithEntityAccess())
{
var gameObject = system.GetGameObjectForEntity(state.EntityManager, entity);
var obiSolver = gameObject?.GetComponent<ObiSolver>();
if (obiSolver == null)
{
continue;
}
#if UNITY_EDITOR
if (obiSolver.isActiveAndEnabled)
{
Debug.LogError($"ObiSolver {gameObject.name} is enabled but running in manual mode. Please disable the component to avoid unexpected behavior.", gameObject);
}
#endif
// Use localTransform for fixed updated position compared to transform (which is updated from localToWorld)
var interpolatedLocalPosition = gameObject.transform.localPosition;
var interpolatedLocalRotation = gameObject.transform.localRotation;
gameObject.transform.localPosition = localTransformRef.ValueRO.Position;
gameObject.transform.localRotation = localTransformRef.ValueRO.Rotation;
if (steps > 0)
{
obiSolver.StartSimulation(timeStep, steps);
}
obiSolver.Render(timeAhead);
gameObject.transform.localPosition = interpolatedLocalPosition;
gameObject.transform.localRotation = interpolatedLocalRotation;
}
}
}I think the only thing now that I need to modify the asset now for is to add a setting to be able to scale the inertia wind when set in World mode, which might be a nice setting to have in general. For reference; the code below `ApplyInertialForcesJob`.
Code:
if (inertialWind)
{
float4 wsPos = inertialFrame.frame.TransformPoint(positions[i]);
wind[i] -= inertialFrame.frame.InverseTransformVector(inertialFrame.VelocityAtPoint(wsPos));
}
