How to create an 8-way burst of projectiles?

  1. What do you want to achieve? Keep it simple and clear!
    I want to make a part clone and shoot 8 projectiles in different directions, if that makes sense? Example below.

  2. What is the issue? Include screenshots / videos if possible!
    I simply don’t know how to do it efficiently, I suppose I could make the projectile with a script for moving forward and place it in replicated storage, clone it inside the part, and make them face different directions manually, although I’m not sure if that would lag the game or if there’s a simpler way. Forgive me, I’m VERY new to scripting.

  3. What solutions have you tried so far? Did you look for solutions on the Developer Hub?
    I looked for solutions multiple times, but I couldn’t find any, likely because I’m unsure how to word it. Any ideas would be appreciated.

4 Likes

Is something like this what you’re looking to create?

3 Likes

if they’re bursting away from the center one, then yes

2 Likes

With a small bit of Trigononmetry, its possible, all you are really doing here is using math.sin() and math.cos() to create a circle around the object, and dividing the circle into 8 parts, and placing the points into each corresponding part.

From there you can apply an “offset” direction to have them move into the said random direction.

3 Likes

After you have the projectile made, you can store it in ServerStorage. Then use a function to clone it 8 times and add 45 degrees to the Y value of the orientation each time. Add an attachment each time as well for a LinearVelocity instance. To make it move, use the LinearVelocity with a high MaxForce to keep it in the air and change the Z value of the VectorVelocity to control the speed (Make sure RelativeTo is set to Attachment 0 and Attachment 0 is set to the attachment of the object).

4 Likes

Is the diagram you made a top-down view, or do you want something like a shotgun effect?

3 Likes

I guess you can try using Vector3 or BodyVelocity on each part and make them work via a RemoteEvent I guess

4 Likes

the diagram i made is in fact a top-down view, yes

3 Likes

this seems useful, although i am unsure how i would do that, any examples?

2 Likes

A way that I like to get vectors without using trig is to do cframe operations. You can get evenly rotated lookvectors and then use the lookvectors when creating your new projectiles.

local PROJECTILE_COUNT = 8

for i=1, PROJECTILE_COUNT do
	local lookVec = CFrame.Angles(0,math.pi*2*(i/PROJECTILE_COUNT),0).LookVector
	fireProjectile(firePos, lookVec)
end
3 Likes

I’ll try this out, but could you clear up what you mean by the “fireProjectile(firePos, lookVec)” line? I assume that’s meant to be a function that I create to spawn the projectiles?

2 Likes

The Angles in CFrames are measured in Radians, which is a part of Trigonometry, so you are still using Trig to calulate a Rotation here.

3 Likes

Here’s a more or less complete walkthrough.

Firstly, cloning 8 parts and positioning them would cause absolutely no performance issues. If you ever want to create large amounts of parts, use the module below:

Anyways, the hardest part of this question is positioning the projectiles because we’ll need to do some math.

We firstly need the CFrame of the center of the part, because all of the projectiles will be spawned relative to that position.

local center = part.CFrame

Since you want an octagonal pattern, we want to clone 8 projectiles; this is best done in a for loop

for i = 1, 8 do
	local clone = proj:Clone()
	
	clone.CFrame = center -- Set their CFrame to the center first
	clone.Parent = workspace -- Make sure we can see the clones
end

Next, we need to rotate the parts. I’m assuming the drawing you provided is a bird’s eye view - if so, we’ll have to rotate them along the y-axis.

for i = 1, 8 do
	local clone = proj:Clone()
	
	clone.CFrame = center * CFrame.Angles(0, math.pi * 2 / 8 * i, 0)
	clone.Parent = workspace
end

CFrame.Angles(0, math.pi * 2 / 8 * i, 0) describes the rotation of the clone. It takes radians so we’re using pi; the degree equivalent would be 360 / 8 * i, or 45 * i (pi radians = 180 degrees). We want the projectiles to be evenly rotated about the center, so you can think about this as slicing a cake into 8 slices; each cut would be 360 degrees / 8 = 45 degrees apart. This is essentially what the loop would create:

Finally, to get the projectiles to move, you’d want to use some sort of physics mover like LinearVelocity (use whatever you want if you don’t like it). LinearVelocity requires an Attachment, so make sure your projectile has one. We need to set the VectorVelocity property, which describes the direction and force of the physics mover.

The direction we want is the direction the clones are facing, which is described by their LookVector property. We’d then need to multiply this by some number of your choosing to get them moving at your preferred speed.

Your code should look something like this finally:

for i = 1, 8 do
	local clone = proj:Clone()

	clone.CFrame = center * CFrame.Angles(0, math.pi * 2 / 8 * i, 0)
	clone.Parent = workspace
	
-- You would want to put this in a separate function
	local linearVelocity = Instance.new("LinearVelocity")
	linearVelocity.Attachment0 = clone.Attachment -- Whatever your attachment is called
	linearVelocity.VectorVelocity = clone.CFrame.LookVector * 100 -- Adjust this to your liking
	linearVelocity.Parent = clone
end
3 Likes

There’s different ways to do this, I personally use CFrame because they’re what I’m more familiar with.
I created a sample script you can look over and experiment with.
I will try to break down what my script is doing the best I can (This more or less isn’t optimized and more of a demonstration)

local RunService = game:GetService("RunService")
--This is a Part but can be any CFrame really, provided you fix up any references to it (since the script -will expect it to be a Part, it will attempt to index .CFrame of it (which isn't a valid member of CFrame.))
local Origin = script.Parent

local function shootProjectiles(numberOfParts, startingDistance, projectileLifeTime, speed)
	--Table for storing all newly created projectiles
	local parts = {}

	--Creates the desired numbber of Parts and Positions them accordingly in a circular arrangment
	for count = 1, numberOfParts do
		local smile = Instance.new("Decal")
		smile.Face = Enum.NormalId.Front
		smile.Texture = "rbxasset://textures/face.png"

		local Part = Instance.new("Part")
		Part.Size = Vector3.new(2, 2, 2)
		Part.Anchored = true
		Part.BottomSurface = Enum.SurfaceType.Smooth
		Part.TopSurface = Enum.SurfaceType.Smooth

		--Position newly created Part around Origin using Trigonometry
		local angle = (math.pi*2/numberOfParts)*count
		Part.CFrame = Origin.CFrame * CFrame.new(math.sin(angle)*startingDistance, math.cos(angle)*startingDistance, 0)
		
		smile.Parent = Part
		Part.Parent = workspace
		
		table.insert(parts, Part)
	end

	--Updates all Parts within the 'parts' table and moves them away from the Origin each frame.
	--elapsedTime keeps track of how much time has bassed since starting projectile movement
	--Lifetime determines how long parts will move before being destroyed
	--originStore stores Position of the Origin on reference
	local originStore = Origin.CFrame
	local elapsedTime = 0
	local Connection
	Connection = RunService.Heartbeat:Connect(function(deltaTime)
		elapsedTime += deltaTime
		if elapsedTime >= projectileLifeTime then
			--Terminate Connection and Destroy all Parts when total elapsed time is equal to lifeTime
			Connection:Disconnect()
			for _, part in ipairs(parts) do
				part:Destroy()
			end
		else
			for _, part in ipairs(parts) do
				--For each Part, calculate distance and direction from origin
				local distance = speed*deltaTime
				local displacementFromOrigin = part.CFrame.Position - originStore.Position
				local direction = displacementFromOrigin.Unit
				
				--Applies calculated translation to the Part's Position
				part.CFrame += (distance*direction)
			end
		end
	end)
end

shootProjectiles(16, 1, 10, 10)

First, the Script will place newly created parts in a circular arrangement around an Origin using Trigonometry. It then will set up a RunService.Heartbeat Connection to update the position of every Part every frame relative to itself and the Origin. (RunService.Heartbeat will pass a value called deltaTime that describes the time between each frame, and I use this in distance calculation to account for time between frames.)

Others have given great answers and I just wanted to provide how I would go about doing this via CFrames in hopes that you can learn something from it. (I love CFrames, what can I say? :face_with_hand_over_mouth: )

3 Likes

I really appreciate you going in-depth and explaining all this to me, but I’m a little lost in the final result. You said I need to put the last 4 lines as a separate function? Should I make the function like:

function linVel()
    local linearVelocity = Instance.new("LinearVelocity")
	linearVelocity.Attachment0 = clone.Attachment -- Whatever your attachment is called
	linearVelocity.VectorVelocity = clone.CFrame.LookVector * 100 -- Adjust this to your liking
	linearVelocity.Parent = clone
end

…and then call to that function where you put “You would want to put this in a separate function”? Sorry if I’m a little dense.

1 Like

If you’re going to be creating many more projectiles, you should place the linear velocity creation code in a separate function so you won’t have to repeat yourself those extra times - it’s a good (and necessary) practice for writing clean, easily workable code.

The function should look like this. The part parameter is whatever part you want to apply the velocity to; in this case, you would pass clone or whatever variable you’re using to store the projectile clone as the argument, and forceScalar is a number describing how fast you want the part to travel.

function addLinearVelocity(part, forceScalar)
	local linearVelocity = Instance.new("LinearVelocity")
	linearVelocity.Attachment0 = part:FindFirstChildOfClass("Attachment")
	linearVelocity.VectorVelocity = part.CFrame.LookVector * forceScalar
	linearVelocity.Parent = part
	
	return linearVelocity
end
1 Like

I gave this one a go, and it’s very customizable, so I easily altered it to get what I am looking for, thanks much! I’d like to thank everyone for helping on this problem as well, of course!

1 Like

reposted cuz accidental deletion and devforum didnt let me undelete

local projectile = pathtoprojectile
local centre = pathtocentre

local studs = 20 --how far the projectile goes
local speed = 1 --how fast in seconds the projectile goes before disappearing
local damage = 10 --how much damage the projectile deals

function isDecimal(number)
   return math.floor(number) ~= number
   --isDecimal(0.5)
   --0 ~= 0.5 == true
end

function shootProjectiles(amount)
   local projectiles = amount or 8
   if type(projectiles) ~= "number" then projectiles = 8 end --if we put "HELLO", it will be 8
   if isDecimal(projectiles) then projectiles = 8 end
   for i = projectiles, 360, (360 / projectiles) do
      local clonedProjectile = projectile:Clone()
      clonedProjectile.CFrame = CFrame.lookAt(centre.CFrame.Position, Vector3.new(i, 0, 0)
      clonedProjectile.Parent = workspace
      game.Debris:AddItem(clonedProjectile, speed)
      game:GetService("TweenService"):Create(clonedProjectile, TweenInfo.new(speed, Enum.EasingStyle.Exponential), {CFrame = CFrame + CFrame.LookVector * studs}):Play()
      local debounce = false
      clonedProjectile.Touched:Connect(function(hit)
         if debounce then return end
         if hit and hit.Parent:FindFirstChildWhichIsA("Humanoid") then
            debounce = true
            hit.Parent.Humanoid.Health -= damage
            clonedProjectile:Destroy()
         end
      end)
   end
end

while true do
   shootProjectiles() --shoot 8 projectiles by default
   task.wait(0.25)
   shootProjectiles(5) --shoot 5 projectiles
   task.wait(0.25)
   shootProjectiles(true) --shoot 8 projectiles, because of type checking
   task.wait(0.25)
   shootProjectiles(0.1) --shoot 8 projectiles, because decimals are not accepted
   task.wait(0.25)
end
also this feels like a #help-and-feedback:scripting-support post
2 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.