Drawing projectile curves

How’dy!
If you’re familiar with shooters, or games in which you have the ability to throw items (Ex. Grenades, Boxes, etc) you may notice that they help you estimate where your item is going to land with a visual curve displaying it’s estimated path.
I’m wondering the best possible way to achieve something similar to that, I’m thinking some sort of raycasting based on the bodygyro, although I’m not 100% sure how I’d go about that.

Throwing items is going to be a very big part of the game, so if it’s clunky or hacky (Basically if I made it without knowing how) the game won’t be enjoyable to play.

Thanks for reading!

4 Likes

For most customization on the beam (glow, transparency, color, texture) you probably want to use beams. Whatever you use (beams, parts, GUI) you will still need to figure out the actual flight path. The best way to ensure you are predicting the correct path is to not estimate the flight curve and set that as the display, but to calculate a custom flight curve and use that for both your display and the actual projectile flying; that way Roblox physics can not interfere with your indicator.

What’s the best method to accomplish this?
Some sort of raycasting?

Beams and ray casting could be a working solution but the problem is that rays are a line that is finite in one way but not in the other, so it means that curves cannot be accomplished with a single ray I guess

what i recommend is you find the angle between the start lookvector and finish lookvector, like

angle = math.acos(startLookVector:Dot(endLookVector))

then in a loop, rotate it each iteration by the divison of angle / times
like

local throwSpeed = 2.5
local duration = 10

for i=1, duration do
     grenade.CFrame = grenade.CFrame * CFrame.new(0, 0, -throwSpeed) * CFrame.Angles(angle / duration, 0, 0)
     game['Run Service'].Stepped:Wait()
end

you will have to adjust it a lot, but this is a pretty good way of doing it

1 Like

Would I use Bezier curves?

That might work, but in the case that it fails or doesn’t work properly two things will happen:
1 The item will freeze in place if it is anchored, if it’s not it will behave odd
2 It will eat up a lot of memory regardless

true, and ik this sounds like a ton of work, but you could make it extremely reliable and smooth if you fire to each client to do that math

Yes, Bezier Curves would be good in a case of a grenade launch trail for example. You would need to get the initial position of the grenade and the final position that the grenade achieves. Now you would need to get a method to display that (Parts, GUIs or beams).

Beams will work, but how would I get the ending position?

Well, you could get the maximal set magnitude and add it with the direction the object goes at, because I’m sure you would want to limit the distance so the object doesn’t fly at 500 studs.

Raycasting is not necessarily required, but it can help to predict a point where your projectile will stop/hit/bounce in the world. The basic premise is to create a function for the flight path dependant on starting location, starting velocity (usually the player’s character velocity plus their “arm strength”), and flight direction. Optionally you want a custom curve function for each projectile you make (heavier projectiles might fly less far, paper planes might fly very far). The ideal curve is a parabola (that’s how gravity works) but the more factors you implement the more it can differ. Here’s an example using only air resistance as a factor (projectile gets slower the longer it goes):

Flight path 45° arc: https://i.imgur.com/hPOYYHI.png
Same path but blocked by a wall: https://i.imgur.com/wPO3D7W.png

You can play around with the code if you want to increase the curve resolution.

local GLOBAL_GRAVITY = 2

local function path(steps, position, velocity, gravity)
	local gravityVector = gravity*Vector3.new(0, -1, 0)
	local d = velocity + gravityVector
	local newVelocity = (velocity+gravityVector)*.8
	if steps == 1 then
		return position + d, newVelocity
	elseif steps == 0 then
		return position, velocity
	end
	return path(steps-1, position, newVelocity, gravity) + d, newVelocity
end

--Starting position for our indicator, and parent of beam attachments
local p = workspace.Part

--Variables
local position = Vector3.new() --Attachments are in part center
local velocity = Vector3.new(0, 1, -1).Unit * 20 --Length of vector is throwing speed in studs/s

local function makeBeam(a1, a2)
	local b = Instance.new("Beam")
	b.Attachment0 = a1
	b.Attachment1 = a2
	b.Color = ColorSequence.new(Color3.new(1, 0, 0))
	b.Transparency = NumberSequence.new(0)
	b.Width0 = 0.2
	b.Width1 = 0.2
	b.FaceCamera = true
	b.Parent = a1
end
local function makeHitIndicator(pos)
	local part = Instance.new("Part")
	part.Name = "Indicator"
	part.Anchored = true
	part.CanCollide = false
	part.Color = Color3.new(1, 0, 0)
	part.Shape = Enum.PartType.Ball
	part.Size = Vector3.new(0.4, 0.4, 0.4)
	part.Position = pos
	part.Parent = p
end

--We will draw the curve until it hits something, or 100 cycles pass
local lastAttachment
for i = 0, 100 do
	local pos, vel = path(i, position, velocity, GLOBAL_GRAVITY)
	local ray = Ray.new(p.Position+pos, vel)
	local raycastResult, hitPos = workspace:FindPartOnRayWithIgnoreList(ray, {p})
	local a = Instance.new("Attachment")
	if raycastResult then
		a.WorldPosition = hitPos - p.Position
		if lastAttachment then
			makeBeam(lastAttachment, a)
		end
		makeHitIndicator(hitPos)
		a.Parent = p
		break
	else
		a.Position = pos
	end
	a.Parent = p
	if lastAttachment then
		makeBeam(lastAttachment, a)
	end
	lastAttachment = a
end
12 Likes

To add on to my first reply, you can then save the calculated points in a table everytime you make a new indicator, and iterate through that table to (with a second-long tween) reposition your projectile every second. Your projectile will then follow the exact flight path you previously calculated.

You need to mess with the server-client boundaries here because indicators should be local but the flight path needs to be server-side.

1 Like