Just to give some idea of scale, here’s my main FixedUpdate() function in the boat sim. The function calls throughout all this are in many cases much longer than all of this code, and many of those functions iterate over a few hundred triangles every physics cycle. That means I’m iterating over all of the triangles not just once, but multiple times per physics cycle doing different computations on them, and I’m doing all this 100 times per second which is at least double what I see other people around here typically running. I can’t do 20 boats at a time with this (arcade games need not apply for this method), but can get away with doing a few boats at least. As you can see there are a lot of “if” statements here, and there are a few more in some of those function calls that aren’t shown here. Many of those “if” statements are processed on every triangle. So while your FixedUpdate() might have a few “ifs,” mine is probably in the hundreds if not thousands. Probably something else you’re doing in there is slowing you down, a few “ifs” every frame is nothing.
This class is a little less than 2000 lines and probably half or more of that is run every physics cycle. So you really can do a monumental amount of work in FixedUpdate() if you’re careful and pay close attention to optimizing things. In early development when this was all much smaller I was using 50%+ of the CPU just for physics on one boat. By paying close attention to data structuring, not calling functions (especially math functions) when they can be hand coded, I got that down to probably 5% CPU for one boat.
Someone said something about trying to do the work every few frames instead of every frame. That’s a good idea. Another one that’s similar is to try to spread it out. For example: I wrote the physics engine for VRC Pro (an R/C car racing sim) which included a particle system for engine smoke and dust blowing around and so on. The physics there ran 250Hz or 500Hz depending on the vehicle (AFAIK most Unity games run only 50Hz), and the smoke/dust was done every physics frame. So to make the particle system run stupidly fast I split the particle array into groups and only moved one of the groups every frame. Think I got something like an 800% speed improvement that way which meant I could run a lot more particles.
The idea there was to do the same amount of particle system work every frame (move xx particles), but only one group of them at a time. I.e., on physics frame 1 I’d move particles 0-500, on frame 2 I’d move particles 500-1000, and so on. (It wasn’t really 500, I don’t remember how many it ended up being. I just put a couple numbers in there to tune it by hand until it looked good enough and performed well).
None of this was matched up to the graphics frame rate at all, it was all equivalent to doing it in FixedUpdate(), but you couldn’t tell by looking at it. So there was not really much (if any) CPU spiking from the particle system because only part of the work was done every physics cycle and graphics frame.
So rather than skipping the work for several frames and then doing it all at once, it’s better imo to try to spread it out over a few frames if you can. Sometimes you can do that, other times you can’t. You’re the only one that could know what will work for you though, of course.
So yeah, I suspect that if you’re seeing spikes that are so game-stuttering, you’re probably calling some enormously slow functions in there somewhere. You should be able to do many thousands of lines of code in FixedUpdate() without seeing even a hiccup.
void FixedUpdate() {
int numLoops;
float tStep;
if (isPropeller)
{
numLoops = numLoopsIsPropeller;// 10;
forcesFluidDynamicIsPropeller.numberOfForces = 0;
}
else
{
numLoops = 1;
}
tStep = Time.fixedDeltaTime / numLoops;
propTorque = 0; //Reset
for (int iLoop = 0; iLoop < numLoops; iLoop++)
{
if (skipFixedUpdate || skipPhysics)
{
return;
}
if (isPropeller)
{
propAngleChange = propRadPerSec * tStep * Mathf.Rad2Deg;
transform.Rotate(0, 0, propAngleChange);
}
transformRight = transform.right;
transformUp = transform.up;
transformForward = transform.forward;
transformPositionFixedUpdate = transform.position;
positionCenterOfMass = useRigidBody.worldCenterOfMass;
TransformPhysicsTrianglesFromLocalToWorldPosition(physicsTrianglesLocal, physicsTrianglesLocal.position, physicsTrianglesWorld.position);
TransformPhysicsTrianglesFromLocalToWorldPosition(physicsTrianglesLocal, physicsTrianglesLocal.vert0, physicsTrianglesWorld.vert0);
TransformPhysicsTrianglesFromLocalToWorldPosition(physicsTrianglesLocal, physicsTrianglesLocal.vert1, physicsTrianglesWorld.vert1);
TransformPhysicsTrianglesFromLocalToWorldPosition(physicsTrianglesLocal, physicsTrianglesLocal.vert2, physicsTrianglesWorld.vert2);
TransformPhysicsTrianglesFromLocalToWorldDirection(physicsTrianglesLocal, physicsTrianglesLocal.faceNormal, physicsTrianglesWorld.faceNormal);
TransformPhysicsTrianglesFromLocalToWorldDirection(physicsTrianglesLocal, physicsTrianglesLocal.vNormalized0, physicsTrianglesWorld.vNormalized0);
TransformPhysicsTrianglesFromLocalToWorldDirection(physicsTrianglesLocal, physicsTrianglesLocal.vNormalized1, physicsTrianglesWorld.vNormalized1);
TransformPhysicsTrianglesFromLocalToWorldDirection(physicsTrianglesLocal, physicsTrianglesLocal.vNormalized2, physicsTrianglesWorld.vNormalized2);
//restore face areas in physicsTrianglesWorld. This only needs to be done if triangle splitting is enabled because it will overwrite the face areas.
if (splitTrianglesAtWaterline)
{
for (int i = 0; i < physicsTrianglesWorld.numberOfTriangles; i++)
{
physicsTrianglesWorld.faceArea[i] = physicsTrianglesLocal.faceArea[i];
}
interceptPointPairs.numberOfPointPairs = 0; //Reset the counter. The SplitTriangles function will increase the number internally as intercepts are found.
SplitTriangles(physicsTrianglesWorld, interceptPointPairs);
}
ComputeWorldPointVelocities(physicsTrianglesWorld, useRigidBody, physicsTrianglesWorld.position, physicsTrianglesWorld.faceVelocity);
if (isPropeller)
{
AddPropVelocitiesFromWorldPositionsSpinAxisZ(physicsTrianglesWorld.numberOfTriangles, propRadPerSec, transform.forward, physicsTrianglesWorld.position, physicsTrianglesWorld.faceVelocity);
}
DetermineIfTriangleIsAboveOrBelowWaterline(physicsTrianglesWorld, indicesWaterlineAbove, indicesWaterlineBelow);
if (buoyancy)
{
forcesBuoyancy.numberOfForces = 0; //Reset number of forces to 0 so we can call the Compute functions more than once without having the forces array overwrite itself.
ComputeBuoyancyForces(forcesBuoyancy, physicsTrianglesWorld, waterPlaneNormal, waterPlanePositionWorld, indicesWaterlineBelow);
AddForcesToRigidBody(forcesBuoyancy, useRigidBody,numLoops);
}
if (fluidDynamics)
{
forcesFluidDynamic.numberOfForces = 0; //Reset number of forces to 0 so we can call the Compute functions more than once without having the forces array overwrite itself.
ComputeFluidDynamicForces(forcesFluidDynamic, physicsTrianglesWorld, waterPlaneNormal, waterPlanePositionWorld, indicesWaterlineAbove, airDensity);
totalHydrodynamicForce.x = 0;
totalHydrodynamicForce.y = 0;
totalHydrodynamicForce.z = 0;
ComputeFluidDynamicForces(forcesFluidDynamic, physicsTrianglesWorld, waterPlaneNormal, waterPlanePositionWorld, indicesWaterlineBelow, waterDensity);
AddForcesToRigidBody(forcesFluidDynamic, useRigidBody, numLoops);
if(isPropeller)
AppendForcesIsPropeller(forcesFluidDynamic, forcesFluidDynamicIsPropeller);
}
ComputeWaterlineFrontPoint(interceptPointPairs); //This needs to be done before skin friction because the Reynold's number used there depends on the length to the front waterline intersection.
if (skinFriction)
{
forcesSkinFriction.numberOfForces = 0; //Reset number of forces to 0 so we can call the Compute functions more than once without having the forces array overwrite itself.
ComputeSkinFrictionForces(forcesSkinFriction, physicsTrianglesWorld, waterPlaneNormal, waterPlanePositionWorld, indicesWaterlineBelow, waterDensity);
AddForcesToRigidBody(forcesSkinFriction, useRigidBody, numLoops);
}
if (isPropeller)
{
propTorque -= ComputePropellerTorqueFromWorldCoords(forcesFluidDynamic);
}
}
propTorque = propTorque / numLoops;
}