Using attachments, how would I keep a Part in the same relative place?

If I have a Part (Part A) and another Part (Part B) parented to an Attachment that is then parented to Part A, How would I keep the relative position of Part B the same no matter what happens to the size of Part A?

For example, if Part B is positioned close to the edge of Part A and then Part A’s size changes, Part B’s position would then update to be set to close to the edge of the new size.

Is this possible? Thank you.

3 Likes

You should be able to calculate the desired position of part B as follows:

partAOldSize = partA.Size
partA:GetPropertyChangedSignal("Size"):Connect(function()
    partANewSize = partA.Size

    scale = partANewSize / partAOldSize

    relativeScale = partA.CFrame:VectorToWorldSpace(scale)

    newPartBPosition = (partB.Position - partA.Position) * relativeScale + partA.Position

    partB.CFrame = partB.CFrame - partB.Position + newPartBPosition

    partAOldSize = partANewSize
end)

That should make it so the position of the attachment scales relatively to part A. If you’d like to have a part stay the same distance from the edge of part A, you could add an attachment to the edge of A then add an attachment to that attachment that has the displacement from the edge (by add attachment you might need to add a part with the attachments).

1 Like

This works somewhat, but if Part A is consistently spinning then it kind of falls apart, the parts stop lining up and end up drifting away after enough position updates.

Am I using the code correctly? It didn’t seem to work when I was using the attachment

local partA = script.Parent
local partB = partA.Attachment.Partb
local partAOldSize = partA.Size

local function onChange()
	local partANewSize = partA.Size

	local scale = partANewSize / partAOldSize

	local relativeScale = partA.CFrame:VectorToWorldSpace(scale)

	local newPartBPosition = (partB.Position - partA.Position) * relativeScale + partA.Position

	partB.CFrame = partB.CFrame - partB.Position + newPartBPosition

	partAOldSize = partANewSize
end


partA:GetPropertyChangedSignal("Size"):Connect(function()
	onChange()
end)

image

I apologise if I am doing something wrong.

Also would it be possible to change the code so if the position of PartA changes PartB updates to the same position as well? To give the illusion of moving with PartA. I can do it already but I was wondering if there was an easier way.

Oh, that’s an oversight of mine. First, it doesn’t update when the part moves. Second, the size is being stored every frame so the imprecision of the numbers being stored builds up every time.

Please try this code instead:

local RunService = game:GetService("RunService")

local partA = ...
local partB = ...

-- The values used to calculate the position of B are stored once, so no precision can build up over many times setting the position of B
local originalPartASize = partA.Size
local originalOffset = partA.CFrame:ToObjectSpace(partB.CFrame)

RunService.RenderStepped:Connect(function()
    -- Calculate the position of the part if there were no scale changes
    local unscaledPartBCFrame = partA.CFrame * originalOffset

    -- Calculate the desired position based on the overal scale change
    local scale = partA.Size / originalPartASize
    local relativeScale = partA.CFrame:VectorToWorldSpace(scale)
    local newPartBPosition = (unscaledPartBCFrame.Position - partA.Position) * relativeScale + partA.Position

    -- Replace the position with the newly calculated position
    local scaledPartBCFrame = unscaledPartBCFrame - unscaledPartBCFrame.Position + newPartBPosition

    -- Set the CFrame of partB to the calculated one
    partB.CFrame = scaledPartBCFrame
end)
1 Like

This works! But I have another problem (I’m so sorry for asking you constantly instead of just trying myself, I’ve been trying to figure this out on my own for like over a week and I just cant be bothered anymore… lol)

If PartA is spinning, it messes up the movement of the PartB(s), and if the size of the Part changes on the Y axis, it offsets the position of the PartB(s)

Medal_wFsl8FbHUr

Medal_Bk7mmT95Gm

Would it be possible to fix this?

Maybe you could try and add an “offset” CFrameValue to each PartB upon their creation, set it to PartB’s CFrame - PartA’s CFrame, and then whenever the size or position of PartA changes, set every PartB’s CFrame to PartA’s CFrame + the PartB’s offset value CFrame?

Doesn’t work sadly, I haven’t been able to find a fix for this after all this time, I don’t understand how the other plate games are doing this lol

Just making sure, does WeldConstraint work?
Also could you show me the code for your implementation of my idea?

1 Like

WeldConstraint did somewhat work on a previous version of this although I ran into some kind of issue that I can’t remember. I want a system that can do three things:

  • Update the position of PartBs to be relative to the scale of partA’s size
  • Update the position of PartBs whenever PartA moves
  • Make sure this works while partA is moving/spinning/flipping and if PartBs are added while partA is spinning/flipping (This causes problems in all the ways I’ve tried)

The code I used is just this.

local partA = script.Parent
local partB = partA.Partb
local RunService = game:GetService("RunService")

RunService.Heartbeat:Connect(function()
	print(partB.CFrame * partA.CFrame:Inverse()) -- To get the offset to put inside the CFrameValue inside of PartB

	partB.CFrame = (partA.CFrame * partB.Offset.Value)
end)

I know this is inefficient and laggy but this is just to check code works before I bother adding it to the actual game. PartB already exists in this situation so I add the CFrameValue to the part directly in studio

The code “works”, but all it does is update and follow the position of PartA if it moves, it doesn’t care about the size of partA changing. Sorry if this is just me being stupid, I have been trying to figure this out for so long now that my brain is just tired haha

Oh, sure, but like
First off what the hell does Inverse() do I’m not a math guy I’m a scripting guy
Second off could you maybe add a part that said something about the parts “Original Size” to the CFrameValue (maybe another value inside of it) and added something like
partB.CFrame = (partA.CFrame * partB.Offset.Value) + ((OriginalSize - partA.Size.Y) / 2)?

1 Like

what the hell does Inverse() do

:Inverse() just returns the values of the CFrame but negative (I think…), so doing partB.CFrame * PartA.Cframe:Inverse() is the equivalent of doing partB.CFrame - PartA.CFrame

added something like partB.CFrame = (partA.CFrame * partB.Offset.Value) + ((OriginalSize - partA.Size.Y) / 2)?

This code works better but now I am running into the issue with spinning/flipping…
Medal_hpZI3lbnm4

To be honest if this is too hard I might end up just ignoring the relative scale part and just making sure it follows the position, which would mean going back to my WeldConstraint setup which eh isn’t that bad

1 Like

To be honest I really don’t know why that code wouldn’t work with spinning…
I am assuming you set the Offset value correctly (including rotation) and that it’s a CFrame value, so if they aren’t please say so.

Also, what is the issue with WeldConstraints in the first place?

1 Like

I will also proceed to test my code in a test place, to see if it works with spinning.

The CFrame value is indeed set to the offset, the rotation is just 0, 0, 0 as neither PartA or PartB have any rotation applied to them upon server start.

I do not remember the issue with WeldConstraints specifically, but if I had to make a guess, probably something to do with updating the position relatively when partA changes size.

1 Like

Hmm.
But, I have had some weird results with testing, but maybe I just put in the offset wrong…
In this case, Part A is at 0, 0.5, 0 and Part B is at -4.125, 1.5, -5.25 (ontop of Part A which is 1 stud tall)
Offset is set to -4.125, 1, -5.25.
There are 2 scripts, one inside Part A to make it spin 100 times (which is probably irrelevant but ill put it here anyway)

Spinning Script
local p = script.Parent
local ts = game:GetService("TweenService")
local ti = TweenInfo.new(100, Enum.EasingStyle.Linear, Enum.EasingDirection.In, 1, false)
local tg = {}
tg.Orientation = Vector3.new(0, 3600, 0)
local tw = ts:Create(p, ti, tg)
tw:Play()

And the second script is in PartB and how it moves with PartA.

local p = script.Parent
local pa = workspace.PartA
game:GetService("RunService").Heartbeat:Connect(function()
	p.CFrame = pa.CFrame * script.Parent.Offset.Value:Inverse()
end)

And… the results are interesting…
I could record it but I can’t find the file path to the video… so…
It worked… CORRECTLY!
PartB perfectly followed the PartA while it was spinning…
But, PartB happened to be upside down, which is fine.
I have yet to test this with PartA moving, and I will now.
EDIT: I have tested it with PartA moving, and it works perfectly! However, Vertical Sizing doesn’t work, but that could be fixed easily. Let me take the code for making it go up properly, and change the offset to put PartB ontop of PartA.

Good news! I’ve managed to tweak the script just enough for it to work with Vertical Sizing!
As well as changing the offsets Y value to -1, I’ve changed Part B’s script a little!

local p = script.Parent
local pa = workspace.PartA
local ogs = pa.OriginalSize
game:GetService("RunService").Heartbeat:Connect(function()
	if ogs.Value.Y == pa.Size.Y then
		p.CFrame = (pa.CFrame * script.Parent.Offset.Value:Inverse())
	else
		p.CFrame = (pa.CFrame * script.Parent.Offset.Value:Inverse()) + Vector3.new(0, ((pa.Size.Y - ogs.Value.Y) / 2), 0)
	end
end)

Now, it works with Moving, Spinning, and Vertical Sizing, all at the same time!

@dilbertron2

1 Like

This is great! I can’t believe it works!

Thank you so much for your help and being fast & responsive!

1 Like

No problem! :slight_smile:

char limit

1 Like