How to position a cframe relative to another cframe?

Sorry if this has been posted before but I can’t figure this out. I want to offset a cframe pretty much. Heres what I have now:

local Vector3Offset = Vector3.new(2,5,0) -- 2 studs sideways, 5 studs up, 0 fowards
local CFrameOffset = Start.CFrame * Vector3Offset -- technically a vector3 despite the name

Part.CFrame = CFrameOffset -- error bc its a vector, not a cframe

This would be the perfect solution if I needed to set the parts position, not the cframe.
CFrameOffset isn’t a cframe, its actually a vector 3. So my next idea was to just do this:

local Vector3Offset = Vector3.new(2,5,0) -- 2 studs sideways, 5 studs up, 0 fowards
local CFrameOffset = Start.CFrame * CFrame.new(Vector3Offset)

Part.CFrame = CFrameOffset

But that didn’t work either and when setting a part’s cframe to CFrameOffset it just went to a random place.

Part.CFrame = Start.CFrame * CFrame.new(2,5,0)

My final attempt was this, and it didn’t work either.

Any ideas on how I can achieve an offset while it still being a cframe?
(Sorry if this still makes 0 sense)

TLDR: I want to achieve this effect:

local Vector3Offset = Vector3.new(2,5,0) -- 2 studs sideways, 5 studs up, 0 fowards
local CFrameOffset = Start.CFrame * Vector3Offset -- technically a vector3 despite the name

Part.CFrame = CFrameOffset -- error bc its a vector, not a cframe

But getting a cframe as the end result INSTEAD of a vector3.

btw I suck at math regarding cframes and stuff so sorry if this is super obvious

1 Like

CFrame:ToWorldSpace()

You can look at Object and World space on the docs for more info on how you can use it.

So something like this?

local Vector3Offset = Vector3.new(2,5,0) -- 2 studs sideways, 5 studs up, 0 fowards
local CFrameOffset = Start.CFrame * Vector3Offset -- technically a vector3 despite the name

Part.CFrame = CFrame:ToWorldSpace(CFrameOffset)

Not really.

ToWorldSpace is a sort of “transformation”. Basically you moving the center of the world to the CFrame.

So if you wanted a partB that is 2 studs in front of the partA you can do this.

local OFFSET_CFRAME = CFrame.new(0,0,2)
PartA.CFrame = PartB.CFrame:ToWorldSpace(OFFSET_CFRAME)

Note: CFrame also includes rotation so any rotation to the CFrame will rotate the entire Offsetted CFrame around that point.

1 Like

Basically you don’t really need this.

I still don’t think I have it working yet. I’m currently trying this:

local randomCFrameOffset = CFrame.new(positiveOrNegative(rng:NextNumber(3,5)),positiveOrNegative(rng:NextNumber(2,2.5)),0)
-- generates a random offset, that can be -5 or positive 5 studs sideways etc
-- the offset works fine

local rngCF = Start.CFrame:ToWorldSpace(randomCFrameOffset)
-- I tried tweening a part starting Start.CFrame to rngCF and it didn't work. 
-- I expected it to kinda go near the start brick but it floated far away to a random position

Sorry, I’m confused.

What is the goal and how is it behaving currently? Could you show me a screenshot of what is happening?

to world space will make the object in the argument’s position become 0,0,0. Then you can place something relative to that. If you want to put a part five studs above another part, then you could use Part.CFrame = OtherPart:ToWorldSpace(0,5,0)

This still isn’t working for some reason and I don’t know why. I’m trying to make a jojo barrage attack where arms will start at the HumanoidRootPart Cframe, curve towards a randomly generated cframe and then go to an end point. Yet the arms just fly backwards or sideways depending on how I stand and then go to the goal position. My offset code:

local randomCFrameOffset = CFrame.new(rng:NextNumber(3,5),rng:NextNumber(2,2.5),0)
local randomPositionToCurveTo = arm.CFrame:ToWorldSpace(randomCFrameOffset)

(Sorry for the long response my internet is being extremely slow rn)

I’m not sure why either because that code is correct and is how you offset relative to another CFrame. This probably means that something else is causing this not to work not related to the code you are posting.

If you are trying to do this CFraming to a player’s character’s limbs, I am going to guess that this is why it is not working. Your arms are welded to your character and have Motor6D joints holding them together (which are also involved in animations). If you are CFraming them manually, I believe it’s basically going to fight with the physics engine and cause weird things to happen. You should instead modify the Motor6D joints manually.

I’m simply cloning a part named arm from replicated storage. Its just a rectangle that I’m trying to have move along a bezier curve.

It works, but once I turn it no longer goes side to side. Just a vertical curve with no horizontal movement. I can send the bezier curve stuff here, maybe that will work?

local function QuadraticBezier(Time,Point0,Point1,Point2)
			return (1-Time)^2*Point0+2*(1-Time)*Time*Point1+Time^2*Point2; -- bezier curve path
		end

		local rng = Random.new()
		
		local function positiveOrNegative(num)
			local num2 = rng:NextInteger(1,2)
			
			if num2 == 1 then
				return num
			else
				return -num
			end
		end
		
		local randomCFrameOffset = CFrame.new(positiveOrNegative(rng:NextNumber(3,5)),positiveOrNegative(rng:NextNumber(2,2.5)), 0)
		local rngCF = arm.CFrame:ToWorldSpace(randomCFrameOffset)
		
		local pivotCFrame = CFrame.new(endPoint)
		local armStartCf = arm.CFrame

		local timeElapsed = 0
		local speed = 1
		local duration = (armStartCf.p - pivotCFrame.Position).Magnitude / speed
		local tweenDelay = .1

		local TS = game:GetService("TweenService")

		while timeElapsed < duration do
			local progress = timeElapsed / duration

			local pointAtTimeElapsed = QuadraticBezier(progress,armStartCf.p,(armStartCf * rngCF).p, pivotCFrame.p)
			local lookAt = QuadraticBezier(progress+0.01,armStartCf.p,(armStartCf * rngCF).p, pivotCFrame.p)

			if timeElapsed == 0 then
				arm.CFrame = CFrame.new(pointAtTimeElapsed, QuadraticBezier(progress+.01,armStartCf.p,(arm.CFrame * rngCF).p, pivotCFrame.p)) -- Makes the arm face the right way
			end

			timeElapsed += speed

			local tween = TS:Create(arm,TweenInfo.new(tweenDelay),{CFrame = CFrame.new(pointAtTimeElapsed,lookAt)})
			tween:Play()

			wait(tweenDelay)
		end
		
		local tween = TS:Create(arm,TweenInfo.new(.4),{Transparency = 1})
		tween:Play()
		
		tween.Completed:Wait()
		
		arm:Destroy()

I genuinely have 0 clue when it comes to cframes and all of that so sorry if I’m being frustrating

Can you send a GIF of what it looks like for you? I tested the code you sent and this is what it looks like for me:

Is this correct?

Thats exactly how mine should look yet just imagine that the arm/projectile/moving part just fires back extremely far and then goes to the goal. I would attach a video but like I said my internet has been super slow the past few hours.

  • Is there any other code/scripts that could be interfering with it?
  • Is the moving part unanchored? It should be anchored.

If you can’t send a video, then can you link a place that I can join to see it instead?

Incase you also want it, below is the full code I used for my attached video. You just need to create two anchored parts in Workspace called Target and Arm. You should be able to move and rotate the original Arm part while the game is running with the studio draggers to change the “spawn” point and direction.

local function QuadraticBezier(Time,Point0,Point1,Point2)
	return (1-Time)^2*Point0+2*(1-Time)*Time*Point1+Time^2*Point2; -- bezier curve path
end

local rng = Random.new()

local function positiveOrNegative(num)
	local num2 = rng:NextInteger(1,2)

	if num2 == 1 then
		return num
	else
		return -num
	end
end

while true do
	local arm = workspace.Arm:Clone()
	arm.Parent = workspace
	local endPoint = workspace.Target.Position
	
	local randomCFrameOffset = CFrame.new(positiveOrNegative(rng:NextNumber(3,5)),positiveOrNegative(rng:NextNumber(2,2.5)), 0)
	local rngCF = arm.CFrame:ToWorldSpace(randomCFrameOffset)

	local pivotCFrame = CFrame.new(endPoint)
	local armStartCf = arm.CFrame

	local timeElapsed = 0
	local speed = 1
	local duration = (armStartCf.p - pivotCFrame.Position).Magnitude / speed
	local tweenDelay = .1

	local TS = game:GetService("TweenService")

	while timeElapsed < duration do
		local progress = timeElapsed / duration

		local pointAtTimeElapsed = QuadraticBezier(progress,armStartCf.p,(armStartCf * rngCF).p, pivotCFrame.p)
		local lookAt = QuadraticBezier(progress+0.01,armStartCf.p,(armStartCf * rngCF).p, pivotCFrame.p)

		if timeElapsed == 0 then
			arm.CFrame = CFrame.new(pointAtTimeElapsed, QuadraticBezier(progress+.01,armStartCf.p,(arm.CFrame * rngCF).p, pivotCFrame.p)) -- Makes the arm face the right way
		end

		timeElapsed += speed

		local tween = TS:Create(arm,TweenInfo.new(tweenDelay),{CFrame = CFrame.new(pointAtTimeElapsed,lookAt)})
		tween:Play()

		wait(tweenDelay)
	end

	local tween = TS:Create(arm,TweenInfo.new(.4),{Transparency = 1})
	tween:Play()

	tween.Completed:Wait()

	arm:Destroy()
end
1 Like

Nope, I simply have one function clone the arm, cframe it to the purple guy’s humanoid root part and then call the bezier curve function.

It is anchored.

Video (Sorry for bad quality, idk why it captured studio like this:)

I’m just really confused as to why it behaves this way even though I’m using the same exact code.

Not sure if this matters but this is how I’m calling the function:

local arm = game.ReplicatedStorage.Assets.StandModels.Arms.Arm:Clone()
			
arm.BrickColor = stand:WaitForChild("Torso").BrickColor
arm.Transparency = .1
arm.Parent = workspace.Debris
arm.CFrame = hrp.CFrame
			
-- bezier curve
util.CreateBezierCurveArm(arm, (hrp.Position + (hrp.CFrame.LookVector * 3.5)))

I have that function in a module in replicated storage as well named “Utilities”.

sorry for the late response I was sleeping

Alright, I was able to reproduce the glitchy flinging effect that you are having now (that changes depending on your character position and rotation). After looking at the code for a bit, I found that there were some extra multiplications being done to rngCF inside the QuadraticBezier function parameters:

  • local pointAtTimeElapsed = QuadraticBezier(progress,armStartCf.p,(armStartCf * rngCF).p, pivotCFrame.p)
  • local lookAt = QuadraticBezier(progress+0.01,armStartCf.p,(armStartCf * rngCF).p, pivotCFrame.p)
  • arm.CFrame = CFrame.new(pointAtTimeElapsed, QuadraticBezier(progress+.01,armStartCf.p,(arm.CFrame * rngCF).p, pivotCFrame.p))

The third parameter “(armStartCf * rngCF).p” in all of those should be changed to just “rngCF.p”. After changing that and testing it, the arm projectile no longer flings in strange directions for me.

Why?

Earlier in the script, rngCF is defined like this:

local rngCF = arm.CFrame:ToWorldSpace(randomCFrameOffset)

This line is perfectly fine and will give you a CFrame that is offset relative to arm.CFrame by randomCFrameOffset. The CFrame will be in what is called world space, which means it is relative to the world origin (0,0,0) in your game. On the other hand, the variable randomCFrameOffset is in object space and is relative to an arbitrary point in your game (like another object’s position, or even itself).

The method ToWorldSpace takes a CFrame that is in object space and transforms it to world space, relative to the CFrame you are operating on (in this case, arm.CFrame). The problem arises when you multiply this world space CFrame again in those function parameters.

When you multiply two CFrames together, this will give you the same result as the ToWorldSpace method:

--these lines are equivalent
local newcframe1 = cframe1:ToWorldSpace(cframe2)
local newcframe2 = cframe1 * cframe2

print(newcframe1 == newcframe2) --will always print true, if you ignore any floating point errors

So essentially, your code was doing this:

arm.CFrame:ToWorldSpace(arm.CFrame:ToWorldSpace(randomCFrameOffset)).p

Hopefully this fixes your problem now!

1 Like

Thanks, it works for the most part! The arms no longer fly everywhere, they do stick to the front of the stand which is great! There is one more issue though, it seems that if I face a different way, the arms no longer spread out? Heres what I mean:


I believe this is caused by the fact I only have numbers on one axis (rng:NextNumber(3,5)),positiveOrNegative(rng:NextNumber(2,2.5)), 0)
I thought that making the cframe relative would prevent this from happening which is why I made this post in the first place :sweat_smile:


Thanks for all of the help so far btw!

Can you send more of the code from where you are calling util.CreateBezierCurveArm? Also what the Size and Orientation is for the main part(s) of arm and hrp.

Is hrp your HumanoidRootPart, or the NPC? And how is that NPC being moved (in terms of the code/math)?

You are correct in what you thought. I don’t think this code is the direct culprit, as that is what I had it as in mine and the arms correctly position the exact same no matter which way I was turning my character. I did not have an NPC though, so it could have to do with that then.

Your CFraming could be breaking from code that ran before it if it is doing something to the orientation or position of another part that then breaks the ToWorldSpace (like moving something with LookVector and getting rid of the orientation so it’s not actually facing the right direction even if it looks like it)

The hrp is the NPC’s “HumanoidRootPart” floating in front of me. It is moved on the client using body gyros and body positions. All of the parts within the NPC’s network ownership is set to the player.

Could forces have something to do with this?

NPC Positioning code:

local offset = nil
	if (attacking.Value == true) then
		offset = Vector3.new(0,0,-3.5)
		bodyPos.D = 100
	else
		offset = Vector3.new(-2,2,2.5)
		bodyPos.D = 700
	end

	local targetPos = (hrp.CFrame * offset)
	bodyPos.Position = targetPos

	gyro.CFrame = hrp.CFrame

-- hrp in this script refers to the player's root part, not the npc's root part.

Sorry, my syntax may be wrong. It’s been a while since I’ve used that. Look on the DevHub page for CFrame and it will give you some correct examples.