Projectile Aim-Assist

Hey, and thanks for reading in advance.

One of the classes in the Fat Princess clone I’m making has the ability to fire a bow. Ordinarily, the firing point that the trajectory simulation uses is calculated based on your mouse’s screen position, but since people can move relatively quickly, I’m attempting to create a sort of pseudo-aim-assist that hijacks your sent position if your mouse was hovering over a targetable entity by automatically firing at their root.

Problem there is, the arrow’s simulated trajectory arcs downwards. The solution seemed simple enough - simply adjust the targeted position of the arrow upwards based on the distance between yourself and the target. I’m having trouble finding the exact math, though - I assume I’m likely going to need some complicated ballistics formula, but I’m not sure just yet.

Clientside snippet:

Client = function(Character, Animator, InputObject)
			local shootAnim = LoadAnim(Character, Animator, 2991996122, "ShootA")
			local fakeArrow = Character:FindFirstChild("Arrow")

			local castLength = 500
			local hitCon

			local targetParams = RaycastParams.new()
			targetParams.FilterDescendantsInstances = {Character, workspace.CurrentCamera}
			targetParams.FilterType = Enum.RaycastFilterType.Blacklist
			targetParams.IgnoreWater = true

			hitCon = shootAnim:GetMarkerReachedSignal("Fire"):Connect(function()
				local arrowKey = Core:GenerateKey(9)
				local screenRay, fireTo

				if InputObject.UserInputType.Name:find("Gamepad") then
					screenRay = workspace.CurrentCamera:ScreenPointToRay(
						workspace.CurrentCamera.ViewportSize.X/2,
						(workspace.CurrentCamera.ViewportSize.Y - 32) * .3,
						1
					)
				else
					screenRay = workspace.CurrentCamera:ScreenPointToRay(InputObject.Position.X, InputObject.Position.Y, 1)
				end

				screenParams.FilterDescendantsInstances = {Character, workspace.CurrentCamera}
				fireTo = workspace:Raycast(screenRay.Origin, screenRay.Direction * castLength, screenParams)

				if fireTo then
					local livingTarget = Core:FetchFromPart(fireTo.Instance)
					
					if livingTarget and livingTarget:FindFirstChild("HumanoidRootPart") then
						-- This is where I'd be doing the adjustments
						fireTo = livingTarget.HumanoidRootPart.Position
					else
						fireTo = fireTo.Position
					end
				else
					fireTo = screenRay.Origin + screenRay.Direction * castLength
				end

				if fakeArrow then
					fakeArrow.Transparency = 1
				end

				local attackCF = CFrame.new(
					Character.HumanoidRootPart.Position + (fireTo - Character.HumanoidRootPart.Position).Unit * 4,
					fireTo
				)

				local simulatedPos = attackCF.Position
				local shotVelocity = attackCF.LookVector * Classes.Ranger.Primary.SHOT_SPEED

				local g = Vector3.new(0, -game.Workspace.Gravity, 0)
				local t = Classes.Ranger.Primary.SHOT_LIFETIME

				local lastPos = simulatedPos - shotVelocity.Unit
				local lastDelta = 1/30
				local nt = 0

				--
				Effect("Arrow", {
					cf = attackCF, speed = Classes.Ranger.Primary.SHOT_SPEED,
					lt = Classes.Ranger.Primary.SHOT_LIFETIME,
					col = game.Players.LocalPlayer.TeamColor.Color,
					ignite = Core:HasStatus(Character, "Ignite"),
					ID = arrowKey
				})

				Effect("GameAudio", {
					sound = RS.GameAudio.Attacks.BowFire,
					pos = Character:FindFirstChild("HumanoidRootPart")
				})

				task.spawn(function()
					while (nt < t * 2) and workspace.CurrentCamera:FindFirstChild(arrowKey) do
						local hitQuery, skewAngle, thisDelta

						simulatedPos = .25 * g * nt * nt + shotVelocity * nt + attackCF.Position
						skewAngle = (simulatedPos - lastPos).Unit
						lastPos = simulatedPos

						hitQuery = workspace:Raycast(simulatedPos,
							skewAngle * ((Classes.Ranger.Primary.SHOT_SPEED * lastDelta) + Classes.Ranger.Primary.SHOT_LENGTH),
							targetParams
						)

						if hitQuery then
							local dataPacket = {Instance = hitQuery.Instance, Position = hitQuery.Position, Direction = skewAngle}
							RS.Remotes.ProjectileHit:FireServer("Primary", dataPacket, arrowKey)
						end

						thisDelta = RUN.Heartbeat:Wait()
						nt = nt + thisDelta; lastDelta = thisDelta
					end
				end)

				RS.Remotes.SendAction:FireServer("Primary", {ID = arrowKey, CF = attackCF, ShotVelocity = shotVelocity})

				Character.Humanoid.AutoRotate = false

				Character.HumanoidRootPart.CFrame = CFrame.new(
					Character.HumanoidRootPart.Position,
					Vector3.new(fireTo.X, Character.HumanoidRootPart.Position.Y, fireTo.Z)
				)

				hitCon:Disconnect()
			end)

			Effect("GameAudio", {
				sound = RS.GameAudio.Misc.BowString,
				pos = Character:FindFirstChild("HumanoidRootPart")
			})

			shootAnim:Play()

			while shootAnim.IsPlaying and Core:CanAct(Character) do
				RUN.Heartbeat:Wait()
			end

			hitCon:Disconnect()
			Character.Humanoid.AutoRotate = true
			screenParams.FilterDescendantsInstances = {}

			if fakeArrow then
				fakeArrow.Transparency = 0
			end
		end,

I’m not following an exact gravitational constant, but I assume that can be accounted for.
Any help or advice is appreciated!

1 Like

You will have to use calculate the initial velocity of the launch, there is a post by EgoMoose that explains how to do just that (in the first section):

Verify that the mouse is aiming at an enemy entity, and if that’s the case, calculate the needed velocity to reach from to reach from the arrow’s origination point to the root.

this video should help you with the math

1 Like