Hi there!
I am looking to achieve a similar result as seen in this post by @EgoMoose. However, I am trying to use VectorForce
s 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 changingpathTime
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!