How do I move an object along a trajectory with a VectorForce?

Hi there!

I am looking to achieve a similar result as seen in this post by @EgoMoose. However, I am trying to use VectorForces instead of setting an initial velocity. This would give me greater control of speed, drag, and acceleration.

By modifying the code in EgoMoose’s tutorial, I was able to come up with this:

Here is the code for the above results:

local players = game:GetService("Players")
local runService = game:GetService("RunService")

local player = players.LocalPlayer

local pathTime = 1
local mouse = player:GetMouse()
local hrp = player.CharacterAdded:Wait():WaitForChild("HumanoidRootPart")
local projectile = script.Parent:WaitForChild("Handle")

local attach0 = Instance.new("Attachment", workspace.Terrain)
local attach1 = Instance.new("Attachment", workspace.Terrain)

local beam = Instance.new("Beam", workspace.Terrain)
beam.Attachment0 = attach0
beam.Attachment1 = attach1

local gravity = workspace.Gravity
local gravityVector = Vector3.new(0, -gravity, 0)

local function getBeamInfo(g, v0, x0, t1)
	-- calculate the bezier points
	local c = 0.5*0.5*0.5;
	local p3 = 0.5*g*t1*t1 + v0*t1 + x0;
	local p2 = p3 - (g*t1*t1 + v0*t1)/3;
	local p1 = (c*g*t1*t1 + 0.5*v0*t1 + x0 - c*(x0+p3))/(3*c) - p2;

	-- the curve sizes
	local curve0 = (p1 - x0).magnitude;
	local curve1 = (p2 - p3).magnitude;

	-- build the world CFrames for the attachments
	local b = (x0 - p3).unit;
	local r1 = (p1 - x0).unit;
	local u1 = r1:Cross(b).unit;
	local r2 = (p2 - p3).unit;
	local u2 = r2:Cross(b).unit;
	b = u1:Cross(r1).unit;

	local cf1 = CFrame.new(
		x0.x, x0.y, x0.z,
		r1.x, u1.x, b.x,
		r1.y, u1.y, b.y,
		r1.z, u1.z, b.z
	)

	local cf2 = CFrame.new(
		p3.x, p3.y, p3.z,
		r2.x, u2.x, b.x,
		r2.y, u2.y, b.y,
		r2.z, u2.z, b.z
	)

	return curve0, -curve1, cf1, cf2;
end

local function renderBeam(dt)
	local initialVelocity = hrp.CFrame * Vector3.new(0, 2, -2)
	local initialPosition = (mouse.Hit.p - initialVelocity - 0.5*gravityVector*pathTime*pathTime)/pathTime;

	local curve0, curve1, cf1, cf2 = getBeamInfo(gravityVector, initialPosition, initialVelocity, pathTime);
	beam.CurveSize0 = curve0;
	beam.CurveSize1 = curve1;
	
	-- convert world space CFrames to be relative to the attachment parent
	attach0.CFrame = attach0.Parent.CFrame:inverse() * cf1;
	attach1.CFrame = attach1.Parent.CFrame:inverse() * cf2;
end

local function onClick()
	local initialPosition = hrp.CFrame * Vector3.new(0, 2, -2)
	local hitPosition = mouse.Hit.Position
	
	-- calculate the initialVelocity needed to reach mouse.Hit.p
	local initialVelocity = (hitPosition - initialPosition - 0.5*gravityVector*pathTime*pathTime)/pathTime
	
	local newProjectile = projectile:Clone()
	newProjectile.CFrame = CFrame.new(initialPosition)
	newProjectile.CanCollide = true
	
	local projectileMass = newProjectile.Mass

	local attachment = Instance.new("Attachment")
	attachment.Parent = newProjectile
	
	local vectorForce = Instance.new("VectorForce")
	vectorForce.Attachment0 = attachment
	vectorForce.RelativeTo = Enum.ActuatorRelativeTo.World
	vectorForce.Force = initialVelocity + Vector3.new(0, projectileMass * gravity, 0)
	vectorForce.Parent = newProjectile
	
	local updateConnection = nil
	local touchedConnection = nil
	
	local passedTime = 0
	
	local function getPositionAtTime(t)
		return 0.5*gravityVector*t*t + initialVelocity*t + hitPosition
	end

	local function getVelocityAtTime(t)
		return gravityVector*t + initialVelocity
	end
	
	updateConnection = runService.RenderStepped:Connect(function(deltaTime)
		passedTime += deltaTime
		
		--if passedTime > pathTime then updateConnection:Disconnect() vectorForce.Force = Vector3.zero return end -- disconnect event once time runs out
				
		local position = getPositionAtTime(passedTime)
		local velocity = getVelocityAtTime(passedTime)
		
		local force = velocity * (projectileMass / 12) * Vector3.new(gravity / 4*pathTime, gravity / 2*pathTime, gravity / 4*pathTime) * .25 + Vector3.new(0, gravity * projectileMass, 0)
		--local force = velocity + Vector3.new(0, gravity * projectileMass - 10, 0)
		
		if velocity:Dot(velocity) == 0 then
			velocity = getVelocityAtTime(passedTime - deltaTime) * Vector3.new(1, 0, 1)
		end
		
		local cframe = CFrame.lookAt(position, position + force)
		local x, y, z = cframe:ToOrientation()

		vectorForce.Force = force
		print(passedTime)

		newProjectile.Orientation = Vector3.new(math.deg(x), math.deg(y), math.deg(z))
	end)
	
	touchedConnection = newProjectile.Touched:Connect(function(hit)
		if not hit:IsDescendantOf(player.Character) then
			updateConnection:Disconnect()
			touchedConnection:Disconnect()
			
			vectorForce.Force = Vector3.zero
		end
	end)

	newProjectile.Parent = game.Workspace
end

mouse.Button1Down:Connect(onClick)
runService.RenderStepped:Connect(renderBeam)

Unfortunately, this still leaves a few problems:

  • pathTime isn’t accurate, and changing pathTime will cause even more inaccuracies.
  • Many numbers in the equations are guesses and have no meaning behind them
  • Changing workspace.Gravity also causes inaccuracies.

Ideally, I would like to include a speed variable to control the speed of the projectile as it follows the trajectory. A speed of 1 would have the total path time be the same as pathTime, and a speed of 2 would have the total path time of half of pathTime (totalPathTime = pathTime / speed).

Any help would be greatly appreciated! I have a limited knowledge of calculus and trigonometry (hence the occasional random-numbers-in-formula-guesses), so apologies in advance if I seem confused in replies. Thank you!

1 Like

Forces apply accelerations, not velocities. Put a constant-Force VectorForce inside a part, and it will get faster and faster over time in that direction.

In other words, you can’t really “use VectorForces instead of setting an initial velocity”—you probably still want to set an initial velocity, and use a VectorForce to apply changes to the acceleration during flight.

Could you be a little more specific about what you want to accomplish?

  • If you’re applying a constant force (say, a steady wind), this is easily done by setting the VectorForce to a constant value. Drawing the beam would be as simple as adding that constant value to the g parameter.

  • If you’re trying to add drag by applying a different force every frame, you can do that with Stepped. While you could still write an equation for this trajectory, drawing it with a Beam would be impossible, since it would no longer be quadratic.

Hi! Sorry, I definitely should have added more clarity in the topic.

Essentially, I’m trying to move an object from point A to point B, thrusting the object up before it reaches point B. I would like to have it be physically accurate (to a certain degree of course), so CFraming or AlignPosition would not be ideal.

Think of the Starhopper Test:

The object doesn’t just have an initial velocity, it has a thrust pointing near-downwards (thus, the object goes up). The Starhopper uses a limited gimbal mechanism, but for this topic, I don’t need it to be a limited gimbal. Also, since there is no wind, air, or other external forces besides gravity in Roblox, I’m not looking to replicate any of the guide fins or orientational gas bursts. Just looking for the movement with the thrust at the bottom.

It’s possible though that this may be too advanced, or I’m missing an important feature, such as how the object still has to produce an downward thrust as the object descends to account for a smooth landing.

Ah… it’s not really a projectile motion problem anymore since you’re adding external forces.

The problem is also pretty unspecified still. I mean, your roblox rocket can output any force, in any direction. Therefore, it could follow any arbitrary continuous path you want. There are an infinite number of ways to get from A to B.

Another way to put it: an ICBM, a starhopper, and an airplane all get from A to B by applying thrust.

I mean, I could just say “set the vector force to (b-a) + Vector3.new(0, gravity, 0)”. That’ll get you from A to B.

I could also tell you how to move the part straight up from A, slow to a stop in the air, and then shoot straight diagonally towards B. I could also tell you how to move in a corkscrew spiral towards B.

So is your question literally “how do I simulate the star hopper in roblox?” That’s somewhat beyond my understanding, but maybe a more answerable question.

Looking at it again, that would essentially be my goal for this topic. Calculating the thrust and thrust angle needed to have an object follow a parabolic path from point A to point B, with a height variable if possible. I would use the logic behind it for something else, but getting the logic in the first place would be a great start.

Thank you for helping me narrow this down, I think I’ll make revisions to the OP and go from there!

To start, it won’t really be parabolic. A parabolic arc is achieved with a constant downward acceleration. Since you’re changing the acceleration (force) during flight, it will vaguely be an arc-shaped path, but not a parabola.

Maybe it would also help if you mentioned what application this is for.

That’s fine, it doesn’t have to be a perfect parabola - just looking for that basic arc shape.

By swapping out a VectorForce for a LinearVelocity, I was able get results I was happy with as it fixed many of the issues I was experiencing. I have a feeling that achieving the goal seen in the Starhopper video is quite a bit more complex though, as the engine only thrusts downwards. I’ll experiment a lot now with the LinearVelocity, as it should be easier to control since it’s a constant velocity, and not an acceleration like you stated above.