Simpler part movement in server scripts - Kinematic

(Background - at this point, I am a somewhat seasoned roblox developer - this post comes from my experience teaching others how to use the platform…)

Currently, in roblox, it is too hard for novice users to build multiplayer experiences that are not jittery and glitchy feeling if they need to move objects around.

This can be traced back to that doing the simplest possible thing in code to manipulate the world is unfortunately, right now, not the correct thing to do - when perhaps it should be?

Consider the following server script:

local part = script.Parent
local startPos = part.Position
part.Anchored = true

game["Run Service"].Heartbeat:Connect(function()
    local sinWave = math.sin(tick()) * 5 
    part.Position = startPos + Vector3.new(0, sinWave, 0)
end)

What this script does should be obvious to even people coming from off-platform - it takes a part, anchors it, and then on Heartbeat makes it bob up and down. (you could use a while(true) loop, too)
But right now in roblox if you applied this to a server script, several things would be wrong with it:

  1. the part would be replicated to all clients at 20hz, making them look unsmooth
  2. if a player tried to stand on the part, they would not move correctly
  3. if it touches other physics parts, they will not move correctly either

(not smooth and other parts dont interact properly)
serverMovingPlatform1

Here is the official roblox way to make this work smoothly, in as few steps as possible:

local part = script.Parent
local startPos = part.Position

local localAttachment = Instance.new("Attachment")
localAttachment.Parent = part

local worldAttachment = Instance.new("Attachment")
worldAttachment.Parent = game.Workspace.Terrain  -- Aww come on, seriously...

local alignPosition = Instance.new("AlignPosition")
alignPosition.Parent = part
alignPosition.RigidityEnabled = true
alignPosition.Attachment0 = localAttachment
alignPosition.Attachment1 = worldAttachment

local alignOrientation = Instance.new("AlignOrientation")
alignOrientation.Parent = part
alignOrientation.RigidityEnabled = true
alignOrientation.Attachment0 = localAttachment
alignOrientation.Attachment1 = worldAttachment

part:SetNetworkOwner(nil) --Dont forget this or its glitchy

game["Run Service"].Heartbeat:Connect(function()
	local sinWave = math.sin(tick()) * 5 
	worldAttachment.Position = startPos + Vector3.new(0, sinWave, 0)
end)

( This method also has a pretty big drawback if the part tries pass through other geometry - it glitches pretty hard and is capable of exploding the physics sim if it penetrates an assembly. )


What I propose is a third option - an Kinematic flag (or Instance?) for parts and assemblies.

Kinematic objects would simply be able to be CFramed/Positioned/Orientated to wherever you want, and would be 20hz interpolated from server to client.

On the client, the assembly would automatically set its velocity and angular velocity to automatically derived values based on the delta every frame. Kinematic parts would also never change network ownership.

This would make the correct way to animate a colliding part on the server as so:

local part = script.Parent
local startPos = part.Position

part.Kinematic = true  --Tada!

game["Run Service"].Heartbeat:Connect(function()
    local sinWave = math.sin(tick()) * 5 
    part.Position = startPos + Vector3.new(0, sinWave, 0)
end)
46 Likes

Great suggestion that would help beginner devs while making things easier for more experienced devs.

1 Like

Though the recommended solution is fine, I would prefer a new Instance that is similar to the Constraints but merged all into one.

For example, an Instance called ‘Mover’: (Can be parented under a Model or BasePart)

Properties:

Mover.Enabled = true → Determines whether or not the object will be affected by the Constraint.
Mover.AffectedByGravity = true → Makes it so the Y-Axis is ignored as it happens with Humanoids.
Mover.Position = Vector3.zero → Makes the object move towards that position.
Mover.Orientation = Vector3.zero → Makes the object rotate into the designated orientation.
Mover.CFrame = CFrame.new() → Affects both the Position and Orientation.
Mover.Speed = 16 → Makes the object move at that speed.
Mover.Responsiveness = true → Controls how quickly the object reaches its goal.
Mover.Rigid = true → Reacts as quickly as possible to complete the alignment.

Events:

Mover.Reached → Triggers when the object reaches the designated position.
Mover.Stopped → Triggers when the Stop function gets called.

Functions:

Mover:MoveTo(Vector) → Makes the object move towards that position just like it works with Humanoid, affects the CFrame, Position, and Rotation.
Mover:WalkTo(Vector) → Makes the object move towards that position using pathfinding automatically.
Mover:Stop() → Makes the object stay in the current position.


Though you can argue it’s basically a Humanoid, have in mind that the Humanoid lacks a lot of the features I mentioned and this mover will make our life easier.


Anyways:

Either way, I support this feature since it reduces all the code mentioned and is not as confusing for beginners.

2 Likes

This seems like it should be its own feature request, it’s a cool idea.

To clarify the intent of kinematic feature, this object is not simulated, it’s not free to move or fall - the exact cframe of the object is provided by game code every frame.

Concepts like moveTo() and reached() wouldn’t apply here - the object is meant to be animated by code alone.

5 Likes

Just to clarify a few things:
AlignPosition and AlignOrientation now have singe attachment modes that allow you to define a world-space target CFrame.

It’s also typical to create a lot of these instances during edit-time (like you’ve done with the Part). This would make your script much shorter.

Additionally, this means a Heartbeat loop updating CFrame every frame is no longer necessary. If you keep rigidity disabled you can just tune the force/velocity the AlignPosition uses to reach its goal, and give a final target position. The part will move at your desired speed to the target.

Using these systems can improve performance in your experience since it will allow it to use various throttling systems and better replication.

6 Likes

Thank you so much for the response!

Additionally, this means a Heartbeat loop updating CFrame every frame is no longer necessary. If you keep rigidity disabled you can just tune the force/velocity the AlignPosition uses to reach its goal, and give a final target position. The part will move at your desired speed to the target.

Hmm, I feel like I’ve been misunderstood a little bit here…

What I’m trying to answer here is like a day 1 question for off-platform devs - “How do I move a part around smoothly on the server?”.

What seems like the right answer and should just work - which is to grab a part and move it around every frame - is not the right answer, and does not work as expected

A Kinematics flag is just one possible solution out of a big range of solutions that get the same result, in this case based loosely on how unity handles the issue:

Another solution could just be a “This lerps” flag for position replication. In fact a bunch of properties could probably use that…

Also, I hope I can convince you that programmatically frame-by-frame animating the position of a part to act as a door, a baddie, a bridge, an elevator, etc. is a very common game design mechanism?

Having to use targets and timers and attachments and tuning PID-style physics controllers and adjusting forces feels like it should be part of a big bag of amazing tools you can use to optimize your approach later on, not where users should be expected to start out with.

For completeness, here it is updated to single attachment mode:

local part = script.Parent
local startPos = part.Position

local worldAttachment = Instance.new("Attachment")
worldAttachment.Parent = part

local alignPosition = Instance.new("AlignPosition")
alignPosition.Parent = part
alignPosition.RigidityEnabled = true
alignPosition.Mode = Enum.PositionAlignmentMode.OneAttachment
alignPosition.Attachment0 = worldAttachment

local alignOrientation = Instance.new("AlignOrientation")
alignOrientation.Parent = part
alignOrientation.RigidityEnabled = true
alignOrientation.Mode = Enum.OrientationAlignmentMode.OneAttachment
alignOrientation.Attachment0 = worldAttachment

part:SetNetworkOwner(nil) 

game["Run Service"].Heartbeat:Connect(function()
    local sinWave = math.sin(tick()) * 5
    alignPosition.Position = startPos + Vector3.new(0, sinWave, 0)
end)

(ps. there is no code snippet for using alignPosition in the devhub, it took me more tinkering than I am comfortable admitting to make this work to even make this post…)

7 Likes

The correct answer to “How do I move a part around smoothly on the server?” is to move via constraints/simulation. The Roblox engine is designed from the ground up to work this way.

The following is from our Physics Best Practices post:

CFrame with care.
Editing a part’s CFrame (CFraming) should be reserved for one-off constructions at run-time. Remember that changing a CFrame is essentially a teleport, and shouldn’t be happening every frame. This is because you are potentially sending network signals to every player observing the part, without interpolation, and causing jittery motion. CFraming to update “local-only” parts may be a valid strategy, but is more likely to cause over-constrained situations and undesirable collisions. Essentially, CFraming should be reserved for specific teleportation, like resetting a part’s position to be physically simulated after.

Specifically, your proposed answer of “grab a part and move it around every frame” doesn’t work well because the Character is a simulated rigid body. So you have this simulated body resting on a part that’s continually being teleported into its feet.

I understand that “programmatically frame-by-frame animating” is common in video games. However, any game would still have trouble providing smooth interaction between simulated and non-simulated objects. I will say that we are working on additional features that allow you to create physics-driven animations and kinematic motion.

Primarily, our goal is to make the physics-first solution the simplest and most-straight forward. In a lot of cases, you can actually get Parts moving around without any need to script at all. It is where we expect our users to start out with, and is what we want to continue to make simpler.

That being said, critiques on the simplicity of using the physics/constraints system are valid. We want the workflow to be as quick and seamless as possible, and I definitely realize it’s not all there yet.

Finally, here’s my code snippet for creating a moving platform like yours using simulation:

local alignPosition = script.Parent.AlignPosition
local part = script.Parent

local startPos = part.Position
local targetPos = Vector3.new(3,10,0)

alignPosition.Position = startPos

game["Run Service"].Heartbeat:Connect(function()

	if((part.Position - startPos).Magnitude < 0.1) then
		alignPosition.Position = targetPos
	elseif ((part.Position - targetPos).Magnitude < 0.1) then
		alignPosition.Position = startPos
	end

end)

The Constraint instances were created at edit-time, just like you had done with the BasePart in your example. They are very quick to set up, here’s the final product:

4 Likes

It’s possible and you can do it, but I think what the op was getting at was this:

At edit time you spent like 30 seconds editing properties. The entire process is not that straight forward and honestly a newer developer is more likely to understand how a loop works compared to the new body movers

1 Like

Thank you again for the detailed answer, it helps me really understand where the design and current thinking is at with everything.

I’m glad to hear that Roblox is open minded about the whole issue and I am pleased there are new systems in the works, especially if it lowers the amount of work required to set up an object just to move it around.

It might be robloxes official answer that you’re expected to get the physics sim and constrains involved to move a part around smoothly on the server, but I gotta say this is a bad call and I really hope this changes in the future - hence this feature request!

Especially when you consider that this limit doesn’t exist on the client…

Consider even a limited part of this proposal - for the cost of simply allowing us to smoothly interpolate server owned parts without getting the simulation involved - you’d get a massively simpler dev experience, consistency between client and server, and if we really want the part to behave kinematically, well, we can actually just do it ourselves by setting the vel and avel per frame.

As a side note, I (and a bunch of other devs) already use manually set per frame velocities on anchored parts on the client to get this kinematic behavior.

Compared to constraints it performs better, everything interacts exactly as expected, and frankly we don’t have to care about stray collisions breaking our animated objects….

3 Likes

Hmm, I don’t think this applies here.

Kinematic parts/CFramed parts penetrating simulated objects does indeed glitch out, if you don’t set the correct per-frame velocity and angular velocity as well. But doing this is pretty easy, and it already gives you the correct results if you do it in roblox.

kinematicBody2

To illustrate that the kinematic penetration problem already isn’t a big deal, the red and blue platforms here are on the client and being per-frame animated.

Red platform penetrates and glitches, as you said. But, the blue platform also has its velocity being set every heartbeat as:

    local posDelta = instance.CFrame.Position - lastCFrame.Position
    lastCFrame = instance.CFrame
    instance.AssemblyLinearVelocity = posDelta / deltaTime  

Yes, it’s an approximation (better to set it at 240hz?) but as you can see, you still get accurate enough results.

The thing I am really looking to address is how you go about getting smooth visual interpolation of server objects.

5 Likes

True, manually setting the velocity is a viable solution to get smoother interactions.

And getting something that is anchored but has velocity to interpolate wouldn’t be unreasonable, and we can look into that eventually.

5 Likes