HowTo: Physics-Based TweenService

This tutorial is great, but wouldn’t using Stepped be more efficient, as renderstepped is designed to run before rendering the frame on a client?, also stepped runs before the physics are calculated, and renderstepped is not avaible from the server.

2 Likes

If you have a fairly large projectile that hits other objects and you want to transfer the kinetic energy like Newton’s Cradle, setting the AseemblyLinearVelocity might help.

However, for bullets, you might want to use raycasts and only tween bullets for visual effect.

In short, bullets wouldn’t be a very great use-case for this

1 Like

For performance reasons explained in the post, you’d want to do this on each client rather than the server.

However, if you really want to do this on the Server, using .Stepped comes with some warnings in the documentation:

There is no guarantee that functions connected to this event will fire at the exact same time, or in any specific order. For an alternative where the priority can be specified, see RunService:BindToRenderStep().

In my examples, we get around this by setting the RenderPriority to 99, which is right before input. If you want to do this on the server, it might be appropriate to use RunService.PreSimulation to catch the engine right before it runs physics calculations. Note that I haven’t really tested that, so do with that information what you will.

-- Server code (please don't use this on the server)
RunService.PreSimulation:Connect(onRenderStepped) -- Connect to the example function above

the problem is that even if you dont care about cheaters gaining ownership and manipulating the boxes, what if the boxes are designed to be left alone there.

if no client can calculate the physics for props on platforms it would just break, But i see there is no easy solution now that you tell me this.

I’m mostly having trouble understanding your question but there are definitely solutions that satisfy replication, cheating and generally working

Why not use .Stepped since that also runs before physics? Binding all of these moving platforms on RS creates significantly more performance drawbacks per frame than it would with Heartbeat or Stepped.

Id only use RenderStepped for camera or input related work.

So I went to go mess around and find out why there’s warnings all over .Stepped… As it turns out, the docs weren’t kidding when they said that RunService.Stepped can’t guarantee the order it’s fired at

Correction: .Stepped does not result in glitchy behavior when arguments for dt are passed in the correct order. Thank you Stratiz.

If looking for an event of RunService to connect to, RunService.PreSimulation is probably the best. I had replied to @CCTVStudios about this but I just now tested it and I can confirm it works great.

I’d still argue that we’re using BindToRenderStep at the appropriate RenderPriority. If you look at how BindToRenderStep is being used in the examples, you can see that the RenderPriority is set to 99.

The thinking behind this is that 99 renders right before input, allowing the computer to run our code & calculate the visual location of the model, then accept input, then render camera, then simulate character, and then simulate physics. That way you can hypothetically walk on a moving platform.

2 Likes

Cool thread! Reminds me of how it was done here: Kinematic Physics Module - Simple part movement in server scripts

This result doesnt make sense. Can I see the code snippet you’re using to test stepped? I suspect you’re using the wrong stepped argument. Remember that stepped’s first return argument isnt delta time, and is instead the second argument in the callback.

Also the warnings in the docs are saying that the order in which you connect to the event does not guarentee that will be the same order the connected functions are fired in, not that stepped runs at random times.

Probably better to just cframe model root parts using bulkmoveto. PivotTo is like the slowest way to move stuff

You’re correct, I assumed the first argument was dt and not time

Did some testing and I’m not sure if there are any conclusive gains from using .Stepped over .PreSimulation
Feels like preference at this point

Currently testing BulkMoveTo over PivotTo for tyridge

So I went ahead and stress-tested my game by turning off StreamingEnabled, which is arguably the most important optimization in my game

Part Count: After & Before StreamingEnabled
I must preface that the following tests are done with StreamingEnabled OFF in order to stress test. I have many moving models in my game and turning StreamingEnabled off jumps the number from

9 moving models with 50 moving parts (StreamingEnabled)
to
85 moving models with 771 moving parts (StreamingDisabled)

PivotTo vs BulkMoveTo
Here are my testing results with StreamingEnabled OFF:
PivotTo (No AssemblyLinearVelocity) frame time: ~5ms
BulkMoveTo (No AssemblyLinearVelocity) frame time: ~4.5ms

While BulkMoveTo can shave off 0.5ms - 1ms off of frame times, there are magnitudes larger performance hogs in the microprofiler unrelated to moving the model. For instance, the for loop to set set the AssemblyLinearVelocity of 771 parts (extreme case) adds about 1ms per frame…

LDL PGS Solver Performance
Beyond this, it turns out that the more unanchored parts you have being influenced by a moving model, the harder the LDLPGSSolver is working. Before the 200 parts spawn in the video, physicsStepped took around ~1ms. After the 200 parts spawn, the PGS solver jumps all the way up to ~17ms. That’s really pushing it considering you have about 16.6ms per frame for a 60hz framerate.

Conclusions
Simply deleting the parts means that you get performance back. Interestingly, if I turn back on StreamingEnabled, the frame time of physicsStepped goes from ~17ms to ~12ms. Regardless, this seems to be a function of physics being calculated for unanchored parts by LDLPGSSolver.

In short, while BulkMoveTo can make small improvements to frame time, there are much larger performance gains to be realized from the following:

  1. Whitelist models that need to use velocity & angular velocity influence
  2. Use a StreamingEnabled radius to cut back on moving parts on the client
5 Likes

Previously for moving assemblies with physics on, I’ve Tweened a rigid AlignPosition constraint on an unanchored PrimaryPart that everything else is welded to. How does this method perform compared to what you’ve done?

1 Like

There’s no documentation on how the performance of AlignPosition can be measured in the microprofiler. They may be using PID like the old BodyPosition mover or they migrated this into the PGS solver such that it solves for the position given a series of mathematical constraints. Not sure. Bottom line is that it’s very performant as it stands.

The methods stated here are no more performant than simply setting 2 properties on every BasePart within a model: AssemblyLinearVelocity and AssemblyAngularVelocity. After these properties are set, the script is done with its job. The rest is handed to Roblox’s LDL PGS Solver to handle physics.

My speculation is that both would perform equally well, but the AlignPosition my or may not have an edge being more tightly integrated into the physics solver.

Recently I have discovered some “low-accuracy, high-precision” issues that could be attributed to velocity calculations being 1 frame too late. So I am actively experimenting with solving for AssemblyLinearVelocitiess 1 frame ahead of the current frame given that the tween path is already known and we can peek future positions several milliseconds ahead of the current frame to solve for future AssemblyLinearVelocity. Standby for that report.

Would there be a way to make the tween reverse this way? Your videos show that, but I simply cannot figure it out for the life of me.

Not to diminish the great work you have done on this , but I think you should point out that this isn’t really tween based physics but a new type of tween physics lite. :thinking:

The only reason I make this nitpick is that your example videos using the tween physics you created; the physics actually looks wrong. :face_with_raised_eyebrow:
When your platform is moving around, the parts should be falling off due to the variables of friction, mass, velocity, etc. Your tween physics appears to be ignoring a lot of variables where the parts just land and don’t move relative to those variables of the moving platform. So then, the reason your tween physics is faster might be because it is ignoring a lot of stuff that the Roblox built-in physics solver has to account for. :wink:

Yes, there is a way to play the tween in reverse and you would do this by the alpha value you pass to TweenService:GetValue(alpha, easingDirection, easingStyle)

Think of alpha as a percent completion of the tween. In fact, you can multiply alpha by 100 to get a percentage. alpha = 0 means 0%, alpha = 1 means 100%.

If you want to keep the easingDirection the same and play the tween in reverse, instead of incrementing the alpha from 0 → 1, you have to decrement the alpha from 1 → 0.

-- Forward
local startCFrame = CFrame.new(0,0,0)
local endCFrame = CFrame.new(0, 10, 0)

local tweenTime = 10 -- seconds
local alpha = 0
local runServiceConnection
runServiceConnection = RunService.PreSimulation:Connect(function(deltaTime)
     -- deltaTime is the time in seconds since the last frame
     -- We need to know what percent of tweenTime this deltaTime was to increment alpha
     local alphaStepped = deltaTime / tweenTime
     alpha += alphaStepped
     if alpha >= 1 then 
          alpha -= 1 -- Reset alpha back to 0 with remainder
     end

     local tweenAlpha = TweenService:GetValue(alpha, Enum.EasingDirection.InOut, Enum.EasingStyle.Sine)

     -- Find the new CFrame between start and end based on TweenAlpha
     local newCFrame = startCFrame:Lerp(tweenAlpha, endCFrame)

     -- Set the CFrame of the part here (or use PivotTo)
     part.CFrame = newCFrame
end)

Then here is backwards

-- Backwards
local startCFrame = CFrame.new(0,0,0)
local endCFrame = CFrame.new(0, 10, 0)

local tweenTime = 10 -- seconds
local alpha = 1
local runServiceConnection
runServiceConnection = RunService.PreSimulation:Connect(function(deltaTime)
     -- deltaTime is the time in seconds since the last frame
     -- We need to know what percent of tweenTime this deltaTime was to decrement alpha
     local alphaStepped = deltaTime / tweenTime
     alpha -= alphaStepped
     if alpha <= 0 then 
          alpha += 1 -- Reset alpha back to 1 with remainder
     end

     local tweenAlpha = TweenService:GetValue(alpha, Enum.EasingDirection.InOut, Enum.EasingStyle.Sine)

     -- Find the new CFrame between start and end based on TweenAlpha
     local newCFrame = startCFrame:Lerp(tweenAlpha, endCFrame)
     
     -- Set the CFrame of the part here (or use PivotTo)
     part.CFrame = newCFrame
end)
2 Likes

Aha, but that’s where you’re mistaken. You’re correct that there is a physics error here, but you haven’t found it. I’ll be addressing it in a separate reply. Back to this topic, though:

The kinetic energy transfer from the tweening model to touching assemblies is completely and 100% calculated by Roblox’s PGS solver. In no way does this code ever manipulate the physics of touching assemblies directly. If you read the code with the intent to understand, you would see this.

Because it relies on Roblox’s PGS solver, this method completely respects all physical properties including density, friction, and elasticity.

The above test shows parts being dropped with custom physical properties with high elasticity (bounciness), low density (lightweight), and low friction (slippery). This directly refutes the claim.

1 Like

you are actually insane for figuring this out lmfao

1 Like

Update: I realized I had incorrect calculations for _alpha to wrap back to 0

Instead of

_alpha += (deltaTime/tweenTime) % 1

It needed to be

_alpha += (deltaTime/tweenTime)
_alpha %= 1 -- This wraps _alpha between 0 and 1

If you’ve been trying the example code and it wasn’t working properly, this is probably why. The code has been corrected.

Also I have yet to reply with my physics updates to make this support irregularly-shaped assemblies.